Skip to content

Commit

Permalink
Merge pull request #210 from reagento/develop
Browse files Browse the repository at this point in the history
v1.3
  • Loading branch information
Tishka17 authored Aug 15, 2024
2 parents 8e91a52 + 17f8636 commit ec0c378
Show file tree
Hide file tree
Showing 107 changed files with 3,158 additions and 450 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/frameworks-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
- "3.10"
- "3.11"
- "3.12"
- "3.13.0-rc.1"

steps:
- uses: actions/checkout@v4
Expand All @@ -33,5 +34,5 @@ jobs:
- name: Run tests
run: |
tox -e aiohttp-latest,fastapi-latest,aiogram-latest,telebot-latest,flask-latest,litestar-latest,starlette-latest,faststream-latest,arq-latest,taskiq-latest,sanic-latest
find requirements -name "*-latest.txt" -exec basename {} .txt \; | xargs -d , tox -e
6 changes: 5 additions & 1 deletion .github/workflows/setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ jobs:
- name: Run ruff
run: |
ruff check .
- name: Run mypy
run: |
mypy src
- name: Run tests
run: |
tox
65 changes: 65 additions & 0 deletions .mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[mypy]
exclude = ^src/dishka/(_adaptix|integrations)/

[mypy-dishka._adaptix.*]
disable_error_code = attr-defined

[mypy-dishka._adaptix.type_tools.normalize_type]
disable_error_code = arg-type

[mypy-pydantic.*]
ignore_missing_imports = True

[mypy-aiogram.*]
ignore_missing_imports = True

[mypy-aiogram_dialog.*]
ignore_missing_imports = True

[mypy-aiohttp.*]
ignore_missing_imports = True

[mypy-arq.*]
ignore_missing_imports = True

[mypy-asgi.*]
ignore_missing_imports = True

[mypy-click.*]
ignore_missing_imports = True

[mypy-fastapi.*]
ignore_missing_imports = True

[mypy-faststream.*]
ignore_missing_imports = True

[mypy-flask.*]
ignore_missing_imports = True

[mypy-grpc.*]
ignore_missing_imports = True

[mypy-grpcio.*]
ignore_missing_imports = True

[mypy-google.*]
ignore_missing_imports = True

[mypy-litestar.*]
ignore_missing_imports = True

[mypy-sanic.*]
ignore_missing_imports = True

[mypy-sanic_routing.*]
ignore_missing_imports = True

[mypy-starlette.*]
ignore_missing_imports = True

[mypy-taskiq.*]
ignore_missing_imports = True

[mypy-telebot.*]
ignore_missing_imports = True
2 changes: 2 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ lint.ignore = [
"SIM108",
"SIM114",
"TRY003",
"TCH003",
"PLW2901",
"RET505",
"PLR0913",
"UP038",
"TCH001",
"SIM103",
]

[lint.per-file-ignores]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ from dishka import Provider
provider = Provider()
```

3. Register functions which provide dependencies. Do not forget to place correct typehints for parameters and result. We use `scope=Scope.APP` for dependencies which ar created only once in application lifetime, and `scope=Scope.REQUEST` for those which should be recreated for each processing request/event/etc.
3. Register functions which provide dependencies. Do not forget to place correct typehints for parameters and result. 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.

```python
from dishka import Provider, Scope
Expand Down Expand Up @@ -240,8 +240,8 @@ from dishka import from_context, Provider, provide, Scope
class MyProvider(Provider):
scope = Scope.REQUEST

app = from_context(provides=App, scope=Scope.APP)
request = from_context(provides=RequestClass)
app = from_context(App, scope=Scope.APP)
request = from_context(RequestClass)

@provide
def get_a(self, request: RequestClass, app: App) -> A:
Expand Down
10 changes: 6 additions & 4 deletions docs/advanced/context.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ Working with context data consists of three parts:
.. code-block:: python
from framework import Request
from dishka import Provider, make_container, Scope, from_context, provide
class MyProvider:
scope=Scope.REQUEST
class MyProvider(Provider):
scope = Scope.REQUEST
# declare source
request = from_context(provides=Request, scope=Scope.REQUEST)
Expand All @@ -33,8 +35,8 @@ Working with context data consists of three parts:
while True:
request = broker.recv()
# provide REQUEST-scoped context variable
with container(context={Request:request}) as request_container:
a = request_container.get(A)
with container(context={Request: request}) as request_container:
a = request_container.get(A)
.. note::

Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/scopes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ In dishka scope determines a lifespan of dependency. Firstly, when creating prov

The set of scopes is defined once per container and providers should use the same scopes. You are not limited to standard scopes and can create custom ones, but it is hardly ever needed.

In most cases you need only 2 scopes. ``APP``-scope is usually entered on container creation and ``REQUEST``-scope is the on you go into during some event processing:
In most cases you need only 2 scopes. ``APP``-scope is usually entered on container creation and ``REQUEST``-scope is the one you go into during some event processing:

