mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-25 02:53:56 +03:00
Add Starlette lifespan handler implementation
This commit is contained in:
parent
2c998b8448
commit
fa0f0f0208
39
examples/miniapps/starlette-lifespan/README.rst
Normal file
39
examples/miniapps/starlette-lifespan/README.rst
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
Integration With Starlette-based Frameworks
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
This is a `Starlette <https://www.starlette.io/>`_ +
|
||||||
|
`Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application
|
||||||
|
utilizing `lifespan API <https://www.starlette.io/lifespan/>`_.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Pretty much `any framework built on top of Starlette <https://www.starlette.io/third-party-packages/#frameworks>`_
|
||||||
|
supports this feature (`FastAPI <https://fastapi.tiangolo.com/advanced/events/#lifespan>`_,
|
||||||
|
`Xpresso <https://xpresso-api.dev/latest/tutorial/lifespan/>`_, etc...).
|
||||||
|
|
||||||
|
Run
|
||||||
|
---
|
||||||
|
|
||||||
|
Create virtual environment:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python -m venv env
|
||||||
|
. env/bin/activate
|
||||||
|
|
||||||
|
Install requirements:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
To run the application do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python example.py
|
||||||
|
# or (logging won't be configured):
|
||||||
|
uvicorn --factory example:container.app
|
||||||
|
|
||||||
|
After that visit http://127.0.0.1:8000/ in your browser or use CLI command (``curl``, ``httpie``,
|
||||||
|
etc).
|
59
examples/miniapps/starlette-lifespan/example.py
Executable file
59
examples/miniapps/starlette-lifespan/example.py
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from logging import basicConfig, getLogger
|
||||||
|
|
||||||
|
from dependency_injector.containers import DeclarativeContainer
|
||||||
|
from dependency_injector.ext.starlette import Lifespan
|
||||||
|
from dependency_injector.providers import Factory, Resource, Self, Singleton
|
||||||
|
from starlette.applications import Starlette
|
||||||
|
from starlette.requests import Request
|
||||||
|
from starlette.responses import JSONResponse
|
||||||
|
from starlette.routing import Route
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
log = getLogger(__name__)
|
||||||
|
log.info("Inittializing resources")
|
||||||
|
yield
|
||||||
|
log.info("Cleaning up resources")
|
||||||
|
|
||||||
|
|
||||||
|
async def homepage(request: Request) -> JSONResponse:
|
||||||
|
global count
|
||||||
|
response = JSONResponse({"hello": "world", "count": count})
|
||||||
|
count += 1
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class Container(DeclarativeContainer):
|
||||||
|
__self__ = Self()
|
||||||
|
lifespan = Singleton(Lifespan, __self__)
|
||||||
|
logging = Resource(
|
||||||
|
basicConfig,
|
||||||
|
level="DEBUG",
|
||||||
|
datefmt="%Y-%m-%d %H:%M",
|
||||||
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||||
|
)
|
||||||
|
init = Resource(init)
|
||||||
|
app = Factory(
|
||||||
|
Starlette,
|
||||||
|
debug=True,
|
||||||
|
lifespan=lifespan,
|
||||||
|
routes=[Route("/", homepage)],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
container.app,
|
||||||
|
factory=True,
|
||||||
|
# NOTE: `None` prevents uvicorn from configuring logging, which is
|
||||||
|
# impossible via CLI
|
||||||
|
log_config=None,
|
||||||
|
)
|
3
examples/miniapps/starlette-lifespan/requirements.txt
Normal file
3
examples/miniapps/starlette-lifespan/requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dependency-injector
|
||||||
|
starlette
|
||||||
|
uvicorn
|
53
src/dependency_injector/ext/starlette.py
Normal file
53
src/dependency_injector/ext/starlette.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import sys
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
from typing import Any, Callable, Coroutine, Optional
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11): # pragma: no cover
|
||||||
|
from typing import Self
|
||||||
|
else: # pragma: no cover
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from dependency_injector.containers import Container
|
||||||
|
|
||||||
|
|
||||||
|
class Lifespan:
|
||||||
|
"""A starlette lifespan handler performing container resource initialization and shutdown.
|
||||||
|
|
||||||
|
See https://www.starlette.io/lifespan/ for details.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from dependency_injector.containers import DeclarativeContainer
|
||||||
|
from dependency_injector.ext.starlette import Lifespan
|
||||||
|
from dependency_injector.providers import Factory, Self, Singleton
|
||||||
|
from starlette.applications import Starlette
|
||||||
|
|
||||||
|
class Container(DeclarativeContainer):
|
||||||
|
__self__ = Self()
|
||||||
|
lifespan = Singleton(Lifespan, __self__)
|
||||||
|
app = Factory(Starlette, lifespan=lifespan)
|
||||||
|
|
||||||
|
:param container: container instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
container: Container
|
||||||
|
|
||||||
|
def __init__(self, container: Container) -> None:
|
||||||
|
self.container = container
|
||||||
|
|
||||||
|
def __call__(self, app: Any) -> Self:
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aenter__(self) -> None:
|
||||||
|
result = self.container.init_resources()
|
||||||
|
|
||||||
|
if result is not None:
|
||||||
|
await result
|
||||||
|
|
||||||
|
async def __aexit__(self, *exc_info: Any) -> None:
|
||||||
|
result = self.container.shutdown_resources()
|
||||||
|
|
||||||
|
if result is not None:
|
||||||
|
await result
|
41
tests/unit/ext/test_starlette.py
Normal file
41
tests/unit/ext/test_starlette.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from typing import AsyncIterator, Iterator
|
||||||
|
from unittest.mock import ANY
|
||||||
|
|
||||||
|
from pytest import mark
|
||||||
|
|
||||||
|
from dependency_injector.containers import DeclarativeContainer
|
||||||
|
from dependency_injector.ext.starlette import Lifespan
|
||||||
|
from dependency_injector.providers import Resource
|
||||||
|
|
||||||
|
|
||||||
|
class TestLifespan:
|
||||||
|
@mark.parametrize("sync", [False, True])
|
||||||
|
@mark.asyncio
|
||||||
|
async def test_context_manager(self, sync: bool) -> None:
|
||||||
|
init, shutdown = False, False
|
||||||
|
|
||||||
|
def sync_resource() -> Iterator[None]:
|
||||||
|
nonlocal init, shutdown
|
||||||
|
|
||||||
|
init = True
|
||||||
|
yield
|
||||||
|
shutdown = True
|
||||||
|
|
||||||
|
async def async_resource() -> AsyncIterator[None]:
|
||||||
|
nonlocal init, shutdown
|
||||||
|
|
||||||
|
init = True
|
||||||
|
yield
|
||||||
|
shutdown = True
|
||||||
|
|
||||||
|
class Container(DeclarativeContainer):
|
||||||
|
x = Resource(sync_resource if sync else async_resource)
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
lifespan = Lifespan(container)
|
||||||
|
|
||||||
|
async with lifespan(ANY) as scope:
|
||||||
|
assert scope is None
|
||||||
|
assert init
|
||||||
|
|
||||||
|
assert shutdown
|
Loading…
Reference in New Issue
Block a user