Skip to main content

A Python optional dependency manager.

Project description

optional-dependency-manager

Tests pre-commit codecov Python 3.10+ PyPI

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.toml extras 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

optional_dependency_manager-0.2.0.tar.gz (10.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

optional_dependency_manager-0.2.0-py3-none-any.whl (11.5 kB view details)

Uploaded Python 3

File details

Details for the file optional_dependency_manager-0.2.0.tar.gz.

File metadata

File hashes

Hashes for optional_dependency_manager-0.2.0.tar.gz
Algorithm Hash digest
SHA256 91e9f4d35f5e4115de2cdec069c476c783df82be876b1d9addf48dd0ecb2c902
MD5 29ae344e562a4a9dde23dae6a53a6435
BLAKE2b-256 d3a325d8a4bcfaabe107801e8c46180af8d88763585b6503c92dce792b059cbf

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file optional_dependency_manager-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for optional_dependency_manager-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 544dbb29329a16d0f3eb67813561fc9d6dd97f5d845fdb8b10de818fd0ca2d6e
MD5 04f491037836e989cb5d5d83b7b5d28e
BLAKE2b-256 bc88698c5f01ef6142fad51ee8f0eadb3fae1e73513ce49ffb0fec44c14a943f

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page