A Python optional dependency manager.
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file optional_dependency_manager-0.2.0.tar.gz.
File metadata
- Download URL: optional_dependency_manager-0.2.0.tar.gz
- Upload date:
- Size: 10.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91e9f4d35f5e4115de2cdec069c476c783df82be876b1d9addf48dd0ecb2c902
|
|
| MD5 |
29ae344e562a4a9dde23dae6a53a6435
|
|
| BLAKE2b-256 |
d3a325d8a4bcfaabe107801e8c46180af8d88763585b6503c92dce792b059cbf
|
Provenance
The following attestation bundles were made for optional_dependency_manager-0.2.0.tar.gz:
Publisher:
release.yaml on forge-labs-dev/optional-dependency-manager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
optional_dependency_manager-0.2.0.tar.gz -
Subject digest:
91e9f4d35f5e4115de2cdec069c476c783df82be876b1d9addf48dd0ecb2c902 - Sigstore transparency entry: 781571246
- Sigstore integration time:
-
Permalink:
forge-labs-dev/optional-dependency-manager@71df41494d1cc4921b7cc162101e889136c0a2be -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/forge-labs-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@71df41494d1cc4921b7cc162101e889136c0a2be -
Trigger Event:
push
-
Statement type:
File details
Details for the file optional_dependency_manager-0.2.0-py3-none-any.whl.
File metadata
- Download URL: optional_dependency_manager-0.2.0-py3-none-any.whl
- Upload date:
- Size: 11.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
544dbb29329a16d0f3eb67813561fc9d6dd97f5d845fdb8b10de818fd0ca2d6e
|
|
| MD5 |
04f491037836e989cb5d5d83b7b5d28e
|
|
| BLAKE2b-256 |
bc88698c5f01ef6142fad51ee8f0eadb3fae1e73513ce49ffb0fec44c14a943f
|
Provenance
The following attestation bundles were made for optional_dependency_manager-0.2.0-py3-none-any.whl:
Publisher:
release.yaml on forge-labs-dev/optional-dependency-manager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
optional_dependency_manager-0.2.0-py3-none-any.whl -
Subject digest:
544dbb29329a16d0f3eb67813561fc9d6dd97f5d845fdb8b10de818fd0ca2d6e - Sigstore transparency entry: 781571254
- Sigstore integration time:
-
Permalink:
forge-labs-dev/optional-dependency-manager@71df41494d1cc4921b7cc162101e889136c0a2be -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/forge-labs-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@71df41494d1cc4921b7cc162101e889136c0a2be -
Trigger Event:
push
-
Statement type: