metaclass-registry 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,48 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ venv/
25
+ ENV/
26
+ env/
27
+ .venv
28
+
29
+ # Testing
30
+ .pytest_cache/
31
+ .coverage
32
+ htmlcov/
33
+ .tox/
34
+
35
+ # IDEs
36
+ .vscode/
37
+ .idea/
38
+ *.swp
39
+ *.swo
40
+ *~
41
+
42
+ # Documentation
43
+ docs/_build/
44
+ site/
45
+
46
+ # OS
47
+ .DS_Store
48
+ Thumbs.db
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tristan Simas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: metaclass-registry
3
+ Version: 0.1.0
4
+ Summary: Zero-boilerplate metaclass-driven plugin registry system with lazy discovery and caching
5
+ Project-URL: Homepage, https://github.com/trissim/metaclass-registry
6
+ Project-URL: Documentation, https://metaclass-registry.readthedocs.io
7
+ Project-URL: Repository, https://github.com/trissim/metaclass-registry
8
+ Project-URL: Issues, https://github.com/trissim/metaclass-registry/issues
9
+ Author-email: Tristan Simas <tristan.simas@mail.mcgill.ca>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: cache,discovery,lazy-loading,metaclass,plugin,registry
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.9
24
+ Provides-Extra: dev
25
+ Requires-Dist: black>=23.0; extra == 'dev'
26
+ Requires-Dist: mypy>=1.0; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
28
+ Requires-Dist: pytest>=7.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
30
+ Provides-Extra: docs
31
+ Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'docs'
32
+ Requires-Dist: sphinx>=7.0; extra == 'docs'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # metaclass-registry
36
+
37
+ **Zero-boilerplate metaclass-driven plugin registry system with lazy discovery and caching**
38
+
39
+ [![PyPI version](https://badge.fury.io/py/metaclass-registry.svg)](https://badge.fury.io/py/metaclass-registry)
40
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
42
+
43
+ ## Features
44
+
45
+ - **Zero Boilerplate**: No custom metaclasses, no manual registry creation, just class attributes
46
+ - **Lazy Discovery**: Plugins discovered automatically on first access
47
+ - **Registry Inheritance**: Child classes inherit parent's registry for clean interface hierarchies
48
+ - **Secondary Registries**: Auto-populate related registries from primary registry
49
+ - **Persistent Caching**: Cache discovery results across process restarts
50
+ - **Auto-Configuration**: Automatic inference of discovery packages and recursive settings
51
+ - **Type-Safe**: Full type hints and mypy support
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ from metaclass_registry import AutoRegisterMeta
57
+
58
+ # Define a base class with registry configuration
59
+ class PluginBase(metaclass=AutoRegisterMeta):
60
+ __registry_key__ = 'plugin_name' # Attribute to use as registry key
61
+
62
+ plugin_name: str = None # Subclasses set this to register
63
+
64
+ # Access the auto-created registry
65
+ PLUGINS = PluginBase.__registry__
66
+
67
+ # Define plugins - they auto-register!
68
+ class MyPlugin(PluginBase):
69
+ plugin_name = 'my_plugin'
70
+
71
+ def run(self):
72
+ return "Hello from my plugin!"
73
+
74
+ # Use the registry
75
+ print(list(PLUGINS.keys())) # ['my_plugin']
76
+ plugin = PLUGINS['my_plugin']()
77
+ print(plugin.run()) # "Hello from my plugin!"
78
+ ```
79
+
80
+ ## Installation
81
+
82
+ ```bash
83
+ pip install metaclass-registry
84
+ ```
85
+
86
+ ## Why metaclass-registry?
87
+
88
+ Most plugin systems require boilerplate code:
89
+
90
+ **Before** (Traditional approach):
91
+ ```python
92
+ # Custom metaclass per registry
93
+ class PluginMeta(type):
94
+ def __new__(mcs, name, bases, namespace):
95
+ cls = super().__new__(mcs, name, bases, namespace)
96
+ if hasattr(cls, 'plugin_name') and cls.plugin_name:
97
+ PLUGINS[cls.plugin_name] = cls
98
+ return cls
99
+
100
+ # Manual registry creation
101
+ PLUGINS = {}
102
+
103
+ # Base class with custom metaclass
104
+ class PluginBase(metaclass=PluginMeta):
105
+ plugin_name = None
106
+ ```
107
+
108
+ **After** (metaclass-registry):
109
+ ```python
110
+ # Just class attributes!
111
+ class PluginBase(metaclass=AutoRegisterMeta):
112
+ __registry_key__ = 'plugin_name'
113
+
114
+ # Access auto-created registry
115
+ PLUGINS = PluginBase.__registry__
116
+ ```
117
+
118
+ ## Advanced Features
119
+
120
+ ### Registry Inheritance
121
+
122
+ ```python
123
+ class BackendBase(metaclass=AutoRegisterMeta):
124
+ __registry_key__ = 'backend_type'
125
+
126
+ class StorageBackend(BackendBase):
127
+ pass # Inherits BackendBase.__registry__
128
+
129
+ class ReadOnlyBackend(BackendBase):
130
+ pass # Also inherits BackendBase.__registry__
131
+
132
+ # All share the SAME registry!
133
+ assert StorageBackend.__registry__ is BackendBase.__registry__
134
+ ```
135
+
136
+ ### Secondary Registries
137
+
138
+ ```python
139
+ METADATA_HANDLERS = {}
140
+
141
+ class MicroscopeHandler(metaclass=AutoRegisterMeta):
142
+ __registry_key__ = 'microscope_type'
143
+ __secondary_registries__ = [
144
+ SecondaryRegistry(
145
+ registry_dict=METADATA_HANDLERS,
146
+ key_source=PRIMARY_KEY,
147
+ attr_name='metadata_handler_class'
148
+ )
149
+ ]
150
+ ```
151
+
152
+ ### Custom Key Extractors
153
+
154
+ ```python
155
+ def extract_key_from_suffix(cls):
156
+ """Extract 'foo' from 'FooHandler'."""
157
+ name = cls.__name__
158
+ if name.endswith('Handler'):
159
+ return name[:-7].lower()
160
+ return None
161
+
162
+ class Handler(metaclass=AutoRegisterMeta):
163
+ __registry_key__ = 'handler_type'
164
+ __key_extractor__ = extract_key_from_suffix
165
+ ```
166
+
167
+ ## Documentation
168
+
169
+ Full documentation available at [metaclass-registry.readthedocs.io](https://metaclass-registry.readthedocs.io)
170
+
171
+ ## License
172
+
173
+ MIT License - see LICENSE file for details
174
+
175
+ ## Contributing
176
+
177
+ Contributions welcome! Please see CONTRIBUTING.md for guidelines.
178
+
179
+ ## Credits
180
+
181
+ Developed by Tristan Simas as part of the OpenHCS project.
@@ -0,0 +1,147 @@
1
+ # metaclass-registry
2
+
3
+ **Zero-boilerplate metaclass-driven plugin registry system with lazy discovery and caching**
4
+
5
+ [![PyPI version](https://badge.fury.io/py/metaclass-registry.svg)](https://badge.fury.io/py/metaclass-registry)
6
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ ## Features
10
+
11
+ - **Zero Boilerplate**: No custom metaclasses, no manual registry creation, just class attributes
12
+ - **Lazy Discovery**: Plugins discovered automatically on first access
13
+ - **Registry Inheritance**: Child classes inherit parent's registry for clean interface hierarchies
14
+ - **Secondary Registries**: Auto-populate related registries from primary registry
15
+ - **Persistent Caching**: Cache discovery results across process restarts
16
+ - **Auto-Configuration**: Automatic inference of discovery packages and recursive settings
17
+ - **Type-Safe**: Full type hints and mypy support
18
+
19
+ ## Quick Start
20
+
21
+ ```python
22
+ from metaclass_registry import AutoRegisterMeta
23
+
24
+ # Define a base class with registry configuration
25
+ class PluginBase(metaclass=AutoRegisterMeta):
26
+ __registry_key__ = 'plugin_name' # Attribute to use as registry key
27
+
28
+ plugin_name: str = None # Subclasses set this to register
29
+
30
+ # Access the auto-created registry
31
+ PLUGINS = PluginBase.__registry__
32
+
33
+ # Define plugins - they auto-register!
34
+ class MyPlugin(PluginBase):
35
+ plugin_name = 'my_plugin'
36
+
37
+ def run(self):
38
+ return "Hello from my plugin!"
39
+
40
+ # Use the registry
41
+ print(list(PLUGINS.keys())) # ['my_plugin']
42
+ plugin = PLUGINS['my_plugin']()
43
+ print(plugin.run()) # "Hello from my plugin!"
44
+ ```
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install metaclass-registry
50
+ ```
51
+
52
+ ## Why metaclass-registry?
53
+
54
+ Most plugin systems require boilerplate code:
55
+
56
+ **Before** (Traditional approach):
57
+ ```python
58
+ # Custom metaclass per registry
59
+ class PluginMeta(type):
60
+ def __new__(mcs, name, bases, namespace):
61
+ cls = super().__new__(mcs, name, bases, namespace)
62
+ if hasattr(cls, 'plugin_name') and cls.plugin_name:
63
+ PLUGINS[cls.plugin_name] = cls
64
+ return cls
65
+
66
+ # Manual registry creation
67
+ PLUGINS = {}
68
+
69
+ # Base class with custom metaclass
70
+ class PluginBase(metaclass=PluginMeta):
71
+ plugin_name = None
72
+ ```
73
+
74
+ **After** (metaclass-registry):
75
+ ```python
76
+ # Just class attributes!
77
+ class PluginBase(metaclass=AutoRegisterMeta):
78
+ __registry_key__ = 'plugin_name'
79
+
80
+ # Access auto-created registry
81
+ PLUGINS = PluginBase.__registry__
82
+ ```
83
+
84
+ ## Advanced Features
85
+
86
+ ### Registry Inheritance
87
+
88
+ ```python
89
+ class BackendBase(metaclass=AutoRegisterMeta):
90
+ __registry_key__ = 'backend_type'
91
+
92
+ class StorageBackend(BackendBase):
93
+ pass # Inherits BackendBase.__registry__
94
+
95
+ class ReadOnlyBackend(BackendBase):
96
+ pass # Also inherits BackendBase.__registry__
97
+
98
+ # All share the SAME registry!
99
+ assert StorageBackend.__registry__ is BackendBase.__registry__
100
+ ```
101
+
102
+ ### Secondary Registries
103
+
104
+ ```python
105
+ METADATA_HANDLERS = {}
106
+
107
+ class MicroscopeHandler(metaclass=AutoRegisterMeta):
108
+ __registry_key__ = 'microscope_type'
109
+ __secondary_registries__ = [
110
+ SecondaryRegistry(
111
+ registry_dict=METADATA_HANDLERS,
112
+ key_source=PRIMARY_KEY,
113
+ attr_name='metadata_handler_class'
114
+ )
115
+ ]
116
+ ```
117
+
118
+ ### Custom Key Extractors
119
+
120
+ ```python
121
+ def extract_key_from_suffix(cls):
122
+ """Extract 'foo' from 'FooHandler'."""
123
+ name = cls.__name__
124
+ if name.endswith('Handler'):
125
+ return name[:-7].lower()
126
+ return None
127
+
128
+ class Handler(metaclass=AutoRegisterMeta):
129
+ __registry_key__ = 'handler_type'
130
+ __key_extractor__ = extract_key_from_suffix
131
+ ```
132
+
133
+ ## Documentation
134
+
135
+ Full documentation available at [metaclass-registry.readthedocs.io](https://metaclass-registry.readthedocs.io)
136
+
137
+ ## License
138
+
139
+ MIT License - see LICENSE file for details
140
+
141
+ ## Contributing
142
+
143
+ Contributions welcome! Please see CONTRIBUTING.md for guidelines.
144
+
145
+ ## Credits
146
+
147
+ Developed by Tristan Simas as part of the OpenHCS project.
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "metaclass-registry"
7
+ version = "0.1.0"
8
+ description = "Zero-boilerplate metaclass-driven plugin registry system with lazy discovery and caching"
9
+ authors = [{name = "Tristan Simas", email = "tristan.simas@mail.mcgill.ca"}]
10
+ license = {text = "MIT"}
11
+ readme = "README.md"
12
+ requires-python = ">=3.9"
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ "Topic :: Utilities",
24
+ ]
25
+ keywords = ["metaclass", "registry", "plugin", "discovery", "lazy-loading", "cache"]
26
+
27
+ dependencies = []
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=7.0",
32
+ "pytest-cov>=4.0",
33
+ "black>=23.0",
34
+ "ruff>=0.1.0",
35
+ "mypy>=1.0",
36
+ ]
37
+ docs = [
38
+ "sphinx>=7.0",
39
+ "sphinx-rtd-theme>=2.0",
40
+ ]
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/trissim/metaclass-registry"
44
+ Documentation = "https://metaclass-registry.readthedocs.io"
45
+ Repository = "https://github.com/trissim/metaclass-registry"
46
+ Issues = "https://github.com/trissim/metaclass-registry/issues"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/metaclass_registry"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ include = [
53
+ "/src",
54
+ "/tests",
55
+ "/README.md",
56
+ "/LICENSE",
57
+ ]
58
+
59
+ [tool.pytest.ini_options]
60
+ testpaths = ["tests"]
61
+ python_files = ["test_*.py"]
62
+ python_classes = ["Test*"]
63
+ python_functions = ["test_*"]
64
+ addopts = "--cov=metaclass_registry --cov-report=term-missing --cov-report=html"
65
+
66
+ [tool.black]
67
+ line-length = 100
68
+ target-version = ["py39", "py310", "py311", "py312"]
69
+
70
+ [tool.ruff]
71
+ line-length = 100
72
+ target-version = "py39"
73
+ select = ["E", "F", "I", "N", "W", "UP"]
74
+
75
+ [tool.mypy]
76
+ python_version = "3.9"
77
+ warn_return_any = true
78
+ warn_unused_configs = true
79
+ disallow_untyped_defs = true
@@ -0,0 +1,45 @@
1
+ """
2
+ metaclass-registry: Zero-boilerplate metaclass-driven plugin registry system.
3
+
4
+ This package provides a generic metaclass infrastructure for automatic plugin
5
+ registration with lazy discovery, caching, and zero boilerplate.
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ from .core import (
11
+ AutoRegisterMeta,
12
+ RegistryConfig,
13
+ PRIMARY_KEY,
14
+ SecondaryRegistry,
15
+ LazyDiscoveryDict,
16
+ SecondaryRegistryDict,
17
+ extract_key_from_handler_suffix,
18
+ extract_key_from_backend_suffix,
19
+ make_suffix_extractor,
20
+ )
21
+ from .discovery import discover_registry_classes, discover_registry_classes_recursive
22
+ from .cache import RegistryCacheManager
23
+ from .exceptions import RegistryError, DiscoveryError, CacheError
24
+
25
+ __all__ = [
26
+ # Core
27
+ "AutoRegisterMeta",
28
+ "RegistryConfig",
29
+ "PRIMARY_KEY",
30
+ "SecondaryRegistry",
31
+ "LazyDiscoveryDict",
32
+ "SecondaryRegistryDict",
33
+ "extract_key_from_handler_suffix",
34
+ "extract_key_from_backend_suffix",
35
+ "make_suffix_extractor",
36
+ # Discovery
37
+ "discover_registry_classes",
38
+ "discover_registry_classes_recursive",
39
+ # Cache
40
+ "RegistryCacheManager",
41
+ # Exceptions
42
+ "RegistryError",
43
+ "DiscoveryError",
44
+ "CacheError",
45
+ ]
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def get_home_dir() -> str:
7
+ """
8
+ Get the user's home directory in a cross-platform manner.
9
+
10
+ Uses pathlib.Path.home() which works reliably on Unix and Windows.
11
+
12
+ Returns:
13
+ str: The user's home directory path.
14
+ """
15
+ return str(Path.home())