Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan committed Jul 27, 2024
1 parent e0a58d7 commit dc72380
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Injection

![PyPI - Version](https://img.shields.io/pypi/v/deps-injection?label=pypi%20version)
![PyPI - Version](https://img.shields.io/pypi/v/deps-injection?label=pypi%20version&color=012111012)
![GitHub License](https://img.shields.io/github/license/nightblure/injection?color=012111012)

![PyPI - Python Version](https://img.shields.io/pypi/pyversions/deps-injection)
Expand Down
2 changes: 2 additions & 0 deletions docs/containers/declarative-container.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Declarative container
soon...
2 changes: 2 additions & 0 deletions docs/containers/resolving.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Resolving
soon...
26 changes: 24 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,43 @@
:maxdepth: 1
:caption: Introduction
introduction/concepts-and-key_features
introduction/concepts-and-features
introduction/installation
.. toctree::
:maxdepth: 1
:caption: Containers
containers/declarative-container
containers/resolving
.. toctree::
:maxdepth: 1
:caption: Providers
providers/transient
providers/callable
providers/partial_callable
providers_coroutine
providers/coroutine
providers/singleton
providers/object
providers/provided_instance
.. toctree::
:maxdepth: 1
:caption: Integration with web frameworks
integration-with-web-frameworks/fastapi
integration-with-web-frameworks/flask
integration-with-web-frameworks/litestart
integration-with-web-frameworks/drf
.. toctree::
:maxdepth: 1
:caption: Testing
testing/provider-overriding
.. toctree::
:maxdepth: 1
:caption: DEV section
Expand Down
2 changes: 2 additions & 0 deletions docs/integration-with-web-frameworks/drf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Django REST Framework
soon...
2 changes: 2 additions & 0 deletions docs/integration-with-web-frameworks/fastapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# FastAPI
soon...
2 changes: 2 additions & 0 deletions docs/integration-with-web-frameworks/flask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Flask
soon..
2 changes: 2 additions & 0 deletions docs/integration-with-web-frameworks/litestart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Litestar
soon..
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
# Concepts and key features
# Concepts and features

With `Injection` you can implement the principle of Dependency Injection. This is needed for ...
## Concept
With `Injection` you can implement the principle of **Dependency Injection**.
**Dependency Injection** (**DI**) - providing a process to an external software component.
This is a specific form of “**inversion of control**” (**IoC**) when applied to dependency management.
In accordance with the verification principle, the responsible entity outsources the construction
of the dependencies it requires from outside, specifically designed for this general mechanism
([Wikipedia source](https://en.wikipedia.org/wiki/Dependency_injection)).

Using this framework you can reduce [coupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming))
and stop monkeypatching your tests.
Instead of monkeypatching you can pass mock objects or any other objects as parameters.

---

## Features

The public API of this framework is almost completely identical to
[Dependency Injector](https://python-dependency-injector.ets-labs.org/index.html#),
because the author found it successful and understandable.
In addition, this will provide an easy migration to the current framework with
[Dependency Injector](https://python-dependency-injector.ets-labs.org/index.html#)
(see [migration from Dependency Injector](https://injection.readthedocs.io/latest/dev/migration-from-dependency-injector.html)).

## Features and advantages
Other features and advantages:

* support **Python 3.8-3.12**;
* works with **FastAPI, Flask, Litestar** and **Django REST Framework**;
Expand All @@ -21,8 +31,4 @@ In addition, this will provide an easy migration to the current framework with
* **multiple containers**;
* providers - delegate object creation and lifecycle management to providers;
* **overriding** dependencies for tests without wiring;
* **100%** code coverage and very simple code;
* good [documentation](https://injection.readthedocs.io/latest/);
* intuitive and almost identical api with [dependency-injector](https://github.com/ets-labs/python-dependency-injector),
which will allow you to easily migrate to injection
(see [migration from dependency injector](https://injection.readthedocs.io/latest/dev/migration-from-dependency-injector.html));
* **100%** code coverage and very simple code.
169 changes: 169 additions & 0 deletions docs/testing/provider-overriding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Provider overriding

DI container provides, in addition to direct dependency injection, another very important functionality:
**dependencies or providers overriding**.

Any provider registered with the container can be overridden.
This can help you replace objects with simple stubs, or with other objects.
**Override affects all providers that use the overridden provider (_see example_)**.

## Example

```python
from pydantic_settings import BaseSettings
from sqlalchemy import create_engine, Engine, text
from testcontainers.postgres import PostgresContainer
from injection import DeclarativeContainer, providers, Provide, inject


class SomeSQLADao:
def __init__(self, *, sqla_engine: Engine):
self.engine = sqla_engine
self._connection = None

def __enter__(self):
self._connection = self.engine.connect()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self._connection.close()

def exec_query(self, query: str):
return self._connection.execute(text(query))


class Settings(BaseSettings):
db_url: str = 'some_production_db_url'


class DIContainer(DeclarativeContainer):
settings = providers.Singleton(Settings)
sqla_engine = providers.Singleton(create_engine, settings.provided.db_url)
some_sqla_dao = providers.Transient(SomeSQLADao, sqla_engine=sqla_engine)


@inject
def exec_query_example(some_sqla_dao=Provide["some_sqla_dao"]):
with some_sqla_dao:
result = some_sqla_dao.exec_query('SELECT 234')

return next(result)


def main():
pg_container = PostgresContainer(image='postgres:alpine3.19')
pg_container.start()
db_url = pg_container.get_connection_url()

"""
We override only settings, but this override will also affect the 'sqla_engine'
and 'some_sqla_dao' providers because the 'settings' provider is used by them!
"""
local_testing_settings = Settings(db_url=db_url)
DIContainer.settings.override(local_testing_settings)

try:
result = exec_query_example()
assert result == (234,)
finally:
DIContainer.settings.reset_override()
pg_container.stop()


if __name__ == '__main__':
main()

```

The example above shows how overriding a nested provider ('_settings_')
affects another provider ('_engine_' and '_some_sqla_dao_').

## Override multiple providers

The example above looked at overriding only one settings provider,
but the container also provides the ability to override
multiple providers at once with method ```override_providers```.

The code above could remain the same except that
the single provider override could be replaced with the following code:

```python
def main():
pg_container = PostgresContainer(image='postgres:alpine3.19')
pg_container.start()
db_url = pg_container.get_connection_url()

local_testing_settings = Settings(db_url=db_url)
providers_for_overriding = {
'settings': local_testing_settings,
# more values...
}
with DIContainer.override_providers(providers_for_overriding):
try:
result = exec_query_example()
assert result == (234,)
finally:
pg_container.stop()
```

## Overriding of singleton provider
If singleton attribute is used in other singleton or resource and this other provider is initialized,
then in case of overriding of the first singleton, second one will be cached with original value.

Same with resetting overriding. Here is an example.

```python
from dataclasses import dataclass

from injection import Provide, DeclarativeContainer, providers, inject

DEFAULT_REDIS_URL = 'url_1'
MOCK_REDIS_URL = 'url_2'


@dataclass
class Settings:
redis_url: str = DEFAULT_REDIS_URL


class Redis:
def __init__(self, url: str):
self.url = url


class DIContainer(DeclarativeContainer):
settings = providers.Singleton(Settings)
redis = providers.Singleton(Redis, url=settings.provided.redis_url)


@inject
def func(redis: Redis = Provide[DIContainer.redis]):
return redis.url


def test_case_1():
DIContainer.settings.override(Settings(redis_url=MOCK_REDIS_URL))

assert func() == MOCK_REDIS_URL

DIContainer.settings.reset_override()
# DIContainer.redis.reset_cache() # FIX OF ASSERTION ERROR

assert func() == DEFAULT_REDIS_URL # ASSERTION ERROR


def test_case_2():
assert DIContainer.redis().url == DEFAULT_REDIS_URL

DIContainer.settings.override(Settings(redis_url=MOCK_REDIS_URL))
# DIContainer.redis.reset_cache() # FIX OF ASSERTION ERROR

redis_url = func()
assert redis_url == MOCK_REDIS_URL # ASSERTION ERROR


if __name__ == "__main__":
test_case_1()
test_case_2()

```

0 comments on commit dc72380

Please sign in to comment.