optional-dependency-manager
A Python library for managing optional dependencies with lazy loading and version validation.
Features
- Lazy Loading: Dependencies are only imported when first accessed, reducing startup time
- Version Validation: Automatically validates installed versions against specifiers
- Decorator API: Simple string-based decorator interface for classes and functions
- Metadata Integration: Can read version specifiers from your package's
pyproject.tomlextras or dependency groups - Dependency Reporting: Generate reports of all optional dependencies and their status
Installation
pip install optional-dependency-manager
Quick Start
from optional_dependency_manager import OptionalDependencyManager
odm = OptionalDependencyManager()
@odm("numpy>=1.20.0")
class DataProcessor:
def process(self, data):
np = self.modules["numpy"]
return np.array(data).mean()
# numpy is only imported when accessing self.modules
processor = DataProcessor()
result = processor.process([1, 2, 3, 4, 5])
Usage
String Syntax
The decorator accepts module specifications as strings with a concise syntax:
@odm("numpy") # any version
@odm("numpy>=1.20.0") # version specifier
@odm("numpy@ml") # from extra or dependency group "ml"
@odm("numpy>=1.20 as np") # with alias
@odm("sklearn->scikit-learn") # distribution name differs from import
@odm("numpy", "pandas>=2.0") # multiple modules
Basic Usage with Classes
from optional_dependency_manager import OptionalDependencyManager
odm = OptionalDependencyManager()
@odm("pandas>=2.0.0", "numpy>=1.20.0 as np")
class DataAnalyzer:
def analyze(self, data):
pd = self.modules["pandas"]
np = self.modules["np"] # using alias
df = pd.DataFrame(data)
return np.mean(df.values)
Basic Usage with Functions
@odm("requests>=2.25.0")
def fetch_data(url, modules):
# modules is injected as a keyword argument
response = modules["requests"].get(url)
return response.json()
Reading Specifiers from Package Metadata
If your package defines optional dependencies in pyproject.toml, you can read specifiers directly using the @ syntax.
Using optional-dependencies (extras)
# pyproject.toml
[project.optional-dependencies]
ml = ["numpy>=1.20.0", "pandas>=2.0.0"]
odm = OptionalDependencyManager(source="my-package")
@odm("numpy@ml", "pandas@ml")
class MLModel:
def train(self, data):
np = self.modules["numpy"]
pd = self.modules["pandas"]
# ...
Using dependency-groups (PEP 735)
For packages using uv or other tools supporting PEP 735 dependency groups:
# pyproject.toml
[dependency-groups]
ml = ["numpy>=1.20.0", "pandas>=2.0.0"]
odm = OptionalDependencyManager(source="my-package")
# Same syntax - automatically resolves to group if not found in extras
@odm("numpy@ml", "pandas@ml")
class MLModel:
def train(self, data):
np = self.modules["numpy"]
pd = self.modules["pandas"]
# ...
The @ syntax automatically checks both extras and dependency groups. If a name exists in both, an error is raised asking you to disambiguate.
To use dependency groups, install with the groups extra:
pip install optional-dependency-manager[groups]
Note: Dependency groups are only accessible during development (editable installs) since they are not included in package metadata.
Handling Import Errors
When a dependency is missing or has an incompatible version, an ImportError is raised with helpful information:
@odm("nonexistent>=1.0.0")
class MyClass:
pass
instance = MyClass()
instance.modules # Raises ImportError
ImportError: Missing or incompatible dependencies:
- nonexistent: not installed (requires >=1.0.0)
Generating Dependency Reports
You can generate a report of all registered optional dependencies:
odm = OptionalDependencyManager()
@odm("numpy>=1.20.0")
class ClassA:
pass
@odm("pandas>=2.0.0")
class ClassB:
pass
# Generate report (triggers loading to check versions)
reports = odm.report()
for r in reports:
print(f"{r.module_name}: {r.status} (used by {r.used_by})")
if r.installed_version:
print(f" Installed: {r.installed_version}")
if r.specifier:
print(f" Required: {r.specifier}")
Packages with Different Import Names
Some packages have different PyPI names and import names (e.g., scikit-learn vs sklearn). Use the -> syntax:
@odm("sklearn>=1.0.0->scikit-learn")
class Classifier:
def fit(self, X, y):
sklearn = self.modules["sklearn"]
return sklearn.linear_model.LogisticRegression().fit(X, y)
Full Syntax Reference
| Syntax | Description |
|---|---|
"numpy" |
Any version of numpy |
"numpy>=1.20" |
numpy with version constraint |
"numpy@ml" |
numpy from extra or group "ml" (auto-resolved) |
"numpy as np" |
numpy with alias "np" |
"numpy->numpy-pkg" |
import as numpy, but package name is numpy-pkg |
"numpy@ml as np" |
from extra/group with alias |
"sklearn@ml->scikit-learn as sk" |
full syntax with all options |
API Reference
OptionalDependencyManager
class OptionalDependencyManager:
def __init__(self, source: str | None = None):
"""
Initialize the manager.
Args:
source: Package name for reading metadata (optional)
"""
def __call__(self, *args: str):
"""Decorator for classes or functions."""
def report(self) -> list[ModuleReport]:
"""Generate a report of all registered dependencies."""
ModuleReport
@dataclass
class ModuleReport:
module_name: str
specifier: str | None
extra: str | None
group: str | None
installed_version: str | None
status: Literal["satisfied", "missing", "version_mismatch"]
used_by: str
Development
# Install dev dependencies
uv sync --group dev
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=optional_dependency_manager
# Type checking
uv run mypy src/
# Linting
uv run ruff check .
# Formatting
uv run ruff format .
License
MIT License - see LICENSE for details.
Metadata
Release files for optional-dependency-manager 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| optional_dependency_manager-0.2.0.tar.gz | 10.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| optional_dependency_manager-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.6 kB
Release files / optional_dependency_manager-0.2.0.tar.gz
| Download URL | optional_dependency_manager-0.2.0.tar.gz |
|---|---|
| Size | 10.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
91e9f4d35f5e4115de2cdec069c476c783df82be876b1d9addf48dd0ecb2c902
|
|
BLAKE2b-256 checksum How to use checksums |
d3a325d8a4bcfaabe107801e8c46180af8d88763585b6503c92dce792b059cbf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Dec 29, 2025.
Transparency logRelease files / optional_dependency_manager-0.2.0-py3-none-any.whl
| Download URL | optional_dependency_manager-0.2.0-py3-none-any.whl |
|---|---|
| Size | 11.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
544dbb29329a16d0f3eb67813561fc9d6dd97f5d845fdb8b10de818fd0ca2d6e
|
|
BLAKE2b-256 checksum How to use checksums |
bc88698c5f01ef6142fad51ee8f0eadb3fae1e73513ce49ffb0fec44c14a943f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Dec 29, 2025.
Transparency logSecurity for optional-dependency-manager 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for optional_dependency_manager-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Dec 29, 2025.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.