"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.
We need to produce a 2-3 sentence plain-text summary, objective, factual, no marketing language, no superlatives, no calls to action. Must be at most 60 words. No quotes, no markdown, no bullet points, no headings. Just plain text. Summarize: skill is about using optional dependencies via pyproject.toml extras pattern to support multiple backends without forcing all dependencies. Problem: forcing all dependencies leads to unnecessary installs, version conflicts, slower install.
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