Skip to main content

A lightweight Python library for lazy-loading registries with namespace support and type safety.

Project description

lazyregistry

CI codecov pypi Python Versions License: MIT Code style: ruff

A lightweight Python library for lazy-loading registries with namespace support and type safety

Installation

# Install with pip
$ pip install lazyregistry

# Add to your project with uv
$ uv add "lazyregistry"

Quick Start

from lazyregistry import Registry

registry = Registry(name="plugins")

# Register by import string (lazy - imported on access)
registry["json"] = "json:dumps"

# Register by instance (immediate - already imported)
import pickle
registry["pickle"] = pickle.dumps

# Import happens here
serializer = registry["json"]

Features

  • Lazy imports - Defer expensive imports until first access
  • Instance registration - Register both import strings and direct objects
  • Namespaces - Organize multiple registries
  • Generic types - Type hints for IDE autocomplete on registry access
  • Eager loading - Optional immediate import for critical components
  • Pretrained models - Built-in support for save_pretrained/from_pretrained pattern

💡 Want to lazy-load your entire package with full type checking support? See Combining with lazy-loader.

Examples

Run examples: uv run python examples/<example>.py

1. Plugin System

examples/plugin_system.py - Extensible plugin architecture with decorator-based registration:

from lazyregistry import Registry

PLUGINS = Registry(name="plugins")

def plugin(name: str):
    def decorator(cls):
        PLUGINS[name] = cls
        return cls
    return decorator

@plugin("uppercase")
class UppercasePlugin:
    def process(self, text: str) -> str:
        return text.upper()

# Execute plugins
PluginManager.execute("uppercase", "hello")  # "HELLO"
PluginManager.pipeline("hello", "uppercase", "reverse")  # "OLLEH"

2. Pretrained Models

examples/pretrained.py - HuggingFace-style save/load with two patterns:

Basic (config only):

from lazyregistry import NAMESPACE
from lazyregistry.pretrained import AutoRegistry, PretrainedConfig, PretrainedMixin

# Each model has its own config with type identifier
class BertConfig(PretrainedConfig):
    model_type: str = "bert"
    hidden_size: int = 768

class GPT2Config(PretrainedConfig):
    model_type: str = "gpt2"
    hidden_size: int = 768

# Base model class
class BaseModel(PretrainedMixin):
    config_class = PretrainedConfig

class AutoModel(AutoRegistry):
    registry = NAMESPACE["models"]
    config_class = PretrainedConfig
    type_key = "model_type"

# Register with decorator - models inherit from BaseModel
@AutoModel.register_module("bert")
class BertModel(BaseModel):
    config_class = BertConfig

# Or register directly
AutoModel.registry["gpt2"] = "transformers:GPT2Model"  # Lazy import
AutoModel.registry["t5"] = T5Model                     # Direct

# Save and auto-load
config = BertConfig(hidden_size=1024)
model = BertModel(config=config)
model.save_pretrained("./model")
loaded = AutoModel.from_pretrained("./model")  # Auto-detects type

Advanced (config + custom state):

class Tokenizer(PretrainedMixin):
    def __init__(self, *args, vocab: dict[str, int] | None = None, **kwargs):
        super().__init__(*args, **kwargs)
        self.vocab = vocab or {}

    def save_pretrained(self, path):
        super().save_pretrained(path)
        # Save additional state (vocabulary)
        Path(path).joinpath("vocab.txt").write_text(...)

    @classmethod
    def from_pretrained(cls, path):
        config = cls.config_class.model_validate_json(...)
        vocab = ...  # Load vocabulary
        return cls(config=config, vocab=vocab)

API Reference

Core Classes

ImportString - String that represents an import path with lazy loading capability

from lazyregistry import ImportString

# Create an import string
import_str = ImportString("json:dumps")

# Load the object when needed
func = import_str.load()
func({"key": "value"})  # '{"key": "value"}'

Registry[K, V] - Named registry with lazy import support

registry = Registry(name="plugins")

# Dict-style assignment (auto-converts strings to ImportString)
registry["key"] = "module:object"      # Lazy
registry["key2"] = actual_object       # Immediate

# Configure behavior
registry.eager_load = True
registry["key3"] = "module:object"     # Eager load

Namespace - Container for multiple registries

from lazyregistry import NAMESPACE

# Direct access to .registry for registration
NAMESPACE["models"]["bert"] = "transformers:BertModel"
NAMESPACE["models"]["gpt2"] = GPT2Model

# Access registered items
model = NAMESPACE["models"]["bert"]

LazyImportDict[K, V] - Base class for custom implementations

Same dict-style API as Registry, but with configurable behavior:

from lazyregistry.registry import LazyImportDict

registry = LazyImportDict()

# Configure behavior via attributes
registry.auto_import_strings = True   # Auto-convert strings to ImportString (default: True)
registry.eager_load = False           # Load immediately on assignment (default: False)

# Use like a normal dict
registry["key"] = "module:object"
registry.update({"key2": "module:object2"})

Pretrained Pattern

PretrainedMixin - Save/load with Pydantic config

class MyConfig(PretrainedConfig):
    model_type: str = "my_model"

