lazyregistry


Namelazyregistry JSON
Version 0.3.2 PyPI version JSON
download
home_pageNone
SummaryA lightweight Python library for lazy-loading registries with namespace support and type safety.
upload_time2025-11-05 12:40:17
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
keywords lazy-loading namespace plugin pretrained registry type-safe
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # lazyregistry

[![CI](https://github.com/milkclouds/lazyregistry/actions/workflows/ci.yml/badge.svg)](https://github.com/milkclouds/lazyregistry/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/milkclouds/lazyregistry/branch/main/graph/badge.svg)](https://codecov.io/gh/milkclouds/lazyregistry)
[![pypi](https://img.shields.io/pypi/v/lazyregistry.svg)](https://pypi.python.org/pypi/lazyregistry)
[![Python Versions](https://img.shields.io/pypi/pyversions/lazyregistry.svg)](https://pypi.org/project/lazyregistry/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)

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

## Installation

```bash
# Install with pip
$ pip install lazyregistry

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

## Quick Start

```python
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
- **Type-safe** - Full generic type support
- **Eager loading** - Optional immediate import for critical components
- **Pretrained models** - Built-in support for save_pretrained/from_pretrained pattern

## Examples

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

### 1. Plugin System

[`examples/plugin_system.py`](examples/plugin_system.py) - Extensible plugin architecture with decorator-based registration:

```python
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`](examples/pretrained.py) - HuggingFace-style save/load with two patterns:

**Basic (config only):**
```python
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
model = BertModel(BertConfig(hidden_size=1024))
model.save_pretrained("./model")
loaded = AutoModel.from_pretrained("./model")  # Auto-detects type
```

**Advanced (config + custom state):**
```python
class Tokenizer(PretrainedMixin):
    def __init__(self, config: PretrainedConfig, vocab: dict[str, int] | None = None):
        super().__init__(config)
        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, vocab=vocab)
```

## API Reference

### Core Classes

**`ImportString`** - String that represents an import path with lazy loading capability
```python
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
```python
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
```python
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:
```python
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
```python
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:
```python
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:**
```python
# 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:**
```python
# 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"]
```

## Testing

Run tests with coverage:

```bash
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

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "lazyregistry",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "lazy-loading, namespace, plugin, pretrained, registry, type-safe",
    "author": null,
    "author_email": "Suhwan Choi <milkclouds00@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/d1/3b/48a1f0f4d4b2643c93de7885a69b00f9982efce7994fc0e9f6c857e71774/lazyregistry-0.3.2.tar.gz",
    "platform": null,
    "description": "# lazyregistry\n\n[![CI](https://github.com/milkclouds/lazyregistry/actions/workflows/ci.yml/badge.svg)](https://github.com/milkclouds/lazyregistry/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/milkclouds/lazyregistry/branch/main/graph/badge.svg)](https://codecov.io/gh/milkclouds/lazyregistry)\n[![pypi](https://img.shields.io/pypi/v/lazyregistry.svg)](https://pypi.python.org/pypi/lazyregistry)\n[![Python Versions](https://img.shields.io/pypi/pyversions/lazyregistry.svg)](https://pypi.org/project/lazyregistry/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)\n\nA lightweight Python library for lazy-loading registries with namespace support and type safety\n\n## Installation\n\n```bash\n# Install with pip\n$ pip install lazyregistry\n\n# Add to your project with uv\n$ uv add \"lazyregistry\"\n```\n\n## Quick Start\n\n```python\nfrom lazyregistry import Registry\n\nregistry = Registry(name=\"plugins\")\n\n# Register by import string (lazy - imported on access)\nregistry[\"json\"] = \"json:dumps\"\n\n# Register by instance (immediate - already imported)\nimport pickle\nregistry[\"pickle\"] = pickle.dumps\n\n# Import happens here\nserializer = registry[\"json\"]\n```\n\n## Features\n\n- **Lazy imports** - Defer expensive imports until first access\n- **Instance registration** - Register both import strings and direct objects\n- **Namespaces** - Organize multiple registries\n- **Type-safe** - Full generic type support\n- **Eager loading** - Optional immediate import for critical components\n- **Pretrained models** - Built-in support for save_pretrained/from_pretrained pattern\n\n## Examples\n\nRun examples: `uv run python examples/<example>.py`\n\n### 1. Plugin System\n\n[`examples/plugin_system.py`](examples/plugin_system.py) - Extensible plugin architecture with decorator-based registration:\n\n```python\nfrom lazyregistry import Registry\n\nPLUGINS = Registry(name=\"plugins\")\n\ndef plugin(name: str):\n    def decorator(cls):\n        PLUGINS[name] = cls\n        return cls\n    return decorator\n\n@plugin(\"uppercase\")\nclass UppercasePlugin:\n    def process(self, text: str) -> str:\n        return text.upper()\n\n# Execute plugins\nPluginManager.execute(\"uppercase\", \"hello\")  # \"HELLO\"\nPluginManager.pipeline(\"hello\", \"uppercase\", \"reverse\")  # \"OLLEH\"\n```\n\n### 2. Pretrained Models\n\n[`examples/pretrained.py`](examples/pretrained.py) - HuggingFace-style save/load with two patterns:\n\n**Basic (config only):**\n```python\nfrom lazyregistry import NAMESPACE\nfrom lazyregistry.pretrained import AutoRegistry, PretrainedConfig, PretrainedMixin\n\n# Each model has its own config with type identifier\nclass BertConfig(PretrainedConfig):\n    model_type: str = \"bert\"\n    hidden_size: int = 768\n\nclass GPT2Config(PretrainedConfig):\n    model_type: str = \"gpt2\"\n    hidden_size: int = 768\n\n# Base model class\nclass BaseModel(PretrainedMixin):\n    config_class = PretrainedConfig\n\nclass AutoModel(AutoRegistry):\n    registry = NAMESPACE[\"models\"]\n    config_class = PretrainedConfig\n    type_key = \"model_type\"\n\n# Register with decorator - models inherit from BaseModel\n@AutoModel.register_module(\"bert\")\nclass BertModel(BaseModel):\n    config_class = BertConfig\n\n# Or register directly\nAutoModel.registry[\"gpt2\"] = \"transformers:GPT2Model\"  # Lazy import\nAutoModel.registry[\"t5\"] = T5Model                     # Direct\n\n# Save and auto-load\nmodel = BertModel(BertConfig(hidden_size=1024))\nmodel.save_pretrained(\"./model\")\nloaded = AutoModel.from_pretrained(\"./model\")  # Auto-detects type\n```\n\n**Advanced (config + custom state):**\n```python\nclass Tokenizer(PretrainedMixin):\n    def __init__(self, config: PretrainedConfig, vocab: dict[str, int] | None = None):\n        super().__init__(config)\n        self.vocab = vocab or {}\n\n    def save_pretrained(self, path):\n        super().save_pretrained(path)\n        # Save additional state (vocabulary)\n        Path(path).joinpath(\"vocab.txt\").write_text(...)\n\n    @classmethod\n    def from_pretrained(cls, path):\n        config = cls.config_class.model_validate_json(...)\n        vocab = ...  # Load vocabulary\n        return cls(config, vocab=vocab)\n```\n\n## API Reference\n\n### Core Classes\n\n**`ImportString`** - String that represents an import path with lazy loading capability\n```python\nfrom lazyregistry import ImportString\n\n# Create an import string\nimport_str = ImportString(\"json:dumps\")\n\n# Load the object when needed\nfunc = import_str.load()\nfunc({\"key\": \"value\"})  # '{\"key\": \"value\"}'\n```\n\n**`Registry[K, V]`** - Named registry with lazy import support\n```python\nregistry = Registry(name=\"plugins\")\n\n# Dict-style assignment (auto-converts strings to ImportString)\nregistry[\"key\"] = \"module:object\"      # Lazy\nregistry[\"key2\"] = actual_object       # Immediate\n\n# Configure behavior\nregistry.eager_load = True\nregistry[\"key3\"] = \"module:object\"     # Eager load\n```\n\n**`Namespace`** - Container for multiple registries\n```python\nfrom lazyregistry import NAMESPACE\n\n# Direct access to .registry for registration\nNAMESPACE[\"models\"][\"bert\"] = \"transformers:BertModel\"\nNAMESPACE[\"models\"][\"gpt2\"] = GPT2Model\n\n# Access registered items\nmodel = NAMESPACE[\"models\"][\"bert\"]\n```\n\n**`LazyImportDict[K, V]`** - Base class for custom implementations\n\nSame dict-style API as `Registry`, but with configurable behavior:\n```python\nfrom lazyregistry.registry import LazyImportDict\n\nregistry = LazyImportDict()\n\n# Configure behavior via attributes\nregistry.auto_import_strings = True   # Auto-convert strings to ImportString (default: True)\nregistry.eager_load = False           # Load immediately on assignment (default: False)\n\n# Use like a normal dict\nregistry[\"key\"] = \"module:object\"\nregistry.update({\"key2\": \"module:object2\"})\n```\n\n### Pretrained Pattern\n\n**`PretrainedMixin`** - Save/load with Pydantic config\n```python\nclass MyConfig(PretrainedConfig):\n    model_type: str = \"my_model\"\n\nclass MyModel(PretrainedMixin):\n    config_class = MyConfig\n\nmodel.save_pretrained(\"./path\")\nloaded = MyModel.from_pretrained(\"./path\")\n```\n\n**`AutoRegistry`** - Auto-detect model type from config\n\nThe `type_key` parameter (defaults to \"model_type\") determines which config field is used for type detection.\n\nThree ways to register:\n```python\nfrom lazyregistry.pretrained import PretrainedConfig, PretrainedMixin\n\n# Each model has its own config class with type identifier\nclass BertConfig(PretrainedConfig):\n    model_type: str = \"bert\"\n    hidden_size: int = 768\n\nclass GPT2Config(PretrainedConfig):\n    model_type: str = \"gpt2\"\n    hidden_size: int = 768\n\n# Base model class\nclass BaseModel(PretrainedMixin):\n    config_class = PretrainedConfig\n\nclass AutoModel(AutoRegistry):\n    registry = NAMESPACE[\"models\"]\n    config_class = PretrainedConfig\n    type_key = \"model_type\"  # Can use any field name\n\n# 1. Decorator registration - models inherit from BaseModel\n@AutoModel.register_module(\"bert\")\nclass BertModel(BaseModel):\n    config_class = BertConfig\n\n# 2. Direct registration via .registry\nAutoModel.registry[\"gpt2\"] = GPT2Model                   # Direct instance\nAutoModel.registry[\"t5\"] = \"transformers:T5Model\"        # Lazy import string\n\n# 3. Bulk registration via .registry.update() - useful for many models\nAutoModel.registry.update({\n    \"roberta\": RobertaModel,\n    \"albert\": \"transformers:AlbertModel\",\n    \"electra\": \"transformers:ElectraModel\",\n})\n\n# Auto-detect and load\nloaded = AutoModel.from_pretrained(\"./path\")  # Detects type from config\n```\n\n## Why?\n\n**Before:**\n```python\n# All imports happen upfront\nfrom heavy_module_1 import ClassA\nfrom heavy_module_2 import ClassB\nfrom heavy_module_3 import ClassC\n\nREGISTRY = {\"a\": ClassA, \"b\": ClassB, \"c\": ClassC}\n```\n\n**After:**\n```python\n# Import only what you use\nfrom lazyregistry import Registry\n\nregistry = Registry(name=\"components\")\nregistry.register(\"a\", \"heavy_module_1:ClassA\")\nregistry.register(\"b\", \"heavy_module_2:ClassB\")\nregistry.register(\"c\", \"heavy_module_3:ClassC\")\n\n# Only ClassA is imported\ncomponent = registry[\"a\"]\n```\n\n## Testing\n\nRun tests with coverage:\n\n```bash\nuv run pytest tests/ --cov=lazyregistry --cov-report=term-missing\n```\n\nThe test suite includes:\n- **Core registry tests** - LazyImportDict, Registry, Namespace functionality\n- **Pretrained tests** - save/load patterns, AutoRegistry, custom state\n- **Example tests** - Verify all examples run correctly\n\n## License\n\nMIT\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A lightweight Python library for lazy-loading registries with namespace support and type safety.",
    "version": "0.3.2",
    "project_urls": {
        "Changelog": "https://github.com/milkclouds/lazyregistry/releases",
        "Homepage": "https://github.com/milkclouds/lazyregistry",
        "Issues": "https://github.com/milkclouds/lazyregistry/issues",
        "Repository": "https://github.com/milkclouds/lazyregistry"
    },
    "split_keywords": [
        "lazy-loading",
        " namespace",
        " plugin",
        " pretrained",
        " registry",
        " type-safe"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "91bcd98d745177640302c1c0fc8a37d365b7477d5bf2d5ebc2a4365505b1cb86",
                "md5": "87738271e8140c240d75ff9ee998bbd3",
                "sha256": "4300ca022ab47f5324d6c83d73ddf596b17af60d69ec808a7d9b3c4d3839a365"
            },
            "downloads": -1,
            "filename": "lazyregistry-0.3.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "87738271e8140c240d75ff9ee998bbd3",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 9761,
            "upload_time": "2025-11-05T12:40:15",
            "upload_time_iso_8601": "2025-11-05T12:40:15.902998Z",
            "url": "https://files.pythonhosted.org/packages/91/bc/d98d745177640302c1c0fc8a37d365b7477d5bf2d5ebc2a4365505b1cb86/lazyregistry-0.3.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d13b48a1f0f4d4b2643c93de7885a69b00f9982efce7994fc0e9f6c857e71774",
                "md5": "73cfd751653e9fe1ee21b28f867573f0",
                "sha256": "500d9f970c7ba2d17808a4b9d58eb50d2f19e7e139a23bef43f4fb362dfd735e"
            },
            "downloads": -1,
            "filename": "lazyregistry-0.3.2.tar.gz",
            "has_sig": false,
            "md5_digest": "73cfd751653e9fe1ee21b28f867573f0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 15873,
            "upload_time": "2025-11-05T12:40:17",
            "upload_time_iso_8601": "2025-11-05T12:40:17.087476Z",
            "url": "https://files.pythonhosted.org/packages/d1/3b/48a1f0f4d4b2643c93de7885a69b00f9982efce7994fc0e9f6c857e71774/lazyregistry-0.3.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-11-05 12:40:17",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "milkclouds",
    "github_project": "lazyregistry",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "lazyregistry"
}
        
Elapsed time: 3.90268s