Repository: reagento/dishka
Pull request #300 (2024-11-08 04:26:20)

Run code from README in tests
* gh-287


Patch:
diff --git a/README.md b/README.md
index 88aa9fe..9fd8044 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/container/index.rst b/docs/container/index.rst
index 94bd501..b192635 100644
--- a/docs/container/index.rst
+++ b/docs/container/index.rst
@@ -46,7 +46,7 @@ or if you created *async* container:
         pass
 
 Container is needed for retrieving objects. To do it you need to call ``get(DependencyType)`` (and ``await`` it for async container).
-All retrieved dependencies are stored inside container of corresponding scope until you exit that scope. So, if you call ``get`` multiple times you will receive the same instance. The rule is followed for indirect dependencies as well. Multiple dependencies of the same scope have their own cache.
+All retrieved dependencies are stored inside container of corresponding scope until you exit that scope. So, you if you call ``get`` multiple times you will receive the same instance. The rule is followed for indirect dependencies as well. Multiple dependencies of the same scope have their own cache.
 
 .. code-block:: python
 
diff --git a/docs/provider/from_context.rst b/docs/provider/from_context.rst
index 67f640a..f129d05 100644
--- a/docs/provider/from_context.rst
+++ b/docs/provider/from_context.rst
@@ -25,7 +25,7 @@ You can put some data manually when entering scope and rely on it in your provid
         pass
 
 
-* Do you want to override the ``from_context``? To do this, specify the parameter ``override=True``. This can be checked when passing proper ``validation_settings`` when creating container.
+* Do you want to override the from_context? To do this, specify the parameter ``override=True``. This can be checked when passing proper ``validation_settings`` when creating container.
 
 .. code-block:: python
 
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index bce8ed5..d165318 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 0000000..1402642
--- /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/src/dishka/integrations/aiogram_dialog.py b/src/dishka/integrations/aiogram_dialog.py
index 05539d5..4d9a49d 100644
--- a/src/dishka/integrations/aiogram_dialog.py
+++ b/src/dishka/integrations/aiogram_dialog.py
@@ -1,12 +1,13 @@
 __all__ = ["inject"]
 
 from collections.abc import Callable
-from typing import Any, Final, TypeVar, cast
+from typing import Any, Final, ParamSpec, TypeVar, cast
 
 from dishka import AsyncContainer
 from dishka.integrations.base import wrap_injection
 
 T = TypeVar("T")
+P = ParamSpec("P")
 TWO: Final = 2
 CONTAINER_NAME: Final = "dishka_container"
 
@@ -25,7 +26,7 @@ def _container_getter(
     return cast(AsyncContainer, container)
 
 
-def inject(func: Callable[..., T]) -> Callable[..., T]:
+def inject(func: Callable[P, T]) -> Callable[P, T]:
     return wrap_injection(
         func=func,
         is_async=True,


Test patch:
diff --git a/tests/unit/test_quickstart_example.py b/tests/unit/test_quickstart_example.py
new file mode 100644
index 0000000..21152e1
--- /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)