class MyModel(PretrainedMixin):
    config_class = MyConfig

model.save_pretrained("./path")
loaded = MyModel.from_pretrained("./path")

AutoRegistry - Auto-detect model type from config

The type_key parameter (defaults to "model_type") determines which config field is used for type detection.

Three ways to register:

from lazyregistry.pretrained import PretrainedConfig, PretrainedMixin

# Each model has its own config class with type identifier
class BertConfig(PretrainedConfig):
    model_type: str = "bert"
    hidden_size: int = 768

class GPT2Config(PretrainedConfig):
    model_type: str = "gpt2"
    hidden_size: int = 768

# Base model class
class BaseModel(PretrainedMixin):
    config_class = PretrainedConfig

class AutoModel(AutoRegistry):
    registry = NAMESPACE["models"]
    config_class = PretrainedConfig
    type_key = "model_type"  # Can use any field name

# 1. Decorator registration - models inherit from BaseModel
@AutoModel.register_module("bert")
class BertModel(BaseModel):
    config_class = BertConfig

# 2. Direct registration via .registry
AutoModel.registry["gpt2"] = GPT2Model                   # Direct instance
AutoModel.registry["t5"] = "transformers:T5Model"        # Lazy import string

# 3. Bulk registration via .registry.update() - useful for many models
AutoModel.registry.update({
    "roberta": RobertaModel,
    "albert": "transformers:AlbertModel",
    "electra": "transformers:ElectraModel",
})

# Auto-detect and load
loaded = AutoModel.from_pretrained("./path")  # Detects type from config

Why?

Before:

# All imports happen upfront
from heavy_module_1 import ClassA
from heavy_module_2 import ClassB
from heavy_module_3 import ClassC

REGISTRY = {"a": ClassA, "b": ClassB, "c": ClassC}

After:

# Import only what you use
from lazyregistry import Registry

registry = Registry(name="components")
registry.register("a", "heavy_module_1:ClassA")
registry.register("b", "heavy_module_2:ClassB")
registry.register("c", "heavy_module_3:ClassC")

# Only ClassA is imported
component = registry["a"]

Tips

Combining with lazy-loader for Full Lazy Package Imports

For packages with many heavy dependencies, you can combine lazyregistry with lazy-loader to achieve both lazy module imports and lazy registry lookups:

# mypackage/__init__.py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    # Static analysis sees these imports (IDE autocomplete, mypy, pyright)
    from .auto import AutoModel as AutoModel
    from .bert import BertModel as BertModel
    from .gpt2 import GPT2Model as GPT2Model
else:
    # Runtime uses lazy-loader (nothing imported until accessed)
    import lazy_loader as lazy

    __getattr__, __dir__, __all__ = lazy.attach(
        __name__,
        submod_attrs={
            "auto": ["AutoModel"],
            "bert": ["BertModel"],
            "gpt2": ["GPT2Model"],
        },
    )
# mypackage/auto.py
from lazyregistry import Registry
from lazyregistry.pretrained import AutoRegistry

class AutoModel(AutoRegistry):
    registry = Registry(name="models")

# String references - actual imports deferred until registry access
AutoModel.registry.update({
    "bert": "mypackage.bert:BertModel",
    "gpt2": "mypackage.gpt2:GPT2Model",
})

Benefits:

  • Double lazy loading: lazy-loader defers module imports + lazyregistry defers registry lookups
  • Full type checking: TYPE_CHECKING block provides IDE autocomplete and static analysis
  • Zero import cost: Heavy dependencies (torch, sklearn, etc.) only load when actually used
  • Clean public API: Users import naturally with from mypackage import AutoModel

Testing

Run tests with coverage:

uv run pytest tests/ --cov=lazyregistry --cov-report=term-missing

The test suite includes:

  • Core registry tests - LazyImportDict, Registry, Namespace functionality
  • Pretrained tests - save/load patterns, AutoRegistry, custom state
  • Example tests - Verify all examples run correctly

License

MIT

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

lazyregistry-0.4.0.tar.gz (17.4 kB view details)

Uploaded Source

Built Distribution

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

lazyregistry-0.4.0-py3-none-any.whl (11.4 kB view details)

Uploaded Python 3

File details

Details for the file lazyregistry-0.4.0.tar.gz.

File metadata

  • Download URL: lazyregistry-0.4.0.tar.gz
  • Upload date:
  • Size: 17.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for lazyregistry-0.4.0.tar.gz
Algorithm Hash digest
SHA256 dcb7b57dc9d9135ca1a65f5d3a5fe5869b25b3f8a16f1e7e64da776e276416d4
MD5 999e6cd6392b769fd42189e91665606f
BLAKE2b-256 bd17d24453979cdb7bbfd6767f186f50dc859d29d107451bebd63f39027a74e6

See more details on using hashes here.

File details

Details for the file lazyregistry-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: lazyregistry-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 11.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for lazyregistry-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 62c740be733bad43f236a466f539998d54694a78275f41d3ffe488855cf10da0
MD5 f431adc550862286659fbc55456e3a8f
BLAKE2b-256 2f62278614207289246cadddb166280baef4fb14b765ebb201a2686f6c9350ad

See more details on using hashes here.

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