Skip to content

Commit 9d69943

Browse files
committed
Merge branch 'release/4.48.0'
2 parents 8460465 + dd84a1b commit 9d69943

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1051
-398
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
8+
[*.{py,pyi,pxd,pyx}]
9+
ij_visual_guides = 80,88

docs/api/asgi-lifespan.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
dependency_injector.ext.starlette
2+
=================================
3+
4+
.. automodule:: dependency_injector.ext.starlette
5+
:members:
6+
:inherited-members:
7+
:show-inheritance:
8+
9+
.. disqus::

docs/api/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ API Documentation
22
=================
33

44
.. toctree::
5-
:maxdepth: 2
5+
:maxdepth: 2
66

77
top-level
88
providers
99
containers
1010
wiring
1111
errors
12+
asgi-lifespan

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
#
7373
# This is also used if you do content translation via gettext catalogs.
7474
# Usually you set "language" from the command line for these cases.
75-
language = None
75+
language = "en"
7676

7777
# There are two options for replacing |today|: either, you set today to some
7878
# non-false value, then it is used:

docs/examples/django.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Container is wired to the ``views`` module in the app config ``web/apps.py``:
7878

7979
.. literalinclude:: ../../examples/miniapps/django/web/apps.py
8080
:language: python
81-
:emphasize-lines: 13
81+
:emphasize-lines: 12
8282

8383
Tests
8484
-----

docs/examples/fastdepends.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
.. _fastdepends-example:
2+
3+
FastDepends example
4+
===================
5+
6+
.. meta::
7+
:keywords: Python,Dependency Injection,FastDepends,Example
8+
:description: This example demonstrates a usage of the FastDepends and Dependency Injector.
9+
10+
11+
This example demonstrates how to use ``Dependency Injector`` with `FastDepends <https://github.com/Lancetnik/FastDepends>`_, a lightweight dependency injection framework inspired by FastAPI's dependency system, but without the web framework components.
12+
13+
Basic Usage
14+
-----------
15+
16+
The integration between FastDepends and Dependency Injector is straightforward. Simply use Dependency Injector's ``Provide`` marker within FastDepends' ``Depends`` function:
17+
18+
.. code-block:: python
19+
20+
import sys
21+
22+
from dependency_injector import containers, providers
23+
from dependency_injector.wiring import inject, Provide
24+
from fast_depends import Depends
25+
26+
27+
class CoefficientService:
28+
@staticmethod
29+
def get_coefficient() -> float:
30+
return 1.2
31+
32+
33+
class Container(containers.DeclarativeContainer):
34+
service = providers.Factory(CoefficientService)
35+
36+
37+
@inject
38+
def apply_coefficient(
39+
a: int,
40+
coefficient_provider: CoefficientService = Depends(Provide[Container.service]),
41+
) -> float:
42+
return a * coefficient_provider.get_coefficient()
43+
44+
45+
container = Container()
46+
container.wire(modules=[sys.modules[__name__]])
47+
48+
apply_coefficient(100) == 120.0

docs/examples/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ Explore the examples to see the ``Dependency Injector`` in action.
2222
fastapi
2323
fastapi-redis
2424
fastapi-sqlalchemy
25+
fastdepends
2526

2627
.. disqus::

docs/introduction/key_features.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Key features of the ``Dependency Injector``:
3131

3232
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
3333

34-
.. code-block:: plain
34+
.. code-block:: text
3535
3636
Explicit is better than implicit
3737

docs/main/changelog.rst

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,31 @@ that were made in every particular version.
77
From version 0.7.6 *Dependency Injector* framework strictly
88
follows `Semantic versioning`_
99

10+
4.48.0
11+
------
12+
13+
- Improve performance of wiring (`#897 <https://github.com/ets-labs/python-dependency-injector/pull/897>`_)
14+
- Add Context Manager support to Resource provider (`#899 <https://github.com/ets-labs/python-dependency-injector/pull/899>`_)
15+
- Add support for async generator injections (`#900 <https://github.com/ets-labs/python-dependency-injector/pull/900>`_)
16+
- Fix unintended dependency on ``typing_extensions`` (`#902 <https://github.com/ets-labs/python-dependency-injector/pull/902>`_)
17+
- Add support for Fast Depends (`#898 <https://github.com/ets-labs/python-dependency-injector/pull/898>`_)
18+
- Add ``resource_type`` parameter to init and shutdown resources using specialized providers (`#858 <https://github.com/ets-labs/python-dependency-injector/pull/858>`_)
19+
20+
4.47.1
21+
------
22+
23+
- Fix typing for wiring marker (`#892 <https://github.com/ets-labs/python-dependency-injector/pull/896>`_)
24+
- Strip debug symbols in wheels
25+
1026
4.47.0
11-
-------
27+
------
1228

