anydi 0.55.1__tar.gz → 0.56.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.
anydi-0.56.0/PKG-INFO ADDED
@@ -0,0 +1,267 @@
1
+ Metadata-Version: 2.4
2
+ Name: anydi
3
+ Version: 0.56.0
4
+ Summary: Dependency Injection library
5
+ Keywords: dependency injection,dependencies,di,async,asyncio,application
6
+ Author: Anton Ruhlov
7
+ Author-email: Anton Ruhlov <antonruhlov@gmail.com>
8
+ License-Expression: MIT
9
+ Classifier: Intended Audience :: Information Technology
10
+ Classifier: Intended Audience :: System Administrators
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Topic :: Internet
14
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Typing :: Typed
19
+ Classifier: Environment :: Web Environment
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: License :: OSI Approved :: MIT License
22
+ Classifier: Programming Language :: Python :: 3
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Programming Language :: Python :: 3.14
28
+ Classifier: Programming Language :: Python :: 3 :: Only
29
+ Requires-Dist: typing-extensions>=4.15.0,<5
30
+ Requires-Dist: anyio>=3.7.1
31
+ Requires-Dist: wrapt>=1.17.0
32
+ Requires-Python: >=3.10.0, <3.15
33
+ Project-URL: Repository, https://github.com/antonrh/anydi
34
+ Description-Content-Type: text/markdown
35
+
36
+ # AnyDI
37
+
38
+ <div style="text-align: center;">
39
+
40
+ Modern, lightweight Dependency Injection library using type annotations.
41
+
42
+ [![CI](https://github.com/antonrh/anydi/actions/workflows/ci.yml/badge.svg)](https://github.com/antonrh/anydi/actions/workflows/ci.yml)
43
+ [![Coverage](https://codecov.io/gh/antonrh/anydi/branch/main/graph/badge.svg)](https://codecov.io/gh/antonrh/anydi)
44
+ [![Documentation](https://readthedocs.org/projects/anydi/badge/?version=latest)](https://anydi.readthedocs.io/en/latest/)
45
+
46
+ </div>
47
+
48
+ ---
49
+ Documentation
50
+
51
+ http://anydi.readthedocs.io/
52
+
53
+ ---
54
+
55
+ `AnyDI` is a modern, lightweight Dependency Injection library suitable for any synchronous or asynchronous applications with Python 3.10+, based on type annotations ([PEP 484](https://peps.python.org/pep-0484/)).
56
+
57
+ The key features are:
58
+
59
+ * **Type-safe**: Dependency resolution is driven by type hints.
60
+ * **Async-ready**: Works the same for sync and async providers or injections.
61
+ * **Scoped**: Built-in singleton, transient, and request lifetimes.
62
+ * **Simple**: Small surface area keeps boilerplate low.
63
+ * **Fast**: Resolver still adds only microseconds of overhead.
64
+ * **Named**: `Annotated[...]` makes multiple bindings per type simple.
65
+ * **Managed**: Providers can open/close resources via context managers.
66
+ * **Modular**: Compose containers or modules for large apps.
67
+ * **Scanning**: Auto-discovers injectable callables.
68
+ * **Integrated**: Extensions for popular frameworks.
69
+ * **Testable**: Override providers directly in tests.
70
+
71
+ ## Installation
72
+
73
+ ```shell
74
+ pip install anydi
75
+ ```
76
+
77
+ ## Comprehensive Example
78
+
79
+ ### Define a Service (`app/services.py`)
80
+
81
+ ```python
82
+ class GreetingService:
83
+ def greet(self, name: str) -> str:
84
+ return f"Hello, {name}!"
85
+ ```
86
+
87
+ ### Create the Container and Providers (`app/container.py`)
88
+
89
+ ```python
90
+ from anydi import Container
91
+
92
+ from app.services import GreetingService
93
+
94
+
95
+ container = Container()
96
+
97
+
98
+ @container.provider(scope="singleton")
99
+ def service() -> GreetingService:
100
+ return GreetingService()
101
+ ```
102
+
103
+ ### Resolve Dependencies Directly
104
+
105
+ ```python
106
+ from app.container import container
107
+ from app.services import GreetingService
108
+
109
+
110
+ service = container.resolve(GreetingService)
111
+
112
+ if __name__ == "__main__":
113
+ print(service.greet("World"))
114
+ ```
115
+
116
+ ### Inject Into Functions (`app/main.py`)
117
+
118
+ ```python
119
+ from anydi import Inject
120
+
121
+ from app.container import container
122
+ from app.services import GreetingService
123
+
124
+
125
+ @container.inject
126
+ def greet(service: GreetingService = Inject()) -> str:
127
+ return service.greet("World")
128
+
129
+
130
+ if __name__ == "__main__":
131
+ print(greet())
132
+ ```
133
+
134
+ ### Test with Overrides (`tests/test_app.py`)
135
+
136
+ ```python
137
+ from unittest import mock
138
+
139
+ from app.container import container
140
+ from app.services import GreetingService
141
+ from app.main import greet
142
+
143
+
144
+ def test_greet() -> None:
145
+ service_mock = mock.Mock(spec=GreetingService)
146
+ service_mock.greet.return_value = "Mocked"
147
+
148
+ with container.override(GreetingService, service_mock):
149
+ result = greet()
150
+
151
+ assert result == "Mocked"
152
+ ```
153
+
154
+ ### Integrate with FastAPI (`app/api.py`)
155
+
156
+ ```python
157
+ from typing import Annotated
158
+
159
+ import anydi.ext.fastapi
160
+ from fastapi import FastAPI
161
+ from anydi.ext.fastapi import Inject
162
+
163
+ from app.container import container
164
+ from app.services import GreetingService
165
+
166
+
167
+ app = FastAPI()
168
+
169
+
170
+ @app.get("/greeting")
171
+ async def greet(
172
+ service: Annotated[GreetingService, Inject()]
173
+ ) -> dict[str, str]:
174
+ return {"greeting": service.greet("World")}
175
+
176
+
177
+ anydi.ext.fastapi.install(app, container)
178
+ ```
179
+
180
+ ### Test the FastAPI Integration (`test_api.py`)
181
+
182
+ ```python
183
+ from unittest import mock
184
+
185
+ from fastapi.testclient import TestClient
186
+
187
+ from app.api import app
188
+ from app.container import container
189
+ from app.services import GreetingService
190
+
191
+
192
+ client = TestClient(app)
193
+
194
+
195
+ def test_api_greeting() -> None:
196
+ service_mock = mock.Mock(spec=GreetingService)
197
+ service_mock.greet.return_value = "Mocked"
198
+
199
+ with container.override(GreetingService, service_mock):
200
+ response = client.get("/greeting")
201
+
202
+ assert response.json() == {"greeting": "Mocked"}
203
+ ```
204
+
205
+ ### Integrate with Django Ninja
206
+
207
+ Install the Django integration extras:
208
+
209
+ ```sh
210
+ pip install 'anydi-django[ninja]'
211
+ ```
212
+
213
+ Expose the container factory (`app/container.py`):
214
+
215
+ ```python
216
+ from anydi import Container
217
+
218
+ from app.services import GreetingService
219
+
220
+
221
+ container = Container()
222
+
223
+
224
+ @container.provider(scope="singleton")
225
+ def service() -> GreetingService:
226
+ return GreetingService()
227
+ ```
228
+
229
+ Configure Django (`settings.py`):
230
+
231
+ ```python
232
+ INSTALLED_APPS = [
233
+ ...
234
+ "anydi_django",
235
+ ]
236
+
237
+ ANYDI = {
238
+ "CONTAINER_FACTORY": "app.container.container",
239
+ "PATCH_NINJA": True,
240
+ }
241
+ ```
242
+
243
+ Wire Django Ninja (`urls.py`):
244
+
245
+ ```python
246
+ from typing import Annotated, Any
247
+
248
+ from anydi import Inject
249
+ from django.http import HttpRequest
250
+ from django.urls import path
251
+ from ninja import NinjaAPI
252
+
253
+ from app.services import GreetingService
254
+
255
+
256
+ api = NinjaAPI()
257
+
258
+
259
+ @api.get("/greeting")
260
+ def greet(request: HttpRequest, service: Annotated[GreetingService, Inject()]) -> Any:
261
+ return {"greeting": service.greet("World")}
262
+
263
+
264
+ urlpatterns = [
265
+ path("api/", api.urls),
266
+ ]
267
+ ```
anydi-0.56.0/README.md ADDED
@@ -0,0 +1,232 @@
1
+ # AnyDI
2
+
3
+ <div style="text-align: center;">
4
+
5
+ Modern, lightweight Dependency Injection library using type annotations.
6
+
7
+ [![CI](https://github.com/antonrh/anydi/actions/workflows/ci.yml/badge.svg)](https://github.com/antonrh/anydi/actions/workflows/ci.yml)
8
+ [![Coverage](https://codecov.io/gh/antonrh/anydi/branch/main/graph/badge.svg)](https://codecov.io/gh/antonrh/anydi)
9
+ [![Documentation](https://readthedocs.org/projects/anydi/badge/?version=latest)](https://anydi.readthedocs.io/en/latest/)
10
+
11
+ </div>
12
+
13
+ ---
14
+ Documentation
15
+
16
+ http://anydi.readthedocs.io/
17
+
18
+ ---
19
+
20
+ `AnyDI` is a modern, lightweight Dependency Injection library suitable for any synchronous or asynchronous applications with Python 3.10+, based on type annotations ([PEP 484](https://peps.python.org/pep-0484/)).
21
+
22
+ The key features are:
23
+
24
+ * **Type-safe**: Dependency resolution is driven by type hints.
25
+ * **Async-ready**: Works the same for sync and async providers or injections.
26
+ * **Scoped**: Built-in singleton, transient, and request lifetimes.
27
+ * **Simple**: Small surface area keeps boilerplate low.
28
+ * **Fast**: Resolver still adds only microseconds of overhead.
29
+ * **Named**: `Annotated[...]` makes multiple bindings per type simple.
30
+ * **Managed**: Providers can open/close resources via context managers.
31
+ * **Modular**: Compose containers or modules for large apps.
32
+ * **Scanning**: Auto-discovers injectable callables.
33
+ * **Integrated**: Extensions for popular frameworks.
34
+ * **Testable**: Override providers directly in tests.
35
+
36
+ ## Installation
37
+
38
+ ```shell
39
+ pip install anydi
40
+ ```
41
+
42
+ ## Comprehensive Example
43
+
44
+ ### Define a Service (`app/services.py`)
45
+
46
+ ```python
47
+ class GreetingService:
48
+ def greet(self, name: str) -> str:
49
+ return f"Hello, {name}!"
50
+ ```
51
+
52
+ ### Create the Container and Providers (`app/container.py`)
53
+
54
+ ```python
55
+ from anydi import Container
56
+
57
+ from app.services import GreetingService
58
+
59
+
60
+ container = Container()
61
+
62
+
63
+ @container.provider(scope="singleton")
64
+ def service() -> GreetingService:
65
+ return GreetingService()
66
+ ```
67
+
68
+ ### Resolve Dependencies Directly
69
+
70
+ ```python
71
+ from app.container import container
72
+ from app.services import GreetingService
73
+
74
+
75
+ service = container.resolve(GreetingService)
76
+
77
+ if __name__ == "__main__":
78
+ print(service.greet("World"))
79
+ ```
80
+
81
+ ### Inject Into Functions (`app/main.py`)
82
+
83
+ ```python
84
+ from anydi import Inject
85
+
86
+ from app.container import container
87
+ from app.services import GreetingService
88
+
89
+
90
+ @container.inject
91
+ def greet(service: GreetingService = Inject()) -> str:
92
+ return service.greet("World")
93
+
94
+
95
+ if __name__ == "__main__":
96
+ print(greet())
97
+ ```
98
+
99
+ ### Test with Overrides (`tests/test_app.py`)
100
+
101
+ ```python
102
+ from unittest import mock
103
+
104
+ from app.container import container
105
+ from app.services import GreetingService
106
+ from app.main import greet
107
+
108
+
109
+ def test_greet() -> None:
110
+ service_mock = mock.Mock(spec=GreetingService)
111
+ service_mock.greet.return_value = "Mocked"
112
+
113
+ with container.override(GreetingService, service_mock):
114
+ result = greet()
115
+
116
+ assert result == "Mocked"
117
+ ```
118
+
119
+ ### Integrate with FastAPI (`app/api.py`)
120
+
121
+ ```python
122
+ from typing import Annotated
123
+
124
+ import anydi.ext.fastapi
125
+ from fastapi import FastAPI
126
+ from anydi.ext.fastapi import Inject
127
+
128
+ from app.container import container
129
+ from app.services import GreetingService
130
+
131
+
132
+ app = FastAPI()
133
+
134
+
135
+ @app.get("/greeting")
136
+ async def greet(
137
+ service: Annotated[GreetingService, Inject()]
138
+ ) -> dict[str, str]:
139
+ return {"greeting": service.greet("World")}
140
+
141
+
142
+ anydi.ext.fastapi.install(app, container)
143
+ ```
144
+
145
+ ### Test the FastAPI Integration (`test_api.py`)
146
+
147
+ ```python
148
+ from unittest import mock
149
+
150
+ from fastapi.testclient import TestClient
151
+
152
+ from app.api import app
153
+ from app.container import container
154
+ from app.services import GreetingService
155
+
156
+
157
+ client = TestClient(app)
158
+
159
+
160
+ def test_api_greeting() -> None:
161
+ service_mock = mock.Mock(spec=GreetingService)
162
+ service_mock.greet.return_value = "Mocked"
163
+
164
+ with container.override(GreetingService, service_mock):
165
+ response = client.get("/greeting")
166
+
167
+ assert response.json() == {"greeting": "Mocked"}
168
+ ```
169
+
170
+ ### Integrate with Django Ninja
171
+
172
+ Install the Django integration extras:
173
+
174
+ ```sh
175
+ pip install 'anydi-django[ninja]'
176
+ ```
177
+
178
+ Expose the container factory (`app/container.py`):
179
+
180
+ ```python
181
+ from anydi import Container
182
+
183
+ from app.services import GreetingService
184
+
185
+
186
+ container = Container()
187
+
188
+
189
+ @container.provider(scope="singleton")
190
+ def service() -> GreetingService:
191
+ return GreetingService()
192
+ ```
193
+
194
+ Configure Django (`settings.py`):
195
+
196
+ ```python
197
+ INSTALLED_APPS = [
198
+ ...
199
+ "anydi_django",
200
+ ]
201
+
202
+ ANYDI = {
203
+ "CONTAINER_FACTORY": "app.container.container",
204
+ "PATCH_NINJA": True,
205
+ }
206
+ ```
207
+
208
+ Wire Django Ninja (`urls.py`):
209
+
210
+ ```python
211
+ from typing import Annotated, Any
212
+
213
+ from anydi import Inject
214
+ from django.http import HttpRequest
215
+ from django.urls import path
216
+ from ninja import NinjaAPI
217
+
218
+ from app.services import GreetingService
219
+
220
+
221
+ api = NinjaAPI()
222
+
223
+
224
+ @api.get("/greeting")
225
+ def greet(request: HttpRequest, service: Annotated[GreetingService, Inject()]) -> Any:
226
+ return {"greeting": service.greet("World")}
227
+
228
+
229
+ urlpatterns = [
230
+ path("api/", api.urls),
231
+ ]
232
+ ```
@@ -671,13 +671,17 @@ class Container:
671
671
  ) -> None:
672
672
  self._scanner.scan(packages=packages, tags=tags)
673
673
 
674
- # == Testing ==
674
+ # == Testing / Override Support ==
675
675
 
676
676
  @contextlib.contextmanager
677
677
  def override(self, interface: Any, instance: Any) -> Iterator[None]:
678
- raise RuntimeError(
679
- "Dependency overriding is not supported in this container.\n"
680
- "Wrap your container with `anydi.testing.Container` instead.\n"
681
- "Example:\n\n"
682
- " container = TestContainer.from_container(container)"
683
- )
678
+ """Override a dependency with a specific instance for testing."""
679
+ if not self.has_provider_for(interface):
680
+ raise LookupError(
681
+ f"The provider interface `{type_repr(interface)}` not registered."
682
+ )
683
+ self._resolver.add_override(interface, instance)
684
+ try:
685
+ yield
686
+ finally:
687
+ self._resolver.remove_override(interface)