``APP`` |rarr| ``REQUEST``

Expand Down
4 changes: 2 additions & 2 deletions docs/advanced/testing/index.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Testing with dishka
***************************

Testing you code does not always require the whole application to be started. You can have unit tests for separate components and even integration tests which check only specific links. In many cases you do not need IoC-container: you create objects with a power of **Dependency Injection** and not framework.
Testing your code does not always require the whole application to be started. You can have unit tests for separate components and even integration tests which check only specific links. In many cases you do not need IoC-container: you create objects with a power of **Dependency Injection** and not framework.

For other cases which require calling functions located on application boundaries you need a container. These cases include testing you view functions with mocks of business logic and testing the application as a whole. Comparing to a production mode you will still have same implementations for some classes and others will be replaced with mocks. Luckily, in ``dishka`` your container is not an implicit global thing and can be replaced easily.
For other cases which require calling functions located on application boundaries you need a container. These cases include testing your view functions with mocks of business logic and testing the application as a whole. Comparing to a production mode you will still have same implementations for some classes and others will be replaced with mocks. Luckily, in ``dishka`` your container is not an implicit global thing and can be replaced easily.

There are many options to make providers with mock objects. If you are using ``pytest`` then you can

Expand Down
6 changes: 3 additions & 3 deletions docs/alternatives.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Fastapi depends provides simple but effective API to inject dependencies, but th
* It can be used only inside fastapi.
* You cannot use it for lazy initialization of singletons
* It mixes up Dependency Injection and Request decomposition. That leads to incorrect openapi specification or even broken app.
* You have to declare each dependencies with ``Depends`` on each level of application. So ether you business logic contains details of IoC-container or you have to duplicate constructor signatures.
* You have to declare each dependency with ``Depends`` on each level of application. So either your business logic contains details of IoC-container or you have to duplicate constructor signatures.
* It is not very fast in runtime, though you might never notice that
* Almost all examples in documentation ignore ``dependency_overrides``, which is actually a main thing to use fastapi as IoC-container.

Expand All @@ -169,14 +169,14 @@ Why not svcs?

On first approach ``dishka`` and ``svcs`` have similar api, but ``svcs`` does much less automation:

1. In svcs all binding between classes is done manually by calling ``container`` inside ach factory. In dishka you can just add class if you have type-hinted its ``__init__``. Additionally, in ``svsc`` you cannot use this information to validate graph or somehow visualize.
1. In svcs all binding between classes is done manually by calling ``container`` inside each factory. In dishka you can just add class if you have type-hinted its ``__init__``. Additionally, in ``svsc`` you cannot use this information to validate graph or somehow visualize.
2. While ``svsc`` caches dependencies there is no scope hierarchy. You can create multiple containers to make lazy singletons, but they are not thread-safe.
3. There are no predefined patterns like multiple providers and class-based providers. So the only way to make your container modular you need to decide how to do it. With ``dishka`` you can reuse ``providers`` making different combinations for different environments or cases.

Why not rodi?
=============================

``Rodi`` is pretty simple and fast. Though is misses most of the useful features.
``Rodi`` is pretty simple and fast. Though it misses most of the useful features.

* It has auto-wiring, but no isolated components.
* No resources finalization. You can somehow track what to finalize using your instance of ``ActivationScope``, but you have to write it on your own.
Expand Down
6 changes: 3 additions & 3 deletions docs/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ Standard scopes are (excluding skipped):
You decide when to enter and exit them, but it is done one by one. If you entered ``APP`` scope then the next step deeper will enter ``REQUEST`` scope.

.. note::
``APP`` scope can be used for lazy initialisation of singletons, while ``REQUEST`` scope is good for processing events like HTTP-requests or messenger updates. It is unlikely that you will need other scopes
``APP`` scope can be used for lazy initialization of singletons, while ``REQUEST`` scope is good for processing events like HTTP-requests or messenger updates. It is unlikely that you will need other scopes


In dishka dependencies are lazy - they are created when you first request them. If the same dependency is requested multiple time within one scope then the same instance is returned (you can disable it for each dependency separately). A created dependency is kept until you exit the scope. And in that moment it is not just dropped away, but corresponding finalization steps are done. You can enter same scope multiple times concurrently so to have multiple instances of objects you can work simultaneously.
In dishka dependencies are lazy - they are created when you first request them. If the same dependency is requested multiple times within one scope then the same instance is returned (you can disable it for each dependency separately). A created dependency is kept until you exit the scope. And in that moment it is not just dropped away, but corresponding finalization steps are done. You can enter same scope multiple times concurrently so to have multiple instances of objects you can work simultaneously.

