dioxide 0.1.0__cp311-abi3-macosx_11_0_arm64.whl

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.

Potentially problematic release.


This version of dioxide might be problematic. Click here for more details.

dioxide/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """dioxide: Fast, Rust-backed declarative dependency injection for Python."""
2
+
3
+ from .container import Container
4
+ from .decorators import _clear_registry, _get_registered_components, component
5
+ from .scope import Scope
6
+
7
+ __version__ = '0.1.0'
8
+ __all__ = [
9
+ 'Container',
10
+ 'Scope',
11
+ '_clear_registry',
12
+ '_get_registered_components',
13
+ 'component',
14
+ ]
Binary file
@@ -0,0 +1,17 @@
1
+ """Type stubs for Rust core module."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import TypeVar
5
+
6
+ T = TypeVar('T')
7
+
8
+ class Container:
9
+ """Rust-backed container implementation."""
10
+
11
+ def __init__(self) -> None: ...
12
+ def register_instance(self, py_type: type[T], instance: T) -> None: ...
13
+ def register_class(self, py_type: type[T], implementation: type[T]) -> None: ...
14
+ def register_factory(self, py_type: type[T], factory: Callable[[], T]) -> None: ...
15
+ def resolve(self, py_type: type[T]) -> T: ...
16
+ def is_empty(self) -> bool: ...
17
+ def __len__(self) -> int: ...
dioxide/container.py ADDED
@@ -0,0 +1,171 @@
1
+ """Dependency injection container."""
2
+
3
+ import inspect
4
+ from collections.abc import Callable
5
+ from typing import Any, TypeVar, get_type_hints
6
+
7
+ from dioxide._dioxide_core import Container as RustContainer
8
+
9
+ T = TypeVar('T')
10
+
11
+
12
+ class Container:
13
+ """
14
+ Dependency injection container.
15
+
16
+ The container builds a dependency graph from registered components
17
+ and resolves dependencies using the Rust-backed core.
18
+ """
19
+
20
+ def __init__(self) -> None:
21
+ """Initialize the container."""
22
+ self._rust_core = RustContainer()
23
+
24
+ def register_instance(self, component_type: type[T], instance: T) -> None:
25
+ """
26
+ Register a pre-created instance for a given type.
27
+
28
+ Args:
29
+ component_type: Type to register
30
+ instance: Pre-created instance to return for this type
31
+
32
+ Raises:
33
+ KeyError: If type is already registered
34
+ """
35
+ self._rust_core.register_instance(component_type, instance)
36
+
37
+ def register_class(self, component_type: type[T], implementation: type[T]) -> None:
38
+ """
39
+ Register a class to instantiate for a given type.
40
+
41
+ Args:
42
+ component_type: Type to register
43
+ implementation: Class to instantiate (calls __init__ with no args)
44
+
45
+ Raises:
46
+ KeyError: If type is already registered
47
+ """
48
+ self._rust_core.register_class(component_type, implementation)
49
+
50
+ def register_factory(self, component_type: type[T], factory: Callable[[], T]) -> None:
51
+ """
52
+ Register a factory function for a given type.
53
+
54
+ Args:
55
+ component_type: Type to register
56
+ factory: Callable that returns an instance (called with no args)
57
+
58
+ Raises:
59
+ KeyError: If type is already registered
60
+ """
61
+ self._rust_core.register_factory(component_type, factory)
62
+
63
+ def resolve(self, component_type: type[T]) -> T:
64
+ """
65
+ Resolve a component instance.
66
+
67
+ Args:
68
+ component_type: Type to resolve
69
+
70
+ Returns:
71
+ Instance of the requested type
72
+
73
+ Raises:
74
+ KeyError: If type is not registered
75
+ """
76
+ return self._rust_core.resolve(component_type)
77
+
78
+ def is_empty(self) -> bool:
79
+ """
80
+ Check if container has no registered providers.
81
+
82
+ Returns:
83
+ True if container is empty, False otherwise
84
+ """
85
+ return self._rust_core.is_empty()
86
+
87
+ def __len__(self) -> int:
88
+ """
89
+ Get count of registered providers.
90
+
91
+ Returns:
92
+ Number of registered providers
93
+ """
94
+ return len(self._rust_core)
95
+
96
+ def scan(self) -> None:
97
+ """
98
+ Discover and register all @component decorated classes.
99
+
100
+ This method finds all classes marked with the @component decorator
101
+ and registers them with the container, automatically setting up
102
+ dependency injection based on type hints.
103
+
104
+ Components are registered as singletons by default.
105
+ """
106
+ from dioxide.decorators import _get_registered_components
107
+ from dioxide.scope import Scope
108
+
109
+ for component_class in _get_registered_components():
110
+ # Create a factory that auto-injects dependencies
111
+ factory = self._create_auto_injecting_factory(component_class)
112
+
113
+ # Check the scope
114
+ scope = getattr(component_class, '__rivet_scope__', Scope.SINGLETON)
115
+
116
+ if scope == Scope.SINGLETON:
117
+ # Wrap the factory in a singleton wrapper
118
+ singleton_factory = self._create_singleton_factory(factory)
119
+ self.register_factory(component_class, singleton_factory)
120
+ else:
121
+ # For non-singletons, just register the factory
122
+ self.register_factory(component_class, factory)
123
+
124
+ def _create_singleton_factory(self, factory: Callable[[], T]) -> Callable[[], T]:
125
+ """
126
+ Wrap a factory function to return the same instance each time.
127
+
128
+ Args:
129
+ factory: The factory function to wrap
130
+
131
+ Returns:
132
+ A factory that caches the first instance and returns it on subsequent calls
133
+ """
134
+ instance_holder: list[T] = [] # Use list to avoid closure issues
135
+
136
+ def singleton_wrapper() -> T:
137
+ if not instance_holder:
138
+ instance_holder.append(factory())
139
+ return instance_holder[0]
140
+
141
+ return singleton_wrapper
142
+
143
+ def _create_auto_injecting_factory(self, cls: type[T]) -> Callable[[], T]:
144
+ """
145
+ Create a factory function that auto-injects dependencies from type hints.
146
+
147
+ Args:
148
+ cls: The class to create a factory for
149
+
150
+ Returns:
151
+ A factory function that resolves dependencies and instantiates the class
152
+ """
153
+ try:
154
+ init_signature = inspect.signature(cls.__init__)
155
+ type_hints = get_type_hints(cls.__init__)
156
+ except (ValueError, AttributeError):
157
+ # No __init__ or no type hints - just instantiate directly
158
+ return cls
159
+
160
+ # Build factory that resolves dependencies
161
+ def factory() -> T:
162
+ kwargs: dict[str, Any] = {}
163
+ for param_name in init_signature.parameters:
164
+ if param_name == 'self':
165
+ continue
166
+ if param_name in type_hints:
167
+ dependency_type = type_hints[param_name]
168
+ kwargs[param_name] = self.resolve(dependency_type)
169
+ return cls(**kwargs)
170
+
171
+ return factory
dioxide/decorators.py ADDED
@@ -0,0 +1,61 @@
1
+ """Decorator for marking classes as DI components."""
2
+
3
+ from typing import Any, TypeVar
4
+
5
+ from dioxide.scope import Scope
6
+
7
+ T = TypeVar('T')
8
+
9
+ # Global registry for @component decorated classes
10
+ _component_registry: set[type[Any]] = set()
11
+
12
+
13
+ def component(
14
+ cls: type[T] | None = None,
15
+ *,
16
+ scope: Scope = Scope.SINGLETON,
17
+ ) -> type[T] | Any:
18
+ """
19
+ Mark a class as a dependency injection component.
20
+
21
+ Can be used with or without arguments:
22
+ @component
23
+ class Service:
24
+ pass
25
+
26
+ @component(scope=Scope.FACTORY)
27
+ class Factory:
28
+ pass
29
+
30
+ Args:
31
+ cls: The class being decorated (when used without parentheses)
32
+ scope: Lifecycle scope (SINGLETON or FACTORY), defaults to SINGLETON
33
+
34
+ Returns:
35
+ Decorated class with DI metadata
36
+ """
37
+
38
+ def decorator(target_cls: type[T]) -> type[T]:
39
+ # Store DI metadata on the class
40
+ target_cls.__rivet_scope__ = scope # type: ignore[attr-defined]
41
+ # Add to global registry for auto-discovery
42
+ _component_registry.add(target_cls)
43
+ return target_cls
44
+
45
+ # Support both @component and @component()
46
+ if cls is None:
47
+ # Called with arguments: @component(scope=...)
48
+ return decorator
49
+ else:
50
+ # Called without arguments: @component
51
+ return decorator(cls)
52
+
53
+
54
+ def _get_registered_components() -> set[type[Any]]:
55
+ """Internal: Get all registered component classes."""
56
+ return _component_registry.copy()
57
+
58
+
59
+ def _clear_registry() -> None:
60
+ """Internal: Clear the component registry (for testing)."""
61
+ _component_registry.clear()
dioxide/py.typed ADDED
File without changes
dioxide/scope.py ADDED
@@ -0,0 +1,10 @@
1
+ """Dependency injection scopes."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class Scope(str, Enum):
7
+ """Component lifecycle scope."""
8
+
9
+ SINGLETON = 'singleton'
10
+ FACTORY = 'factory'
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: dioxide
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Rust
12
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
+ Classifier: Typing :: Typed
14
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
15
+ Requires-Dist: pytest-cov>=4.1 ; extra == 'dev'
16
+ Requires-Dist: behave>=1.2.6 ; extra == 'dev'
17
+ Requires-Dist: mypy>=1.8 ; extra == 'dev'
18
+ Requires-Dist: ruff>=0.1 ; extra == 'dev'
19
+ Requires-Dist: isort>=5.13 ; extra == 'dev'
20
+ Requires-Dist: tox>=4.0 ; extra == 'dev'
21
+ Requires-Dist: pre-commit>=3.0 ; extra == 'dev'
22
+ Requires-Dist: mutmut>=2.4 ; extra == 'dev'
23
+ Provides-Extra: dev
24
+ License-File: LICENSE
25
+ Summary: Fast, Rust-backed declarative dependency injection for Python
26
+ Keywords: dependency-injection,di,ioc,rust,container
27
+ Author-email: Mike Lane <mikelane@gmail.com>
28
+ License: MIT
29
+ Requires-Python: >=3.11
30
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
31
+ Project-URL: Homepage, https://github.com/mikelane/dioxide
32
+ Project-URL: Repository, https://github.com/mikelane/dioxide
33
+ Project-URL: Issues, https://github.com/mikelane/dioxide/issues
34
+
35
+ # dioxide
36
+
37
+ **Fast, Rust-backed declarative dependency injection for Python**
38
+
39
+ [![CI](https://github.com/mikelane/dioxide/workflows/CI/badge.svg)](https://github.com/mikelane/dioxide/actions)
40
+ [![Python Version](https://img.shields.io/pypi/pyversions/dioxide)](https://pypi.org/project/dioxide/)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
42
+
43
+ ---
44
+
45
+ ## Overview
46
+
47
+ `dioxide` is a dependency injection framework for Python that combines:
48
+
49
+ - **Declarative Python API** - Simple decorators and type hints
50
+ - **Rust-backed performance** - Fast graph construction and resolution via PyO3
51
+ - **Type safety** - Full support for mypy and type checkers
52
+ - **Clean architecture** - Encourages loose coupling and testability
53
+
54
+ ## Status
55
+
56
+ 🚧 **Work in Progress** - Currently implementing the v0.1 Walking Skeleton.
57
+
58
+ See [Issues](https://github.com/mikelane/dioxide/issues) and [Milestones](https://github.com/mikelane/dioxide/milestones) for current progress.
59
+
60
+ ## Vision
61
+
62
+ Traditional Python DI frameworks like `dependency-injector` are feature-rich but can be slow with large dependency graphs. `dioxide` aims to provide:
63
+
64
+ 1. **Fast graph construction** using Rust's `petgraph`
65
+ 2. **Type-based resolution** via Python's `__annotations__`
66
+ 3. **Lifecycle management** with proper shutdown ordering
67
+ 4. **Clear error messages** for missing dependencies and cycles
68
+
69
+ ## Quick Start (Planned API)
70
+
71
+ ```python
72
+ from dioxide import Container, Scope, component
73
+
74
+ @component(scope=Scope.SINGLETON)
75
+ class DatabaseConnection:
76
+ def __init__(self, connection_string: str):
77
+ self.connection_string = connection_string
78
+
79
+ @component(scope=Scope.SINGLETON)
80
+ class UserRepository:
81
+ def __init__(self, db: DatabaseConnection):
82
+ self.db = db
83
+
84
+ # Create container and register components
85
+ container = Container()
86
+ container.register_value('connection_string', 'postgresql://localhost/mydb')
87
+ container.register(DatabaseConnection)
88
+ container.register(UserRepository)
89
+
90
+ # Resolve dependencies
91
+ repo = container.resolve(UserRepository)
92
+ # repo.db is automatically injected
93
+
94
+ # Clean shutdown
95
+ container.shutdown()
96
+ ```
97
+
98
+ ## Features
99
+
100
+ ### v0.1 Walking Skeleton (In Progress)
101
+ - [x] Basic project structure
102
+ - [ ] Type-based dependency resolution
103
+ - [ ] Singleton and factory scopes
104
+ - [ ] Value injection by parameter name
105
+ - [ ] Duplicate registration prevention
106
+ - [ ] Clear error messages
107
+
108
+ ### v0.2 Core Features (Planned)
109
+ - [ ] Named tokens for disambiguation
110
+ - [ ] Circular dependency detection
111
+ - [ ] Graceful shutdown with reverse ordering
112
+ - [ ] Comprehensive test coverage (>95%)
113
+ - [ ] Mutation testing with `mutmut`
114
+
115
+ ### Future Enhancements (Backlog)
116
+ - [ ] Conditional registration
117
+ - [ ] Provider functions
118
+ - [ ] Property injection
119
+ - [ ] Performance benchmarks vs. `dependency-injector`
120
+ - [ ] Documentation site
121
+
122
+ ## Development
123
+
124
+ ### Prerequisites
125
+
126
+ - Python 3.11+
127
+ - Rust 1.70+
128
+ - [uv](https://github.com/astral-sh/uv) for Python package management
129
+ - [maturin](https://github.com/PyO3/maturin) for building Rust extensions
130
+
131
+ ### Setup
132
+
133
+ ```bash
134
+ # Clone the repository
135
+ git clone https://github.com/mikelane/dioxide.git
136
+ cd dioxide
137
+
138
+ # Install dependencies with uv
139
+ uv venv
140
+ source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
141
+ uv pip install -e ".[dev]"
142
+
143
+ # Build the Rust extension
144
+ maturin develop
145
+
146
+ # Run tests
147
+ pytest
148
+
149
+ # Run all quality checks
150
+ tox
151
+ ```
152
+
153
+ ### Development Workflow
154
+
155
+ ```bash
156
+ # Format code
157
+ tox -e format
158
+
159
+ # Lint
160
+ tox -e lint
161
+
162
+ # Type check
163
+ tox -e type
164
+
165
+ # Run tests for all Python versions
166
+ tox
167
+
168
+ # Run tests with coverage
169
+ tox -e cov
170
+
171
+ # Mutation testing
172
+ tox -e mutate
173
+ ```
174
+
175
+ ### Pre-commit Hooks
176
+
177
+ Install pre-commit hooks to ensure code quality:
178
+
179
+ ```bash
180
+ pre-commit install
181
+ ```
182
+
183
+ ## Architecture
184
+
185
+ ```
186
+ dioxide/
187
+ ├── python/dioxide/ # Python API
188
+ │ ├── __init__.py
189
+ │ ├── container.py # Main Container class
190
+ │ ├── decorators.py # @component decorator
191
+ │ └── scope.py # Scope enum
192
+ ├── rust/src/ # Rust core
193
+ │ └── lib.rs # PyO3 bindings and graph logic
194
+ ├── tests/ # Python tests
195
+ └── pyproject.toml # Project configuration
196
+ ```
197
+
198
+ ### Key Design Decisions
199
+
200
+ 1. **Rust for graph operations** - Dependency graphs can get complex; Rust's performance and safety help scale
201
+ 2. **Python-first API** - Developers work in pure Python; Rust is an implementation detail
202
+ 3. **Type hints as the contract** - Leverage Python's type system for DI metadata
203
+ 4. **Explicit over implicit** - Registration is manual to avoid surprises
204
+ 5. **Test-driven development** - Every feature starts with failing tests
205
+
206
+ ## Comparison to Other Frameworks
207
+
208
+ | Feature | dioxide | dependency-injector | injector |
209
+ |---------|----------|---------------------|----------|
210
+ | Type-based DI | ✅ | ✅ | ✅ |
211
+ | Rust-backed | ✅ | ❌ | ❌ |
212
+ | Scopes | ✅ | ✅ | ✅ |
213
+ | Lifecycle | ✅ | ✅ | ❌ |
214
+ | Cycle detection | ✅ (planned) | ❌ | ❌ |
215
+ | Performance* | 🚀 (goal) | ⚡ | ⚡ |
216
+
217
+ *Benchmarks coming in v0.2
218
+
219
+ ## Contributing
220
+
221
+ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
222
+
223
+ ### Roadmap
224
+
225
+ - v0.1: Walking skeleton with basic DI
226
+ - v0.2: Production-ready core features
227
+ - v0.3: Performance optimization and benchmarks
228
+ - v1.0: Stable API
229
+
230
+ ## License
231
+
232
+ MIT License - see [LICENSE](LICENSE) for details.
233
+
234
+ ## Acknowledgments
235
+
236
+ - Inspired by [dependency-injector](https://github.com/ets-labs/python-dependency-injector) and Spring Framework
237
+ - Built with [PyO3](https://github.com/PyO3/pyo3) and [maturin](https://github.com/PyO3/maturin)
238
+ - Graph algorithms powered by [petgraph](https://github.com/petgraph/petgraph)
239
+
240
+ ---
241
+
242
+ **Note**: This project is under active development. APIs may change before v1.0.
243
+
@@ -0,0 +1,11 @@
1
+ dioxide-0.1.0.dist-info/METADATA,sha256=udsDz2a8K_cgarNh_Txl_u1jm_wiU_xXf-K258wPctI,7212
2
+ dioxide-0.1.0.dist-info/WHEEL,sha256=kelVgS1CONjYuT6OEIC92ofaP9Aiw74EQCpLzMjs2Gg,103
3
+ dioxide-0.1.0.dist-info/licenses/LICENSE,sha256=JpEmJvRYOTIUt0UjgvpDrd3U94Wnbt_Grr5z-xU2jtk,1066
4
+ dioxide/__init__.py,sha256=YULuPVqRmnBmFFRx9vMM7MAV4LKJmertLobbhFc7utA,357
5
+ dioxide/_dioxide_core.abi3.so,sha256=treUgOU1PvwrtEHBdMRNz-wsk6DOvnEwYsjbdHA4Nd0,472912
6
+ dioxide/_rivet_core.pyi,sha256=duJvzENpwRtl9HZIjLYrVIbCkMZscCKeVvIgEaAgnQk,594
7
+ dioxide/container.py,sha256=dU3d0nhjiHTPT08z0kYkNujEcLq2WvO9-Gqjdzr8xh0,5549
8
+ dioxide/decorators.py,sha256=EAxMn_HbudrH6V1bREb-dFOzkcRMBK9MlLPww9uALT4,1616
9
+ dioxide/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ dioxide/scope.py,sha256=C6pAAVwNu04CSwUyIphCwOlec9DPAMWT8Q4Js48HGkE,174
11
+ dioxide-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.9.6)
3
+ Root-Is-Purelib: false
4
+ Tag: cp311-abi3-macosx_11_0_arm64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mike Lane
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.