From 1fb5ac3ef5d8ea3e8105e2365b193ee39b975e7b Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Mon, 11 Nov 2024 20:49:16 +0200 Subject: [PATCH] A lot of changes --- README.md | 23 ++---- docs/quickstart.rst | 101 +++++++------------------- docs/quickstart_example.py | 55 ++++++++++++++ tests/unit/test_quickstart_example.py | 8 ++ 4 files changed, 99 insertions(+), 88 deletions(-) create mode 100644 docs/quickstart_example.py create mode 100644 tests/unit/test_quickstart_example.py diff --git a/README.md b/README.md index 88aa9fe7..9fd80446 100644 --- a/README.md +++ b/README.md @@ -57,16 +57,9 @@ class SomeClient: ... ``` -4. Create Provider instance. It is only used to setup all factories providing your objects. +3. Create Provider instance and setup how to provide dependencies. -```python -from dishka import Provider - -provider = Provider() -``` - - -5. Setup how to provide dependencies. +Providers are only used to setup all factories providing your objects. We use `scope=Scope.APP` for dependencies which are created only once in application lifetime, and `scope=Scope.REQUEST` for those which should be recreated for each processing request/event/etc. @@ -89,12 +82,12 @@ from dishka import Provider, provide, Scope class ConnectionProvider(Provider): @provide(scope=Scope.REQUEST) def new_connection(self) -> Iterable[Connection]: - conn = sqlite3.connect() + conn = sqlite3.connect(":memory:") yield conn conn.close() ``` -6. Create main `Container` instance passing providers, and step into `APP` scope. +4. Create main `Container` instance passing providers, and step into `APP` scope. ```python from dishka import make_container @@ -102,7 +95,7 @@ from dishka import make_container container = make_container(service_provider, ConnectionProvider()) ``` -7. Container holds dependencies cache and is used to retrieve them. Here, you can use `.get` method to access APP-scoped dependencies: +5. Container holds dependencies cache and is used to retrieve them. Here, you can use `.get` method to access APP-scoped dependencies: ```python client = container.get(SomeClient) # `SomeClient` has Scope.APP, so it is accessible here @@ -110,7 +103,7 @@ client = container.get(SomeClient) # same instance of `SomeClient` ``` -8. You can enter and exit `REQUEST` scope multiple times after that using context manager: +6. You can enter and exit `REQUEST` scope multiple times after that using context manager: ```python # subcontainer to access more short-living objects @@ -125,13 +118,13 @@ with container() as request_container: ``` -9. Close container in the end: +7. Close container in the end: ```python container.close() ``` -10. If you are using supported framework add decorators and middleware for it. +8. If you are using supported framework add decorators and middleware for it. For more details see [integrations doc](https://dishka.readthedocs.io/en/latest/integrations/index.html) ```python diff --git a/docs/quickstart.rst b/docs/quickstart.rst index bce8ed56..d1653183 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -9,99 +9,54 @@ Quickstart 2. Write your classes, fill type hints. Imagine, you have two classes: Service (kind of business logic) and DAO (kind of data access) and some external api client: -.. code-block:: python - - class DAO(Protocol): - ... - - class Service: - def __init__(self, dao: DAO): - ... - - class DAOImpl(DAO): - def __init__(self, connection: Connection): - ... - - class SomeClient: - ... - -4. Create Provider instance. It is only used to setup all factories providing your objects. - -.. code-block:: python - - from dishka import Provider - - provider = Provider() +.. literalinclude:: ./quickstart_example.py + :language: python + :lines: 6-18 +3. Create Provider instance and setup how to provide dependencies. -5. Setup how to provide dependencies. +Providers are only used to setup all factories providing your objects. We use ``scope=Scope.APP`` for dependencies which are created only once in application lifetime, and ``scope=Scope.REQUEST`` for those which should be recreated for each processing request/event/etc. To read more about scopes, refer :ref:`scopes` -.. code-block:: python - - from dishka import Provider, Scope - - service_provider = Provider(scope=Scope.REQUEST) - service_provider.provide(Service) - service_provider.provide(DAOImpl, provides=DAO) - service_provider.provide(SomeClient, scope=Scope.APP) # override provider scope +.. literalinclude:: ./quickstart_example.py + :language: python + :lines: 20-25 To provide connection we might need to write some custom code: -.. code-block:: python +.. literalinclude:: ./quickstart_example.py + :language: python + :lines: 27-34 - from dishka import Provider, provide, Scope +4. Create main ``Container`` instance passing providers, and step into ``APP`` scope. - class ConnectionProvider(Provider): - @provide(scope=Scope.REQUEST) - def new_connection(self) -> Iterable[Connection]: - conn = sqlite3.connect() - yield conn - conn.close() +.. literalinclude:: ./quickstart_example.py + :language: python + :lines: 37-39 +5. Container holds dependencies cache and is used to retrieve them. Here, you can use ``.get`` method to access APP-scoped dependencies: -6. Create main ``Container`` instance passing providers, and step into ``APP`` scope. - -.. code-block:: python - - from dishka import make_container - - container = make_container(service_provider, ConnectionProvider()) - -7. Container holds dependencies cache and is used to retrieve them. Here, you can use ``.get`` method to access APP-scoped dependencies: - -.. code-block:: python - - client = container.get(SomeClient) # `SomeClient` has Scope.APP, so it is accessible here - client = container.get(SomeClient) # same instance of `SomeClient` +.. literalinclude:: ./quickstart_example.py + :language: python + :lines: 41-42 -8. You can enter and exit ``REQUEST`` scope multiple times after that using context manager: +6. You can enter and exit ``REQUEST`` scope multiple times after that using context manager: -.. code-block:: python - - # subcontainer to access more short-living objects - with container() as request_container: - service = request_container.get(Service) - service = request_container.get(Service) # same service instance - # at this point connection will be closed as we exited context manager - - # new subcontainer to have a new lifespan for request processing - with container() as request_container: - service = request_container.get(Service) # new service instance - - -9. Close container in the end: - -.. code-block:: python +.. literalinclude:: ./quickstart_example.py + :language: python + :lines: 45-53 - container.close() +7. Close container in the end: +.. literalinclude:: ./quickstart_example.py + :language: python + :lines: 55 -10. If you are using supported framework add decorators and middleware for it. +8. If you are using supported framework add decorators and middleware for it. For more details see :ref:`integrations` .. code-block:: python diff --git a/docs/quickstart_example.py b/docs/quickstart_example.py new file mode 100644 index 00000000..14026429 --- /dev/null +++ b/docs/quickstart_example.py @@ -0,0 +1,55 @@ +import sqlite3 + +from typing import Protocol, Iterable +from sqlite3 import Connection + +class DAO(Protocol): + ... + +class Service: + def __init__(self, dao: DAO): + ... + +class DAOImpl(DAO): + def __init__(self, connection: Connection): + ... + +class SomeClient: + ... + +from dishka import Provider, Scope + +service_provider = Provider(scope=Scope.REQUEST) +service_provider.provide(Service) +service_provider.provide(DAOImpl, provides=DAO) +service_provider.provide(SomeClient, scope=Scope.APP) # override provider scope + +from dishka import Provider, provide, Scope + +class ConnectionProvider(Provider): + @provide(scope=Scope.REQUEST) + def new_connection(self) -> Iterable[Connection]: + conn = sqlite3.connect(":memory:") + yield conn + conn.close() + + +from dishka import make_container + +container = make_container(service_provider, ConnectionProvider()) + +client = container.get(SomeClient) # `SomeClient` has Scope.APP, so it is accessible here +client = container.get(SomeClient) # same instace of `SomeClient` + + +# subcotaniner to access more short-living objects +with container() as request_container: + service = request_container.get(Service) + service = request_container.get(Service) # same service instance +# at this point connection will be closed as we exited context manager + +# new subcontainer to have a new lifespan for request processing +with container() as request_container: + service = request_container.get(Service) # new service instance + +container.close() diff --git a/tests/unit/test_quickstart_example.py b/tests/unit/test_quickstart_example.py new file mode 100644 index 00000000..21152e14 --- /dev/null +++ b/tests/unit/test_quickstart_example.py @@ -0,0 +1,8 @@ +import runpy +from pathlib import Path + +ROOT = Path(__file__).parent.parent.parent.resolve() +QUICKSTART_EXAMPLE_PATH = ROOT / "docs/quickstart_example.py" + +def test_readme_example(): + runpy.run_path(QUICKSTART_EXAMPLE_PATH)