Compare commits

..

No commits in common. "master" and "3.3.3" have entirely different histories.

714 changed files with 54469 additions and 43442 deletions

7
.coveragerc Normal file
View File

@ -0,0 +1,7 @@
[run]
source = src/dependency_injector
omit = tests/unit
plugins = Cython.Coverage
[html]
directory=reports/unittests/

View File

@ -1,12 +0,0 @@
version = 1
test_patterns = ["tests/**/test_*.py"]
exclude_patterns = ["docs/**"]
[[analyzers]]
name = "python"
enabled = true
[analyzers.meta]
runtime_version = "3.x.x"

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
github: rmk135

View File

@ -1,113 +0,0 @@
name: Publishing
on:
workflow_dispatch:
push:
tags:
- '*'
jobs:
tests:
name: Run tests
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: pip install tox
- run: tox
env:
TOXENV: 3.13
linters:
name: Run linters
runs-on: ubuntu-24.04
strategy:
matrix:
toxenv: [flake8, pydocstyle, mypy, pylint]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: pip install tox
- run: tox
env:
TOXENV: ${{ matrix.toxenv }}
build-sdist:
name: Build source tarball
needs: [tests, linters]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: |
python -m pip install --upgrade build
python -m build --sdist
- uses: actions/upload-artifact@v4
with:
name: cibw-sdist
path: ./dist/*
build-wheels:
name: Build wheels
needs: [tests, linters]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-14]
env:
CIBW_SKIP: cp27-*
steps:
- uses: actions/checkout@v3
- name: Build wheels
uses: pypa/cibuildwheel@v2.20.0
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl
publish:
name: Publish on PyPI
needs: [build-sdist, build-wheels]
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@v4
with:
pattern: cibw-*
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
# For publishing to Test PyPI, uncomment next two lines:
# password: ${{ secrets.TEST_PYPI_API_TOKEN }}
# repository_url: https://test.pypi.org/legacy/
publish-docs:
name: Publish docs
needs: [publish]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: pip install awscli
- run: pip install -r requirements-doc.txt
- run: pip install -e .
- run: (cd docs && make clean html)
- run: |
aws s3 sync docs/_build/html s3://python-dependency-injector-docs --delete
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --path "/*" > /dev/null
echo "Cache invalidation triggered"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}

View File

@ -1,82 +0,0 @@
name: Tests and linters
on: [push, pull_request, workflow_dispatch]
jobs:
tests-on-legacy-versions:
name: Run tests on legacy versions
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.7]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- run: pip install tox
- run: tox
env:
TOXENV: ${{ matrix.python-version }}
test-on-different-versions:
name: Run tests
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, "3.10", 3.11, 3.12, 3.13]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- run: pip install tox
- run: tox
env:
TOXENV: ${{ matrix.python-version }}
test-different-pydantic-versions:
name: Run tests with different pydantic versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.12"
- run: pip install tox
- run: tox -e pydantic-v1,pydantic-v2
test-coverage:
name: Run tests with coverage
runs-on: ubuntu-latest
env:
DEPENDENCY_INJECTOR_DEBUG_MODE: 1
PIP_VERBOSE: 1
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.12
- run: pip install tox 'cython>=3,<4'
- run: tox -vv
env:
TOXENV: coveralls
linters:
name: Run linters
runs-on: ubuntu-latest
strategy:
matrix:
toxenv: [flake8, pydocstyle, mypy, pylint]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: pip install tox
- run: tox
env:
TOXENV: ${{ matrix.toxenv }}

20
.gitignore vendored
View File

@ -36,7 +36,6 @@ reports/
.cache
nosetests.xml
coverage.xml
.hypothesis/
# Translations
*.mo
@ -55,7 +54,7 @@ target/
.idea/
# Virtualenv
venv*/
venv/
# SQLite
*.db
@ -63,13 +62,10 @@ venv*/
# Vim Rope
.ropeproject/
# Cython artifacts
src/**/*.c
src/**/*.h
src/**/*.so
src/**/*.html
# Workspace for samples
.workspace/
.vscode/
# C extensions
src/dependency_injector/*.h
src/dependency_injector/*.so
src/dependency_injector/containers/*.h
src/dependency_injector/containers/*.so
src/dependency_injector/providers/*.h
src/dependency_injector/providers/*.so

49
.pylintrc Normal file
View File

@ -0,0 +1,49 @@
[MASTER]
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=utils,tests
[MESSAGES CONTROL]
# Disable the message(s) with the given id(s).
# disable-msg=
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=5
[TYPECHECK]
ignore-mixin-members=yes
# ignored-classes=
zope=no
# generated-members=providedBy,implementedBy,rawDataReceived
[DESIGN]
# Maximum number of arguments for function / method
max-args=10
# Maximum number of locals for function / method body
max-locals=20
# Maximum number of return / yield for function / method body
max-returns=10
# Maximum number of branch for function / method body
max-branchs=10
# Maximum number of statements in function / method body
max-statements=60
# Maximum number of parents for a class (see R0901).
max-parents=10
# Maximum number of attributes for a class (see R0902).
max-attributes=30
# Minimum number of public methods for a class (see R0903).
min-public-methods=0
# Maximum number of public methods for a class (see R0904).
max-public-methods=30

33
.travis.yml Normal file
View File

@ -0,0 +1,33 @@
sudo: false
install:
- pip install tox
- pip install cython
- make cythonize
script:
- tox
language:
- python
matrix:
include:
- python: 2.7
env: TOXENV=coveralls DEPENDENCY_INJECTOR_DEBUG_MODE=1
- python: 2.7
env: TOXENV=pylint
- python: 2.7
env: TOXENV=flake8
- python: 2.7
env: TOXENV=pydocstyle
- python: 2.6
env: TOXENV=py26
- python: 2.7
env: TOXENV=py27
- python: 3.3
env: TOXENV=py33
- python: 3.4
env: TOXENV=py34
- python: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- python: pypy
env: TOXENV=pypy

View File

@ -1,24 +1,6 @@
Dependency Injector Contributors
================================
+ Roman Mogylatov (rmk135)
+ Roman Mogilatov (rmk135)
+ Konstantin vz'One Enchant (sirkonst)
+ Terrence Brannon (metaperl)
+ Stanislav Lobanov (asyncee)
+ James Lafa (jameslafa)
+ Vlad Ghita (vlad-ghita)
+ Jeroen Rietveld (jeroenrietveld)
+ Dmitry Kuzmin (xotonic)
+ supakeen (supakeen)
+ Bruno P. Kinoshita (kinow)
+ RobinsonMa (RobinsonMa)
+ Rüdiger Busche (JarnoRFB)
+ Dmitry Rassoshenko (rda-dev)
+ Fotis Koutoupas (kootoopas)
+ Shubhendra Singh Chauhan (withshubh)
+ sonthonaxrk (sonthonaxrk)
+ Ngo Thanh Loi (Leonn) (loingo95)
+ Thiago Hiromi (thiromi)
+ Felipe Rubio (krouw)
+ Anton Petrov (anton-petrov)
+ ZipFile (ZipFile)

View File

@ -1,4 +1,4 @@
Copyright (c) 2024, Roman Mogylatov
Copyright (c) 2017, ETS Labs
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -1,9 +1,6 @@
recursive-include src/dependency_injector *.py* *.c
recursive-include tests *.py
recursive-include src/dependency_injector *.py *.pyx *.pxd *.c
include README.rst
include CONTRIBUTORS.rst
include LICENSE.rst
include requirements.txt
include setup.py
include tox.ini
include py.typed

View File

@ -1,6 +1,14 @@
VERSION := $(shell python setup.py --version)
export COVERAGE_RCFILE := pyproject.toml
CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
CYTHON_DIRECTIVES =
ifdef DEPENDENCY_INJECTOR_DEBUG_MODE
CYTHON_DIRECTIVES += -Xprofile=True
CYTHON_DIRECTIVES += -Xlinetrace=True
endif
clean:
# Clean sources
@ -17,49 +25,41 @@ clean:
find examples -name '*.py[co]' -delete
find examples -name '__pycache__' -delete
build: clean
# Compile C extensions
python setup.py build_ext --inplace
cythonize:
# Compile Cython to C
cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
# Move all Cython html reports
mkdir -p reports/cython/
find src -name '*.html' -exec mv {} reports/cython/ \;
docs-live:
sphinx-autobuild docs docs/_build/html
build: clean cythonize
# Compile C extensions
python setup.py build_ext --inplace
install: uninstall clean build
install: uninstall clean cythonize
pip install -ve .
uninstall:
- pip uninstall -y -q dependency-injector 2> /dev/null
test:
test: build
# Unit tests with coverage report
coverage erase
coverage run -m pytest -c tests/.configs/pytest.ini
coverage report
coverage html
coverage run --rcfile=./.coveragerc -m unittest2 discover tests/unit
coverage report --rcfile=./.coveragerc
coverage html --rcfile=./.coveragerc
check:
flake8 src/dependency_injector/
flake8 examples/
# Static analysis
flake8 --max-complexity=10 src/dependency_injector/
flake8 --max-complexity=10 examples/
# Code style analysis
pydocstyle src/dependency_injector/
pydocstyle examples/
mypy tests/typing
test-publish: build
# Create distributions
python -m build --sdist
# Upload distributions to PyPI
twine upload --repository testpypi dist/dependency-injector-$(VERSION)*
publish:
# Merge release to master branch
git checkout master
git merge --no-ff release/$(VERSION) -m "Merge branch 'release/$(VERSION)' into master"
git push origin master
publish: cythonize
# Create and upload build
python setup.py sdist upload
# Create and upload tag
git tag -a $(VERSION) -m 'version $(VERSION)'
git push --tags

View File

@ -1,230 +1,397 @@
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/logo.svg
:target: https://github.com/ets-labs/python-dependency-injector
====================================================================
Dependency Injector - Dependency injection microframework for Python
====================================================================
|
*Dependency Injector* is a dependency injection microframework for Python.
It was designed to be unified, developer-friendly tool that helps to implement
dependency injection design pattern in formal, pretty, Pythonic way.
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: License
*Dependency Injector* framework key features are:
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Supported Python versions
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Supported Python implementations
+ Easy, smart, pythonic style.
+ Obvious, clear structure.
+ Extensibility and flexibility.
+ High performance.
+ Memory efficiency.
+ Thread safety.
+ Documentation.
+ Semantic versioning.
.. image:: https://pepy.tech/badge/dependency-injector
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
*Dependency Injector* providers are implemented as C extension types using
Cython.
.. image:: https://pepy.tech/badge/dependency-injector/month
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
Status
------
.. image:: https://pepy.tech/badge/dependency-injector/week
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
| *PyPi* | .. image:: https://img.shields.io/pypi/v/dependency_injector.svg |
| | :target: https://pypi.python.org/pypi/dependency_injector/ |
| | :alt: Latest Version |
| | .. image:: https://img.shields.io/pypi/l/dependency_injector.svg |
| | :target: https://pypi.python.org/pypi/dependency_injector/ |
| | :alt: License |
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
| *Python versions and implementations* | .. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg |
| | :target: https://pypi.python.org/pypi/dependency_injector/ |
| | :alt: Supported Python versions |
| | .. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg |
| | :target: https://pypi.python.org/pypi/dependency_injector/ |
| | :alt: Supported Python implementations |
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
| *Builds and tests coverage* | .. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master |
| | :target: https://travis-ci.org/ets-labs/python-dependency-injector |
| | :alt: Build Status |
| | .. image:: https://coveralls.io/repos/ets-labs/python-dependency-injector/badge.svg |
| | :target: https://coveralls.io/r/ets-labs/python-dependency-injector |
| | :alt: Coverage Status |
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
| *Github* | .. image:: https://img.shields.io/github/watchers/ets-labs/python-dependency-injector.svg?style=social&label=Watch |
| | :target: https://github.com/ets-labs/python-dependency-injector |
| | :alt: Github watchers |
| | .. image:: https://img.shields.io/github/stars/ets-labs/python-dependency-injector.svg?style=social&label=Star |
| | :target: https://github.com/ets-labs/python-dependency-injector |
| | :alt: Github stargazers |
| | .. image:: https://img.shields.io/github/forks/ets-labs/python-dependency-injector.svg?style=social&label=Fork |
| | :target: https://github.com/ets-labs/python-dependency-injector |
| | :alt: Github forks |
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
.. image:: https://img.shields.io/pypi/wheel/dependency-injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Wheel
Dependency injection
--------------------
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
:target: https://github.com/ets-labs/python-dependency-injector/actions
:alt: Build Status
`Dependency injection`_ is a software design pattern that implements
`Inversion of control`_ for resolving dependencies. Formally, if object **A**
depends on object **B**, object **A** must not create or import object **B**
directly. Instead of this object **A** must provide a way for *injecting*
object **B**. The responsibilities of objects creation and dependencies
injection are delegated to external code - the *dependency injector*.
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
:alt: Coverage Status
Popular terminology of dependency injection pattern:
What is ``Dependency Injector``?
================================
+ Object **A**, that is dependant on object **B**, is often called -
the *client*.
+ Object **B**, that is a dependency, is often called - the *service*.
+ External code that is responsible for creation of objects and injection
of dependencies is often called - the *dependency injector*.
``Dependency Injector`` is a dependency injection framework for Python.
There are several ways of how *service* can be injected into the *client*:
It helps implement the dependency injection principle.
+ by passing it as ``__init__`` argument (constructor / initializer injection)
+ by setting it as attribute's value (attribute injection)
+ by passing it as method's argument (method injection)
Key features of the ``Dependency Injector``:
Dependency injection pattern has few strict rules that should be followed:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
that help assemble your objects.
See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev/stage environment to replace API clients with stubs etc. See
`Provider overriding <https://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
environment variables, and dictionaries.
See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_.
- **Containers**. Provides declarative and dynamic containers.
See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_.
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc.
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
- **Asynchronous**. Supports asynchronous injections.
See `Asynchronous injections <https://python-dependency-injector.ets-labs.org/providers/async.html>`_.
- **Typing**. Provides typing stubs, ``mypy``-friendly.
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
- **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
+ The *client* delegates to the *dependency injector* the responsibility
of injecting its dependencies - the *service(s)*.
+ The *client* doesn't know how to create the *service*, it knows only
interface of the *service*. The *service* doesn't know that it is used by
the *client*.
+ The *dependency injector* knows how to create the *client* and
the *service*, it also knows that the *client* depends on the *service*,
and knows how to inject the *service* into the *client*.
+ The *client* and the *service* know nothing about the *dependency injector*.
Dependency injection pattern provides the following advantages:
+ Control on application structure.
+ Decreased coupling between application components.
+ Increased code reusability.
+ Increased testability.
+ Increased maintainability.
+ Reconfiguration of system without rebuilding.
Example of dependency injection
-------------------------------
Let's go through next example:
.. image:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/engines_cars/diagram.png
:width: 100%
:align: center
Listing of ``example.engines`` module:
.. code-block:: python
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
"""Dependency injection example, engines module."""
class Container(containers.DeclarativeContainer):
class Engine(object):
"""Example engine base class.
config = providers.Configuration()
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
service = providers.Factory(
Service,
api_client=api_client,
)
Engine is a heart of every car. Engine is a very common term and could be
implemented in very different ways.
"""
@inject
def main(service: Service = Provide[Container.service]) -> None:
...
class GasolineEngine(Engine):
"""Gasoline engine."""
if __name__ == "__main__":
container = Container()
container.config.api_key.from_env("API_KEY", required=True)
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
container.wire(modules=[__name__])
class DieselEngine(Engine):
"""Diesel engine."""
main() # <-- dependency is injected automatically
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
class ElectroEngine(Engine):
"""Electro engine."""
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
Listing of ``example.cars`` module:
When you do testing, you call the ``container.api_client.override()`` method to replace the real API
client with a mock. When you call ``main()``, the mock is injected.
.. code-block:: python
You can override any provider with another provider.
"""Dependency injection example, cars module."""
It also helps you in a re-configuring project for different environments: replace an API client
with a stub on the dev or stage.
With the ``Dependency Injector``, object assembling is consolidated in a container. Dependency injections are defined explicitly.
This makes it easier to understand and change how an application works.
class Car(object):
"""Example car."""
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
:target: https://github.com/ets-labs/python-dependency-injector
def __init__(self, engine):
"""Initializer."""
self._engine = engine # Engine is injected
Visit the docs to know more about the
`Dependency injection and inversion of control in Python <https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html>`_.
Next example demonstrates creation of several cars with different engines:
.. code-block:: python
"""Dependency injection example, Cars & Engines."""
import example.cars
import example.engines
if __name__ == '__main__':
gasoline_car = example.cars.Car(example.engines.GasolineEngine())
diesel_car = example.cars.Car(example.engines.DieselEngine())
electro_car = example.cars.Car(example.engines.ElectroEngine())
While previous example demonstrates advantages of dependency injection, there
is a disadvantage demonstration as well - creation of car requires additional
code for specification of dependencies. Nevertheless, this disadvantage could
be easily avoided by using a dependency injection framework for creation of
inversion of control container (IoC container).
Example of creation of several inversion of control containers (IoC containers)
using *Dependency Injector*:
.. code-block:: python
"""Dependency injection example, Cars & Engines IoC containers."""
import example.cars
import example.engines
import dependency_injector.containers as containers
import dependency_injector.providers as providers
class Engines(containers.DeclarativeContainer):
"""IoC container of engine providers."""
gasoline = providers.Factory(example.engines.GasolineEngine)
diesel = providers.Factory(example.engines.DieselEngine)
electro = providers.Factory(example.engines.ElectroEngine)
class Cars(containers.DeclarativeContainer):
"""IoC container of car providers."""
gasoline = providers.Factory(example.cars.Car,
engine=Engines.gasoline)
diesel = providers.Factory(example.cars.Car,
engine=Engines.diesel)
electro = providers.Factory(example.cars.Car,
engine=Engines.electro)
if __name__ == '__main__':
gasoline_car = Cars.gasoline()
diesel_car = Cars.diesel()
electro_car = Cars.electro()
Dependency Injector structure
-----------------------------
Dependency Injector is a microframework and has a very simple structure.
There are 2 main entities: providers & containers.
.. image:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/internals.png
:width: 100%
:align: center
Providers
~~~~~~~~~
Providers are strategies of accessing objects. They define how particular
objects are provided.
- **Provider** - base provider class.
- **Callable** - provider that calls wrapped callable on every call. Supports
positional & keyword argument injections.
- **Factory** - provider that creates new instance of specified class on every
call. Supports positional & keyword argument injections, as well as
attribute injections.
- **Singleton** - provider that creates new instance of specified class on first
call and returns same instance on every next call. Supports positional &
keyword argument injections, as well as attribute injections.
- **Object** - provider that returns provided instance "as is".
- **ExternalDependency** - provider that can be useful for development of
self-sufficient libraries / modules / applications that has required
external dependencies.
- **Configuration** - provider that helps with implementing late static binding
of configuration options - use first, define later.
Containers
~~~~~~~~~~
Containers are collections of providers. Main purpose of containers is to
group providers.
- **DeclarativeContainer** - is inversion of control container that could be
defined in declarative manner. It should cover most of the cases when list
of providers that would be included in container is deterministic
(container will not change its structure in runtime).
- **DynamicContainer** - is an inversion of control container with dynamic
structure. It should cover most of the cases when list of providers that
would be included in container is non-deterministic and depends on
application's flow or its configuration (container's structure could be
determined just after application will be started and will do some initial
work, like parsing list of containers providers from the configuration).
Dependency Injector in action
-----------------------------
Brief example below is a simplified version of inversion of control
containters from one of the real-life applications. This example demonstrates
usage of *Dependency Injector* inversion of control containers & providers
for specifying all application components and their dependencies beetween
each other in one module. Besides other listed above advantages, it gives a
great opportunity to control & manage application's structure in one place.
.. code-block:: python
"""Example of dependency injection in Python."""
import logging
import sqlite3
import boto3
import example.main
import example.services
import dependency_injector.containers as containers
import dependency_injector.providers as providers
class Core(containers.DeclarativeContainer):
"""IoC container of core component providers."""
config = providers.Configuration('config')
logger = providers.Singleton(logging.Logger, name='example')
class Gateways(containers.DeclarativeContainer):
"""IoC container of gateway (API clients to remote services) providers."""
database = providers.Singleton(sqlite3.connect, Core.config.database.dsn)
s3 = providers.Singleton(
boto3.client, 's3',
aws_access_key_id=Core.config.aws.access_key_id,
aws_secret_access_key=Core.config.aws.secret_access_key)
class Services(containers.DeclarativeContainer):
"""IoC container of business service providers."""
users = providers.Factory(example.services.UsersService,
db=Gateways.database,
logger=Core.logger)
auth = providers.Factory(example.services.AuthService,
db=Gateways.database,
logger=Core.logger,
token_ttl=Core.config.auth.token_ttl)
photos = providers.Factory(example.services.PhotosService,
db=Gateways.database,
s3=Gateways.s3,
logger=Core.logger)
class Application(containers.DeclarativeContainer):
"""IoC container of application component providers."""
main = providers.Callable(example.main.main,
users_service=Services.users,
auth_service=Services.auth,
photos_service=Services.photos)
Next example demonstrates run of example application defined above:
.. code-block:: python
"""Run example application."""
import sys
import logging
from containers import Core, Application
if __name__ == '__main__':
# Configure platform:
Core.config.update({'database': {'dsn': ':memory:'},
'aws': {'access_key_id': 'KEY',
'secret_access_key': 'SECRET'},
'auth': {'token_ttl': 3600}})
Core.logger().addHandler(logging.StreamHandler(sys.stdout))
# Run application:
Application.main(uid=sys.argv[1],
password=sys.argv[2],
photo=sys.argv[3])
You can get more *Dependency Injector* examples in ``/examples`` directory on
GitHub:
https://github.com/ets-labs/python-dependency-injector
Installation
------------
The package is available on the `PyPi`_::
*Dependency Injector* library is available on `PyPi`_::
pip install dependency-injector
pip install dependency_injector
Documentation
-------------
The documentation is available `here <https://python-dependency-injector.ets-labs.org/>`_.
*Dependency Injector* documentation is hosted on ReadTheDocs:
Examples
--------
- `User's guide`_
- `API docs`_
Choose one of the following:
Feedback & Support
------------------
- `Application example (single container) <https://python-dependency-injector.ets-labs.org/examples/application-single-container.html>`_
- `Application example (multiple containers) <https://python-dependency-injector.ets-labs.org/examples/application-multiple-containers.html>`_
- `Decoupled packages example (multiple containers) <https://python-dependency-injector.ets-labs.org/examples/decoupled-packages.html>`_
- `Boto3 example <https://python-dependency-injector.ets-labs.org/examples/boto3.html>`_
- `Django example <https://python-dependency-injector.ets-labs.org/examples/django.html>`_
- `Flask example <https://python-dependency-injector.ets-labs.org/examples/flask.html>`_
- `Aiohttp example <https://python-dependency-injector.ets-labs.org/examples/aiohttp.html>`_
- `Sanic example <https://python-dependency-injector.ets-labs.org/examples/sanic.html>`_
- `FastAPI example <https://python-dependency-injector.ets-labs.org/examples/fastapi.html>`_
- `FastAPI + Redis example <https://python-dependency-injector.ets-labs.org/examples/fastapi-redis.html>`_
- `FastAPI + SQLAlchemy example <https://python-dependency-injector.ets-labs.org/examples/fastapi-sqlalchemy.html>`_
Feel free to post questions, bugs, feature requests, proposals etc. on
*Dependency Injector* GitHub Issues:
Tutorials
---------
https://github.com/ets-labs/python-dependency-injector/issues
Choose one of the following:
Your feedback is quite important!
- `Flask web application tutorial <https://python-dependency-injector.ets-labs.org/tutorials/flask.html>`_
- `Aiohttp REST API tutorial <https://python-dependency-injector.ets-labs.org/tutorials/aiohttp.html>`_
- `Asyncio monitoring daemon tutorial <https://python-dependency-injector.ets-labs.org/tutorials/asyncio-daemon.html>`_
- `CLI application tutorial <https://python-dependency-injector.ets-labs.org/tutorials/cli.html>`_
Concept
-------
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
.. code-block:: bash
Explicit is better than implicit
You need to specify how to assemble and where to inject the dependencies explicitly.
The power of the framework is in its simplicity.
``Dependency Injector`` is a simple tool for the powerful concept.
Frequently asked questions
--------------------------
What is dependency injection?
- dependency injection is a principle that decreases coupling and increases cohesion
Why should I do the dependency injection?
- your code becomes more flexible, testable, and clear 😎
How do I start applying the dependency injection?
- you start writing the code following the dependency injection principle
- you register all of your application components and their dependencies in the container
- when you need a component, you specify where to inject it or get it from the container
What price do I pay and what do I get?
- you need to explicitly specify the dependencies
- it will be extra work in the beginning
- it will payoff as project grows
Have a question?
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
Found a bug?
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
Want to help?
- |star| Star the ``Dependency Injector`` on the `Github <https://github.com/ets-labs/python-dependency-injector/>`_
- |new| Start a new project with the ``Dependency Injector``
- |tell| Tell your friend about the ``Dependency Injector``
Want to contribute?
- |fork| Fork the project
- |pull| Open a pull request to the ``develop`` branch
.. _PyPi: https://pypi.org/project/dependency-injector/
.. |star| unicode:: U+2B50 U+FE0F .. star sign1
.. |new| unicode:: U+1F195 .. new sign
.. |tell| unicode:: U+1F4AC .. tell sign
.. |fork| unicode:: U+1F500 .. fork sign
.. |pull| unicode:: U+2B05 U+FE0F .. pull sign
.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection
.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control
.. _PyPi: https://pypi.python.org/pypi/dependency_injector
.. _User's guide: http://python-dependency-injector.ets-labs.org/
.. _API docs: http://python-dependency-injector.ets-labs.org/api/

View File

@ -1,9 +0,0 @@
.no-border {
border: 0 !important;
box-shadow: none !important;
-webkit-box-shadow: none !important;
}
.no-border td {
border: 0px !important;
padding: 0px 10px 0px 0px !important;
}

10
docs/_static/disqus.js vendored Normal file
View File

@ -0,0 +1,10 @@
var disqus_shortname;
var disqus_identifier;
(function() {{
var disqus_thread = $("#disqus_thread");
disqus_shortname = disqus_thread.data('disqus-shortname');
disqus_identifier = disqus_thread.data('disqus-identifier');
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
}})();

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1 +0,0 @@
<iframe src="https://github.com/sponsors/rmk135/button" title="Sponsor Dependency Injector" height="32" width="114" style="border: 0; border-radius: 6px;"></iframe>

View File

@ -1,9 +1,9 @@
dependency_injector.containers
==============================
``dependency_injector.containers``
----------------------------------
.. automodule:: dependency_injector.containers
:members:
:inherited-members:
:show-inheritance:
:special-members:
.. disqus::

View File

@ -1,5 +1,5 @@
dependency_injector.errors
==========================
``dependency_injector.errors``
------------------------------
.. automodule:: dependency_injector.errors
:members:

View File

@ -4,8 +4,7 @@ API Documentation
.. toctree::
:maxdepth: 2
top-level
providers
containers
wiring
errors
top_level
providers
containers
errors

View File

@ -1,5 +1,5 @@
dependency_injector.providers
=============================
``dependency_injector.providers``
---------------------------------
.. automodule:: dependency_injector.providers
:members:

View File

@ -1,5 +1,5 @@
dependency_injector
===================
``dependency_injector``
-----------------------
.. automodule:: dependency_injector
:members: __version__

View File

@ -1,7 +0,0 @@
dependency_injector.wiring
=============================
.. automodule:: dependency_injector.wiring
:members:
.. disqus::

View File

@ -15,54 +15,50 @@
import os
import re
import sys
import alabaster
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(".."))
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = "1.0"
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named "sphinx.ext.*") or your custom
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"alabaster",
"sphinx.ext.autodoc",
"sphinx_disqus.disqus",
]
extensions = ['sphinx.ext.autodoc',
'sphinxcontrib.disqus']
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = [".rst", ".md"]
source_suffix = ".rst"
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = "utf-8-sig"
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
master_doc = 'index'
# General information about the project.
project = "Dependency Injector"
copyright = "2024, Roman Mogylatov"
author = "Roman Mogylatov"
project = u'Dependency Injector'
copyright = u'2017, ETS Labs'
author = u'ETS Labs'
# The version info for the project you"re documenting, acts as replacement for
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# Getting version:
with open("../src/dependency_injector/__init__.py") as init_file:
version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1)
with open('../src/dependency_injector/__init__.py') as init_file:
version = re.search('__version__ = \'(.*?)\'', init_file.read()).group(1)
# The full version, including alpha/beta/rc tags.
release = version
@ -76,19 +72,19 @@ language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ""
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = "%B %d, %Y"
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, "()" will be appended to :func: etc. cross-reference text.
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
@ -100,7 +96,7 @@ exclude_patterns = ["_build"]
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
@ -116,16 +112,15 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# html_theme = "sphinx_rtd_theme"
html_theme = "alabaster"
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_context = {}
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [alabaster.get_path()]
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@ -141,24 +136,21 @@ html_theme_path = [alabaster.get_path()]
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = "favicon.ico"
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_css_files = [
"custom.css",
]
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not "", a "Last updated on:" timestamp is inserted at every page bottom,
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = "%b %d, %Y"
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
@ -192,50 +184,61 @@ html_css_files = [
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ""
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# "da", "de", "en", "es", "fi", "fr", "hu", "it", "ja"
# "nl", "no", "pt", "ro", "ru", "sv", "tr"
#html_search_language = "en"
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only "ja" uses this config value
#html_search_options = {"type": "default"}
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = "scorer.js"
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = "dependency_injectordoc"
htmlhelp_basename = 'dependency_injectordoc'
# on_rtd is whether we are on readthedocs.org, this line of code grabbed from
# docs.readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to
# specify it
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ("letterpaper" or "a4paper").
#"papersize": "letterpaper",
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ("10pt", "11pt" or "12pt").
#"pointsize": "10pt",
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#"preamble": "",
#'preamble': '',
# Latex figure (float) alignment
#"figure_align": "htbp",
#'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, "dependency_injector.tex", u"Dependency Injector Documentation",
u"Roman Mogylatov", "manual"),
(master_doc, 'dependency_injector.tex', u'Dependency Injector Documentation',
u'ETS Labs', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@ -264,7 +267,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, "Dependency Injector", u"Dependency Injector Documentation",
(master_doc, 'Dependency Injector', u'Dependency Injector Documentation',
[author], 1)
]
@ -278,9 +281,9 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, "Dependency Injector", u"Dependency Injector Documentation",
author, "Dependency Injector", "Dependency injection microframework for Python",
"Miscellaneous"),
(master_doc, 'Dependency Injector', u'Dependency Injector Documentation',
author, 'Dependency Injector', 'Dependency injection microframework for Python',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
@ -289,25 +292,12 @@ texinfo_documents = [
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: "footnote", "no", or "inline".
#texinfo_show_urls = "footnote"
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node"s menu.
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
autodoc_member_order = "bysource"
autodoc_member_order = 'bysource'
disqus_shortname = "python-dependency-injector"
html_theme_options = {
"github_user": "ets-labs",
"github_repo": "python-dependency-injector",
"github_type": "star",
"github_button": True,
"github_banner": True,
"logo": "logo.svg",
"description": "Dependency injection framework for Python by Roman Mogylatov",
"code_font_size": "10pt",
"analytics_id": "UA-67012059-1",
"donate_url": "https://github.com/sponsors/rmk135",
}
disqus_shortname = 'python-dependency-injector'

View File

@ -1,18 +0,0 @@
.. _check-container-dependencies:
Check container dependencies
----------------------------
To check container dependencies use method ``.check_dependencies()``.
.. literalinclude:: ../../examples/containers/check_dependencies.py
:language: python
:lines: 3-
:emphasize-lines: 12
Method ``.check_dependencies()`` raises an error if container has any undefined dependencies.
If all dependencies are provided or have defaults, no error is raised.
See also: :ref:`dependency-provider`.
.. disqus::

View File

@ -1,23 +0,0 @@
Container copying
-----------------
You can create declarative container copies using ``@containers.copy()`` decorator.
.. literalinclude:: ../../examples/containers/declarative_copy_decorator1.py
:language: python
:lines: 3-
:emphasize-lines: 18-22
Decorator ``@containers.copy()`` copies providers from source container to destination container.
Destination container provider will replace source provider, if names match.
Decorator ``@containers.copy()`` helps you when you create derived declarative containers
from the base one. Base container often keeps default dependencies while derived containers define
overriding providers. Without ``@containers.copy()`` decorator, overridden providers are available
in the derived container, but base class dependencies continue to be bound to the base class providers.
.. literalinclude:: ../../examples/containers/declarative_copy_decorator2.py
:language: python
:lines: 11-
.. disqus::

View File

@ -1,72 +1,58 @@
Declarative container
---------------------
Declarative containers
----------------------
.. currentmodule:: dependency_injector.containers
:py:class:`DeclarativeContainer` is a class-based style of the providers definition.
:py:class:`DeclarativeContainer` is inversion of control container that
could be defined in declarative manner. It should cover most of the cases
when list of providers that would be included in container is deterministic
(container will not change its structure in runtime).
You create the declarative container subclass, put the providers as attributes and create the
container instance.
Declarative containers have to extend base declarative container class -
:py:class:`dependency_injector.containers.DeclarativeContainer`.
Declarative container's providers have to be defined like container's class
attributes. Every provider in container has name. This name should follow
``some_provider`` convention, that is standard naming convention for
attribute names in Python.
.. note::
Declarative containers have several features that could be useful
for some kind of operations on container's providers, please visit API
documentation for getting full list of features -
:py:class:`dependency_injector.containers.DeclarativeContainer`.
Here is an simple example of defining declarative container with several
factories:
.. image:: /images/containers/declarative.png
:width: 85%
:align: center
.. literalinclude:: ../../examples/containers/declarative.py
:language: python
:lines: 3-
:linenos:
The declarative container providers should only be used when you have the container instance.
Working with the providers of the container on the class level will influence all further
instances.
Example of declarative containers inheritance:
The declarative container can not have any methods or any other attributes then providers.
The container class provides next attributes:
- ``providers`` - the dictionary of all the container providers
- ``cls_providers`` - the dictionary of the container providers of the current container
- ``inherited_providers`` - the dictionary of all the inherited container providers
.. image:: /images/containers/declarative_inheritance.png
:width: 100%
:align: center
.. literalinclude:: ../../examples/containers/declarative_inheritance.py
:language: python
:lines: 3-
:linenos:
Injections in the declarative container are done the usual way:
Example of declarative containers's provider injections:
.. image:: /images/containers/declarative_injections.png
:width: 100%
:align: center
.. literalinclude:: ../../examples/containers/declarative_injections.py
:language: python
:lines: 3-
:linenos:
You can override container providers while creating a container instance:
.. literalinclude:: ../../examples/containers/declarative_override_providers.py
:language: python
:lines: 3-
:emphasize-lines: 13
Alternatively, you can call ``container.override_providers()`` method when the container instance
already exists:
.. code-block:: python
:emphasize-lines: 3
container = Container()
container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar))
assert isinstance(container.foo(), mock.Mock)
assert isinstance(container.bar(), mock.Mock)
You can also use ``container.override_providers()`` with a context manager to reset
provided overriding after the context is closed:
.. code-block:: python
:emphasize-lines: 3
container = Container()
with container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar)):
assert isinstance(container.foo(), mock.Mock)
assert isinstance(container.bar(), mock.Mock)
assert isinstance(container.foo(), Foo)
assert isinstance(container.bar(), Bar)
.. disqus::

View File

@ -1,25 +1,30 @@
Dynamic container
-----------------
Dynamic containers
------------------
.. currentmodule:: dependency_injector.containers
:py:class:`DynamicContainer` is a collection of the providers defined in the runtime.
:py:class:`DynamicContainer` is an inversion of control container with dynamic
structure. It should cover most of the cases when list of providers that
would be included in container is non-deterministic and depends on
application's flow or its configuration (container's structure could be
determined just after application will be started and will do some initial
work, like parsing list of container's providers from the configuration).
You create the dynamic container instance and put the providers as attributes.
While :py:class:`DeclarativeContainer` acts on class-level,
:py:class:`DynamicContainer` does the same on instance-level.
Here is an simple example of defining dynamic container with several factories:
.. literalinclude:: ../../examples/containers/dynamic.py
:language: python
:lines: 3-
:linenos:
The dynamic container is good for the case when your application structure depends on the
configuration file or some other source that you can reach only after application is already
running (database, api, etc).
In this example we use the configuration to fill in the dynamic container with the providers:
Next example demonstrates creation of dynamic container based on some
configuration:
.. literalinclude:: ../../examples/containers/dynamic_runtime_creation.py
:language: python
:lines: 3-
:linenos:
.. disqus::

View File

@ -1,20 +1,23 @@
.. _containers:
IoC Containers
==============
Containers
==========
Containers are collections of providers. Main purpose of containers is to group
providers.
Containers are collections of the providers.
There are, actually, several popular cases of containers usage:
There are several use cases how you can use containers:
+ Keeping all the providers in a single container (most common).
+ Grouping of the providers from the same architectural layer (for example,
+ Keeping all providers in a single container.
+ Grouping of providers from the same architectural layer (for example,
``Services``, ``Models`` and ``Forms`` containers).
+ Grouping of providers from the same functional groups (for example,
container ``Users``, that contains all functional parts of the ``users``
package).
container ``Users``, that contains all functional parts of ``Users``
component).
Containers module API docs - :py:mod:`dependency_injector.containers`.
Also, for both of these and some other cases, it might be useful to attach
some init / shutdown functionality or something else, that deals with group
of providers.
Containers package API docs - :py:mod:`dependency_injector.containers`.
.. toctree::
:maxdepth: 2
@ -23,7 +26,3 @@ Containers module API docs - :py:mod:`dependency_injector.containers`.
dynamic
specialization
overriding
copying
reset_singletons
check_dependencies
traversal

View File

@ -1,40 +1,43 @@
Container overriding
--------------------
Overriding of containers
------------------------
.. currentmodule:: dependency_injector.containers
The container can be overridden by the other container. All of the providers from the overriding
container will override the providers with the same names in the overridden container.
Containers can be overridden by other containers. This, actually, means that
all of the providers from overriding container will override providers with
the same names in overridden container.
.. literalinclude:: ../../examples/containers/override.py
There are two ways to override :py:class:`DeclarativeContainer` with another
container:
- Use :py:meth:`DeclarativeContainer.override` method.
- Use :py:func:`override` class decorator.
Example of overriding container using :py:meth:`DeclarativeContainer.override`
method:
.. literalinclude:: ../../examples/containers/override_declarative.py
:language: python
:lines: 3-
:linenos:
It helps in a testing. Also you can use it for configuring project for the different
environments: replace an API client with a stub on the dev or stage.
Example of overriding container using :py:func:`override` decorator:
The container also has:
- ``container.overridden`` - tuple of all overriding containers.
- ``container.reset_last_overriding()`` - reset last overriding for each provider in the container.
- ``container.reset_override()`` - reset all overriding in the container.
:py:class:`DynamicContainer` has the same functionality.
Another possible way to override container providers on declarative level is
``@containers.override()`` decorator:
.. literalinclude:: ../../examples/containers/declarative_override_decorator.py
.. literalinclude:: ../../examples/containers/override_declarative_decorator.py
:language: python
:lines: 3-
:emphasize-lines: 12-16
:linenos:
Decorator ``@containers.override()`` takes a container for overriding as an argument.
This container providers will be overridden by the providers with the same names from
the decorated container.
Also there are several useful :py:class:`DeclarativeContainer` methods and
properties that help to work with container overridings:
- :py:attr:`DeclarativeContainer.overridden` - tuple of all overriding
containers.
- :py:meth:`DeclarativeContainer.reset_last_overriding()` - reset last
overriding provider for each container providers.
- :py:meth:`DeclarativeContainer.reset_override()` - reset all overridings
for each container providers.
:py:class:`DynamicContainer` has exactly the same functionality, except of
:py:func:`override` decorator.
It helps to change the behaviour of application by importing extension modules but not a code change.
Imported module can override providers in main container. While the code uses main container as
before, the overridden providers provide components defined in the extension module.
.. disqus::

View File

@ -1,31 +0,0 @@
.. _reset-container-singletons:
Reset container singletons
--------------------------
To reset all container singletons use method ``.reset_singletons()``.
.. literalinclude:: ../../examples/containers/reset_singletons.py
:language: python
:lines: 3-
:emphasize-lines: 16
Method ``.reset_singletons()`` also resets singletons in sub-containers: ``providers.Container`` and
``providers.DependenciesContainer.``
.. literalinclude:: ../../examples/containers/reset_singletons_subcontainers.py
:language: python
:lines: 3-
:emphasize-lines: 21
You can use ``.reset_singletons()`` method with a context manager. Singletons will be reset on
both entering and exiting a context.
.. literalinclude:: ../../examples/containers/reset_singletons_with.py
:language: python
:lines: 3-
:emphasize-lines: 14-15
See also: :ref:`singleton-provider`.
.. disqus::

View File

@ -1,25 +1,27 @@
Specialization of the container provider type
---------------------------------------------
Specialization of containers
----------------------------
.. currentmodule:: dependency_injector.containers
You can make a restriction of the :py:class:`DeclarativeContainer` provider type:
:py:class:`DeclarativeContainer` could be specialized for any kind of needs
via declaring its subclasses.
One of such `builtin` features is a limitation for providers type.
Next example shows usage of this feature with :py:class:`DeclarativeContainer`
in couple with feature of :py:class:`dependency_injector.providers.Factory`
for limitation of its provided type:
.. literalinclude:: ../../examples/containers/declarative_provider_type.py
:language: python
:lines: 3-
:emphasize-lines: 29-31
:linenos:
The emphasized lines will cause an error because ``other_provider`` is not a subtype of the
``ServiceProvider``. This helps to control the content of the container.
The same works for the :py:class:`DynamicContainer`:
Limitation for providers type could be used with :py:class:`DynamicContainer`
as well:
.. literalinclude:: ../../examples/containers/dynamic_provider_type.py
:language: python
:lines: 3-
:emphasize-lines: 23
:linenos:
The emphasized line will also cause an error.
.. disqus::

View File

@ -1,33 +0,0 @@
Container providers traversal
-----------------------------
To traverse container providers use method ``.traverse()``.
.. literalinclude:: ../../examples/containers/traverse.py
:language: python
:lines: 3-
:emphasize-lines: 38
Method ``.traverse()`` returns a generator. Traversal generator visits all container providers.
This includes nested providers even if they are not present on the root level of the container.
Traversal generator guarantees that each container provider will be visited only once.
It can traverse cyclic provider graphs.
Traversal generator does not guarantee traversal order.
You can use ``types=[...]`` argument to filter providers. Traversal generator will only return
providers matching specified types.
.. code-block:: python
:emphasize-lines: 3
container = Container()
for provider in container.traverse(types=[providers.Resource]):
print(provider)
# <dependency_injector.providers.Resource(<function init_database at 0x10bd2cb80>) at 0x10d346b40>
# <dependency_injector.providers.Resource(<function init_cache at 0x10be373a0>) at 0x10d346bc0>
.. disqus::

View File

@ -1,68 +0,0 @@
Chained Factories pattern
=========================
This example demonstrates "Chained Factories" pattern.
The idea of the pattern is in wrapping ``Factory`` into another ``Factory`` that adds
additional arguments.
.. code-block:: python
base_factory = providers.Factory(
SomeClass,
base_argument=1,
)
concrete_factory = providers.Factory(
base_factory,
extra_argument=2,
)
if __name__ == "__main__":
instance = concrete_factory()
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
Sample code
-----------
Listing of the pattern example:
.. literalinclude:: ../../examples/miniapps/factory-patterns/chained_factories.py
:language: python
Arguments priority
------------------
Passing of the arguments works the same way like for any other :ref:`factory-provider`.
.. code-block:: python
# 1. Keyword arguments of upper level factory are added to lower level factory
chained_dict_factory = providers.Factory(
providers.Factory(dict, arg1=1),
arg2=2,
)
print(chained_dict_factory()) # prints: {"arg1": 1, "arg2": 2}
# 2. Keyword arguments of upper level factory have priority
chained_dict_factory = providers.Factory(
providers.Factory(dict, arg1=1),
arg1=2,
)
print(chained_dict_factory()) # prints: {"arg1": 2}
# 3. Keyword arguments provided from context have the most priority
chained_dict_factory = providers.Factory(
providers.Factory(dict, arg1=1),
arg1=2,
)
print(chained_dict_factory(arg1=3)) # prints: {"arg1": 3}
Credits
-------
The "Chained Factories" pattern was suggested by the ``Dependency Injector`` users.
.. disqus::

View File

@ -1,74 +0,0 @@
Factory of Factories pattern
============================
This example demonstrates "Factory of Factories" pattern.
The idea of the pattern is in creating a ``Factory`` that creates another ``Factory`` and adds
additional arguments.
.. code-block:: python
base_factory = providers.Factory(
providers.Factory
SomeClass,
base_argument=1,
)
concrete_factory = providers.Factory(
OtherClass,
instance=base_factory(extra_argument=1),
)
if __name__ == "__main__":
instance = concrete_factory()
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
Sample code
-----------
Listing of the pattern example:
.. literalinclude:: ../../examples/miniapps/factory-patterns/factory_of_factories.py
:language: python
Arguments priority
------------------
Passing of the arguments works the same way like for any other :ref:`factory-provider`.
.. code-block:: python
# 1. Keyword arguments of upper level factory are added to lower level factory
factory_of_dict_factories = providers.Factory(
providers.Factory,
dict,
arg1=1,
)
dict_factory = factory_of_dict_factories(arg2=2)
print(dict_factory()) # prints: {"arg1": 1, "arg2": 2}
# 2. Keyword arguments of upper level factory have priority
factory_of_dict_factories = providers.Factory(
providers.Factory,
dict,
arg1=1,
)
dict_factory = factory_of_dict_factories(arg1=2)
print(dict_factory()) # prints: {"arg1": 2}
# 3. Keyword arguments provided from context have the most priority
factory_of_dict_factories = providers.Factory(
providers.Factory,
dict,
arg1=1,
)
dict_factory = factory_of_dict_factories(arg1=2)
print(dict_factory(arg1=3)) # prints: {"arg1": 3}
Credits
-------
The "Factory of Factories" pattern was suggested by the ``Dependency Injector`` users.
.. disqus::

View File

@ -1,17 +0,0 @@
Other examples
==============
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework
:description: This sections contains assorted Dependency Injector examples.
This sections contains assorted ``Dependency Injector`` examples.
.. toctree::
:maxdepth: 2
use-cases
password-hashing
chained-factories
factory-of-factories

View File

@ -1,30 +0,0 @@
Password hashing example
========================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,Callable
:description: This example demonstrates a usage of the Callable provider.
This example demonstrates an injection of the ``Callable`` provider.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/password-hashing>`_.
Sample code
-----------
Listing of the pattern example:
.. literalinclude:: ../../examples/miniapps/password-hashing/example.py
:language: python
Run the example
---------------
Instructions for running:
.. code-block:: bash
python example.py
.. disqus::

View File

@ -1,74 +0,0 @@
Use cases example
=================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,DependenciesContainer
:description: This example demonstrates a usage of the DependenciesContainer provider.
This example demonstrates a usage of the ``DependenciesContainer`` provider.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
Application structure
---------------------
Example application has next structure:
.. code-block:: bash
./
└── example/
├── __init__.py
├── __main__.py
├── adapters.py
├── containers.py
└── usecases.py
Containers
----------
Listing of the ``example/containers.py``:
.. literalinclude:: ../../examples/miniapps/use-cases/example/containers.py
:language: python
Main module
-----------
Listing of the ``example/__main__.py``:
.. literalinclude:: ../../examples/miniapps/use-cases/example/__main__.py
:language: python
Run the application
-------------------
Instructions for running in the "test" mode:
.. code-block:: bash
python run.py test example@example.com
Instructions for running in the "prod" mode:
.. code-block:: bash
python run.py prod example@example.com
Adapters and use cases
----------------------
Listing of the ``example/adapters.py``:
.. literalinclude:: ../../examples/miniapps/use-cases/example/adapters.py
:language: python
Listing of the ``example/usecases.py``:
.. literalinclude:: ../../examples/miniapps/use-cases/example/usecases.py
:language: python
.. disqus::

View File

@ -1,83 +0,0 @@
.. _aiohttp-example:
Aiohttp example
===============
.. meta::
:keywords: Python,Dependency Injection,Aiohttp,Example
:description: This example demonstrates a usage of the Aiohttp and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Aiohttp <https://docs.aiohttp.org/>`_.
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
:ref:`aiohttp-tutorial` demonstrates how to build this application step-by-step.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── giphynavigator/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── giphy.py
│ ├── handlers.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``giphynavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/containers.py
:language: python
Handlers
--------
Handler has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``giphynavigator/handlers.py``:
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/handlers.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``handlers`` module, creates
``Aiohttp`` app and setup routes.
Listing of ``giphynavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/tests.py
:language: python
:emphasize-lines: 32,59,73
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,89 +0,0 @@
.. _application-multiple-containers:
Application example (multiple containers)
=========================================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,AWS,boto3,client
:description: This example shows how you can create an application using multiple declarative
containers. We build an example Python micro application following the dependency
injection principle. It consists from several services with a domain logic that
have dependencies on database & AWS S3.
This example shows how you can create an application using multiple declarative containers. Using
multiple declarative containers is a good choice for a large application. For
building a moderate or a small size application refer to :ref:`application-single-container`.
We build an example micro application following the dependency injection principle. It consists
of several services with a domain logic. The services have dependencies on database & AWS S3.
.. image:: images/application.png
:width: 100%
:align: center
Start from the scratch or jump to the section:
.. contents::
:local:
:backlinks: none
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_.
Application structure
---------------------
Application consists of an ``example`` package, a configuration file and a ``requirements.txt``
file.
.. code-block:: bash
./
├── example/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ └── services.py
├── config.yml
└── requirements.txt
Containers
----------
Listing of the ``example/containers.py``:
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/containers.py
:language: python
Main module
-----------
Listing of the ``example/__main__.py``:
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/__main__.py
:language: python
Services
--------
Listing of the ``example/services.py``:
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/services.py
:language: python
Configuration
-------------
Listing of the ``config.yml``:
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/config.yml
:language: yaml
Run the application
-------------------
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,95 +0,0 @@
.. _application-single-container:
Application example (single container)
======================================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,AWS,boto3,client
:description: This example shows how you can create an application using a single declarative
container. We build an example Python micro application following the dependency
injection principle. It consists from several services with a domain logic that
have dependencies on database & AWS S3.
This example shows how you can create an application using a single declarative container. Using
a single declarative container is a good choice for small or moderate size application. For
building a large application refer to :ref:`application-multiple-containers`.
We build an example micro application following the dependency injection principle. It consists
of several services with a domain logic. The services have dependencies on database & AWS S3.
.. image:: images/application.png
:width: 100%
:align: center
Start from the scratch or jump to the section:
.. contents::
:local:
:backlinks: none
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_.
Application structure
---------------------
Application consists of an ``example`` package, several configuration files and a
``requirements.txt`` file.
.. code-block:: bash
./
├── example/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ └── services.py
├── config.ini
├── logging.ini
└── requirements.txt
Container
---------
Listing of the ``example/containers.py``:
.. literalinclude:: ../../examples/miniapps/application-single-container/example/containers.py
:language: python
Main module
-----------
Listing of the ``example/__main__.py``:
.. literalinclude:: ../../examples/miniapps/application-single-container/example/__main__.py
:language: python
Services
--------
Listing of the ``example/services.py``:
.. literalinclude:: ../../examples/miniapps/application-single-container/example/services.py
:language: python
Configuration
-------------
Listing of the ``config.ini``:
.. literalinclude:: ../../examples/miniapps/application-single-container/config.ini
:language: ini
Listing of the ``logging.ini``:
.. literalinclude:: ../../examples/miniapps/application-single-container/logging.ini
:language: ini
Run the application
-------------------
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,22 +0,0 @@
.. _boto3-example:
Boto3 example
=============
.. meta::
:keywords: Python,Dependency Injection,Boto3,AWS,Amazon Web Services,S3,SQS,Rout53,EC2,Lambda,Example
:description: This example demonstrates a usage of Boto3 AWS client and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Boto3 <https://github.com/boto/boto3>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/boto3-session>`_.
Listing of ``boto3_session_example.py``:
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
:language: python
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,134 +0,0 @@
.. _decoupled-packages:
Decoupled packages example (multiple containers)
================================================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,AWS,boto3,client
:description: This example shows how to use Dependency Injector to create Python decoupled packages.
To achieve a decoupling each package has a container with the components. When
a component needs a dependency from the outside of the package scope we use the
Dependency provider. The package container has no knowledge on where the
dependencies come from. It states a need that the dependencies must be provided.
This helps to decouple a package from the 3rd party dependencies and other
packages.
This example shows how to use ``Dependency Injector`` to create decoupled packages.
To achieve a decoupling each package has a container with the components. When a component needs a
dependency from the outside of the package scope we use the ``Dependency`` provider. The package
container has no knowledge on where the dependencies come from. It states a need that the
dependencies must be provided. This helps to decouple a package from the 3rd party dependencies
and other packages.
To wire the packages we use an application container. Application container has all 3rd party
dependencies and package containers. It wires the packages and dependencies to create a
complete application.
We build an example micro application that consists of 3 packages:
- ``user`` - a package with user domain logic, depends on a database
- ``photo`` - a package with photo domain logic, depends on a database and AWS S3
- ``analytics`` - a package with analytics domain logic, depends on the ``user`` and ``photo``
package components
.. image:: images/decoupled-packages.png
:width: 100%
:align: center
Start from the scratch or jump to the section:
.. contents::
:local:
:backlinks: none
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
Application structure
---------------------
Application consists of an ``example`` package, a configuration file and a ``requirements.txt``
file.
.. code-block:: bash
./
├── example/
│ ├── analytics/
│ │ ├── __init__.py
│ │ ├── containers.py
│ │ └── services.py
│ ├── photo/
│ │ ├── __init__.py
│ │ ├── containers.py
│ │ ├── entities.py
│ │ └── repositories.py
│ ├── user/
│ │ ├── __init__.py
│ │ ├── containers.py
│ │ ├── entities.py
│ │ └── repositories.py
│ ├── __init__.py
│ ├── __main__.py
│ └── containers.py
├── config.ini
└── requirements.txt
Package containers
------------------
Listing of the ``example/user/containers.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/user/containers.py
:language: python
Listing of the ``example/photo/containers.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/photo/containers.py
:language: python
Listing of the ``example/analytics/containers.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/analytics/containers.py
:language: python
Application container
---------------------
Application container consists of all packages and 3rd party dependencies. Its role is to wire
everything together in a complete application.
Listing of the ``example/containers.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/containers.py
:language: python
.. note::
Package ``analytics`` has dependencies on the repositories from the ``user`` and
``photo`` packages. This is an example of how you can pass the dependencies from one package
to another.
Main module
-----------
Listing of the ``example/__main__.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/__main__.py
:language: python
Configuration
-------------
Listing of the ``config.ini``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/config.ini
:language: ini
Run the application
-------------------
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,99 +0,0 @@
.. _django-example:
Django example
==============
.. meta::
:keywords: Python,Dependency Injection,Django,Example
:description: This example demonstrates a usage of the Django and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Django <https://www.djangoproject.com/>`_.
The example application helps to search for repositories on the Github.
.. image:: images/django.png
:width: 100%
:align: center
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
Application structure
---------------------
Application has standard Django project structure. It consists of ``githubnavigator`` project package and
``web`` application package:
.. code-block:: bash
./
├── githubnavigator/
│ ├── __init__.py
│ ├── asgi.py
│ ├── containers.py
│ ├── services.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── web/
│ ├── templates/
│ │ ├── base.html
│ │ └── index.html
│ ├── __init__.py
│ ├── apps.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── manage.py
└── requirements.txt
Container
---------
Declarative container is defined in ``githubnavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/django/githubnavigator/containers.py
:language: python
Container instance is created in ``githubnavigator/__init__.py``:
.. literalinclude:: ../../examples/miniapps/django/githubnavigator/__init__.py
:language: python
Views
-----
View has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``web/views.py``:
.. literalinclude:: ../../examples/miniapps/django/web/views.py
:language: python
App config
----------
Container is wired to the ``views`` module in the app config ``web/apps.py``:
.. literalinclude:: ../../examples/miniapps/django/web/apps.py
:language: python
:emphasize-lines: 13
Tests
-----
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``web/tests.py``:
.. literalinclude:: ../../examples/miniapps/django/web/tests.py
:language: python
:emphasize-lines: 39,60
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,100 +0,0 @@
.. _fastapi-redis-example:
FastAPI + Redis example
=======================
.. meta::
:keywords: Python,Dependency Injection,FastAPI,Redis,Example
:description: This example demonstrates a usage of the FastAPI, Redis, and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_ and
`Redis <https://redis.io/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-redis>`_.
See also:
- Provider :ref:`async-injections`
- Resource provider :ref:`resource-async-initializers`
- Wiring :ref:`async-injections-wiring`
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── fastapiredis/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── redis.py
│ ├── services.py
│ └── tests.py
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
Redis
-----
Module ``redis`` defines Redis connection pool initialization and shutdown. See ``fastapiredis/redis.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/redis.py
:language: python
Service
-------
Module ``services`` contains example service. Service has a dependency on Redis connection pool.
It uses it for getting and setting a key asynchronously. Real life service will do something more meaningful.
See ``fastapiredis/services.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/services.py
:language: python
Container
---------
Declarative container wires example service with Redis connection pool. See ``fastapiredis/containers.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/containers.py
:language: python
Application
-----------
Module ``application`` creates ``FastAPI`` app, setup endpoint, and init container.
Endpoint ``index`` has a dependency on example service. The dependency is injected using :ref:`wiring` feature.
Listing of ``fastapiredis/application.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace example service with a mock. See ``fastapiredis/tests.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/tests.py
:language: python
:emphasize-lines: 24
Sources
-------
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-redis>`_.
See also:
- Provider :ref:`async-injections`
- Resource provider :ref:`resource-async-initializers`
- Wiring :ref:`async-injections-wiring`
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,121 +0,0 @@
.. _fastapi-sqlalchemy-example:
FastAPI + SQLAlchemy example
============================
.. meta::
:keywords: Python,Dependency Injection,FastAPI,SQLAlchemy,Example
:description: This example demonstrates a usage of the FastAPI, SQLAlchemy, and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_ and
`SQLAlchemy <https://www.sqlalchemy.org/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
Thanks to `@ShvetsovYura <https://github.com/ShvetsovYura>`_ for providing initial example:
`FastAPI_DI_SqlAlchemy <https://github.com/ShvetsovYura/FastAPI_DI_SqlAlchemy>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── webapp/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── database.py
│ ├── endpoints.py
│ ├── models.py
│ ├── repositories.py
│ ├── services.py
│ └── tests.py
├── config.yml
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
Application factory
-------------------
Application factory creates container, wires it with the ``endpoints`` module, creates
``FastAPI`` app, and setup routes.
Application factory also creates database if it does not exist.
Listing of ``webapp/application.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/application.py
:language: python
Endpoints
---------
Module ``endpoints`` contains example endpoints. Endpoints have a dependency on user service.
User service is injected using :ref:`wiring` feature. See ``webapp/endpoints.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/endpoints.py
:language: python
Container
---------
Declarative container wires example user service, user repository, and utility database class.
See ``webapp/containers.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/containers.py
:language: python
Services
--------
Module ``services`` contains example user service. See ``webapp/services.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/services.py
:language: python
Repositories
------------
Module ``repositories`` contains example user repository. See ``webapp/repositories.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/repositories.py
:language: python
Models
------
Module ``models`` contains example SQLAlchemy user model. See ``webapp/models.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/models.py
:language: python
Database
--------
Module ``database`` defines declarative base and utility class with engine and session factory.
See ``webapp/database.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/database.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace repository with a mock. See ``webapp/tests.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/tests.py
:language: python
:emphasize-lines: 25, 45, 58, 74, 86, 97
Sources
-------
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,81 +0,0 @@
.. _fastapi-example:
FastAPI example
===============
.. meta::
:keywords: Python,Dependency Injection,FastAPI,Example
:description: This example demonstrates a usage of the FastAPI and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_.
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── giphynavigator/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── endpoints.py
│ ├── giphy.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``giphynavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/containers.py
:language: python
Endpoints
---------
Endpoint has a dependency on search service. There are also some config options that are used as default values.
The dependencies are injected using :ref:`wiring` feature.
Listing of ``giphynavigator/endpoints.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/endpoints.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``endpoints`` module, creates
``FastAPI`` app, and setup routes.
Listing of ``giphynavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/tests.py
:language: python
:emphasize-lines: 29,57,72
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,91 +0,0 @@
.. _flask-blueprints-example:
Flask blueprints example
========================
.. meta::
:keywords: Python,Dependency Injection,Flask,Blueprints,Example
:description: This example demonstrates a usage of the Flask Blueprints and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Flask <https://flask.palletsprojects.com/en/1.1.x/>`_
blueprints.
The example application helps to search for repositories on the Github.
.. image:: images/flask.png
:width: 100%
:align: center
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── githubnavigator/
│ ├── blueprints
│ │ ├── __init__.py
│ │ └── example.py
│ ├── templates
│ │ ├── base.html
│ │ └── index.py
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``githubnavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/containers.py
:language: python
Blueprints
----------
Blueprint's view has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``githubnavigator/blueprints/example.py``:
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/blueprints/example.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the blueprints, creates
``Flask`` app, and setup routes.
Listing of ``githubnavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``githubnavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/tests.py
:language: python
:emphasize-lines: 44,67
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,89 +0,0 @@
.. _flask-example:
Flask example
=============
.. meta::
:keywords: Python,Dependency Injection,Flask,Example
:description: This example demonstrates a usage of the Flask and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Flask <https://flask.palletsprojects.com/en/1.1.x/>`_.
The example application helps to search for repositories on the Github.
.. image:: images/flask.png
:width: 100%
:align: center
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
:ref:`flask-tutorial` demonstrates how to build this application step-by-step.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── githubnavigator/
│ ├── templates
│ │ ├── base.html
│ │ └── index.py
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── services.py
│ ├── tests.py
│ └── views.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``githubnavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/containers.py
:language: python
Views
-----
View has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``githubnavigator/views.py``:
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/views.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``views`` module, creates
``Flask`` app and setup routes.
Listing of ``githubnavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``githubnavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/tests.py
:language: python
:emphasize-lines: 44,67
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
.. include:: ../sponsor.rst
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 KiB

View File

@ -2,25 +2,18 @@ Examples
========
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Example
:description: Python dependency injection examples.
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: Current section of documentation is designed to provide
several example mini applications that are built on the top
of inversion of control principle and powered by
"Dependency Injector" framework.
Explore the examples to see the ``Dependency Injector`` in action.
Current section of documentation is designed to provide several example mini
applications that are built according to the inversion of control principle
and powered by *Dependency Injector* framework.
.. toctree::
:maxdepth: 2
application-single-container
application-multiple-containers
decoupled-packages
boto3
django
flask
flask-blueprints
aiohttp
sanic
fastapi
fastapi-redis
fastapi-sqlalchemy
.. disqus::
movie_lister
services_miniapp

View File

@ -0,0 +1,130 @@
Movie lister naive example
--------------------------
.. meta::
:description: Movie lister - is a naive example of dependency injection and
inversion of control containers on Python. Original example
was taken from Martin Fowler's article about dependency
injection and inversion of control.
This naive example was taken from Martin Fowler's article about dependency
injection and inversion of control: http://www.martinfowler.com/articles/injection.html
Like Martin says:
.. pull-quote::
*Like all of my examples it's one of those super-simple examples;
small enough to be unreal, but hopefully enough for you to visualize
what's going on without falling into the bog of a real example.*
While original Martin's MovieLister example was a bit modified here, it
makes sense to provide some description. So, the idea of this example is to
create ``movies`` library that can be configured to work with different
movie databases (csv, sqlite, etc...) and provide 2 main features:
1. List all movies that were directed by certain person.
2. List all movies that were released in certain year.
Also this example contains 3 mini applications that are based on ``movies``
library:
1. ``app_csv.py`` - list movies by certain criteria from csv file database.
2. ``app_db.py`` - list movies by certain criteria from sqlite database.
3. ``app_db_csv.py`` - list movies by certain criteria from csv file and
sqlite databases.
Instructions for running:
.. code-block:: bash
python app_csv.py
python app_db.py
python app_db_csv.py
Full code of example could be found on GitHub_.
Movies library
~~~~~~~~~~~~~~
Classes diagram:
.. image:: /images/miniapps/movie_lister/classes.png
:width: 100%
:align: center
Movies library structure:
.. code-block:: bash
/movies
/__init__.py
/finders.py
/listers.py
/models.py
Listing of ``movies/__init__.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/movies/__init__.py
:language: python
:linenos:
Example application
~~~~~~~~~~~~~~~~~~~
Example application structure:
.. code-block:: bash
/example
/__init__.py
/db.py
/main.py
Listing of ``examples/main.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/example/main.py
:language: python
:linenos:
Listing of ``examples/db.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/example/db.py
:language: python
:linenos:
Csv application
~~~~~~~~~~~~~~~
Listing of ``app_csv.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/app_csv.py
:language: python
:linenos:
Database application
~~~~~~~~~~~~~~~~~~~~
Listing of ``app_db.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/app_db.py
:language: python
:linenos:
Csv and database application
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Listing of ``app_db_csv.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/app_db_csv.py
:language: python
:linenos:
.. disqus::
.. _GitHub: https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/movie_lister

View File

@ -1,82 +0,0 @@
.. _sanic-example:
Sanic example
==============
.. meta::
:keywords: Python,Dependency Injection,Sanic,Example
:description: This example demonstrates a usage of the Sanic and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Sanic <https://sanic.readthedocs.io/en/latest/>`_.
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── giphynavigator/
│ ├── __init__.py
│ ├── __main__.py
│ ├── application.py
│ ├── containers.py
│ ├── giphy.py
│ ├── handlers.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``giphynavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/containers.py
:language: python
Handlers
--------
Handler has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``giphynavigator/handlers.py``:
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/handlers.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``handlers`` module, creates
``Sanic`` app and setup routes.
Listing of ``giphynavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/tests.py
:language: python
:emphasize-lines: 34,61,75
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -0,0 +1,76 @@
Services mini application example
---------------------------------
.. meta::
:description: "Services miniapp" is an example mini application that
consists from several services that have dependencies on
some standard and 3rd-party libraries for logging,
interaction with database and remote service via API.
"Services miniapp" example demonstrates usage of
Dependency Injector for creating several IoC containers.
"Services miniapp" is an example mini application that consists from several
services that have dependencies on some standard and 3rd-party libraries for
logging, interaction with database and remote service calls via API.
"Services miniapp" example demonstrates usage of
:doc:`Dependency Injector <../index>` for creating several IoC containers.
Instructions for running:
.. code-block:: bash
python run.py 1 secret photo.jpg
Example application
~~~~~~~~~~~~~~~~~~~
Classes diagram:
.. image:: /images/miniapps/services/classes.png
:width: 100%
:align: center
Example application structure:
.. code-block:: bash
/example
/__init__.py
/main.py
/services.py
Listing of ``example/services.py``:
.. literalinclude:: ../../examples/miniapps/services/example/services.py
:language: python
:linenos:
Listing of ``example/main.py``:
.. literalinclude:: ../../examples/miniapps/services/example/main.py
:language: python
:linenos:
IoC containers
~~~~~~~~~~~~~~
Listing of ``containers.py``:
.. literalinclude:: ../../examples/miniapps/services/containers.py
:language: python
:linenos:
Run application
~~~~~~~~~~~~~~~
Listing of ``run.py``:
.. literalinclude:: ../../examples/miniapps/services/run.py
:language: python
:linenos:
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
docs/images/internals.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,150 +1,87 @@
=================================================================
Dependency Injector --- Dependency injection framework for Python
=================================================================
======================================================================
Dependency Injector --- Dependency injection microframework for Python
======================================================================
.. meta::
:google-site-verification: V1hlKfpgL3AARAElwFcqP4qW1Smsx5bKSRU8O86i20Y
:google-site-verification: 6it89zX0_wccKEhAqbAiYQooS95f0BA8YfesHk6bsNA
:keywords: Python,Dependency injection,DI,Inversion of Control,IoC,
IoC Container,Factory, Singleton, Design Patterns
:description: Dependency Injector is a dependency injection framework
for Python. It helps to maintain you application structure.
It was designed to be unified, developer-friendly
tool that helps to implement dependency injection design
pattern in formal, pretty, Pythonic way. Dependency Injector
provides implementations of such popular design patterns
like IoC container, Factory and Singleton. Dependency
Injector providers are implemented as C extension types
:description: Dependency Injector is a dependency injection microframework
for Python. It was designed to be unified, developer-friendly
tool that helps to implement dependency injection design
pattern in formal, pretty, Pythonic way. Dependency Injector
provides implementations of such popular design patterns
like IoC container, Factory and Singleton. Dependency
Injector providers are implemented as C extension types
using Cython.
.. _index:
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Latest Version
*Dependency Injector* is a dependency injection microframework for Python.
It was designed to be unified, developer-friendly tool that helps to implement
dependency injection design pattern in formal, pretty, Pythonic way.
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: License
*Dependency Injector* framework key features are:
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Supported Python versions
+ Easy, smart, pythonic style.
+ Obvious, clear structure.
+ Extensibility and flexibility.
+ High performance.
+ Memory efficiency.
+ Thread safety.
+ Documentation.
+ Semantic versioning.
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Supported Python implementations
*Dependency Injector* providers are implemented as C extension types using
Cython.
.. image:: https://static.pepy.tech/badge/dependency-injector
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
Status
------
.. image:: https://static.pepy.tech/badge/dependency-injector/month
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
.. image:: https://static.pepy.tech/badge/dependency-injector/week
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
.. image:: https://img.shields.io/pypi/wheel/dependency-injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Wheel
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
:target: https://github.com/ets-labs/python-dependency-injector/actions
:alt: Build Status
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
:alt: Coverage Status
``Dependency Injector`` is a dependency injection framework for Python.
It helps implementing the dependency injection principle.
Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
that help assemble your objects. See :ref:`providers`.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev/stage environment to replace API clients with stubs etc. See
:ref:`provider-overriding`.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
environment variables, and dictionaries. See :ref:`configuration-provider`.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
See :ref:`resource-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
.. code-block:: python
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
service = providers.Factory(
Service,
api_client=api_client,
)
@inject
def main(service: Service = Provide[Container.service]) -> None:
...
if __name__ == "__main__":
container = Container()
container.config.api_key.from_env("API_KEY", required=True)
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
container.wire(modules=[__name__])
main() # <-- dependency is injected automatically
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
With the ``Dependency Injector``, object assembling is consolidated in the container.
Dependency injections are defined explicitly.
This makes it easier to understand and change how the application works.
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
:target: https://github.com/ets-labs/python-dependency-injector
Explore the documentation to know more about the ``Dependency Injector``.
.. _contents:
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
| *PyPi* | .. image:: https://img.shields.io/pypi/v/dependency_injector.svg |
| | :target: https://pypi.python.org/pypi/dependency_injector/ |
| | :alt: Latest Version |
| | .. image:: https://img.shields.io/pypi/l/dependency_injector.svg |
| | :target: https://pypi.python.org/pypi/dependency_injector/ |
| | :alt: License |
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
| *Python versions and implementations* | .. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg |
| | :target: https://pypi.python.org/pypi/dependency_injector/ |
| | :alt: Supported Python versions |
| | .. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg |
| | :target: https://pypi.python.org/pypi/dependency_injector/ |
| | :alt: Supported Python implementations |
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
| *Builds and tests coverage* | .. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master |
| | :target: https://travis-ci.org/ets-labs/python-dependency-injector |
| | :alt: Build Status |
| | .. image:: https://coveralls.io/repos/ets-labs/python-dependency-injector/badge.svg |
| | :target: https://coveralls.io/r/ets-labs/python-dependency-injector |
| | :alt: Coverage Status |
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
| *Github* | .. image:: https://img.shields.io/github/watchers/ets-labs/python-dependency-injector.svg?style=social&label=Watch |
| | :target: https://github.com/ets-labs/python-dependency-injector |
| | :alt: Github watchers |
| | .. image:: https://img.shields.io/github/stars/ets-labs/python-dependency-injector.svg?style=social&label=Star |
| | :target: https://github.com/ets-labs/python-dependency-injector |
| | :alt: Github stargazers |
| | .. image:: https://img.shields.io/github/forks/ets-labs/python-dependency-injector.svg?style=social&label=Fork |
| | :target: https://github.com/ets-labs/python-dependency-injector |
| | :alt: Github forks |
+---------------------------------------+--------------------------------------------------------------------------------------------------------------------+
Contents
--------
.. toctree::
:maxdepth: 2
.. toctree::
:maxdepth: 2
introduction/index
examples/index
tutorials/index
providers/index
containers/index
wiring
examples-other/index
api/index
main/feedback
main/changelog
introduction/index
main/installation
providers/index
containers/index
examples/index
api/index
main/feedback
main/changelog

View File

@ -1,315 +1,134 @@
Dependency injection and inversion of control in Python
=======================================================
-------------------------------------------------------
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Example
:description: This page describes a usage of the dependency injection and inversion of control
in Python. It contains Python examples that show how to implement dependency
injection. It demonstrates a usage of the dependency injection framework
Dependency Injector, its container, Factory, Singleton and Configuration
providers. The example show how to use Dependency Injector providers overriding
feature for testing or configuring project in different environments and explains
why it's better than monkey-patching.
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This article describes benefits of dependency injection and
inversion of control for Python applications. Also it
contains some Python examples that show how dependency
injection and inversion could be implemented. In addition, it
demonstrates usage of dependency injection framework,
IoC container and such popular design pattern as Factory.
History
~~~~~~~
Originally, dependency injection pattern got popular in languages with static
typing, like Java. Dependency injection framework can
significantly improve flexibility of the language with static typing. Also,
implementation of dependency injection framework for language with static
typing is not something that one can do shortly, it could be quite complex
thing to be done well.
While Python is very flexible interpreted language with dynamic typing, there
is a meaning that dependency injection doesn't work for it as well, as it does
for Java. Also there is a meaning that dependency injection framework is
something that Python developer would not ever need, cause dependency injection
could be implemented easily using language fundamentals.
Discussion
~~~~~~~~~~
It is true.
Partly.
Dependency injection, as a software design pattern, has number of
advantages that are common for each language (including Python):
+ Dependency Injection decreases coupling between a class and its dependency.
+ Because dependency injection doesn't require any change in code behavior it
can be applied to legacy code as a refactoring. The result is clients that
are more independent and that are easier to unit test in isolation using
stubs or mock objects that simulate other objects not under test. This ease
of testing is often the first benefit noticed when using dependency
injection.
+ Dependency injection can be used to externalize a system's configuration
details into configuration files allowing the system to be reconfigured
without recompilation (rebuilding). Separate configurations can be written
for different situations that require different implementations of
components. This includes, but is not limited to, testing.
+ Reduction of boilerplate code in the application objects since all work to
initialize or set up dependencies is handled by a provider component.
+ Dependency injection allows a client to remove all knowledge of a concrete
implementation that it needs to use. This helps isolate the client from the
impact of design changes and defects. It promotes reusability, testability
and maintainability.
+ Dependency injection allows a client the flexibility of being configurable.
Only the client's behavior is fixed. The client may act on anything that
supports the intrinsic interface the client expects.
.. note::
While improved testability is one the first benefits of using dependency
injection, it could be easily overwhelmed by monkey-patching technique,
that works absolutely great in Python (you can monkey-patch anything,
anytime). At the same time, monkey-patching has nothing similar with
other advantages defined above. Also monkey-patching technique is
something that could be considered like too dirty to be used in production.
The complexity of dependency injection pattern implementation in Python is
definitely quite lower than in other languages (even with dynamic typing).
.. note::
Low complexity of dependency injection pattern implementation in Python
still means that some code should be written, reviewed, tested and
supported.
Talking about inversion of control, it is a software design principle that
also works for each programming language, not dependending on its typing type.
Inversion of control is used to increase modularity of the program and make
it extensible.
Main design purposes of using inversion of control are:
+ To decouple the execution of a task from implementation.
+ To focus a module on the task it is designed for.
+ To free modules from assumptions about how other systems do what they do and
instead rely on contracts.
+ To prevent side effects when replacing a module.
Example
~~~~~~~
Let's go through next example:
.. image:: /images/miniapps/engines_cars/diagram.png
:width: 100%
:align: center
Listing of ``example.engines`` module:
.. literalinclude:: ../../examples/miniapps/engines_cars/example/engines.py
:language: python
:linenos:
Listing of ``example.cars`` module:
.. literalinclude:: ../../examples/miniapps/engines_cars/example/cars.py
:language: python
:linenos:
Next example demonstrates creation of several cars with different engines:
Originally dependency injection pattern got popular in languages with static typing like Java.
Dependency injection is a principle that helps to achieve an inversion of control. A
dependency injection framework can significantly improve the flexibility of a language
with static typing. Implementation of a dependency injection framework for a language
with static typing is not something that one can do quickly. It will be a quite complex thing
to be done well. And will take time.
.. literalinclude:: ../../examples/miniapps/engines_cars/example_di.py
:language: python
:linenos:
Python is an interpreted language with dynamic typing. There is an opinion that dependency
injection doesn't work for it as well as it does for Java. A lot of the flexibility is already
built-in. Also, there is an opinion that a dependency injection framework is something that
Python developer rarely needs. Python developers say that dependency injection can be implemented
easily using language fundamentals.
While previous example demonstrates advantages of dependency injection, there
is a disadvantage demonstration as well - creation of car requires additional
code for specification of dependencies. Nevertheless, this disadvantage could
be easily avoided by using a dependency injection framework for creation of
inversion of control container (IoC container).
This page describes the advantages of applying dependency injection in Python. It
contains Python examples that show how to implement dependency injection. It demonstrates the usage
of the ``Dependency Injector`` framework, its container, ``Factory``, ``Singleton``,
and ``Configuration`` providers. The example shows how to use providers' overriding feature
of ``Dependency Injector`` for testing or re-configuring a project in different environments and
explains why it's better than monkey-patching.
Example of creation of several inversion of control containers (IoC containers)
using :doc:`Dependency Injector <../index>`:
What is dependency injection?
-----------------------------
.. literalinclude:: ../../examples/miniapps/engines_cars/example_ioc_containers.py
:language: python
:linenos:
Let's see what the dependency injection is.
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
.. image:: images/coupling-cohesion.png
What is coupling and cohesion?
Coupling and cohesion are about how tough the components are tied.
- **High coupling**. If the coupling is high it's like using superglue or welding. No easy way
to disassemble.
- **High cohesion**. High cohesion is like using screws. Quite easy to disassemble and
re-assemble in a different way. It is an opposite to high coupling.
Cohesion often correlates with coupling. Higher cohesion usually leads to lower coupling and vice versa.
Low coupling brings flexibility. Your code becomes easier to change and test.
How to implement the dependency injection?
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
Before:
.. code-block:: python
import os
class ApiClient:
def __init__(self) -> None:
self.api_key = os.getenv("API_KEY") # <-- dependency
self.timeout = int(os.getenv("TIMEOUT")) # <-- dependency
class Service:
def __init__(self) -> None:
self.api_client = ApiClient() # <-- dependency
def main() -> None:
service = Service() # <-- dependency
...
if __name__ == "__main__":
main()
After:
.. code-block:: python
import os
class ApiClient:
def __init__(self, api_key: str, timeout: int) -> None:
self.api_key = api_key # <-- dependency is injected
self.timeout = timeout # <-- dependency is injected
class Service:
def __init__(self, api_client: ApiClient) -> None:
self.api_client = api_client # <-- dependency is injected
def main(service: Service) -> None: # <-- dependency is injected
...
if __name__ == "__main__":
main(
service=Service(
api_client=ApiClient(
api_key=os.getenv("API_KEY"),
timeout=int(os.getenv("TIMEOUT")),
),
),
)
``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a
timeout from a configuration file or even get them from a database.
``Service`` is decoupled from the ``ApiClient``. It does not create it anymore. You can provide a
stub or other compatible object.
Function ``main()`` is decoupled from ``Service``. It receives it as an argument.
Flexibility comes with a price.
Now you need to assemble and inject the objects like this:
.. code-block:: python
main(
service=Service(
api_client=ApiClient(
api_key=os.getenv("API_KEY"),
timeout=int(os.getenv("TIMEOUT")),
),
),
)
The assembly code might get duplicated and it'll become harder to change the application structure.
Here comes the ``Dependency Injector``.
What does the Dependency Injector do?
-------------------------------------
With the dependency injection pattern, objects lose the responsibility of assembling
the dependencies. The ``Dependency Injector`` absorbs that responsibility.
``Dependency Injector`` helps to assemble and inject the dependencies.
It provides a container and providers that help you with the objects assembly.
When you need an object you place a ``Provide`` marker as a default value of a
function argument. When you call this function, framework assembles and injects
the dependency.
.. code-block:: python
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
service = providers.Factory(
Service,
api_client=api_client,
)
@inject
def main(service: Service = Provide[Container.service]) -> None:
...
if __name__ == "__main__":
container = Container()
container.config.api_key.from_env("API_KEY", required=True)
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
container.wire(modules=[__name__])
main() # <-- dependency is injected automatically
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
When you do testing, you call the ``container.api_client.override()`` method to replace the real API
client with a mock. When you call ``main()``, the mock is injected.
You can override any provider with another provider.
It also helps you in a re-configuring project for different environments: replace an API client
with a stub on the dev or stage.
Objects assembling is consolidated in a container. Dependency injections are defined explicitly.
This makes it easier to understand and change how an application works.
Testing, Monkey-patching and dependency injection
-------------------------------------------------
The testability benefit is opposed to monkey-patching.
In Python, you can monkey-patch anything, anytime. The problem with monkey-patching is
that it's too fragile. The cause of it is that when you monkey-patch you do something that
wasn't intended to be done. You monkey-patch the implementation details. When implementation
changes the monkey-patching is broken.
With dependency injection, you patch the interface, not an implementation. This is a way more
stable approach.
Also, monkey-patching is way too dirty to be used outside of the testing code for
re-configuring the project for the different environments.
Conclusion
----------
Dependency injection provides you with three advantages:
- **Flexibility**. The components are loosely coupled. You can easily extend or change the
functionality of a system by combining the components in a different way. You even can do it on
the fly.
- **Testability**. Testing is easier because you can easily inject mocks instead of real objects
that use API or database, etc.
- **Clearness and maintainability**. Dependency injection helps you reveal the dependencies.
Implicit becomes explicit. And "Explicit is better than implicit" (PEP 20 - The Zen of Python).
You have all the components and dependencies defined explicitly in a container. This
provides an overview and control of the application structure. It is easier to understand and
change it.
Is it worth applying dependency injection in Python?
It depends on what you build. The advantages above are not too important if you use Python as a
scripting language. The picture is different when you use Python to create an application. The
larger the application the more significant the benefits.
Is it worth using a framework for applying dependency injection?
The complexity of the dependency injection pattern implementation in Python is
lower than in other languages but it's still in place. It doesn't mean you have to use a
framework but using a framework is beneficial because the framework is:
- Already implemented
- Tested on all platforms and versions of Python
- Documented
- Supported
- Other engineers are familiar with it
An advice at last:
- **Give it a try**. Dependency injection is counter-intuitive. Our nature is that
when we need something the first thought that comes to our mind is to go and get it. Dependency
injection is just like "Wait, I need to state a need instead of getting something right away".
It's like a little investment that will pay-off later. The advice is to just give it a try for
two weeks. This time will be enough for getting your own impression. If you don't like it you
won't lose too much.
- **Common sense first**. Use common sense when applying dependency injection. It is a good
principle, but not a silver bullet. If you do it too much you will reveal too many of the
implementation details. Experience comes with practice and time.
What's next?
------------
Choose one of the following as a next step:
- Look at the application examples:
- :ref:`application-single-container`
- :ref:`application-multiple-containers`
- :ref:`decoupled-packages`
- :ref:`boto3-example`
- :ref:`django-example`
- :ref:`flask-example`
- :ref:`flask-blueprints-example`
- :ref:`aiohttp-example`
- :ref:`sanic-example`
- :ref:`fastapi-example`
- :ref:`fastapi-redis-example`
- :ref:`fastapi-sqlalchemy-example`
- Pass the tutorials:
- :ref:`flask-tutorial`
- :ref:`aiohttp-tutorial`
- :ref:`asyncio-daemon-tutorial`
- :ref:`cli-tutorial`
- Know more about the ``Dependency Injector`` :ref:`key-features`
- Know more about the :ref:`providers`
- Know more about the :ref:`wiring`
- Go to the :ref:`contents`
Useful links
------------
A few useful links related to a dependency injection design pattern for further reading:
+ https://en.wikipedia.org/wiki/Dependency_injection
+ https://martinfowler.com/articles/injection.html
+ https://github.com/ets-labs/python-dependency-injector
+ https://pypi.org/project/dependency-injector/
.. include:: ../sponsor.rst
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -3,16 +3,18 @@ Introduction
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: Current section of the documentation is provides an
overview of the dependency injection, inversion of
control and Dependency Injector framework.
:description: Current section of documentation is designed to give some
overview about dependency injection pattern, inversion of
control principle and "Dependency Injector" framework.
The current section of the documentation provides an overview of the
dependency injection, inversion of control, and the ``Dependency Injector`` framework.
Current section of documentation is designed to give some overview about
dependency injection pattern, inversion of control principle and
*Dependency Injector* framework.
.. toctree::
:maxdepth: 2
what_is_di
di_in_python
key_features
installation
structure

View File

@ -1,42 +0,0 @@
Installation
============
``Dependency Injector`` is available on `PyPI <https://pypi.org/project/dependency-injector/>`_.
To install the latest version you can use ``pip``:
.. code-block:: bash
pip install dependency-injector
Some modules of the ``Dependency Injector`` are implemented as C extensions.
``Dependency Injector`` is distributed as a pre-compiled wheels. Wheels are
available for all supported Python versions on Linux, Windows, and MacOS.
Linux distribution uses `manylinux <https://github.com/pypa/manylinux>`_.
If there is no appropriate wheel for your environment (Python version and OS)
installer will compile the package from sources on your machine. You'll need
a C compiler and Python header files.
To verify the installed version:
.. code-block:: bash
>>> import dependency_injector
>>> dependency_injector.__version__
'4.39.0'
.. note::
When adding ``Dependency Injector`` to ``pyproject.toml`` or ``requirements.txt``
don't forget to pin the version to the current major:
.. code-block:: bash
dependency-injector>=4.0,<5.0
*The next major version can be incompatible.*
All releases are available on the `PyPI release history page <https://pypi.org/project/dependency-injector/#history>`_.
Each release has an appropriate tag. The tags are available on the
`GitHub releases page <https://github.com/ets-labs/python-dependency-injector/releases>`_.
.. disqus::

View File

@ -1,43 +1,48 @@
.. _key-features:
Key features
------------
Key features of Dependency Injector
-----------------------------------
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This article describes key features of the Dependency Injector
framework.
:description: This article describes key features of "Dependency Injector"
framework. It also provides some cases and recommendations
about usage of "Dependency Injector" framework.
Key features of the ``Dependency Injector``:
*Dependency Injector* is a dependency injection framework for Python projects.
It was designed to be unified, developer-friendly tool for managing any kind
of Python objects and their dependencies in formal, pretty way.
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
that help assemble your objects. See :ref:`providers`.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev/stage environment to replace API clients with stubs etc. See
:ref:`provider-overriding`.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
environment variables, and dictionaries. See :ref:`configuration-provider`.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
See :ref:`resource-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
*Dependency Injector* framework key features are:
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
+ Easy, smart, pythonic style.
+ Obvious, clear structure.
+ Extensibility and flexibility.
+ Memory efficiency.
+ Thread safety.
+ Documentation.
+ Semantic versioning.
.. code-block:: plain
*Dependency Injector* framework could be used in different application types:
Explicit is better than implicit
+ Web applications based on Flask, Django or any other web framework.
+ Asynchronous applications based on asyncio, Tornado and Twisted.
+ Standalone frameworks and libraries.
+ GUI applications.
You need to specify how to assemble and where to inject the dependencies explicitly.
*Dependency Injector* framework could be integrated on different project
stages:
+ It could be used in the beginning of development of new applications.
+ It could be integrated into applications that are in active development
stage.
+ It could be used for refactoring of legacy applications.
Components of *Dependency Injector* framework could be used:
+ In composition with each other.
+ Separately between each other.
Main idea of *Dependency Injector* framework is to be useful tool for the
right thing.
The power of the framework is in its simplicity.
``Dependency Injector`` is a simple tool for the powerful concept.
.. disqus::

View File

@ -0,0 +1,50 @@
Structure of Dependency Injector
--------------------------------
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This article describes "Dependency Injector" framework
components and their interaction between each other.
Providers and containers are the former components of
the framework.
Current section describes *Dependency Injector* main entities and their
interaction between each other.
.. image:: /images/internals.png
:width: 100%
:align: center
There are 2 main entities: providers & containers.
Providers
~~~~~~~~~
Providers are strategies of accessing objects. For example,
:py:class:`dependency_injector.providers.Factory` creates new instance
of provided class every time it is called.
:py:class:`dependency_injector.providers.Singleton` creates provided
instance once and returns it on every next call. Base class is -
:py:class:`dependency_injector.providers.Provider`.
Providers could be:
+ Injected into each other.
+ Overridden by each other.
+ Extended.
Containers
~~~~~~~~~~
Containers are collections of providers. They are used for grouping
of providers by some principles. Base class is -
:py:class:`dependency_injector.containers.DeclarativeContainer`.
Containers could be:
+ Overridden by each other.
+ Copied from each other.
+ Extended.
.. disqus::

View File

@ -0,0 +1,128 @@
What is dependency injection and inversion of control?
------------------------------------------------------
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This article provides definition of dependency injection,
inversion of control and dependency inversion. It contains
example code in Python that is refactored to be following
inversion of control principle.
Definition
~~~~~~~~~~
Wikipedia provides quite good definitions of dependency injection pattern
and related principles:
.. glossary::
`Dependency injection`_
In software engineering, dependency injection is a software design
pattern that implements inversion of control for resolving
dependencies. A dependency is an object that can be used (a service).
An injection is the passing of a dependency to a dependent object (a
client) that would use it. The service is made part of the client's
state. Passing the service to the client, rather than allowing a
client to build or find the service, is the fundamental requirement of
the pattern.
Dependency injection allows a program design to follow the dependency
inversion principle. The client delegates to external code (the
injector) the responsibility of providing its dependencies. The client
is not allowed to call the injector code. It is the injecting code
that constructs the services and calls the client to inject them. This
means the client code does not need to know about the injecting code.
The client does not need to know how to construct the services. The
client does not need to know which actual services it is using. The
client only needs to know about the intrinsic interfaces of the
services because these define how the client may use the services.
This separates the responsibilities of use and construction.
`Inversion of control`_
In software engineering, inversion of control (IoC) describes a design
in which custom-written portions of a computer program receive the
flow of control from a generic, reusable library. A software
architecture with this design inverts control as compared to
traditional procedural programming: in traditional programming, the
custom code that expresses the purpose of the program calls into
reusable libraries to take care of generic tasks, but with inversion
of control, it is the reusable code that calls into the custom, or
task-specific, code.
Inversion of control is used to increase modularity of the program and
make it extensible, and has applications in object-oriented
programming and other programming paradigms. The term was popularized
by Robert C. Martin and Martin Fowler.
The term is related to, but different from, the dependency inversion
principle, which concerns itself with decoupling dependencies between
high-level and low-level layers through shared abstractions.
`Dependency inversion`_
In object-oriented programming, the dependency inversion principle
refers to a specific form of decoupling software modules. When
following this principle, the conventional dependency relationships
established from high-level, policy-setting modules to low-level,
dependency modules are reversed, thus rendering high-level modules
independent of the low-level module implementation details. The
principle states:
+ High-level modules should not depend on low-level modules.
Both should depend on abstractions.
+ Abstractions should not depend on details.
Details should depend on abstractions.
The principle inverts the way some people may think about
object-oriented design, dictating that both high- and low-level
objects must depend on the same abstraction.
Example
~~~~~~~
Let's go through the code of ``example.py``:
.. literalinclude:: ../../examples/di_demo/example.py
:language: python
:linenos:
At some point, things defined above mean, that the code from ``example.py``,
could look different, like in ``example_di.py``:
.. literalinclude:: ../../examples/di_demo/example_di.py
:language: python
:linenos:
Best explanation, ever
~~~~~~~~~~~~~~~~~~~~~~
Some times ago `user198313`_ posted awesome `question`_ about dependency
injection on `StackOverflow`_:
.. note::
How to explain dependency injection to a 5-year-old?
And `John Munsch`_ provided absolutely Great answer:
.. note::
When you go and get things out of the refrigerator for yourself, you can
cause problems. You might leave the door open, you might get something
Mommy or Daddy doesn't want you to have. You might even be looking for
something we don't even have or which has expired.
What you should be doing is stating a need, "I need something to drink
with lunch," and then we will make sure you have something when you sit
down to eat.
.. disqus::
.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection
.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control
.. _Dependency inversion: https://en.wikipedia.org/wiki/Dependency_inversion_principle
.. _StackOverflow: http://stackoverflow.com/
.. _question: http://stackoverflow.com/questions/1638919/how-to-explain-dependency-injection-to-a-5-year-old/1639186
.. _user198313: http://stackoverflow.com/users/198313/user198313
.. _John Munsch: http://stackoverflow.com/users/31899/john-munsch

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,12 @@
Feedback
========
To post a question, bug report, a feature proposal or get some help open a
`Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_ or leave a comment
below.
Feel free to post questions, bugs, feature requests, proposals etc. on
*Dependency Injector* GitHub Issues:
https://github.com/ets-labs/python-dependency-injector/issues
Your feedback is quite important!
.. disqus::

View File

@ -0,0 +1,45 @@
Installation
============
*Dependency Injector* framework is distributed by PyPi_.
Latest stable version (and all previous versions) of *Dependency Injector*
framework can be installed from PyPi_:
.. code-block:: bash
# Installing latest version:
pip install dependency_injector
# Installing particular version:
pip install dependency-injector==3.3.2
.. note::
Some components of *Dependency Injector* are implemented as C extension types.
*Dependency Injector* is distributed as an archive with a source code, so
C compiler and Python header files are required for the installation.
Sources can be cloned from GitHub_:
.. code-block:: bash
git clone https://github.com/ets-labs/python-dependency-injector.git
Also all *Dependency Injector* releases can be downloaded from
`GitHub releases page`_.
Verification of currently installed version could be done using
:py:obj:`dependency_injector.VERSION` constant:
.. code-block:: bash
>>> import dependency_injector
>>> dependency_injector.__version__
'3.3.2'
.. _PyPi: https://pypi.python.org/pypi/dependency_injector
.. _GitHub: https://github.com/ets-labs/python-dependency-injector
.. _GitHub releases page: https://github.com/ets-labs/python-dependency-injector/releases
.. disqus::

View File

@ -1,72 +0,0 @@
.. _aggregate-provider:
Aggregate provider
==================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
Aggregate,Polymorphism,Environment Variable,Flexibility
:description: Aggregate provider aggregates other providers.
This page demonstrates how to implement the polymorphism and increase the
flexibility of your application using the Aggregate provider.
:py:class:`Aggregate` provider aggregates a group of other providers.
.. currentmodule:: dependency_injector.providers
.. literalinclude:: ../../examples/providers/aggregate.py
:language: python
:lines: 3-
:emphasize-lines: 24-27
Each provider in the ``Aggregate`` is associated with a key. You can call aggregated providers by providing
their key as a first argument. All positional and keyword arguments following the key will be forwarded to
the called provider:
.. code-block:: python
yaml_reader = container.config_readers("yaml", "./config.yml", foo=...)
You can also retrieve an aggregated provider by providing its key as an attribute name:
.. code-block:: python
yaml_reader = container.config_readers.yaml("./config.yml", foo=...)
To retrieve a dictionary of aggregated providers, use ``.providers`` attribute:
.. code-block:: python
container.config_readers.providers == {
"yaml": <YAML provider>,
"json": <JSON provider>,
}
.. note::
You can not override the ``Aggregate`` provider.
.. note::
When you inject the ``Aggregate`` provider, it is passed "as is".
To use non-string keys or string keys with ``.`` and ``-``, provide a dictionary as a positional argument:
.. code-block:: python
aggregate = providers.Aggregate({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})
.. seealso::
:ref:`selector-provider` to make injections based on a configuration value, environment variable, or a result of a callable.
``Aggregate`` provider is different from the :ref:`selector-provider`. ``Aggregate`` provider doesn't select which provider
to inject and doesn't have a selector. It is a group of providers and is always injected "as is". The rest of the interface
of both providers is similar.
.. note::
``Aggregate`` provider is a successor of :ref:`factory-aggregate-provider` provider. ``Aggregate`` provider doesn't have
a restriction on the provider type, while ``FactoryAggregate`` aggregates only ``Factory`` providers.
.. disqus::

View File

@ -1,110 +0,0 @@
.. _async-injections:
Asynchronous injections
=======================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Providers,Async,Injections,Asynchronous,Await,
Asyncio
:description: Dependency Injector providers support asynchronous injections. This page
demonstrates how make asynchronous dependency injections in Python.
Providers support asynchronous injections.
.. literalinclude:: ../../examples/providers/async.py
:language: python
:emphasize-lines: 26-29
:lines: 3-
If provider has any awaitable injections it switches into async mode. In async mode provider always returns awaitable.
This causes a cascade effect:
.. code-block:: bash
provider1() <── Async mode enabled <──┐
│ │
├──> provider2() │
│ │
├──> provider3() <── Async mode enabled <──┤
│ │ │
│ └──> provider4() <── Async provider ───────┘
└──> provider5()
└──> provider6()
In async mode provider prepares injections asynchronously.
If provider has multiple awaitable dependencies, it will run them concurrently. Provider will wait until all
dependencies are ready and inject them afterwards.
.. code-block:: bash
provider1()
├──> provider2() <── Async mode enabled
├──> provider3() <── Async mode enabled
└──> provider4() <── Async mode enabled
Here is what provider will do for the previous example:
.. code-block:: python
injections = await asyncio.gather(
provider2(),
provider3(),
provider4(),
)
await provider1(*injections)
Overriding behaviour
--------------------
In async mode provider always returns awaitable. It applies to the overriding too. If provider in async mode is
overridden by a provider that doesn't return awaitable result, the result will be wrapped into awaitable.
.. literalinclude:: ../../examples/providers/async_overriding.py
:language: python
:emphasize-lines: 19-24
:lines: 3-
Async mode mechanics and API
----------------------------
By default provider's async mode is undefined.
When provider async mode is undefined, provider will automatically select the mode during the next call.
If the result is awaitable, provider will enable async mode, if not - disable it.
If provider async mode is enabled, provider always returns awaitable. If the result is not awaitable,
provider wraps it into awaitable explicitly. You can safely ``await`` provider in async mode.
If provider async mode is disabled, provider behaves the regular way. It doesn't do async injections
preparation or non-awaitables to awaitables conversion.
Once provider async mode is enabled or disabled, provider will stay in this state. No automatic switching
will be done.
.. image:: images/async_mode.png
You can also use following methods to change provider's async mode manually:
- ``Provider.enable_async_mode()``
- ``Provider.disable_async_mode()``
- ``Provider.reset_async_mode()``
To check the state of provider's async mode use:
- ``Provider.is_async_mode_enabled()``
- ``Provider.is_async_mode_disabled()``
- ``Provider.is_async_mode_undefined()``
See also:
- Wiring :ref:`async-injections-wiring`
- Resource provider :ref:`resource-async-initializers`
- :ref:`fastapi-redis-example`
.. disqus::

View File

@ -1,20 +1,61 @@
Callable provider
=================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Function,Method,Example
:description: Callable provider helps to make dependencies injection into functions. This page
demonstrates how to use a Callable provider.
Callable providers
------------------
.. currentmodule:: dependency_injector.providers
:py:class:`Callable` provider calls a function, a method or another callable.
:py:class:`Callable` provider calls wrapped callable on every call.
.. literalinclude:: ../../examples/providers/callable.py
Callable providers and injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:py:class:`Callable` provider takes a various number of positional and keyword
arguments that are used as wrapped callable injections. Every time, when
:py:class:`Callable` provider is called, positional and keyword argument
injections would be passed as an callable arguments.
Injections are done according to the next rules:
+ All providers (instances of :py:class:`Provider`) are called every time
when injection needs to be done.
+ Providers could be injected "as is" (delegated), if it is defined obviously.
Check out :ref:`callable_providers_delegation`.
+ All other injectable values are provided *"as is"*.
+ Positional context arguments will be appended after :py:class:`Callable`
positional injections.
+ Keyword context arguments have priority on :py:class:`Callable` keyword
injections and will be merged over them.
Example that shows usage of :py:class:`Callable` with positional argument
injections:
.. literalinclude:: ../../examples/providers/callable_args.py
:language: python
:lines: 3-
:linenos:
Next one example shows usage of :py:class:`Callable` with keyword argument
injections:
.. image:: /images/providers/callable.png
:width: 100%
:align: center
.. literalinclude:: ../../examples/providers/callable_kwargs.py
:language: python
:linenos:
.. _callable_providers_delegation:
Callable providers delegation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:py:class:`Callable` provider could be delegated to any other provider via
any kind of injection.
Delegation of :py:class:`Callable` providers is the same as
:py:class:`Factory` providers delegation, please follow
:ref:`factory_providers_delegation` section for examples (with exception
about using :py:class:`DelegatedCallable` instead of
:py:class:`DelegatedFactory`).
``Callable`` provider handles an injection of the dependencies the same way like a
:ref:`factory-provider`.
.. disqus::

View File

@ -1,579 +0,0 @@
.. _configuration-provider:
Configuration provider
======================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
Option,Ini,Json,Yaml,Pydantic,Dict,Environment Variable Interpolation,
Environment Variable Substitution,Environment Variable in Config,
Environment Variable in YAML file,Environment Variable in INI file,Default,Load,Read
:description: Configuration provides configuration options to the other providers. This page
demonstrates how to use Configuration provider to inject the dependencies, load
a configuration from an ini or yaml file, a dictionary, an environment variable,
or a pydantic settings object. This page also describes how to substitute (interpolate)
environment variables in YAML and INI configuration files.
.. currentmodule:: dependency_injector.providers
:py:class:`Configuration` provider provides configuration options to the other providers.
.. literalinclude:: ../../examples/providers/configuration/configuration.py
:language: python
:emphasize-lines: 7,12-13
:lines: 3-
It implements the principle "use first, define later".
.. contents::
:local:
:backlinks: none
Loading from an INI file
------------------------
``Configuration`` provider can load configuration from an ``ini`` file using the
:py:meth:`Configuration.from_ini` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_ini.py
:language: python
:lines: 3-
:emphasize-lines: 12
where ``examples/providers/configuration/config.ini`` is:
.. literalinclude:: ../../examples/providers/configuration/config.ini
:language: ini
Alternatively, you can provide a path to the INI file over the configuration provider argument. In that case,
the container will call ``config.from_ini()`` automatically:
.. code-block:: python
:emphasize-lines: 3
class Container(containers.DeclarativeContainer):
config = providers.Configuration(ini_files=["./config.ini"])
if __name__ == "__main__":
container = Container() # Config is loaded from ./config.ini
:py:meth:`Configuration.from_ini` method supports environment variables interpolation.
.. code-block:: ini
[section]
option1 = ${ENV_VAR}
option2 = ${ENV_VAR}/path
option3 = ${ENV_VAR:default}
See also: :ref:`configuration-envs-interpolation`.
Loading from a YAML file
------------------------
``Configuration`` provider can load configuration from a ``yaml`` file using the
:py:meth:`Configuration.from_yaml` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_yaml.py
:language: python
:lines: 3-
:emphasize-lines: 12
where ``examples/providers/configuration/config.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.yml
:language: ini
Alternatively, you can provide a path to the YAML file over the configuration provider argument. In that case,
the container will call ``config.from_yaml()`` automatically:
.. code-block:: python
:emphasize-lines: 3
class Container(containers.DeclarativeContainer):
config = providers.Configuration(yaml_files=["./config.yml"])
if __name__ == "__main__":
container = Container() # Config is loaded from ./config.yml
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation.
.. code-block:: ini
section:
option1: ${ENV_VAR}
option2: ${ENV_VAR}/path
option3: ${ENV_VAR:default}
See also: :ref:`configuration-envs-interpolation`.
:py:meth:`Configuration.from_yaml` method uses custom version of ``yaml.SafeLoader``.
To use another loader use ``loader`` argument:
.. code-block:: python
import yaml
container.config.from_yaml("config.yml", loader=yaml.UnsafeLoader)
.. note::
Loading of a yaml configuration requires ``PyYAML`` package.
You can install the ``Dependency Injector`` with an extra dependency::
pip install dependency-injector[yaml]
or install ``PyYAML`` directly::
pip install pyyaml
*Don't forget to mirror the changes in the requirements file.*
Loading from a JSON file
------------------------
``Configuration`` provider can load configuration from a ``json`` file using the
:py:meth:`Configuration.from_json` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_json.py
:language: python
:lines: 3-
:emphasize-lines: 12
where ``examples/providers/configuration/config.json`` is:
.. literalinclude:: ../../examples/providers/configuration/config.json
:language: json
Alternatively, you can provide a path to a json file over the configuration provider argument. In that case,
the container will call ``config.from_json()`` automatically:
.. code-block:: python
:emphasize-lines: 3
class Container(containers.DeclarativeContainer):
config = providers.Configuration(json_files=["./config.json"])
if __name__ == "__main__":
container = Container() # Config is loaded from ./config.json
:py:meth:`Configuration.from_json` method supports environment variables interpolation.
.. code-block:: json
{
"section": {
"option1": "${ENV_VAR}",
"option2": "${ENV_VAR}/path",
"option3": "${ENV_VAR:default}"
}
}
See also: :ref:`configuration-envs-interpolation`.
Loading from a Pydantic settings
--------------------------------
``Configuration`` provider can load configuration from a ``pydantic_settings.BaseSettings`` object using the
:py:meth:`Configuration.from_pydantic` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
:language: python
:lines: 3-
:emphasize-lines: 32
To get the data from pydantic settings ``Configuration`` provider calls its ``model_dump()`` method.
If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments.
.. code-block:: python
container.config.from_pydantic(Settings(), exclude={"optional"})
Alternatively, you can provide a ``pydantic_settings.BaseSettings`` object over the configuration provider argument. In that case,
the container will call ``config.from_pydantic()`` automatically:
.. code-block:: python
:emphasize-lines: 3
class Container(containers.DeclarativeContainer):
config = providers.Configuration(pydantic_settings=[Settings()])
if __name__ == "__main__":
container = Container() # Config is loaded from Settings()
.. note::
``Dependency Injector`` doesn't install ``pydantic-settings`` by default.
You can install the ``Dependency Injector`` with an extra dependency::
pip install dependency-injector[pydantic2]
or install ``pydantic-settings`` directly::
pip install pydantic-settings
*Don't forget to mirror the changes in the requirements file.*
.. note::
For backward-compatibility, Pydantic v1 is still supported.
Passing ``pydantic.BaseSettings`` instances will work just as fine as ``pydantic_settings.BaseSettings``.
Loading from a dictionary
-------------------------
``Configuration`` provider can load configuration from a Python ``dict`` using the
:py:meth:`Configuration.from_dict` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_dict.py
:language: python
:lines: 3-
:emphasize-lines: 12-19
Loading from an environment variable
------------------------------------
``Configuration`` provider can load configuration from an environment variable using the
:py:meth:`Configuration.from_env` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_env.py
:language: python
:lines: 3-
:emphasize-lines: 18-20
You can use ``as_`` argument for the type casting of an environment variable value:
.. code-block:: python
:emphasize-lines: 2,6,10
# API_KEY=secret
container.config.api_key.from_env("API_KEY", as_=str, required=True)
assert container.config.api_key() == "secret"
# SAMPLING_RATIO=0.5
container.config.sampling.from_env("SAMPLING_RATIO", as_=float, required=True)
assert container.config.sampling() == 0.5
# TIMEOUT undefined, default is used
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
assert container.config.timeout() == 5
Loading a value
---------------
``Configuration`` provider can load configuration value using the
:py:meth:`Configuration.from_value` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_value.py
:language: python
:lines: 3-
:emphasize-lines: 14-15
Loading from the multiple sources
---------------------------------
``Configuration`` provider can load configuration from the multiple sources. Loaded
configuration is merged recursively over the existing configuration.
.. literalinclude:: ../../examples/providers/configuration/configuration_multiple.py
:language: python
:lines: 3-
:emphasize-lines: 12-13
where ``examples/providers/configuration/config.local.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.local.yml
:language: ini
.. _configuration-envs-interpolation:
Using environment variables in configuration files
--------------------------------------------------
``Configuration`` provider supports environment variables interpolation in configuration files.
Use ``${ENV_NAME}`` in the configuration file to substitute value from environment
variable ``ENV_NAME``.
.. code-block:: ini
section:
option: ${ENV_NAME}
You can also specify a default value using ``${ENV_NAME:default}`` format. If environment
variable ``ENV_NAME`` is undefined, configuration provider will substitute value ``default``.
.. code-block:: ini
[section]
option = ${ENV_NAME:default}
If you'd like to specify a default value for environment variable inside of the application you can use
``os.environ.setdefault()``.
.. literalinclude:: ../../examples/providers/configuration/configuration_env_interpolation_os_default.py
:language: python
:lines: 3-
:emphasize-lines: 12
If environment variable is undefined and doesn't have a default, ``Configuration`` provider
will replace it with an empty value. This is a default behavior. To raise an error on
undefined environment variable that doesn't have a default value, pass argument
``envs_required=True`` to a configuration reading method:
.. code-block:: python
container.config.from_yaml("config.yml", envs_required=True)
See also: :ref:`configuration-strict-mode`.
.. note::
``Configuration`` provider makes environment variables interpolation before parsing. This preserves
original parser behavior. For instance, undefined environment variable in YAML configuration file
will be replaced with an empty value and then YAML parser will load the file.
Original configuration file:
.. code-block:: ini
section:
option: ${ENV_NAME}
Configuration file after interpolation where ``ENV_NAME`` is undefined:
.. code-block:: ini
section:
option:
Configuration provider after parsing interpolated YAML file contains ``None`` in
option ``section.option``:
.. code-block:: python
assert container.config.section.option() is None
If you want to disable environment variables interpolation, pass ``envs_required=None``:
.. code-block:: yaml
:caption: templates.yml
template_string: 'Hello, ${name}!'
.. code-block:: python
>>> container.config.from_yaml("templates.yml", envs_required=None)
>>> container.config.template_string()
'Hello, ${name}!'
Mandatory and optional sources
------------------------------
By default, methods ``.from_yaml()`` and ``.from_ini()`` ignore errors if configuration file does not exist.
You can use this to specify optional configuration files.
If configuration file is mandatory, use ``required`` argument. Configuration provider will raise an error
if required file does not exist.
You can also use ``required`` argument when loading configuration from dictionaries and environment variables.
Mandatory YAML file:
.. code-block:: python
container.config.from_yaml("config.yaml", required=True)
Mandatory INI file:
.. code-block:: python
container.config.from_ini("config.ini", required=True)
Mandatory dictionary:
.. code-block:: python
container.config.from_dict(config_dict, required=True)
Mandatory environment variable:
.. code-block:: python
container.config.api_key.from_env("API_KEY", required=True)
See also: :ref:`configuration-strict-mode`.
Specifying the value type
-------------------------
You can specify the type of the injected configuration value explicitly.
This helps when you read the value from an ini file or an environment variable and need to
convert it into an ``int`` or a ``float``.
.. literalinclude:: ../../examples/providers/configuration/configuration_type.py
:language: python
:lines: 3-
:emphasize-lines: 19
``Configuration`` provider has next helper methods:
- ``.as_int()``
- ``.as_float()``
- ``.as_(callback, *args, **kwargs)``
The last method ``.as_(callback, *args, **kwargs)`` helps to implement other conversions.
.. literalinclude:: ../../examples/providers/configuration/configuration_type_custom.py
:language: python
:lines: 3-
:emphasize-lines: 18
With the ``.as_(callback, *args, **kwargs)`` you can specify a function that will be called
before the injection. The value from the config will be passed as a first argument. The returned
value will be injected. Parameters ``*args`` and ``**kwargs`` are handled as any other injections.
.. _configuration-strict-mode:
Strict mode and required options
--------------------------------
You can use configuration provider in strict mode. In strict mode configuration provider raises an error
on access to any undefined option.
.. literalinclude:: ../../examples/providers/configuration/configuration_strict.py
:language: python
:lines: 3-
:emphasize-lines: 12
Methods ``.from_*()`` in strict mode raise an exception if configuration file does not exist or
configuration data is undefined:
.. code-block:: python
:emphasize-lines: 10,15,20,25,30
class Container(containers.DeclarativeContainer):
config = providers.Configuration(strict=True)
if __name__ == "__main__":
container = Container()
try:
container.config.from_yaml("does-not_exist.yml") # raise exception
except FileNotFoundError:
...
try:
container.config.from_ini("does-not_exist.ini") # raise exception
except FileNotFoundError:
...
try:
container.config.from_pydantic(EmptySettings()) # raise exception
except ValueError:
...
try:
container.config.from_env("UNDEFINED_ENV_VAR") # raise exception
except ValueError:
...
try:
container.config.from_dict({}) # raise exception
except ValueError:
...
Environment variables interpolation in strict mode raises an exception when encounters
an undefined environment variable without a default value.
.. code-block:: ini
section:
option: ${UNDEFINED}
.. code-block:: python
try:
container.config.from_yaml("undefined_env.yml") # raise exception
except ValueError:
...
You can override ``.from_*()`` methods behaviour in strict mode using ``required`` argument:
.. code-block:: python
class Container(containers.DeclarativeContainer):
config = providers.Configuration(strict=True)
if __name__ == "__main__":
container = Container()
container.config.from_yaml("config.yml")
container.config.from_yaml("config.local.yml", required=False)
You can also use ``.required()`` option modifier when making an injection. It does not require to switch
configuration provider to strict mode.
.. literalinclude:: ../../examples/providers/configuration/configuration_required.py
:language: python
:lines: 11-20
:emphasize-lines: 8-9
.. note::
Modifier ``.required()`` should be specified before type modifier ``.as_*()``.
Aliases
-------
You can use ``Configuration`` provider with a context manager to create aliases.
.. literalinclude:: ../../examples/providers/configuration/configuration_alias.py
:language: python
:lines: 3-
:emphasize-lines: 14,22
.. note::
Library ``environs`` is a 3rd party library. You need to install it
separately::
pip install environs
Documentation is available on GitHub: https://github.com/sloria/environs
Injecting invariants
--------------------
You can inject invariant configuration options based on the value of the other configuration
option.
To use that you should provide the switch-value as an item of the configuration option that
contains sections ``config.options[config.switch]``:
- When the value of the ``config.switch`` is ``A``, the ``config.options.A`` is injected
- When the value of the ``config.switch`` is ``B``, the ``config.options.B`` is injected
.. literalinclude:: ../../examples/providers/configuration/configuration_itemselector.py
:language: python
:lines: 3-
:emphasize-lines: 15,30-31,38
.. disqus::

View File

@ -1,27 +0,0 @@
Coroutine provider
==================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Coroutine,Asynchronous,
Asyncio,Example
:description: Coroutine provider creates a coroutine. This page demonstrates how to use a
Coroutine provider.
.. currentmodule:: dependency_injector.providers
:py:class:`Coroutine` provider creates a coroutine.
.. literalinclude:: ../../examples/providers/coroutine.py
:language: python
:lines: 3-
.. note::
The example works on Python 3.7+. For earlier versions use ``loop.run_until_complete()``.
``Coroutine`` provider handles an injection of the dependencies the same way like a
:ref:`factory-provider`.
.. note::
``Coroutine`` provider returns ``True`` for ``asyncio.iscoroutinefunction()`` check.
.. disqus::

View File

@ -1,49 +1,38 @@
.. _create-provider:
Creating a custom provider
==========================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Custom provider, Create
:description: This page demonstrates how to create a custom provider.
Writing of custom providers
---------------------------
.. currentmodule:: dependency_injector.providers
You can create a custom provider.
List of *Dependency Injector* providers could be widened with custom providers.
To create a custom provider you need to follow these rules:
Below are some tips and recommendations that have to be met:
1. New provider class should inherit :py:class:`Provider`.
2. You need to implement the ``Provider._provide()`` method.
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an
equivalent copy of a provider. All providers must be copied with the ``deepcopy()`` function
from the ``providers`` module. It's essential to pass ``memo`` into ``deepcopy`` in order to keep
the preconfigured ``args`` and ``kwargs`` of stored providers. After the a new provider object
is created, use ``Provider._copy_overriding()`` method to copy all overriding providers. See the
example below.
4. If new provider has a ``__init__()`` method, it should call the parent
``Provider.__init__()``.
5. If new provider stores any other providers, these providers should be listed in
``.related`` property. Property ``.related`` also should yield providers from parent
``.related`` property.
1. Every custom provider has to extend base provider class -
:py:class:`Provider`.
2. Cusom provider's ``__init__()`` could be overriden, but parent's
initializer (:py:meth:`Provider.__init__`) has to be called.
3. Providing strategy has to be implemented in custom provider's
:py:meth:`Provider._provide` method. All ``*args`` & ``**kwargs``
that will be recieved by :py:meth:`Provider.__call__` will be
transefed to custom provider's :py:meth:`Provider._provide`.
4. If custom provider is based on some standard providers, it is better to
use delegation of standard providers, then extending of them.
5. If custom provider defines any attributes, it is good to list them in
``__slots__`` attribute (as *Dependency Injector* does). It can save
some memory.
6. If custom provider deals with injections, it is strongly recommended
to be consistent with :py:class:`Factory`, :py:class:`Singleton` and
:py:class:`Callable` providers style.
Example:
.. image:: /images/providers/custom_provider.png
:width: 100%
:align: center
.. literalinclude:: ../../examples/providers/custom_factory.py
:language: python
:lines: 3-
:linenos:
.. note::
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
inheriting a ``Provider`` and use ``Factory`` internally - the last is better.
2. When creating a new provider follow the ``Factory``-like injections style. Consistency matters.
3. Use the ``__slots__`` attribute to make sure nothing could be attached to your provider. You
will also save some memory.
.. note::
If you don't find needed provider in the ``providers`` module and experience troubles creating
one by your own - open a
`Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_.
I'll help you to resolve the issue if that's possible. If the new provider can be useful for
others I'll include it into the ``providers`` module.
.. disqus::

View File

@ -1,38 +0,0 @@
.. _dependency-provider:
Dependency provider
===================
.. currentmodule:: dependency_injector.providers
:py:class:`Dependency` provider is a placeholder for a dependency of a certain type.
To specify a type of the dependency use ``instance_of`` argument: ``Dependency(instance_of=SomeClass)``.
Dependency provider will control that returned object is an instance of ``instance_of`` type.
.. literalinclude:: ../../examples/providers/dependency.py
:language: python
:lines: 3-
:emphasize-lines: 26,35-36
To provide a dependency you need to override the ``Dependency`` provider. You can call
provider ``.override()`` method or provide an overriding provider when creating a container.
See :ref:`provider-overriding`. If you don't provide a dependency, ``Dependency`` provider
will raise an error:
.. literalinclude:: ../../examples/providers/dependency_undefined_error.py
:language: python
:lines: 18-
You also can provide a default for the dependency. To provide a default use ``default`` argument:
``Dependency(..., default=...)``. Default can be a value or a provider. If default is not a provider,
dependency provider will wrap it into the ``Object`` provider.
.. literalinclude:: ../../examples/providers/dependency_default.py
:language: python
:lines: 16-23
:emphasize-lines: 3
See also: :ref:`check-container-dependencies`.
.. disqus::

View File

@ -1,37 +0,0 @@
Dict provider
=============
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Dict,Injection
:description: Dict provider helps to inject a dictionary of the dependencies. This page demonstrates
how to use Dict provider.
.. currentmodule:: dependency_injector.providers
:py:class:`Dict` provider provides a dictionary of values.
.. literalinclude:: ../../examples/providers/dict.py
:language: python
:lines: 3-
:emphasize-lines: 21-24
``Dict`` provider handles keyword arguments the same way as a :ref:`factory-provider`.
To use non-string keys or keys with ``.`` and ``-`` provide a dictionary as a positional argument:
.. code-block:: python
providers.Dict({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})
Example:
.. literalinclude:: ../../examples/providers/dict_non_string_keys.py
:language: python
:lines: 3-
:emphasize-lines: 40-43
.. disqus::

View File

@ -0,0 +1,45 @@
External dependency providers
-----------------------------
.. currentmodule:: dependency_injector.providers
:py:class:`ExternalDependency` provider can be useful for development of
self-sufficient libraries / modules / applications that have required external
dependencies.
For example, you have created self-sufficient library / module / application,
that has dependency on *database connection*.
Second step you want to do is to make this software component to be easy
reusable by wide amount of developers and to be easily integrated into many
applications.
It may be good idea, to move all external dependencies (like
*database connection*) to the top level and make them to be injected on your
software component's initialization. It will make third party developers feel
themselves free about integration of yours component in their applications,
because they would be able to find right place / right way for doing this
in their application's architectures.
At the same time, you can be sure, that your external dependency will be
satisfied with appropriate instance.
Example:
.. note::
Class ``UsersService`` is a part of some library. ``UsersService`` has
dependency on database connection, which can be satisfied with any
DBAPI 2.0 database connection. Being a self-sufficient library,
``UsersService`` doesn't hardcode any kind of database management logic.
Instead of this, ``UsersService`` has external dependency, that has to
be satisfied by cleint's code, out of library's scope.
.. image:: /images/providers/external_dependency.png
.. literalinclude:: ../../examples/providers/external_dependency.py
:language: python
:linenos:
.. disqus::

View File

@ -1,242 +1,110 @@
.. _factory-provider:
Factory provider
================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Factory,Abstract Factory,
Pattern,Example,Aggregate
:description: Factory provider helps to implement dependency injection in Python. This page
demonstrates how to use Factory provider, inject the dependencies, and assemble
object graphs passing the injections deep inside. It also provides the examples
of the Abstract Factory pattern & provider and Factories Aggregation pattern.
Factory providers
-----------------
.. currentmodule:: dependency_injector.providers
:py:class:`Factory` provider creates new objects.
:py:class:`Factory` provider creates new instance of specified class on every
call.
Nothing could be better than brief example:
.. image:: /images/providers/factory.png
:width: 80%
:align: center
.. literalinclude:: ../../examples/providers/factory.py
:language: python
:lines: 3-
:linenos:
The first argument of the ``Factory`` provider is a class, a factory function or a method
that creates an object.
Factory providers and __init__ injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The rest of the ``Factory`` positional and keyword arguments are the dependencies.
``Factory`` injects the dependencies every time when creates a new object. The dependencies are
injected following these rules:
:py:class:`Factory` takes a various number of positional and keyword arguments
that are used as ``__init__()`` injections. Every time, when
:py:class:`Factory` creates new one instance, positional and keyword
argument injections would be passed as an instance's arguments.
+ If the dependency is a provider, this provider is called and the result of the call is injected.
+ If you need to inject the provider itself, you should use the ``.provider`` attribute. More at
:ref:`factory_providers_delegation`.
+ All other dependencies are injected *"as is"*.
+ Positional context arguments are appended after ``Factory`` positional dependencies.
+ Keyword context arguments have the priority over the ``Factory`` keyword dependencies with the
same name.
Injections are done according to the next rules:
.. image:: images/factory_init_injections.png
+ All providers (instances of :py:class:`Provider`) are called every time
when injection needs to be done.
+ Providers could be injected "as is" (delegated), if it is defined obviously.
Check out :ref:`factory_providers_delegation`.
+ All other injectable values are provided *"as is"*.
+ Positional context arguments will be appended after :py:class:`Factory`
positional injections.
+ Keyword context arguments have priority on :py:class:`Factory` keyword
injections and will be merged over them.
For example, if injectable value of injection is a :py:class:`Factory`, it
will provide new one instance (as a result of its call) every time, when
injection needs to be done.
Example below is a little bit more complicated. It shows how to create
:py:class:`Factory` of particular class with ``__init__()`` injections which
injectable values are also provided by another factories:
.. image:: /images/providers/factory_init_injections.png
.. literalinclude:: ../../examples/providers/factory_init_injections.py
:language: python
:lines: 3-
``Factory`` provider can inject attributes. Use ``.add_attributes()`` method to specify
attribute injections.
.. literalinclude:: ../../examples/providers/factory_attribute_injections.py
:language: python
:lines: 3-
:emphasize-lines: 18-18
Passing arguments to the underlying providers
---------------------------------------------
``Factory`` provider can pass the arguments to the underlying providers. This helps when you need
to assemble a nested objects graph and pass the arguments deep inside.
Consider the example:
.. image:: images/factory_init_injections_underlying.png
To create an ``Algorithm`` you need to provide all the dependencies: ``ClassificationTask``,
``Loss``, and ``Regularizer``. The last object in the chain, the ``Regularizer`` has a dependency
on the ``alpha`` value. The ``alpha`` value varies from algorithm to algorithm:
.. code-block:: python
Algorithm(
task=ClassificationTask(
loss=Loss(
regularizer=Regularizer(
alpha=alpha, # <-- the dependency
),
),
),
)
``Factory`` provider helps to deal with the such assembly. You need to create the factories for
all the classes and use special double-underscore ``__`` syntax for passing the ``alpha`` argument:
.. literalinclude:: ../../examples/providers/factory_init_injections_underlying.py
:language: python
:lines: 3-
:emphasize-lines: 44,49
When you use ``__`` separator in the name of the keyword argument the ``Factory`` looks for
the dependency with the same name as the left part of the ``__`` expression.
.. code-block:: none
<dependency>__<keyword for the underlying provider>=<value>
If ``<dependency>`` is found the underlying provider will receive the
``<keyword for the underlying provider>=<value>`` as an argument.
:linenos:
.. _factory_providers_delegation:
Passing providers to the objects
--------------------------------
Factory providers delegation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When you need to inject the provider itself, but not the result of its call, use the ``.provider``
attribute of the provider that you're going to inject.
:py:class:`Factory` provider could be delegated to any other provider via any
kind of injection.
.. image:: images/factory_delegation.png
As it was mentioned earlier, if :py:class:`Factory` is
injectable value, it will be called every time when injection needs to be
done. But sometimes there is a need to inject :py:class:`Factory` provider
itself (not a result of its call) as a dependency. Such injections are called
- *delegated provider injections*.
Saying in other words, delegation of factories - is a way to inject factories
themselves, instead of results of their calls.
.. literalinclude:: ../../examples/providers/factory_delegation.py
:language: python
:lines: 3-
:emphasize-lines: 28
:py:class:`Factory` delegation is performed by wrapping delegated
:py:class:`Factory` into special provider type - :py:class:`Delegate`, that
just returns wrapped :py:class:`Factory`.
.. note:: Any provider has a ``.provider`` attribute.
Actually, there are three ways for creating factory delegates:
.. _factory-string-imports:
String imports
--------------
``Factory`` provider can handle string imports:
.. code-block:: python
class Container(containers.DeclarativeContainer):
service = providers.Factory("myapp.mypackage.mymodule.Service")
You can also make a relative import:
.. code-block:: python
# in myapp/container.py
class Container(containers.DeclarativeContainer):
service = providers.Factory(".mypackage.mymodule.Service")
or import a member of the current module just specifying its name:
.. code-block:: python
class Service:
...
class Container(containers.DeclarativeContainer):
service = providers.Factory("Service")
.. note::
``Singleton``, ``Callable``, ``Resource``, and ``Coroutine`` providers handle string imports
the same way as a ``Factory`` provider.
.. _factory-specialize-provided-type:
Specializing the provided type
------------------------------
You can create a specialized ``Factory`` provider that provides only specific type. For doing
this you need to create a subclass of the ``Factory`` provider and define the ``provided_type``
class attribute.
.. literalinclude:: ../../examples/providers/factory_provided_type.py
:language: python
:lines: 3-
:emphasize-lines: 12-14
.. _abstract-factory:
Abstract factory
----------------
:py:class:`AbstractFactory` provider helps when you need to create a provider of some base class
and the particular implementation is not yet know. ``AbstractFactory`` provider is a ``Factory``
provider with two peculiarities:
+ Provides only objects of a specified type.
+ Must be overridden before usage.
.. image:: images/abstract_factory.png
:width: 100%
:align: center
.. literalinclude:: ../../examples/providers/abstract_factory.py
:language: python
:lines: 3-
:emphasize-lines: 34
.. _factory-aggregate-provider:
Factory aggregate
-----------------
:py:class:`FactoryAggregate` provider aggregates multiple factories.
.. seealso::
:ref:`aggregate-provider` it's a successor of ``FactoryAggregate`` provider that can aggregate
any type of provider, not only ``Factory``.
The aggregated factories are associated with the string keys. When you call the
``FactoryAggregate`` you have to provide one of the these keys as a first argument.
``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.
.. image:: images/factory_aggregate.png
:width: 100%
:align: center
.. literalinclude:: ../../examples/providers/factory_aggregate.py
:language: python
:lines: 3-
:emphasize-lines: 33-37,47
You can get a dictionary of the aggregated providers using ``.providers`` attribute.
To get a game provider dictionary from the previous example you can use
``game_factory.providers`` attribute.
You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the
previous example you can do ``chess = game_factory.chess("John", "Jane")``.
.. note::
You can not override the ``FactoryAggregate`` provider.
.. note::
When you inject the ``FactoryAggregate`` provider it is passed "as is".
To use non-string keys or string keys with ``.`` and ``-``, you can provide a dictionary as a positional argument:
.. code-block:: python
providers.FactoryAggregate({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})
+ ``DelegatedFactory(...)`` - use special type of factory -
:py:class:`DelegatedFactory`. Such factories are always injected as
delegates ("as is").
+ ``Delegate(Factory(...))`` - obviously wrapping factory into
:py:class:`Delegate` provider.
+ ``Factory(...).delegate()`` - calling factory :py:meth:`Factory.delegate`
method, that returns delegate wrapper for current factory.
Example:
.. literalinclude:: ../../examples/providers/factory_aggregate_non_string_keys.py
.. image:: /images/providers/factory_delegation.png
:width: 85%
:align: center
.. literalinclude:: ../../examples/providers/factory_delegation.py
:language: python
:lines: 3-
:emphasize-lines: 30-33,39-40
:linenos:
.. _factory_providers_specialization:
Factory providers specialization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:py:class:`Factory` provider could be specialized for any kind of needs via
creating its subclasses.
One of such specialization features is a limitation to :py:class:`Factory`
provided type:
.. literalinclude:: ../../examples/providers/factory_provided_type.py
:language: python
:linenos:
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Some files were not shown because too many files have changed in this diff Show More