Each object can depend on other objects from the same or previous scopes. So, if you have ``Config`` with scope of *APP* and ``Connection`` with scope of *REQUEST* you cannot have an *APP*-scoped object which requires a connection, but you can have *REQUEST*-scoped object which requires a ``Connection`` or a ``Config`` (or even both).

Expand Down Expand Up @@ -100,7 +100,7 @@ Component
====================
**Component** - is an isolated group of providers within the same container identified by a string. When dependency is requested it is searched only within the same component as its dependant, unless it is declared explicitly.

This allows you to have multiple parts of application build separately without need to think if the use same types.
This allows you to have multiple parts of application build separately without need to think if they use same types.

.. code-block:: python
Expand Down
16 changes: 8 additions & 8 deletions docs/di_intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Imagine you have a business logic which uses some remote API client
service = Service()
service.action()
Here, the ``client`` is a **dependency**. Imagine that you have many methods working with same client and each methods knows how to create the client. Than think about these question?
Here, the ``client`` is a **dependency**. Imagine that you have many methods working with same client and each method knows how to create the client. Than think about these questions?

* How do they get the ``token``? Should the every method read it on its own?
* What if the ``Client`` constructor will require more than one token? Should we copy-paste new parameters to each method?
Expand Down Expand Up @@ -79,12 +79,12 @@ Additionally I need to name anti-patterns, which should be avoided as they canno

* *Singleton*. It's mostly a variant of global variable. It can add laziness, but other characteristics are the same.

* *Monkey patch*. Or `mock.patch()` as an example. It allows to replace behavior in tests but it also relies on details how the class is imported and used instead of its interface. That make tests more fragile and requires more work to support them
* *Monkey patch*. Or `mock.patch()` as an example. It allows to replace behavior in tests but it also relies on details how the class is imported and used instead of its interface. That makes tests more fragile and requires more work to support them

When to inject dependencies?
===================================

For simple cases it is obvious that you have some classes with their requirements and once you start you app you create all of them and wire together. But real applications are more complicated things. They operate dozens or even hundreds of objects in complex hierarchy, they do concurrent processing.
For simple cases it is obvious that you have some classes with their requirements and once you start your app you create all of them and wire together. But real applications are more complicated things. They operate dozens or even hundreds of objects in complex hierarchy, they do concurrent processing.


It is a good idea to separate the code which uses dependencies and the code which creates them. Usually we want to reduce the knowledge about our dependencies in the code which uses them. But it is not always possible as different objects have different lifespan.
Expand Down Expand Up @@ -125,9 +125,9 @@ The trick is how to manage those dependencies when you have a lot of request han
service = request.state.service
service.action()
It works good. You have clean request handlers and you can change middlewares in tests. But it can become a problem if you have lot's of objects which are not cheap to create.
It works good. You have clean request handlers and you can change middlewares in tests. But it can become a problem if you have lots of objects which are not cheap to create.

* The second approach is to create some factory (let's call it **container**) and call it within request handler. You can still use middleware to pass it into handler (check also others features of your framework)
* The second approach is to create some factory (let's call it **container**) and call it within request handler. You can still use middleware to pass it into handler (check also other features of your framework)

.. code-block:: python
Expand Down Expand Up @@ -157,9 +157,9 @@ In both approaches you can control whether the instance is created on each reque
What is IoC-container?
=============================

IoC-container is a special object (or a framework providing such an object) which provides required objects following dependency injection rules and manages their lifetime. DI-framework is another name fur such frameworks.
IoC-container is a special object (or a framework providing such an object) which provides required objects following dependency injection rules and manages their lifetime. DI-framework is another name for such frameworks.

Common mistake is to treat IoC-container as a single way to inject dependencies. It has nothing common with reality. Dependency injection can be done just by passing one object to another, but in complex application it is not so easy to do. As it was shown above you might want to create a separate object to encapsulate all DI-related logic. ``Container`` in previous example is a an example of hand-written primitive IoC-container.
Common mistake is to treat IoC-container as a single way to inject dependencies. It has nothing common with reality. Dependency injection can be done just by passing one object to another, but in complex application it is not so easy to do. As it was shown above you might want to create a separate object to encapsulate all DI-related logic. ``Container`` in previous example is an example of hand-written primitive IoC-container.

Bigger is your application, more complex factories you need, more necessary is to automate creation of a container. You do not need to use IoC-container to test one small part of application, but it can be essential for launching it in whole. Fortunately, there are frameworks for it. But again, beware of spreading container-related details around your application code with an exception on scope boundaries.

Expand All @@ -172,4 +172,4 @@ So, talking about IoC-container we can write-down these ideas:

More about possible requirements you can read in :ref:`technical-requirements`.

So here is the time for **dishka** - an implementation of IoC-container with everything you need.
So here is the time for **dishka** - an implementation of IoC-container with everything you need.
Loading

0 comments on commit ec0c378

Please sign in to comment.