qx-di 0.2.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.
- qx_di-0.2.0/.gitignore +56 -0
- qx_di-0.2.0/PKG-INFO +52 -0
- qx_di-0.2.0/README.md +41 -0
- qx_di-0.2.0/pyproject.toml +19 -0
- qx_di-0.2.0/src/qx/di/__init__.py +68 -0
- qx_di-0.2.0/src/qx/di/container.py +537 -0
- qx_di-0.2.0/src/qx/di/decorators.py +165 -0
- qx_di-0.2.0/src/qx/di/providers.py +118 -0
- qx_di-0.2.0/src/qx/di/py.typed +0 -0
- qx_di-0.2.0/src/qx/di/scope.py +87 -0
- qx_di-0.2.0/tests/test_container.py +323 -0
qx_di-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
*.so
|
|
8
|
+
*.egg
|
|
9
|
+
*.egg-info/
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
eggs/
|
|
13
|
+
.eggs/
|
|
14
|
+
sdist/
|
|
15
|
+
wheels/
|
|
16
|
+
*.egg-link
|
|
17
|
+
|
|
18
|
+
# Virtual environments
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
env/
|
|
22
|
+
ENV/
|
|
23
|
+
|
|
24
|
+
# uv
|
|
25
|
+
.uv/
|
|
26
|
+
|
|
27
|
+
# Testing
|
|
28
|
+
.pytest_cache/
|
|
29
|
+
.coverage
|
|
30
|
+
htmlcov/
|
|
31
|
+
.tox/
|
|
32
|
+
|
|
33
|
+
# Type checking
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
.ruff_cache/
|
|
36
|
+
|
|
37
|
+
# IDE
|
|
38
|
+
.idea/
|
|
39
|
+
.vscode/
|
|
40
|
+
*.swp
|
|
41
|
+
*.swo
|
|
42
|
+
|
|
43
|
+
# OS
|
|
44
|
+
.DS_Store
|
|
45
|
+
Thumbs.db
|
|
46
|
+
|
|
47
|
+
# Docker
|
|
48
|
+
*.env.local
|
|
49
|
+
|
|
50
|
+
# Dist artifacts
|
|
51
|
+
dist/
|
|
52
|
+
|
|
53
|
+
# VS Code extension build artifacts
|
|
54
|
+
extensions/vscode/node_modules/
|
|
55
|
+
extensions/vscode/dist/
|
|
56
|
+
extensions/vscode/*.vsix
|
qx_di-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qx-di
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Qx dependency-injection container: singleton/scoped/transient, async lifecycle, generic-aware resolution
|
|
5
|
+
Author: Qx Engineering
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Requires-Dist: qx-core
|
|
9
|
+
Requires-Dist: typing-extensions>=4.12.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# qx-di
|
|
13
|
+
|
|
14
|
+
Async dependency-injection container for the Qx framework. Supports `SINGLETON`, `SCOPED`, and `TRANSIENT` lifetimes with full async lifecycle, generic-aware resolution, and decorator-based registration.
|
|
15
|
+
|
|
16
|
+
## What lives here
|
|
17
|
+
|
|
18
|
+
- **`qx.di.Container`** — resolves, caches, and scopes dependencies. Supports `register_singleton`, `register_scoped`, `register_transient`, `register_instance`, and `override`.
|
|
19
|
+
- **`qx.di.Scope`** — per-request child scope. Scoped registrations live exactly as long as their scope.
|
|
20
|
+
- **`qx.di.Lifetime`** — `SINGLETON` / `SCOPED` / `TRANSIENT` enum.
|
|
21
|
+
- **`qx.di.singleton` / `scoped` / `transient`** — class decorators that embed registration metadata without touching the class interface.
|
|
22
|
+
- **`qx.di.scan`** — discover and register all decorated classes in a package tree.
|
|
23
|
+
- **`qx.di.injectable`** — low-level decorator for explicit key/lifetime/factory overrides.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from qx.di import Container, Scope, scoped, singleton, scan
|
|
29
|
+
|
|
30
|
+
@singleton()
|
|
31
|
+
class Database:
|
|
32
|
+
def __init__(self, url: str) -> None: ...
|
|
33
|
+
|
|
34
|
+
@scoped(key=UserRepository)
|
|
35
|
+
class PgUserRepository(UserRepository):
|
|
36
|
+
def __init__(self, db: Database) -> None: ...
|
|
37
|
+
|
|
38
|
+
container = Container()
|
|
39
|
+
scan(container, "myapp.infrastructure")
|
|
40
|
+
container.register_instance(str, "postgresql+asyncpg://...")
|
|
41
|
+
|
|
42
|
+
async with container.scope() as scope:
|
|
43
|
+
repo = await container.resolve(UserRepository, scope=scope)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Design rules
|
|
47
|
+
|
|
48
|
+
- **Annotation-driven** — `_call_factory` inspects `get_type_hints()` to resolve constructor parameters. No manual wiring for the common case.
|
|
49
|
+
- **Scope propagation** — pass the active `Scope` into `mediator.send()` and it threads through all transient factory resolutions so that `SCOPED` dependencies (e.g. `UnitOfWork`) are reachable from handlers.
|
|
50
|
+
- **Cycle detection** — circular dependencies raise `ResolutionError` at first resolution, not at registration time.
|
|
51
|
+
- **Override** — `container.override(key, instance)` replaces any registration; used in tests to swap real infrastructure for fakes.
|
|
52
|
+
- No reflection at module-import time. `scan()` defers all class inspection to the first `resolve()` call.
|
qx_di-0.2.0/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# qx-di
|
|
2
|
+
|
|
3
|
+
Async dependency-injection container for the Qx framework. Supports `SINGLETON`, `SCOPED`, and `TRANSIENT` lifetimes with full async lifecycle, generic-aware resolution, and decorator-based registration.
|
|
4
|
+
|
|
5
|
+
## What lives here
|
|
6
|
+
|
|
7
|
+
- **`qx.di.Container`** — resolves, caches, and scopes dependencies. Supports `register_singleton`, `register_scoped`, `register_transient`, `register_instance`, and `override`.
|
|
8
|
+
- **`qx.di.Scope`** — per-request child scope. Scoped registrations live exactly as long as their scope.
|
|
9
|
+
- **`qx.di.Lifetime`** — `SINGLETON` / `SCOPED` / `TRANSIENT` enum.
|
|
10
|
+
- **`qx.di.singleton` / `scoped` / `transient`** — class decorators that embed registration metadata without touching the class interface.
|
|
11
|
+
- **`qx.di.scan`** — discover and register all decorated classes in a package tree.
|
|
12
|
+
- **`qx.di.injectable`** — low-level decorator for explicit key/lifetime/factory overrides.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from qx.di import Container, Scope, scoped, singleton, scan
|
|
18
|
+
|
|
19
|
+
@singleton()
|
|
20
|
+
class Database:
|
|
21
|
+
def __init__(self, url: str) -> None: ...
|
|
22
|
+
|
|
23
|
+
@scoped(key=UserRepository)
|
|
24
|
+
class PgUserRepository(UserRepository):
|
|
25
|
+
def __init__(self, db: Database) -> None: ...
|
|
26
|
+
|
|
27
|
+
container = Container()
|
|
28
|
+
scan(container, "myapp.infrastructure")
|
|
29
|
+
container.register_instance(str, "postgresql+asyncpg://...")
|
|
30
|
+
|
|
31
|
+
async with container.scope() as scope:
|
|
32
|
+
repo = await container.resolve(UserRepository, scope=scope)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Design rules
|
|
36
|
+
|
|
37
|
+
- **Annotation-driven** — `_call_factory` inspects `get_type_hints()` to resolve constructor parameters. No manual wiring for the common case.
|
|
38
|
+
- **Scope propagation** — pass the active `Scope` into `mediator.send()` and it threads through all transient factory resolutions so that `SCOPED` dependencies (e.g. `UnitOfWork`) are reachable from handlers.
|
|
39
|
+
- **Cycle detection** — circular dependencies raise `ResolutionError` at first resolution, not at registration time.
|
|
40
|
+
- **Override** — `container.override(key, instance)` replaces any registration; used in tests to swap real infrastructure for fakes.
|
|
41
|
+
- No reflection at module-import time. `scan()` defers all class inspection to the first `resolve()` call.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "qx-di"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Qx dependency-injection container: singleton/scoped/transient, async lifecycle, generic-aware resolution"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "Qx Engineering" }]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"qx-core",
|
|
11
|
+
"typing-extensions>=4.12.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["hatchling"]
|
|
16
|
+
build-backend = "hatchling.build"
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
packages = ["src/qx"]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Qx dependency-injection container.
|
|
2
|
+
|
|
3
|
+
Public surface — import from ``qx.di``; internal layout may shift.
|
|
4
|
+
|
|
5
|
+
Quick start::
|
|
6
|
+
|
|
7
|
+
from qx.di import Container, singleton, scan
|
|
8
|
+
|
|
9
|
+
@singleton()
|
|
10
|
+
class Clock:
|
|
11
|
+
def now(self) -> datetime: ...
|
|
12
|
+
|
|
13
|
+
@scoped(key=UserRepository)
|
|
14
|
+
class PgUserRepository(UserRepository):
|
|
15
|
+
def __init__(self, db: Database): ...
|
|
16
|
+
|
|
17
|
+
container = Container()
|
|
18
|
+
scan(container, "myapp.infrastructure")
|
|
19
|
+
container.register_instance(Database, Database(url=...))
|
|
20
|
+
|
|
21
|
+
async with container.scope() as s:
|
|
22
|
+
repo = await container.resolve(UserRepository, scope=s)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from qx.di.container import (
|
|
28
|
+
Container,
|
|
29
|
+
RegistrationError,
|
|
30
|
+
ResolutionError,
|
|
31
|
+
)
|
|
32
|
+
from qx.di.decorators import (
|
|
33
|
+
Injectable,
|
|
34
|
+
get_metadata,
|
|
35
|
+
injectable,
|
|
36
|
+
scan,
|
|
37
|
+
scoped,
|
|
38
|
+
singleton,
|
|
39
|
+
transient,
|
|
40
|
+
)
|
|
41
|
+
from qx.di.providers import (
|
|
42
|
+
FactoryProvider,
|
|
43
|
+
InstanceProvider,
|
|
44
|
+
Lifetime,
|
|
45
|
+
Provider,
|
|
46
|
+
)
|
|
47
|
+
from qx.di.scope import Scope
|
|
48
|
+
|
|
49
|
+
__version__ = "0.2.0"
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"Container",
|
|
53
|
+
"FactoryProvider",
|
|
54
|
+
"Injectable",
|
|
55
|
+
"InstanceProvider",
|
|
56
|
+
"Lifetime",
|
|
57
|
+
"Provider",
|
|
58
|
+
"RegistrationError",
|
|
59
|
+
"ResolutionError",
|
|
60
|
+
"Scope",
|
|
61
|
+
"__version__",
|
|
62
|
+
"get_metadata",
|
|
63
|
+
"injectable",
|
|
64
|
+
"scan",
|
|
65
|
+
"scoped",
|
|
66
|
+
"singleton",
|
|
67
|
+
"transient",
|
|
68
|
+
]
|