"Use when supporting multiple backends/providers without forcing all dependencies on users. pyproject.toml extras pattern."
Install
npx skillscat add shimo4228/claude-code-learned-skills/python-optional-dependencies Install via the SkillsCat registry.
Python Optional Dependencies Pattern
Extracted: 2026-02-09
Context: Supporting multiple providers/backends without forcing all dependencies on all users
Problem
You're building a library/CLI that supports multiple backends (e.g., multiple LLM providers, multiple databases, multiple cloud providers), but you don't want to force users to install all dependencies.
Example Scenarios:
- LLM application supporting both Anthropic and OpenAI
- Database library supporting PostgreSQL, MySQL, SQLite
- Cloud SDK supporting AWS, Azure, GCP
- Analytics tool supporting multiple data sources
Naive Approach:
dependencies = [
"anthropic>=0.40.0", # ← Forces everyone to install
"openai>=1.0.0", # ← Even if they only use one
"google-generativeai>=0.3.0",
]Problem: Users installing 50+ MB of unused SDKs, potential version conflicts, slower installation.
Solution: Optional Dependencies
Use [project.optional-dependencies] in pyproject.toml to let users choose.
Step 1: Define Optional Dependency Groups
[project]
name = "myapp"
dependencies = [
# Only core dependencies here
"pydantic>=2.0",
"typer>=0.15.0",
]
[project.optional-dependencies]
# Individual provider groups
anthropic = ["anthropic>=0.40.0"]
openai = ["openai>=1.0.0"]
google = ["google-generativeai>=0.3.0"]
# Convenience groups
all = [
"anthropic>=0.40.0",
"openai>=1.0.0",
"google-generativeai>=0.3.0",
]
# Dev dependencies
dev = [
"pytest>=8.0",
"mypy>=1.11",
]Step 2: Installation
# Install with specific provider
pip install myapp[anthropic]
pip install myapp[openai]
# Install with multiple providers
pip install myapp[anthropic,openai]
# Install everything
pip install myapp[all]
# Dev installation
pip install myapp[all,dev]Implementation Pattern
Step 3: Runtime Availability Check
Create a module to check which optional dependencies are available:
# myapp/providers/__init__.py
"""Provider availability checks."""
# Check Anthropic availability
try:
import anthropic
ANTHROPIC_AVAILABLE = True
except ImportError:
ANTHROPIC_AVAILABLE = False
# Check OpenAI availability
try:
import openai
OPENAI_AVAILABLE = True
except ImportError:
OPENAI_AVAILABLE = False
# Check at least one is available
if not (ANTHROPIC_AVAILABLE or OPENAI_AVAILABLE):
raise ImportError(
"No LLM provider installed. "
"Install at least one:\n"
" pip install myapp[anthropic]\n"
" pip install myapp[openai]\n"
" pip install myapp[all]"
)Step 4: Provider Factory with Runtime Check
# myapp/providers/factory.py
from myapp.providers import ANTHROPIC_AVAILABLE, OPENAI_AVAILABLE
def create_provider(provider_name: str):
"""Create provider instance with availability check."""
if provider_name == "anthropic":
if not ANTHROPIC_AVAILABLE:
raise RuntimeError(
"Anthropic provider not installed.\n"
"Install with: pip install myapp[anthropic]"
)
from myapp.providers.anthropic import AnthropicProvider
return AnthropicProvider()
elif provider_name == "openai":
if not OPENAI_AVAILABLE:
raise RuntimeError(
"OpenAI provider not installed.\n"
"Install with: pip install myapp[openai]"
)
from myapp.providers.openai import OpenAIProvider
return OpenAIProvider()
else:
raise ValueError(f"Unknown provider: {provider_name}")Step 5: CLI Help with Availability Info
# myapp/main.py
import typer
from myapp.providers import ANTHROPIC_AVAILABLE, OPENAI_AVAILABLE
app = typer.Typer()
@app.command()
def process(
provider: str = typer.Option(
...,
help="LLM provider (anthropic/openai)"
),
):
"""Process with specified provider."""
# Show available providers
available = []
if ANTHROPIC_AVAILABLE:
available.append("anthropic")
if OPENAI_AVAILABLE:
available.append("openai")
typer.echo(f"Available providers: {', '.join(available)}")
if provider not in available:
typer.secho(
f"Provider '{provider}' not installed.\n"
f"Install with: pip install myapp[{provider}]",
fg=typer.colors.RED,
)
raise typer.Exit(1)
# Continue with processing
from myapp.providers.factory import create_provider
llm = create_provider(provider)
# ...Testing Strategy
Test with Mocked Imports
# tests/test_providers.py
import sys
from unittest.mock import MagicMock
import pytest
def test_anthropic_provider_unavailable():
"""Test error when Anthropic SDK not installed."""
# Mock missing anthropic module
anthropic_module = sys.modules.get('anthropic')
sys.modules['anthropic'] = None
try:
# Reload to trigger ImportError
import importlib
import myapp.providers
importlib.reload(myapp.providers)
assert not myapp.providers.ANTHROPIC_AVAILABLE
with pytest.raises(RuntimeError, match="not installed"):
from myapp.providers.factory import create_provider
create_provider("anthropic")
finally:
# Restore original module
if anthropic_module:
sys.modules['anthropic'] = anthropic_moduleTest Matrix with tox
# tox.ini
[tox]
envlist = py312-{anthropic,openai,all}
[testenv]
deps =
pytest
anthropic: anthropic>=0.40.0
openai: openai>=1.0.0
all: anthropic>=0.40.0
all: openai>=1.0.0
commands =
pytest tests/Real-World Examples
FastAPI
pip install fastapi # Minimal
pip install fastapi[standard] # All recommended extrasSQLAlchemy
pip install sqlalchemy # Core only
pip install sqlalchemy[postgresql]
pip install sqlalchemy[mysql]
pip install sqlalchemy[asyncio]pytest
pip install pytest # Core
pip install pytest[testing] # Additional test toolsRequests
pip install requests # Core
pip install requests[security] # Security extras
pip install requests[socks] # SOCKS proxy supportAdvanced: Combining Extras
Cross-Product Groups
[project.optional-dependencies]
# Providers
anthropic = ["anthropic>=0.40.0"]
openai = ["openai>=1.0.0"]
# Features
batch = ["aiofiles>=23.0"]
ocr = ["ocrmypdf>=16.0.0"]
# Combinations
anthropic-batch = ["anthropic>=0.40.0", "aiofiles>=23.0"]
openai-batch = ["openai>=1.0.0", "aiofiles>=23.0"]
# All combinations
all = [
"anthropic>=0.40.0",
"openai>=1.0.0",
"aiofiles>=23.0",
"ocrmypdf>=16.0.0",
]Recommendation Groups
[project.optional-dependencies]
# Minimal working setup
recommended = [
"anthropic>=0.40.0",
"rich>=13.0", # Better CLI output
]
# Power user setup
complete = [
"anthropic>=0.40.0",
"openai>=1.0.0",
"rich>=13.0",
"aiofiles>=23.0",
"ocrmypdf>=16.0.0",
]Benefits
For Users
- ✅ Minimal installation: Only install what they need
- ✅ Faster installation: Fewer packages to download
- ✅ Less disk space: No unused dependencies
- ✅ Fewer conflicts: Smaller dependency tree
- ✅ Clear choices: Explicit opt-in for features
For Developers
- ✅ Flexibility: Support many backends without forcing all
- ✅ Testing: Can test each backend independently
- ✅ Maintenance: Can deprecate backends gradually
- ✅ Documentation: Clear dependency requirements per feature
Gotchas
1. Circular Imports
Problem: Checking availability in __init__.py can cause circular imports.
Solution: Use separate _availability.py module:
# myapp/_availability.py
try:
import anthropic
ANTHROPIC_AVAILABLE = True
except ImportError:
ANTHROPIC_AVAILABLE = False
# myapp/__init__.py
from myapp._availability import ANTHROPIC_AVAILABLE2. Type Checking
Problem: mypy complains about imports that might not exist.
Solution: Use TYPE_CHECKING guard:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import anthropic
import openai
def create_provider(name: str):
if name == "anthropic":
import anthropic # Runtime import
# ...3. Documentation
Problem: Users don't know which extras to install.
Solution: Clear documentation in README:
## Installation
Choose the provider(s) you need:
### Anthropic Claude only
\`\`\`bash
pip install myapp[anthropic]
\`\`\`
### OpenAI GPT only
\`\`\`bash
pip install myapp[openai]
\`\`\`
### Both (for comparison)
\`\`\`bash
pip install myapp[all]
\`\`\`When to Use This Pattern
Use optional dependencies when:
- Supporting multiple backends/providers
- Some dependencies are large (> 10 MB)
- Users typically need only 1-2 of N options
- Dependencies might conflict (e.g., different TensorFlow versions)
Don't use optional dependencies when:
- All features are commonly used together
- Dependencies are small (< 1 MB total)
- Complexity outweighs benefits
- Users expect "batteries included"
Related Patterns
ai-era-architecture-principles.md- When to minimize dependenciescost-aware-llm-pipeline.md- Provider abstraction for LLM costs