1329
- Add support for ``Annotated`` type for module and class attribute injection in wiring,
1430
with updated documentation and examples.
1531
See discussion:
1632
https://github.com/ets-labs/python-dependency-injector/pull/721#issuecomment-2025263718
17-
- Fix ``root`` property shadowing in ``ConfigurationOption`` (`#875 https://github.com/ets-labs/python-dependency-injector/pull/875`_)
18-
- Fix incorrect monkeypatching during ``wire()`` that could violate MRO in some classes (`#886 https://github.com/ets-labs/python-dependency-injector/pull/886`_)
33+
- Fix ``root`` property shadowing in ``ConfigurationOption`` (`#875 <https://github.com/ets-labs/python-dependency-injector/pull/875>`_)
34+
- Fix incorrect monkeypatching during ``wire()`` that could violate MRO in some classes (`#886 <https://github.com/ets-labs/python-dependency-injector/pull/886>`_)
1935
- ABI3 wheels are now published for CPython.
2036
- Drop support of Python 3.7.
2137

@@ -371,8 +387,8 @@ Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
371387
- Make refactoring of wiring module and tests.
372388
See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_.
373389
Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution:
374-
- Remove unused imports in tests.
375-
- Use literal syntax to create data structure in tests.
390+
- Remove unused imports in tests.
391+
- Use literal syntax to create data structure in tests.
376392
- Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_.
377393

378394
4.26.0

docs/providers/resource.rst

Lines changed: 166 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,12 @@ When you call ``.shutdown()`` method on a resource provider, it will remove the
6161
if any, and switch to uninitialized state. Some of resource initializer types support specifying custom
6262
resource shutdown.
6363

64-
Resource provider supports 3 types of initializers:
64+
Resource provider supports 4 types of initializers:
6565

6666
- Function
67-
- Generator
68-
- Subclass of ``resources.Resource``
67+
- Context Manager
68+
- Generator (legacy)
69+
- Subclass of ``resources.Resource`` (legacy)
6970

7071
Function initializer
7172
--------------------
@@ -103,8 +104,44 @@ you configure global resource:
103104
104105
Function initializer does not provide a way to specify custom resource shutdown.
105106

106-
Generator initializer
107-
---------------------
107+
Context Manager initializer
108+
---------------------------
109+
110+
This is an extension to the Function initializer. Resource provider automatically detects if the initializer returns a
111+
context manager and uses it to manage the resource lifecycle.
112+
113+
.. code-block:: python
114+
115+
from dependency_injector import containers, providers
116+
117+
class DatabaseConnection:
118+
def __init__(self, host, port, user, password):
119+
self.host = host
120+
self.port = port
121+
self.user = user
122+
self.password = password
123+
124+
def __enter__(self):
125+
print(f"Connecting to {self.host}:{self.port} as {self.user}")
126+
return self
127+
128+
def __exit__(self, exc_type, exc_val, exc_tb):
129+
print("Closing connection")
130+
131+
132+
class Container(containers.DeclarativeContainer):
133+
134+
config = providers.Configuration()
135+
db = providers.Resource(
136+
DatabaseConnection,
137+
host=config.db.host,
138+
port=config.db.port,
139+
user=config.db.user,
140+
password=config.db.password,
141+
)
142+
143+
Generator initializer (legacy)
144+
------------------------------
108145

109146
Resource provider can use 2-step generators:
110147

@@ -154,8 +191,13 @@ object is not mandatory. You can leave ``yield`` statement empty:
154191
argument2=...,
155192
)
156193
157-
Subclass initializer
158-
--------------------
194+
.. note::
195+
196+
Generator initializers are automatically wrapped with ``contextmanager`` or ``asynccontextmanager`` decorator when
197+
provided to a ``Resource`` provider.
198+
199+
Subclass initializer (legacy)
200+
-----------------------------
159201

160202
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
161203

@@ -210,6 +252,72 @@ first argument.
210252
211253
.. _resource-provider-wiring-closing:
212254

