Skip to content

Registry Management

For Framework Developers

This section is for developers building frameworks or plugin ecosystems. If you're just adding deprecations to your own package, use for_package() directly—see the Getting Started guide.

When Do You Need a Registry?

Most packages don't need a registry. Use for_package() directly:

from deprecator import for_package

deprecator = for_package("mypackage")

You need a registry when you're building a framework where:

  • Multiple packages contribute to your ecosystem (plugins, extensions)
  • You want to track deprecations across all contributing packages together
  • You need coordinated deprecation timelines across the ecosystem

API Reference

DeprecatorRegistry

DeprecatorRegistry(*, framework: PackageName)

collection of deprecators bound to a specific framework

we use deprecator as "framework" for unbound deprecators

Source code in deprecator/_registry.py
def __init__(self, *, framework: PackageName) -> None:
    self.framework = framework
    # Cache deprecators by (package_name, version) tuple
    self._deprecators = {}

for_package

for_package(
    package_name: PackageName | str,
    *,
    _version: Version | None = None,
) -> Deprecator

Get or create a deprecator for the given package and version.

:param package_name: Name of the package to create deprecator for :param _version: NOTE: This is private/internal. User code should NOT provide this argument. This is Version of the package. If None, will be looked up automatically. :returns: Deprecator instance for the package

Source code in deprecator/_registry.py
def for_package(
    self, package_name: PackageName | str, *, _version: Version | None = None
) -> Deprecator:
    """Get or create a deprecator for the given package and version.

    :param package_name: Name of the package to create deprecator for
    :param _version:
        NOTE: This is private/internal. User code should NOT provide this argument.
        This is Version of the package. If None, will be looked up automatically.
    :returns: Deprecator instance for the package
    """

    pkg_name = PackageName(package_name)
    if pkg_name not in self._deprecators:
        # Special handling for test packages starting with colon
        if is_test_package(pkg_name):
            # Test packages must provide an explicit version
            if _version is None:
                raise ValueError(
                    f"Test package '{pkg_name}' requires an explicit version"
                )
            package_version = _version
        else:
            package_version = _version or Version(
                importlib.metadata.version(pkg_name)
            )

        # Create the package-specific warning classes
        pending, deprecation, expired_warning = create_package_warning_classes(
            pkg_name, package_version
        )
        self._deprecators[pkg_name] = Deprecator(
            pkg_name,
            package_version,
            pending=pending,
            deprecation=deprecation,
            expired_warning=expired_warning,
            registry=self,
        )

    res = self._deprecators[pkg_name]

    if _version is not None and res.current_version != _version:
        warnings.warn(
            f"Deprecator for {package_name}"
            f" is being requested with a new explicit version,"
            f" but the cached one is for {res.current_version}",
            stacklevel=2,
        )

    return res

registry_for

registry_for(
    *, framework: PackageName | str
) -> DeprecatorRegistry

return a registry bound to a specific package name

Source code in deprecator/__init__.py
def registry_for(*, framework: PackageName | str) -> DeprecatorRegistry:
    """return a registry bound to a specific package name"""
    from ._registry import DeprecatorRegistry

    return DeprecatorRegistry(framework=PackageName(framework))

Core Concept

Registries solve the problem of tracking deprecations across an ecosystem:

┌─────────────────────────────────────────────────────┐
│                 Framework Registry                   │
│                  (e.g., "myframework")              │
├─────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
│  │  core pkg   │  │  plugin A   │  │  plugin B   │ │
│  │ deprecator  │  │ deprecator  │  │ deprecator  │ │
│  └─────────────┘  └─────────────┘  └─────────────┘ │
└─────────────────────────────────────────────────────┘
  • Framework declares a registry: The main framework creates a registry for its ecosystem
  • Contributing packages use the registry: Plugins register their deprecations with the framework's registry
  • Centralized tracking: All deprecations can be tracked and managed together

Usage Examples

Framework: Declare a Registry

# myframework/_deprecations.py - Main framework declares the registry
from deprecator import registry_for

# Create the ecosystem registry
framework_registry = registry_for(framework="myframework")
framework_deprecator = framework_registry.for_package("myframework")

# Framework's own deprecations
OLD_ROUTER = framework_deprecator.define(
    "Router.add_route() is deprecated, use Router.route()",
    warn_in="2.0.0",
    gone_in="3.0.0"
)

Plugin: Join the Ecosystem

# myframework_auth/_deprecations.py - Auth plugin uses framework's registry
from deprecator import registry_for

# Join the framework ecosystem
framework_registry = registry_for(framework="myframework")
auth_deprecator = framework_registry.for_package("myframework-auth")

# This deprecation is grouped with the framework's ecosystem
LEGACY_LOGIN = auth_deprecator.define(
    "login_user() is deprecated, use authenticate()",
    warn_in="0.8.0",
    gone_in="1.0.0"
)

Entry Point Integration

Frameworks can expose their registry via entry points for automatic discovery:

# Framework's pyproject.toml
[project.entry-points."deprecator.registry"]
myframework = "myframework._deprecations:framework_registry"

# Plugin's pyproject.toml
[project.entry-points."deprecator.deprecator"]
myframework-auth = "myframework_auth._deprecations:auth_deprecator"

This enables:

# Show all deprecations in the framework ecosystem
deprecator show-registry myframework

Benefits

  1. Ecosystem Coordination: All packages coordinate deprecation timelines
  2. Consistent User Experience: Users see consistent deprecation patterns
  3. Framework-Wide Planning: Plan major releases considering all ecosystem deprecations
  4. Centralized CLI: Use deprecator show-registry to inspect all deprecations

Caching Behavior

Registry operations are cached for performance:

# Deprecator instances are cached per registry
dep1 = registry.for_package("mypackage")
dep2 = registry.for_package("mypackage")
assert dep1 is dep2  # Same instance

Registry operations are thread-safe through functools.lru_cache, allowing concurrent access during import.

Best Practices

  1. Only frameworks create registries - Plugins should use their framework's registry
  2. Use consistent naming - Registry name should match framework's canonical name
  3. Document for plugin authors - Explain how plugins should use the registry
  4. Standalone packages don't need registries - Just use for_package() directly