255+
Scoping Resources using specialized subclasses
256+
----------------------------------------------
257+
258+
You can use specialized subclasses of ``Resource`` provider to initialize and shutdown resources by type.
259+
Allowing for example to only initialize a subgroup of resources.
260+
261+
.. code-block:: python
262+
263+
class ScopedResource(resources.Resource):
264+
pass
265+
266+
def init_service(name) -> Service:
267+
print(f"Init {name}")
268+
yield Service()
269+
print(f"Shutdown {name}")
270+
271+
class Container(containers.DeclarativeContainer):
272+
273+
scoped = ScopedResource(
274+
init_service,
275+
"scoped",
276+
)
277+
278+
generic = providers.Resource(
279+
init_service,
280+
"generic",
281+
)
282+
283+
284+
To initialize resources by type you can use ``init_resources(resource_type)`` and ``shutdown_resources(resource_type)``
285+
methods adding the resource type as an argument:
286+
287+
.. code-block:: python
288+
289+
def main():
290+
container = Container()
291+
container.init_resources(ScopedResource)
292+
# Generates:
293+
# >>> Init scoped
294+
295+
container.shutdown_resources(ScopedResource)
296+
# Generates:
297+
# >>> Shutdown scoped
298+
299+
300+
And to initialize all resources you can use ``init_resources()`` and ``shutdown_resources()`` without arguments:
301+
302+
.. code-block:: python
303+
304+
def main():
305+
container = Container()
306+
container.init_resources()
307+
# Generates:
308+
# >>> Init scoped
309+
# >>> Init generic
310+
311+
container.shutdown_resources()
312+
# Generates:
313+
# >>> Shutdown scoped
314+
# >>> Shutdown generic
315+
316+
317+
It works using the ``traverse()`` method to find all resources of the specified type, selecting all resources
318+
which are instances of the specified type.
319+
320+
213321
Resources, wiring, and per-function execution scope
214322
---------------------------------------------------
215323

@@ -263,10 +371,11 @@ Asynchronous function initializer:
263371
argument2=...,
264372
)
265373
266-
Asynchronous generator initializer:
374+
Asynchronous Context Manager initializer:
267375

268376
.. code-block:: python
269377
378+
@asynccontextmanager
270379
async def init_async_resource(argument1=..., argument2=...):
271380
connection = await connect()
272381
yield connection
@@ -358,5 +467,54 @@ See also:
358467
- Wiring :ref:`async-injections-wiring`
359468
- :ref:`fastapi-redis-example`
360469

470+
ASGI Lifespan Protocol Support
471+
------------------------------
472+
473+
The :mod:`dependency_injector.ext.starlette` module provides a :class:`~dependency_injector.ext.starlette.Lifespan`
474+
class that integrates resource providers with ASGI applications using the `Lifespan Protocol`_. This allows resources to
475+
be automatically initialized at application startup and properly shut down when the application stops.
476+
477+
.. code-block:: python
478+
479+
from contextlib import asynccontextmanager
480+
from dependency_injector import containers, providers
481+
from dependency_injector.wiring import Provide, inject
482+
from dependency_injector.ext.starlette import Lifespan
483+
from fastapi import FastAPI, Request, Depends, APIRouter
484+
485+
class Connection: ...
486+
487+
@asynccontextmanager
488+
async def init_database():
489+
print("opening database connection")
490+
yield Connection()
491+
print("closing database connection")
492+
493+
router = APIRouter()
494+
495+
@router.get("/")
496+
@inject
497+
async def index(request: Request, db: Connection = Depends(Provide["db"])):
498+
# use the database connection here
499+
return "OK!"
500+
501+
class Container(containers.DeclarativeContainer):
502+
__self__ = providers.Self()
503+
db = providers.Resource(init_database)
504+
lifespan = providers.Singleton(Lifespan, __self__)
505+
app = providers.Singleton(FastAPI, lifespan=lifespan)
506+
_include_router = providers.Resource(
507+
app.provided.include_router.call(),
508+
router,
509+
)
510+
511+
if __name__ == "__main__":
512+
import uvicorn
513+
514+
container = Container()
515+
app = container.app()
516+
uvicorn.run(app, host="localhost", port=8000)
517+
518+
.. _Lifespan Protocol: https://asgi.readthedocs.io/en/latest/specs/lifespan.html
361519

362520
.. disqus::

docs/tutorials/aiohttp.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ Let's check that it works. Open another terminal session and use ``httpie``:
257257
258258
You should see:
259259

260-
.. code-block:: json
260+
.. code-block:: http
261261
262262
HTTP/1.1 200 OK
263263
Content-Length: 844
@@ -596,7 +596,7 @@ and make a request to the API in the terminal:
596596
597597
You should see:
598598

599-
.. code-block:: json
599+
.. code-block:: http
600600
601601
HTTP/1.1 200 OK
602602
Content-Length: 492

0 commit comments

Comments
 (0)