Compare commits

..

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

187 changed files with 208004 additions and 5653 deletions

10
.coveragerc Normal file
View File

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

View File

@ -1,29 +0,0 @@
---
description: Code in Python and Cython
globs:
alwaysApply: false
---
- Follow PEP 8 rules
- When you write imports, split system, 3rd-party, and local imports with a new line
- Have two empty lines between the import block and the rest of the code
- Have an empty line (\n) at the end of every file
- If a file is supposed to be run, always add ``if __name__ == 'main'``
- Always follow a consistent pattern of using double or single quotes
- When there is a class without a docblock, leave one blank line before its members, e.g.:
```python
class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
```
- Avoid shortcuts in names unless absolutely necessary, exceptions:
```
arg
args
kwarg
kwargs
obj
cls
```
- Avoid inline comments unless absolutely necessary

View File

@ -1,7 +0,0 @@
---
description: Build and run tests
globs:
alwaysApply: false
---
- Use Makefile commands to build, test, lint and other similar operations when they are available.
- Activate virtualenv before running any commands by ``. venv/bin/actvate``

View File

@ -1,8 +0,0 @@
---
description: Run examples
globs:
alwaysApply: false
---
- When you run an example from the ``examples/`` folder, switch to the example folder and run it from there.
- If there are instructions on running the examples or its tests in readme, follow them
- Activate virtualenv before running any commands by ``. venv/bin/actvate``

View File

@ -1,9 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{py,pyi,pxd,pyx}]
ij_visual_guides = 80,88

1
.github/FUNDING.yml vendored
View File

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

View File

@ -1,7 +1,6 @@
name: Publishing name: Publishing
on: on:
workflow_dispatch:
push: push:
tags: tags:
- '*' - '*'
@ -10,28 +9,28 @@ jobs:
tests: tests:
name: Run tests name: Run tests
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-python@v4 - uses: actions/setup-python@v2
with: with:
python-version: 3.13 python-version: "3.10"
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:
TOXENV: 3.13 TOXENV: "3.10"
linters: linters:
name: Run linters name: Run linters
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
strategy: strategy:
matrix: matrix:
toxenv: [flake8, pydocstyle, mypy, pylint] toxenv: [flake8, pydocstyle, mypy, pylint]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-python@v4 - uses: actions/setup-python@v2
with: with:
python-version: 3.13 python-version: "3.10"
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:
@ -40,18 +39,15 @@ jobs:
build-sdist: build-sdist:
name: Build source tarball name: Build source tarball
needs: [tests, linters] needs: [tests, linters]
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-python@v4 - uses: actions/setup-python@v2
with: with:
python-version: 3.13 python-version: "3.10"
- run: | - run: python setup.py sdist
python -m pip install --upgrade build - uses: actions/upload-artifact@v2
python -m build --sdist
- uses: actions/upload-artifact@v4
with: with:
name: cibw-sdist
path: ./dist/* path: ./dist/*
build-wheels: build-wheels:
@ -60,65 +56,65 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2022, macos-14] os: [ubuntu-20.04, windows-2019, macOS-10.15]
env: env:
CIBW_ENABLE: pypy CIBW_SKIP: cp27-win*
CIBW_ENVIRONMENT: >-
PIP_CONFIG_SETTINGS="build_ext=-j4"
DEPENDENCY_INJECTOR_LIMITED_API="1"
CFLAGS="-g0"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Build wheels - uses: actions/setup-python@v2
uses: pypa/cibuildwheel@v3.0.0 with:
- uses: actions/upload-artifact@v4 python-version: "3.10"
- run: pip install cibuildwheel==2.1.3
- run: cibuildwheel --output-dir wheelhouse
- uses: actions/upload-artifact@v2
with: with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl path: ./wheelhouse/*.whl
test-publish: build-wheels-linux-aarch64:
name: Upload release to TestPyPI name: Build wheels (ubuntu-latest-aarch64)
needs: [build-sdist, build-wheels] needs: [tests, linters]
runs-on: ubuntu-latest runs-on: ubuntu-20.04
environment: test-pypi
permissions:
id-token: write
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- uses: actions/setup-python@v2
with: with:
pattern: cibw-* python-version: "3.10"
path: dist - run: pip install cibuildwheel==2.1.3
merge-multiple: true - run: cibuildwheel --archs aarch64 --output-dir wheelhouse
- uses: pypa/gh-action-pypi-publish@release/v1 - uses: actions/upload-artifact@v2
with: with:
repository-url: https://test.pypi.org/legacy/ path: ./wheelhouse/*.whl
publish: publish:
name: Upload release to PyPI name: Publish on PyPI
needs: [build-sdist, build-wheels, test-publish] needs: [build-sdist, build-wheels, build-wheels-linux-aarch64]
runs-on: ubuntu-latest runs-on: ubuntu-20.04
environment: pypi
permissions:
id-token: write
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v2
with: with:
pattern: cibw-* name: artifact
path: dist path: dist
merge-multiple: true - uses: pypa/gh-action-pypi-publish@master
- 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: publish-docs:
name: Publish docs name: Publish docs
needs: [publish] needs: [publish]
runs-on: ubuntu-24.04 runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-python@v4 - uses: actions/setup-python@v2
with: with:
python-version: 3.13 python-version: "3.10"
- run: pip install awscli
- run: pip install -r requirements-doc.txt - run: pip install -r requirements-doc.txt
- run: pip install awscli
- run: pip install -e . - run: pip install -e .
- run: (cd docs && make clean html) - run: (cd docs && make clean html)
- run: | - run: |

View File

@ -9,44 +9,32 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10", pypy2, pypy3]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-python@v4 - uses: actions/setup-python@v2
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:
DEPENDENCY_INJECTOR_LIMITED_API: 1
TOXENV: ${{ matrix.python-version }} 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: test-coverage:
name: Run tests with coverage name: Run tests with coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
DEPENDENCY_INJECTOR_DEBUG_MODE: 1 DEPENDENCY_INJECTOR_DEBUG_MODE: 1
PIP_VERBOSE: 1
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-python@v4 - uses: actions/setup-python@v2
with: with:
python-version: 3.12 python-version: "3.10"
- run: pip install tox - run: pip install tox cython
- run: tox -vv - run: make cythonize
- run: tox
env: env:
TOXENV: coveralls TOXENV: coveralls
@ -57,10 +45,10 @@ jobs:
matrix: matrix:
toxenv: [flake8, pydocstyle, mypy, pylint] toxenv: [flake8, pydocstyle, mypy, pylint]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-python@v4 - uses: actions/setup-python@v2
with: with:
python-version: 3.13 python-version: "3.10"
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:

15
.gitignore vendored
View File

@ -15,7 +15,6 @@ lib64/
parts/ parts/
sdist/ sdist/
var/ var/
wheelhouse/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
@ -64,13 +63,13 @@ venv*/
# Vim Rope # Vim Rope
.ropeproject/ .ropeproject/
# Cython artifacts # C extensions
src/**/*.c src/dependency_injector/*.h
src/**/*.h src/dependency_injector/*.so
src/**/*.so src/dependency_injector/containers/*.h
src/**/*.html src/dependency_injector/containers/*.so
src/dependency_injector/providers/*.h
src/dependency_injector/providers/*.so
# Workspace for samples # Workspace for samples
.workspace/ .workspace/
.vscode/

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

View File

@ -20,5 +20,3 @@ Dependency Injector Contributors
+ Ngo Thanh Loi (Leonn) (loingo95) + Ngo Thanh Loi (Leonn) (loingo95)
+ Thiago Hiromi (thiromi) + Thiago Hiromi (thiromi)
+ Felipe Rubio (krouw) + Felipe Rubio (krouw)
+ Anton Petrov (anton-petrov)
+ ZipFile (ZipFile)

View File

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

View File

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

View File

@ -1,6 +1,14 @@
VERSION := $(shell python setup.py --version) VERSION := $(shell python setup.py --version)
export COVERAGE_RCFILE := pyproject.toml CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
CYTHON_DIRECTIVES = -Xlanguage_level=2
ifdef DEPENDENCY_INJECTOR_DEBUG_MODE
CYTHON_DIRECTIVES += -Xprofile=True
CYTHON_DIRECTIVES += -Xlinetrace=True
endif
clean: clean:
# Clean sources # Clean sources
@ -17,17 +25,21 @@ clean:
find examples -name '*.py[co]' -delete find examples -name '*.py[co]' -delete
find examples -name '__pycache__' -delete find examples -name '__pycache__' -delete
build: clean cythonize:
# Compile C extensions # Compile Cython to C
python setup.py build_ext --inplace cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
# Move all Cython html reports # Move all Cython html reports
mkdir -p reports/cython/ mkdir -p reports/cython/
find src -name '*.html' -exec mv {} reports/cython/ \; find src -name '*.html' -exec mv {} reports/cython/ \;
build: clean cythonize
# Compile C extensions
python setup.py build_ext --inplace
docs-live: docs-live:
sphinx-autobuild docs docs/_build/html sphinx-autobuild docs docs/_build/html
install: uninstall clean build install: uninstall clean cythonize
pip install -ve . pip install -ve .
uninstall: uninstall:
@ -36,9 +48,9 @@ uninstall:
test: test:
# Unit tests with coverage report # Unit tests with coverage report
coverage erase coverage erase
coverage run -m pytest coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini
coverage report coverage report --rcfile=./.coveragerc
coverage html coverage html --rcfile=./.coveragerc
check: check:
flake8 src/dependency_injector/ flake8 src/dependency_injector/
@ -49,9 +61,9 @@ check:
mypy tests/typing mypy tests/typing
test-publish: build test-publish: cythonize
# Create distributions # Create distributions
python -m build --sdist python setup.py sdist
# Upload distributions to PyPI # Upload distributions to PyPI
twine upload --repository testpypi dist/dependency-injector-$(VERSION)* twine upload --repository testpypi dist/dependency-injector-$(VERSION)*

View File

@ -35,7 +35,7 @@
:target: https://pypi.org/project/dependency-injector/ :target: https://pypi.org/project/dependency-injector/
:alt: Wheel :alt: Wheel
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master .. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
:target: https://github.com/ets-labs/python-dependency-injector/actions :target: https://github.com/ets-labs/python-dependency-injector/actions
:alt: Build Status :alt: Build Status
@ -48,26 +48,26 @@ What is ``Dependency Injector``?
``Dependency Injector`` is a dependency injection framework for Python. ``Dependency Injector`` is a dependency injection framework for Python.
It helps implement the dependency injection principle. It helps implementing the dependency injection principle.
Key features of the ``Dependency Injector``: Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, - **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
that help assemble your objects. that help assembling your objects.
See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_. 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 - **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 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>`_. `Provider overriding <https://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings, - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, ``pydantic`` settings,
environment variables, and dictionaries. environment variables, and dictionaries.
See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_. See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
- **Containers**. Provides declarative and dynamic containers.
See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread - **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. 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>`_. See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_.
- **Containers**. Provides declarative and dynamic containers. - **Wiring**. Injects dependencies into functions and methods. Helps integrating with
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. other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc.
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_. See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
- **Asynchronous**. Supports asynchronous injections. - **Asynchronous**. Supports asynchronous injections.
@ -75,7 +75,7 @@ Key features of the ``Dependency Injector``:
- **Typing**. Provides typing stubs, ``mypy``-friendly. - **Typing**. Provides typing stubs, ``mypy``-friendly.
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_. See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
- **Performance**. Fast. Written in ``Cython``. - **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported. - **Maturity**. Mature and production-ready. Well-tested, documented and supported.
.. code-block:: python .. code-block:: python
@ -100,7 +100,7 @@ Key features of the ``Dependency Injector``:
@inject @inject
def main(service: Service = Provide[Container.service]) -> None: def main(service: Service = Provide[Container.service]):
... ...
@ -115,18 +115,19 @@ Key features of the ``Dependency Injector``:
with container.api_client.override(mock.Mock()): with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically main() # <-- overridden dependency is injected automatically
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically. When you call ``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 When doing a testing you call the ``container.api_client.override()`` to replace the real API
client with a mock. When you call ``main()``, the mock is injected. client with a mock. When you call ``main()`` the mock is injected.
You can override any provider with another provider. You can override any provider with another provider.
It also helps you in a re-configuring project for different environments: replace an API client It also helps you in configuring project for the different environments: replace an API client
with a stub on the dev or stage. with a stub on the dev or stage.
With the ``Dependency Injector``, object assembling is consolidated in a container. Dependency injections are defined explicitly. With the ``Dependency Injector`` objects assembling is consolidated in the container.
This makes it easier to understand and change how an application works. Dependency injections are defined explicitly.
This makes easier to understand and change how application works.
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg .. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
:target: https://github.com/ets-labs/python-dependency-injector :target: https://github.com/ets-labs/python-dependency-injector
@ -184,27 +185,27 @@ The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/d
You need to specify how to assemble and where to inject the dependencies explicitly. You need to specify how to assemble and where to inject the dependencies explicitly.
The power of the framework is in its simplicity. The power of the framework is in a simplicity.
``Dependency Injector`` is a simple tool for the powerful concept. ``Dependency Injector`` is a simple tool for the powerful concept.
Frequently asked questions Frequently asked questions
-------------------------- --------------------------
What is dependency injection? What is the dependency injection?
- dependency injection is a principle that decreases coupling and increases cohesion - dependency injection is a principle that decreases coupling and increases cohesion
Why should I do the dependency injection? Why should I do the dependency injection?
- your code becomes more flexible, testable, and clear 😎 - your code becomes more flexible, testable, and clear 😎
How do I start applying the dependency injection? How do I start doing the dependency injection?
- you start writing the code following the dependency injection principle - you start writing the code following the dependency injection principle
- you register all of your application components and their dependencies in the container - 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 - 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? What price do I pay and what do I get?
- you need to explicitly specify the dependencies - you need to explicitly specify the dependencies
- it will be extra work in the beginning - it will be an extra work in the beginning
- it will payoff as project grows - it will payoff as the project grows
Have a question? Have a question?
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_ - Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_

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;
}

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

@ -0,0 +1,11 @@
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);
});

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 +0,0 @@
dependency_injector.ext.starlette
=================================
.. automodule:: dependency_injector.ext.starlette
:members:
:inherited-members:
:show-inheritance:
.. disqus::

View File

@ -2,11 +2,10 @@ API Documentation
================= =================
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
top-level top-level
providers providers
containers containers
wiring wiring
errors errors
asgi-lifespan

View File

@ -33,7 +33,7 @@ sys.path.insert(0, os.path.abspath(".."))
extensions = [ extensions = [
"alabaster", "alabaster",
"sphinx.ext.autodoc", "sphinx.ext.autodoc",
"sphinx_disqus.disqus", "sphinxcontrib.disqus",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -52,7 +52,7 @@ master_doc = "index"
# General information about the project. # General information about the project.
project = "Dependency Injector" project = "Dependency Injector"
copyright = "2024, Roman Mogylatov" copyright = "2021, Roman Mogylatov"
author = "Roman Mogylatov" author = "Roman Mogylatov"
# 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
@ -72,7 +72,7 @@ release = version
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = "en" language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
@ -147,9 +147,6 @@ html_favicon = "favicon.ico"
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"] html_static_path = ["_static"]
html_css_files = [
"custom.css",
]
# Add any extra paths that contain custom files (such as robots.txt or # Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied # .htaccess) here, relative to this directory. These files are copied
@ -309,5 +306,4 @@ html_theme_options = {
"description": "Dependency injection framework for Python by Roman Mogylatov", "description": "Dependency injection framework for Python by Roman Mogylatov",
"code_font_size": "10pt", "code_font_size": "10pt",
"analytics_id": "UA-67012059-1", "analytics_id": "UA-67012059-1",
"donate_url": "https://github.com/sponsors/rmk135",
} }

View File

@ -78,6 +78,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -84,6 +84,4 @@ 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>`_. 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:: .. disqus::

View File

@ -90,6 +90,4 @@ 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>`_. 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:: .. disqus::

View File

@ -17,6 +17,4 @@ Listing of ``boto3_session_example.py``:
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py .. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
:language: python :language: python
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -129,6 +129,4 @@ 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>`_. 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:: .. disqus::

View File

@ -78,7 +78,7 @@ Container is wired to the ``views`` module in the app config ``web/apps.py``:
.. literalinclude:: ../../examples/miniapps/django/web/apps.py .. literalinclude:: ../../examples/miniapps/django/web/apps.py
:language: python :language: python
:emphasize-lines: 12 :emphasize-lines: 13
Tests Tests
----- -----
@ -94,6 +94,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -95,6 +95,4 @@ See also:
- Resource provider :ref:`resource-async-initializers` - Resource provider :ref:`resource-async-initializers`
- Wiring :ref:`async-injections-wiring` - Wiring :ref:`async-injections-wiring`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -116,6 +116,4 @@ Sources
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_. 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:: .. disqus::

View File

@ -76,6 +76,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -1,48 +0,0 @@
.. _fastdepends-example:
FastDepends example
===================
.. meta::
:keywords: Python,Dependency Injection,FastDepends,Example
:description: This example demonstrates a usage of the FastDepends and Dependency Injector.
This example demonstrates how to use ``Dependency Injector`` with `FastDepends <https://github.com/Lancetnik/FastDepends>`_, a lightweight dependency injection framework inspired by FastAPI's dependency system, but without the web framework components.
Basic Usage
-----------
The integration between FastDepends and Dependency Injector is straightforward. Simply use Dependency Injector's ``Provide`` marker within FastDepends' ``Depends`` function:
.. code-block:: python
import sys
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
from fast_depends import Depends
class CoefficientService:
@staticmethod
def get_coefficient() -> float:
return 1.2
class Container(containers.DeclarativeContainer):
service = providers.Factory(CoefficientService)
@inject
def apply_coefficient(
a: int,
coefficient_provider: CoefficientService = Depends(Provide[Container.service]),
) -> float:
return a * coefficient_provider.get_coefficient()
container = Container()
container.wire(modules=[sys.modules[__name__]])
apply_coefficient(100) == 120.0

View File

@ -86,6 +86,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -84,6 +84,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -22,6 +22,5 @@ Explore the examples to see the ``Dependency Injector`` in action.
fastapi fastapi
fastapi-redis fastapi-redis
fastapi-sqlalchemy fastapi-sqlalchemy
fastdepends
.. disqus:: .. disqus::

View File

@ -77,6 +77,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -9,11 +9,11 @@ Dependency Injector --- Dependency injection framework for Python
:description: Dependency Injector is a dependency injection framework :description: Dependency Injector is a dependency injection framework
for Python. It helps to maintain you application structure. for Python. It helps to maintain you application structure.
It was designed to be unified, developer-friendly It was designed to be unified, developer-friendly
tool that helps to implement dependency injection design tool that helps to implement dependency injection design
pattern in formal, pretty, Pythonic way. Dependency Injector pattern in formal, pretty, Pythonic way. Dependency Injector
provides implementations of such popular design patterns provides implementations of such popular design patterns
like IoC container, Factory and Singleton. Dependency like IoC container, Factory and Singleton. Dependency
Injector providers are implemented as C extension types Injector providers are implemented as C extension types
using Cython. using Cython.
.. _index: .. _index:
@ -50,7 +50,7 @@ Dependency Injector --- Dependency injection framework for Python
:target: https://pypi.org/project/dependency-injector/ :target: https://pypi.org/project/dependency-injector/
:alt: Wheel :alt: Wheel
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master .. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
:target: https://github.com/ets-labs/python-dependency-injector/actions :target: https://github.com/ets-labs/python-dependency-injector/actions
:alt: Build Status :alt: Build Status
@ -65,23 +65,23 @@ It helps implementing the dependency injection principle.
Key features of the ``Dependency Injector``: Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, - **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
that help assemble your objects. See :ref:`providers`. that help assembling your objects. See :ref:`providers`.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing - **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 and configuring dev / stage environment to replace API clients with stubs etc. See
:ref:`provider-overriding`. :ref:`provider-overriding`.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings, - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, ``pydantic`` settings,
environment variables, and dictionaries. See :ref:`configuration-provider`. environment variables, and dictionaries. See :ref:`configuration-provider`.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread - **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. or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
See :ref:`resource-provider`. See :ref:`resource-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with - **Wiring**. Injects dependencies into functions and methods. Helps integrating with
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`. other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`. - **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`. - **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``. - **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported. - **Maturity**. Mature and production-ready. Well-tested, documented and supported.
.. code-block:: python .. code-block:: python
@ -106,7 +106,7 @@ Key features of the ``Dependency Injector``:
@inject @inject
def main(service: Service = Provide[Container.service]) -> None: def main(service: Service = Provide[Container.service]):
... ...
@ -121,9 +121,9 @@ Key features of the ``Dependency Injector``:
with container.api_client.override(mock.Mock()): with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically main() # <-- overridden dependency is injected automatically
With the ``Dependency Injector``, object assembling is consolidated in the container. With the ``Dependency Injector`` objects assembling is consolidated in the container.
Dependency injections are defined explicitly. Dependency injections are defined explicitly.
This makes it easier to understand and change how the application works. This makes easier to understand and change how application works.
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg .. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
:target: https://github.com/ets-labs/python-dependency-injector :target: https://github.com/ets-labs/python-dependency-injector

View File

@ -11,24 +11,24 @@ Dependency injection and inversion of control in Python
feature for testing or configuring project in different environments and explains feature for testing or configuring project in different environments and explains
why it's better than monkey-patching. why it's better than monkey-patching.
Originally dependency injection pattern got popular in languages with static typing like Java. Originally dependency injection pattern got popular in the languages with a static typing,
Dependency injection is a principle that helps to achieve an inversion of control. A like Java. Dependency injection is a principle that helps to achieve an inversion of control.
dependency injection framework can significantly improve the flexibility of a language Dependency injection framework can significantly improve a flexibility of the language
with static typing. Implementation of a dependency injection framework for a language with a 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 with a 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. to be done well. And will take time.
Python is an interpreted language with dynamic typing. There is an opinion that dependency Python is an interpreted language with a 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 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 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 Python developer rarely needs. Python developers say that dependency injection can be implemented
easily using language fundamentals. easily using language fundamentals.
This page describes the advantages of applying dependency injection in Python. It This page describes the advantages of the dependency injection usage in Python. It
contains Python examples that show how to implement dependency injection. It demonstrates the usage contains Python examples that show how to implement dependency injection. It demonstrates a usage
of the ``Dependency Injector`` framework, its container, ``Factory``, ``Singleton``, of the dependency injection framework ``Dependency Injector``, its container, ``Factory``,
and ``Configuration`` providers. The example shows how to use providers' overriding feature ``Singleton`` and ``Configuration`` providers. The example shows how to use ``Dependency Injector``
of ``Dependency Injector`` for testing or re-configuring a project in different environments and providers overriding feature for testing or configuring project in different environments and
explains why it's better than monkey-patching. explains why it's better than monkey-patching.
What is dependency injection? What is dependency injection?
@ -44,14 +44,14 @@ What is coupling and cohesion?
Coupling and cohesion are about how tough the components are tied. 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 - **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
to disassemble. to disassemble.
- **High cohesion**. High cohesion is like using screws. Quite easy to disassemble and - **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
re-assemble in a different way. It is an opposite to high coupling. assemble back or assemble 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. When the cohesion is high the coupling is low.
Low coupling brings flexibility. Your code becomes easier to change and test. Low coupling brings a flexibility. Your code becomes easier to change and test.
How to implement the dependency injection? How to implement the dependency injection?
@ -66,14 +66,14 @@ Before:
class ApiClient: class ApiClient:
def __init__(self) -> None: def __init__(self):
self.api_key = os.getenv("API_KEY") # <-- dependency self.api_key = os.getenv("API_KEY") # <-- dependency
self.timeout = int(os.getenv("TIMEOUT")) # <-- dependency self.timeout = os.getenv("TIMEOUT") # <-- dependency
class Service: class Service:
def __init__(self) -> None: def __init__(self):
self.api_client = ApiClient() # <-- dependency self.api_client = ApiClient() # <-- dependency
@ -94,18 +94,18 @@ After:
class ApiClient: class ApiClient:
def __init__(self, api_key: str, timeout: int) -> None: def __init__(self, api_key: str, timeout: int):
self.api_key = api_key # <-- dependency is injected self.api_key = api_key # <-- dependency is injected
self.timeout = timeout # <-- dependency is injected self.timeout = timeout # <-- dependency is injected
class Service: class Service:
def __init__(self, api_client: ApiClient) -> None: def __init__(self, api_client: ApiClient):
self.api_client = api_client # <-- dependency is injected self.api_client = api_client # <-- dependency is injected
def main(service: Service) -> None: # <-- dependency is injected def main(service: Service): # <-- dependency is injected
... ...
@ -114,7 +114,7 @@ After:
service=Service( service=Service(
api_client=ApiClient( api_client=ApiClient(
api_key=os.getenv("API_KEY"), api_key=os.getenv("API_KEY"),
timeout=int(os.getenv("TIMEOUT")), timeout=os.getenv("TIMEOUT"),
), ),
), ),
) )
@ -137,7 +137,7 @@ Now you need to assemble and inject the objects like this:
service=Service( service=Service(
api_client=ApiClient( api_client=ApiClient(
api_key=os.getenv("API_KEY"), api_key=os.getenv("API_KEY"),
timeout=int(os.getenv("TIMEOUT")), timeout=os.getenv("TIMEOUT"),
), ),
), ),
) )
@ -149,14 +149,14 @@ Here comes the ``Dependency Injector``.
What does the Dependency Injector do? What does the Dependency Injector do?
------------------------------------- -------------------------------------
With the dependency injection pattern, objects lose the responsibility of assembling With the dependency injection pattern objects loose the responsibility of assembling
the dependencies. The ``Dependency Injector`` absorbs that responsibility. the dependencies. The ``Dependency Injector`` absorbs that responsibility.
``Dependency Injector`` helps to assemble and inject the dependencies. ``Dependency Injector`` helps to assemble and inject the dependencies.
It provides a container and providers that help you with the objects assembly. 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 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 function argument. When you call this function framework assembles and injects
the dependency. the dependency.
.. code-block:: python .. code-block:: python
@ -182,7 +182,7 @@ the dependency.
@inject @inject
def main(service: Service = Provide[Container.service]) -> None: def main(service: Service = Provide[Container.service]):
... ...
@ -197,79 +197,79 @@ the dependency.
with container.api_client.override(mock.Mock()): with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically main() # <-- overridden dependency is injected automatically
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically. When you call ``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 When doing a testing you call the ``container.api_client.override()`` to replace the real API
client with a mock. When you call ``main()``, the mock is injected. client with a mock. When you call ``main()`` the mock is injected.
You can override any provider with another provider. You can override any provider with another provider.
It also helps you in a re-configuring project for different environments: replace an API client It also helps you in configuring project for the different environments: replace an API client
with a stub on the dev or stage. with a stub on the dev or stage.
Objects assembling is consolidated in a container. Dependency injections are defined explicitly. Objects assembling is consolidated in the container. Dependency injections are defined explicitly.
This makes it easier to understand and change how an application works. This makes easier to understand and change how application works.
Testing, Monkey-patching and dependency injection Testing, Monkey-patching and dependency injection
------------------------------------------------- -------------------------------------------------
The testability benefit is opposed to monkey-patching. The testability benefit is opposed to a monkey-patching.
In Python, you can monkey-patch anything, anytime. The problem with monkey-patching is In Python you can monkey-patch
that it's too fragile. The cause of it is that when you monkey-patch you do something that anything, anytime. The problem with a monkey-patching is that it's too fragile. The reason is that
wasn't intended to be done. You monkey-patch the implementation details. When implementation when you monkey-patch you do something that wasn't intended to be done. You monkey-patch the
changes the monkey-patching is broken. 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 With a dependency injection you patch the interface, not an implementation. This is a way more
stable approach. stable approach.
Also, monkey-patching is way too dirty to be used outside of the testing code for Also monkey-patching is a way too dirty to be used outside of the testing code for
re-configuring the project for the different environments. reconfiguring the project for the different environments.
Conclusion Conclusion
---------- ----------
Dependency injection provides you with three advantages: Dependency injection brings you 3 advantages:
- **Flexibility**. The components are loosely coupled. You can easily extend or change the - **Flexibility**. The components are loosely coupled. You can easily extend or change a
functionality of a system by combining the components in a different way. You even can do it on functionality of the system by combining the components different way. You even can do it on
the fly. the fly.
- **Testability**. Testing is easier because you can easily inject mocks instead of real objects - **Testability**. Testing is easy because you can easily inject mocks instead of real objects
that use API or database, etc. that use API or database, etc.
- **Clearness and maintainability**. Dependency injection helps you reveal the dependencies. - **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). 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 You have all the components and dependencies defined explicitly in the container. This
provides an overview and control of the application structure. It is easier to understand and provides an overview and control on the application structure. It is easy to understand and
change it. change it.
Is it worth applying dependency injection in Python? Is it worth to use a dependency injection in Python?
It depends on what you build. The advantages above are not too important if you use Python as a 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 scripting language. The picture is different when you use Python to create an application. The
larger the application the more significant the benefits. larger the application the more significant is the benefit.
Is it worth using a framework for applying dependency injection? Is it worth to use a framework for the dependency injection?
The complexity of the dependency injection pattern implementation in Python is 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 lower than in the 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: framework but using a framework is beneficial because the framework is:
- Already implemented - Already implemented
- Tested on all platforms and versions of Python - Tested on all platforms and versions of Python
- Documented - Documented
- Supported - Supported
- Other engineers are familiar with it - Known to the other engineers
An advice at last: Few advices at last:
- **Give it a try**. Dependency injection is counter-intuitive. Our nature is that - **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 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". injection is just like "Wait, I need to state a need instead of getting something right now".
It's like a little investment that will pay-off later. The advice is to just give it a try for 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 two weeks. This time will be enough for getting your own impression. If you don't like it you
won't lose too much. won't lose too much.
- **Common sense first**. Use common sense when applying dependency injection. It is a good - **Common sense first**. Use a common sense when apply 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 principle, but not a silver bullet. If you do it too much you will reveal too much of the
implementation details. Experience comes with practice and time. implementation details. Experience comes with practice and time.
What's next? What's next?
@ -303,13 +303,12 @@ Choose one of the following as a next step:
Useful links Useful links
------------ ------------
A few useful links related to a dependency injection design pattern for further reading: There are some useful links related to dependency injection design pattern
that could be used for further reading:
+ https://en.wikipedia.org/wiki/Dependency_injection + https://en.wikipedia.org/wiki/Dependency_injection
+ https://martinfowler.com/articles/injection.html + https://martinfowler.com/articles/injection.html
+ https://github.com/ets-labs/python-dependency-injector + https://github.com/ets-labs/python-dependency-injector
+ https://pypi.org/project/dependency-injector/ + https://pypi.org/project/dependency-injector/
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -7,8 +7,8 @@ Introduction
overview of the dependency injection, inversion of overview of the dependency injection, inversion of
control and Dependency Injector framework. control and Dependency Injector framework.
The current section of the documentation provides an overview of the Current section of the documentation provides an overview of the
dependency injection, inversion of control, and the ``Dependency Injector`` framework. dependency injection, inversion of control and the ``Dependency Injector`` framework.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

View File

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

View File

@ -11,33 +11,33 @@ Key features
Key features of the ``Dependency Injector``: Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, - **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
that help assemble your objects. See :ref:`providers`. that help assembling your objects. See :ref:`providers`.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing - **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 and configuring dev / stage environment to replace API clients with stubs etc. See
:ref:`provider-overriding`. :ref:`provider-overriding`.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings, - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, ``pydantic`` settings,
environment variables, and dictionaries. See :ref:`configuration-provider`. environment variables, and dictionaries. See :ref:`configuration-provider`.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread - **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. or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
See :ref:`resource-provider`. See :ref:`resource-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with - **Wiring**. Injects dependencies into functions and methods. Helps integrating with
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`. other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`. - **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`. - **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``. - **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported. - **Maturity**. Mature and production-ready. Well-tested, documented and supported.
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle: The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
.. code-block:: text .. code-block:: plain
Explicit is better than implicit Explicit is better than implicit
You need to specify how to assemble and where to inject the dependencies explicitly. You need to specify how to assemble and where to inject the dependencies explicitly.
The power of the framework is in its simplicity. The power of the framework is in a simplicity.
``Dependency Injector`` is a simple tool for the powerful concept. ``Dependency Injector`` is a simple tool for the powerful concept.
.. disqus:: .. disqus::

View File

@ -1,144 +1,12 @@
Changelog Changelog
========= =========
This document describes all the changes in *Dependency Injector* framework This document describes all the changes in *Dependency Injector* framework
that were made in every particular version. that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_ follows `Semantic versioning`_
4.48.1
------
* Improve performance of ``dependency_injector._cwiring.DependencyResolver``
* Add ``typing-extensions`` as a dependency for older Python versions (<3.11)
* Produce warning on ``@inject``s without ``Provide[...]`` marks
* Add support for `resource_type` in ``Lifespan``s
4.48.0
------
- Improve performance of wiring (`#897 <https://github.com/ets-labs/python-dependency-injector/pull/897>`_)
- Add Context Manager support to Resource provider (`#899 <https://github.com/ets-labs/python-dependency-injector/pull/899>`_)
- Add support for async generator injections (`#900 <https://github.com/ets-labs/python-dependency-injector/pull/900>`_)
- Fix unintended dependency on ``typing_extensions`` (`#902 <https://github.com/ets-labs/python-dependency-injector/pull/902>`_)
- Add support for Fast Depends (`#898 <https://github.com/ets-labs/python-dependency-injector/pull/898>`_)
- Add ``resource_type`` parameter to init and shutdown resources using specialized providers (`#858 <https://github.com/ets-labs/python-dependency-injector/pull/858>`_)
4.47.1
------
- Fix typing for wiring marker (`#892 <https://github.com/ets-labs/python-dependency-injector/pull/896>`_)
- Strip debug symbols in wheels
4.47.0
------
- Add support for ``Annotated`` type for module and class attribute injection in wiring,
with updated documentation and examples.
See discussion:
https://github.com/ets-labs/python-dependency-injector/pull/721#issuecomment-2025263718
- Fix ``root`` property shadowing in ``ConfigurationOption`` (`#875 <https://github.com/ets-labs/python-dependency-injector/pull/875>`_)
- Fix incorrect monkeypatching during ``wire()`` that could violate MRO in some classes (`#886 <https://github.com/ets-labs/python-dependency-injector/pull/886>`_)
- ABI3 wheels are now published for CPython.
- Drop support of Python 3.7.
4.46.0
------
- Add option to disable env var interpolation in configs (`#861 <https://github.com/ets-labs/python-dependency-injector/pull/861>`_)
- Fix ``Closing`` dependency resolution (`#852 <https://github.com/ets-labs/python-dependency-injector/pull/852>`_)
- Add support for ``inspect.iscoroutinefunction()`` in ``Coroutine`` provider (`#830 <https://github.com/ets-labs/python-dependency-injector/pull/830>`_)
- Fix broken wiring of sync inject-decorated methods (`#673 <https://github.com/ets-labs/python-dependency-injector/pull/673>`_)
- Add support for ``typing.Annotated`` (`#721 <https://github.com/ets-labs/python-dependency-injector/pull/721>`_, `#853 <https://github.com/ets-labs/python-dependency-injector/pull/853>`_)
- Documentation updates for movie-lister example (`#747 <https://github.com/ets-labs/python-dependency-injector/pull/747>`_)
- Fix type propagation in ``Provider.provider`` (`#744 <https://github.com/ets-labs/python-dependency-injector/pull/744>`_)
Many thanks for the contributions to:
- `ZipFile <https://github.com/ZipFile>`_
- `Yegor Statkevich <https://github.com/jazzthief>`_
- `Federico Tomasi <https://github.com/federinik>`_
- `Martin Lafrance <https://github.com/martlaf>`_
- `Philip Bjorge <https://github.com/philipbjorge>`_
- `Ilya Kazakov <https://github.com/mrKazzila>`_
4.45.0
--------
- Add Starlette lifespan handler implementation (`#683 <https://github.com/ets-labs/python-dependency-injector/pull/683>`_).
- Raise exception in ``ThreadLocalSingleton`` instead of hiding it in finally (`#845 <https://github.com/ets-labs/python-dependency-injector/pull/845>`_).
- Improve debuggability of ``deepcopy`` errors (`#839 <https://github.com/ets-labs/python-dependency-injector/pull/839>`_).
- Update examples (`#838 <https://github.com/ets-labs/python-dependency-injector/pull/838>`_).
- Upgrade testing dependencies (`#837 <https://github.com/ets-labs/python-dependency-injector/pull/837>`_).
- Add minor fixes to the documentation (`#709 <https://github.com/ets-labs/python-dependency-injector/pull/709>`_).
- Remove ``six`` from the dependencies (`3ba4704 <https://github.com/ets-labs/python-dependency-injector/commit/3ba4704bc1cb00310749fd2eda0c8221167c313c>`_).
Many thanks for the contributions to:
- `ZipFile <https://github.com/ZipFile>`_
- `František Trebuňa <https://github.com/gortibaldik>`_
- `JC (Jonathan Chen) <https://github.com/dijonkitchen>`_
4.44.0
--------
- Implement support for Pydantic 2. PR: `#832 <https://github.com/ets-labs/python-dependency-injector/pull/832>`_.
- Implement `PEP-517 <https://peps.python.org/pep-0517/>`_, `PEP-518 <https://peps.python.org/pep-0518/>`_, and
`PEP-621 <https://peps.python.org/pep-0621/>`_. PR: `#829 <https://github.com/ets-labs/python-dependency-injector/pull/829>`_.
Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
4.43.0
--------
- Add support for Python 3.13.
- Migrate to Cython 3 (version 3.0.11). Many thanks to `ZipFile <https://github.com/ZipFile>`_ for
this contribution `#813 <https://github.com/ets-labs/python-dependency-injector/pull/813>`_.
4.42.0
--------
- Promote release ``4.42.0b1`` to a production release.
- Fix the Disqus comment widget.
4.42.0b1
--------
- Add support of Python 3.12.
- Drop support of Python 2.7, 3.5, and 3.6.
- Regenerate C sources using Cython 0.29.37.
- Update ``cibuildwheel`` to version ``2.20.0``.
4.41.0
------
- Add support of Python 3.11.
- Allow Closing to detect dependent resources `#633 <https://github.com/ets-labs/python-dependency-injector/issues/633>`_,
`#636 <https://github.com/ets-labs/python-dependency-injector/pull/636>`_. Thanks `Jamie Stumme @StummeJ <https://github.com/StummeJ>`_
for the contribution.
- Update CI/CD to use Ubuntu 22.04.
- Update CI/CD to ``actions/checkout@v3``, ``actions/setup-python@v4``, ``actions/upload-artifact@v3``, ``pypa/cibuildwheel@v2.11.3``,
and ``actions/download-artifact@v3``.
- Fix install crash on non-utf8 systems `#644 <https://github.com/ets-labs/python-dependency-injector/pull/644>`_.
- Fix a bug in Windows build with default charset `#635 <https://github.com/ets-labs/python-dependency-injector/pull/635>`_.
- Update FastAPI Redis example to use ``aioredis`` version 2 `#613 <https://github.com/ets-labs/python-dependency-injector/pull/613>`_.
- Update documentation on creating custom providers `#598 <https://github.com/ets-labs/python-dependency-injector/pull/598>`_.
- Regenerate C sources using Cython 0.29.32.
- Fix builds badge.
4.40.0
------
- Add ``Configuration.from_json()`` method to load configuration from a json file.
- Fix bug with wiring not working properly with functions double wrapped by ``@functools.wraps`` decorator.
See issue: `#454 <https://github.com/ets-labs/python-dependency-injector/issues/454>`_.
Many thanks to: `@platipo <https://github.com/platipo>`_, `@MatthieuMoreau0 <https://github.com/MatthieuMoreau0>`_,
`@fabiocerqueira <https://github.com/fabiocerqueira>`_, `@Jitesh-Khuttan <https://github.com/Jitesh-Khuttan>`_.
- Refactor wiring module to store all patched callable data in the ``PatchedRegistry``.
- Improve wording on the "Dependency injection and inversion of control in Python" docs page.
- Add documentation on the ``@inject`` decorator.
- Update typing in the main example and cohesion/coupling correlation definition in
"Dependency injection and inversion of control in Python".
Thanks to `@illia-v (Illia Volochii) <https://github.com/illia-v>`_ for the
PR (`#580 <https://github.com/ets-labs/python-dependency-injector/pull/580>`_).
- Update copyright year.
- Enable skipped test ``test_schema_with_boto3_session()``.
- Update pytest configuration.
- Regenerate C sources using Cython 0.29.30.
4.39.1 4.39.1
------ ------
- Fix bug `#574 <https://github.com/ets-labs/python-dependency-injector/issues/574>`_: - Fix bug `#574 <https://github.com/ets-labs/python-dependency-injector/issues/574>`_:
@ -207,7 +75,7 @@ Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
- Fix a typo in ``Factory`` provider docs ``service.add_attributes(clent=client)`` - Fix a typo in ``Factory`` provider docs ``service.add_attributes(clent=client)``
`#499 <https://github.com/ets-labs/python-dependency-injector/issues/499>`_. `#499 <https://github.com/ets-labs/python-dependency-injector/issues/499>`_.
Thanks to `@rajanjha786 <https://github.com/rajanjha786>`_ for the contribution. Thanks to `@rajanjha786 <https://github.com/rajanjha786>`_ for the contribution.
- Fix a typo in ``boto3`` example - Fix a typo in ``boto3`` example
`#511 <https://github.com/ets-labs/python-dependency-injector/issues/511>`_. `#511 <https://github.com/ets-labs/python-dependency-injector/issues/511>`_.
Thanks to `@whysage <https://github.com/whysage>`_ for the contribution. Thanks to `@whysage <https://github.com/whysage>`_ for the contribution.
@ -395,8 +263,8 @@ Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
- Make refactoring of wiring module and tests. - Make refactoring of wiring module and tests.
See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_. See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_.
Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution: Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution:
- Remove unused imports in tests. - Remove unused imports in tests.
- Use literal syntax to create data structure in tests. - Use literal syntax to create data structure in tests.
- Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_. - Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_.
4.26.0 4.26.0
@ -1407,24 +1275,24 @@ Misc:
------ ------
- Add ``DependenciesContainer`` provider. - Add ``DependenciesContainer`` provider.
- Add "use_cases" example miniapp. - Add "use_cases" example miniapp.
- Update documentation requirements to use fixed version of - Update documentation requirements to use fixed version of
``sphinxcontrib-disqus``. ``sphinxcontrib-disqus``.
3.9.1 3.9.1
----- -----
- Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of - Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of
incompatibility with ``sphinxcontrib-discus``). incompatibility with ``sphinxcontrib-discus``).
- Add badge for docs. - Add badge for docs.
3.9.0 3.9.0
----- -----
- Change initialization of declarative container, so it accepts overriding - Change initialization of declarative container, so it accepts overriding
providers as keyword arguments - providers as keyword arguments -
``DeclarativeContainer(**overriding_providers)``. ``DeclarativeContainer(**overriding_providers)``.
- Add method to dynamic catalog for setting groups of providers - - Add method to dynamic catalog for setting groups of providers -
``DynamicContainer.set_providers(**providers)``. ``DynamicContainer.set_providers(**providers)``.
- Add method to dynamic catalog for overriding groups of providers - - Add method to dynamic catalog for overriding groups of providers -
``DynamicContainer.set_providers(**overriding_providers)``. ``DynamicContainer.set_providers(**overriding_providers)``.
- Rename ``ExternalDependency`` provider to ``Dependency``. - Rename ``ExternalDependency`` provider to ``Dependency``.
- Add default value for ``instance_of`` argument of ``Dependency`` provider - - Add default value for ``instance_of`` argument of ``Dependency`` provider -
@ -1456,7 +1324,7 @@ Misc:
3.7.0 3.7.0
----- -----
- Add ``FactoryAggregate`` provider. - Add ``FactoryAggregate`` provider.
- Add ``Provider.provider`` dynamic attribute that return new provider's - Add ``Provider.provider`` dynamic attribute that return new provider's
delegate (alias of method ``Provider.delegate()``). delegate (alias of method ``Provider.delegate()``).
- Add support of six 1.11.0. - Add support of six 1.11.0.
- Regenerate C sources using Cython 0.27.1. - Regenerate C sources using Cython 0.27.1.
@ -1473,7 +1341,7 @@ Misc:
3.5.0 3.5.0
----- -----
- Add functionality for initializing ``Configuration`` provider with default - Add functionality for initializing ``Configuration`` provider with default
values. values.
3.4.8 3.4.8
@ -1496,7 +1364,7 @@ Misc:
3.4.4 3.4.4
----- -----
- Add ``Provider.last_overriding`` read-only property that points to last - Add ``Provider.last_overriding`` read-only property that points to last
overriding provider, if any. If target provider is not overridden, ``None`` overriding provider, if any. If target provider is not overridden, ``None``
would be returned. would be returned.
- Update example of writing custom providers. - Update example of writing custom providers.
@ -1510,7 +1378,7 @@ Misc:
3.4.2 3.4.2
----- -----
- Make ``Provider`` overriding methods thread safe: - Make ``Provider`` overriding methods thread safe:
``Provider.override(provider)``, ``Provider.reset_last_overriding()``, ``Provider.override(provider)``, ``Provider.reset_last_overriding()``,
``Provider.reset_override()``. ``Provider.reset_override()``.
- Refactor storage locking of ``ThreadSafeSingleton`` provider. - Refactor storage locking of ``ThreadSafeSingleton`` provider.
- Fix few ``pydocstyle`` errors in examples. - Fix few ``pydocstyle`` errors in examples.
@ -1582,8 +1450,8 @@ Misc:
3.2.4 3.2.4
----- -----
- Switch to single version of documentation for getting shorter urls (without - Switch to single version of documentation for getting shorter urls (without
``/en/stable/``). Add appropriate redirects for compatibility with previous ``/en/stable/``). Add appropriate redirects for compatibility with previous
links. links.
- Update copyright date. - Update copyright date.
@ -1602,7 +1470,7 @@ Misc:
3.2.0 3.2.0
----- -----
- Add ``Configuration`` provider for late static binding of configuration - Add ``Configuration`` provider for late static binding of configuration
options. options.
3.1.5 3.1.5
@ -1612,7 +1480,7 @@ Misc:
3.1.4 3.1.4
----- -----
- Move ``inline`` functions from class level to module level for removing them - Move ``inline`` functions from class level to module level for removing them
from virtual table and enable inlining. from virtual table and enable inlining.
3.1.3 3.1.3
@ -1644,34 +1512,34 @@ Misc:
- **Providers** - **Providers**
1. All providers from ``dependency_injector.providers`` package are 1. All providers from ``dependency_injector.providers`` package are
implemented as C extension types using Cython. implemented as C extension types using Cython.
2. Add ``BaseSingleton`` super class for all singleton providers. 2. Add ``BaseSingleton`` super class for all singleton providers.
3. Make ``Singleton`` provider not thread-safe. It makes performance of 3. Make ``Singleton`` provider not thread-safe. It makes performance of
``Singleton`` provider 10x times faster. ``Singleton`` provider 10x times faster.
4. Add ``ThreadSafeSingleton`` provider - thread-safe version of 4. Add ``ThreadSafeSingleton`` provider - thread-safe version of
``Singleton`` provider. ``Singleton`` provider.
5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses 5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses
thread-local storage. thread-local storage.
6. Remove ``provides`` attribute from ``Factory`` and ``Singleton`` 6. Remove ``provides`` attribute from ``Factory`` and ``Singleton``
providers. providers.
7. Add ``set_args()`` and ``clear_args()`` methods for ``Callable``, 7. Add ``set_args()`` and ``clear_args()`` methods for ``Callable``,
``Factory`` and ``Singleton`` providers. ``Factory`` and ``Singleton`` providers.
- **Containers** - **Containers**
1. Module ``dependency_injector.containers`` was split into submodules 1. Module ``dependency_injector.containers`` was split into submodules
without any functional changes. without any functional changes.
- **Utils** - **Utils**
1. Module ``dependency_injector.utils`` is split into 1. Module ``dependency_injector.utils`` is split into
``dependency_injector.containers`` and ``dependency_injector.providers``. ``dependency_injector.containers`` and ``dependency_injector.providers``.
- **Miscellaneous** - **Miscellaneous**
1. Remove ``@inject`` decorator. 1. Remove ``@inject`` decorator.
2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall`` 2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall``
& ``publish`` commands). & ``publish`` commands).
3. Update repository structure: 3. Update repository structure:
@ -1738,7 +1606,7 @@ Misc:
2.0.0 2.0.0
------ ------
- Introduce new injections style for ``Callable``, ``Factory`` & - Introduce new injections style for ``Callable``, ``Factory`` &
``Singleton`` providers. ``Singleton`` providers.
- Drop providers: ``Static``, ``Value``, ``Function``, ``Class``, ``Config``. - Drop providers: ``Static``, ``Value``, ``Function``, ``Class``, ``Config``.
- Increase performance of making injections in 2 times (+100%). - Increase performance of making injections in 2 times (+100%).
@ -1751,8 +1619,8 @@ Misc:
1.17.0 1.17.0
------ ------
- Add ``add_injections()`` method to ``Callable``, ``DelegatedCallable``, - Add ``add_injections()`` method to ``Callable``, ``DelegatedCallable``,
``Factory``, ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` ``Factory``, ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton``
providers. providers.
- Fix bug with accessing to declarative catalog attributes from instance level. - Fix bug with accessing to declarative catalog attributes from instance level.
@ -1780,14 +1648,14 @@ Misc:
- Add "Examples" section into documentation. - Add "Examples" section into documentation.
- Add "Movie Lister" example. - Add "Movie Lister" example.
- Add "Services" example. - Add "Services" example.
- Move project documentation into organisation's domain - Move project documentation into organisation's domain
(dependency-injector.ets-labs.org). (dependency-injector.ets-labs.org).
1.15.2 1.15.2
------ ------
- [Refactoring] split ``catalogs`` module into smaller modules, - [Refactoring] split ``catalogs`` module into smaller modules,
``catalogs`` module become a package. ``catalogs`` module become a package.
- [Refactoring] split ``providers`` module into smaller modules, - [Refactoring] split ``providers`` module into smaller modules,
``providers`` module become a package. ``providers`` module become a package.
- Update introduction documentation. - Update introduction documentation.
@ -1797,7 +1665,7 @@ Misc:
1.15.0 1.15.0
------ ------
- Add ``Provider.provide()`` method. ``Provider.__call__()`` become a - Add ``Provider.provide()`` method. ``Provider.__call__()`` become a
reference to ``Provider.provide()``. reference to ``Provider.provide()``.
- Add provider overriding context. - Add provider overriding context.
- Update main examples and README. - Update main examples and README.
@ -1827,7 +1695,7 @@ Misc:
1.14.6 1.14.6
------ ------
- Add ``cls`` alias for ``provides`` attributes of ``Factory``, - Add ``cls`` alias for ``provides`` attributes of ``Factory``,
``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` providers. ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` providers.
1.14.5 1.14.5
@ -1886,27 +1754,27 @@ Misc:
1.11.1 1.11.1
------ ------
Previous state of *Dependency Injector* framework (0.11.0 version) is Previous state of *Dependency Injector* framework (0.11.0 version) is
considered to be production ready / stable, so current release is considered considered to be production ready / stable, so current release is considered
to be the first major release. to be the first major release.
- Increase major version. - Increase major version.
- Backward compatibility with all previous versions above 0.7.6 has been saved. - Backward compatibility with all previous versions above 0.7.6 has been saved.
0.11.0 0.11.0
------ ------
- Rename ``AbstractCatalog`` to ``DeclarativeCatalog`` - Rename ``AbstractCatalog`` to ``DeclarativeCatalog``
(with backward compatibility). (with backward compatibility).
- Rename ``catalog`` module to ``catalogs`` with backward compatibility. - Rename ``catalog`` module to ``catalogs`` with backward compatibility.
- Implement dynamic binding of providers for ``DeclarativeCatalog``. - Implement dynamic binding of providers for ``DeclarativeCatalog``.
- Add ``DynamicCatalog``. - Add ``DynamicCatalog``.
- Change restrictions for providers-to-catalogs bindings - provider could be - Change restrictions for providers-to-catalogs bindings - provider could be
bound to several catalogs with different names. bound to several catalogs with different names.
- Restrict overriding of providers by themselves. - Restrict overriding of providers by themselves.
- Restrict overriding of catalogs by themselves. - Restrict overriding of catalogs by themselves.
- Make ``DeclarativeCatalog.last_overriding`` attribute to be ``None`` by - Make ``DeclarativeCatalog.last_overriding`` attribute to be ``None`` by
default. default.
- Make ``Provider.last_overriding`` attribute to be ``None`` by - Make ``Provider.last_overriding`` attribute to be ``None`` by
default. default.
- Refactor catalogs and providers modules. - Refactor catalogs and providers modules.
- Add API documentation - Add API documentation
@ -1914,7 +1782,7 @@ to be the first major release.
0.10.5 0.10.5
------ ------
- Add more representable implementation for ``AbstractCatalog`` and - Add more representable implementation for ``AbstractCatalog`` and
``AbstractCatalog.Bundle``. ``AbstractCatalog.Bundle``.
0.10.4 0.10.4
@ -1938,17 +1806,17 @@ to be the first major release.
- Add functionality for creating ``AbstractCatalog`` provider bundles. - Add functionality for creating ``AbstractCatalog`` provider bundles.
- Improve ``AbstractCatalog`` inheritance. - Improve ``AbstractCatalog`` inheritance.
- Improve ``AbstractCatalog`` overriding. - Improve ``AbstractCatalog`` overriding.
- Add images for catalog "Writing catalogs" and "Operating with catalogs" - Add images for catalog "Writing catalogs" and "Operating with catalogs"
examples. examples.
- Add functionality for using positional argument injections with - Add functionality for using positional argument injections with
``Factory``, ``Singleton``, ``Callable`` providers and ``Factory``, ``Singleton``, ``Callable`` providers and
``inject`` decorator. ``inject`` decorator.
- Add functionality for decorating classes with ``@inject``. - Add functionality for decorating classes with ``@inject``.
- Add ``Singleton.injections`` attribute that represents a tuple of all - Add ``Singleton.injections`` attribute that represents a tuple of all
``Singleton`` injections (including args, kwargs, attributes and methods). ``Singleton`` injections (including args, kwargs, attributes and methods).
- Add ``Callable.injections`` attribute that represents a tuple of all - Add ``Callable.injections`` attribute that represents a tuple of all
``Callable`` injections (including args and kwargs). ``Callable`` injections (including args and kwargs).
- Add optimization for ``Injection.value`` property that will compute - Add optimization for ``Injection.value`` property that will compute
type of injection once, instead of doing this on every call. type of injection once, instead of doing this on every call.
- Add ``VERSION`` constant for verification of currently installed version. - Add ``VERSION`` constant for verification of currently installed version.
- Add support of Python 3.5. - Add support of Python 3.5.
@ -1958,7 +1826,7 @@ to be the first major release.
0.9.5 0.9.5
----- -----
- Change provider attributes scope to public. - Change provider attributes scope to public.
- Add ``Factory.injections`` attribute that represents a tuple of all - Add ``Factory.injections`` attribute that represents a tuple of all
``Factory`` injections (including kwargs, attributes and methods). ``Factory`` injections (including kwargs, attributes and methods).
0.9.4 0.9.4
@ -1975,14 +1843,14 @@ to be the first major release.
0.9.1 0.9.1
----- -----
- Add simplified syntax of kwarg injections for ``di.Factory`` and - Add simplified syntax of kwarg injections for ``di.Factory`` and
``di.Singleton`` providers: ``di.Singleton`` providers:
``di.Factory(SomeClass, dependency1=injectable_provider_or_value)``. ``di.Factory(SomeClass, dependency1=injectable_provider_or_value)``.
- Add simplified syntax of kwarg injections for ``di.Callable`` provider: - Add simplified syntax of kwarg injections for ``di.Callable`` provider:
``di.Callable(some_callable, dependency1=injectable_provider_or_value)`` ``di.Callable(some_callable, dependency1=injectable_provider_or_value)``
- Add simplified syntax of kwarg injections for ``@di.inject`` decorator: - Add simplified syntax of kwarg injections for ``@di.inject`` decorator:
``@di.inject(dependency1=injectable_provider_or_value)``. ``@di.inject(dependency1=injectable_provider_or_value)``.
- Optimize ``@di.inject()`` decorations when they were made several times for - Optimize ``@di.inject()`` decorations when they were made several times for
the same callback. the same callback.
- Add minor refactorings. - Add minor refactorings.
- Fix of minor documentation issues. - Fix of minor documentation issues.
@ -2002,21 +1870,21 @@ to be the first major release.
0.7.6 0.7.6
----- -----
- Adding support of six from 1.7.0 to 1.9.0. - Adding support of six from 1.7.0 to 1.9.0.
- Factory / Singleton providers are free from restriction to operate with - Factory / Singleton providers are free from restriction to operate with
classes only. This feature gives a change to use factory method and classes only. This feature gives a change to use factory method and
functions with Factory / Singleton providers. functions with Factory / Singleton providers.
- All attributes of all entities that have to be protected was renamed using - All attributes of all entities that have to be protected was renamed using
``_protected`` manner. ``_protected`` manner.
- Providers extending was improved by implementing overriding logic in - Providers extending was improved by implementing overriding logic in
``Provider.__call__()`` and moving providing logic into ``Provider.__call__()`` and moving providing logic into
``Provider._provide()``. ``Provider._provide()``.
- ``NewInstance`` provider was renamed to ``Factory`` provider. - ``NewInstance`` provider was renamed to ``Factory`` provider.
``NewInstance`` still can be used, but it considered to be deprecated and ``NewInstance`` still can be used, but it considered to be deprecated and
will be removed in further releases. will be removed in further releases.
- ``@inject`` decorator was refactored to keep all injections in - ``@inject`` decorator was refactored to keep all injections in
``_injections`` attribute of decorated callback. It will give a possibility to ``_injections`` attribute of decorated callback. It will give a possibility to
track all the injections of particular callbacks and gives some performance track all the injections of particular callbacks and gives some performance
boost due minimizing number of calls for doing injections. boost due minimizing number of calls for doing injections.
- A lot of documentation updates were made. - A lot of documentation updates were made.
- A lot of examples were added. - A lot of examples were added.

View File

@ -136,69 +136,25 @@ To use another loader use ``loader`` argument:
*Don't forget to mirror the changes in the requirements file.* *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 Loading from a Pydantic settings
-------------------------------- --------------------------------
``Configuration`` provider can load configuration from a ``pydantic_settings.BaseSettings`` object using the ``Configuration`` provider can load configuration from a ``pydantic`` settings object using the
:py:meth:`Configuration.from_pydantic` method: :py:meth:`Configuration.from_pydantic` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py .. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 32 :emphasize-lines: 31
To get the data from pydantic settings ``Configuration`` provider calls its ``model_dump()`` method. To get the data from pydantic settings ``Configuration`` provider calls ``Settings.dict()`` method.
If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments. If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments.
.. code-block:: python .. code-block:: python
container.config.from_pydantic(Settings(), exclude={"optional"}) container.config.from_pydantic(Settings(), exclude={"optional"})
Alternatively, you can provide a ``pydantic_settings.BaseSettings`` object over the configuration provider argument. In that case, Alternatively, you can provide a ``pydantic`` settings object over the configuration provider argument. In that case,
the container will call ``config.from_pydantic()`` automatically: the container will call ``config.from_pydantic()`` automatically:
.. code-block:: python .. code-block:: python
@ -215,23 +171,18 @@ the container will call ``config.from_pydantic()`` automatically:
.. note:: .. note::
``Dependency Injector`` doesn't install ``pydantic-settings`` by default. ``Dependency Injector`` doesn't install ``pydantic`` by default.
You can install the ``Dependency Injector`` with an extra dependency:: You can install the ``Dependency Injector`` with an extra dependency::
pip install dependency-injector[pydantic2] pip install dependency-injector[pydantic]
or install ``pydantic-settings`` directly:: or install ``pydantic`` directly::
pip install pydantic-settings pip install pydantic
*Don't forget to mirror the changes in the requirements file.* *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 Loading from a dictionary
------------------------- -------------------------
@ -366,19 +317,6 @@ See also: :ref:`configuration-strict-mode`.
assert container.config.section.option() is None 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 Mandatory and optional sources
------------------------------ ------------------------------

View File

@ -16,11 +16,10 @@ To create a custom provider you need to follow these rules:
1. New provider class should inherit :py:class:`Provider`. 1. New provider class should inherit :py:class:`Provider`.
2. You need to implement the ``Provider._provide()`` method. 2. You need to implement the ``Provider._provide()`` method.
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an 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 equivalent copy of a provider. All providers must be copied with a ``deepcopy()`` function
from the ``providers`` module. It's essential to pass ``memo`` into ``deepcopy`` in order to keep from the ``providers`` module. After the a new provider object is created use
the preconfigured ``args`` and ``kwargs`` of stored providers. After the a new provider object ``Provider._copy_overriding()`` method to copy all overriding providers. See the example
is created, use ``Provider._copy_overriding()`` method to copy all overriding providers. See the below.
example below.
4. If new provider has a ``__init__()`` method, it should call the parent 4. If new provider has a ``__init__()`` method, it should call the parent
``Provider.__init__()``. ``Provider.__init__()``.
5. If new provider stores any other providers, these providers should be listed in 5. If new provider stores any other providers, these providers should be listed in
@ -34,7 +33,7 @@ To create a custom provider you need to follow these rules:
.. note:: .. note::
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or 1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
inheriting a ``Provider`` and use ``Factory`` internally - the last is better. 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. 2. When create 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 3. Use the ``__slots__`` attribute to make sure nothing could be attached to your provider. You
will also save some memory. will also save some memory.

View File

@ -61,12 +61,11 @@ When you call ``.shutdown()`` method on a resource provider, it will remove the
if any, and switch to uninitialized state. Some of resource initializer types support specifying custom if any, and switch to uninitialized state. Some of resource initializer types support specifying custom
resource shutdown. resource shutdown.
Resource provider supports 4 types of initializers: Resource provider supports 3 types of initializers:
- Function - Function
- Context Manager - Generator
- Generator (legacy) - Subclass of ``resources.Resource``
- Subclass of ``resources.Resource`` (legacy)
Function initializer Function initializer
-------------------- --------------------
@ -104,44 +103,8 @@ you configure global resource:
Function initializer does not provide a way to specify custom resource shutdown. Function initializer does not provide a way to specify custom resource shutdown.
Context Manager initializer Generator initializer
--------------------------- ---------------------
This is an extension to the Function initializer. Resource provider automatically detects if the initializer returns a
context manager and uses it to manage the resource lifecycle.
.. code-block:: python
from dependency_injector import containers, providers
class DatabaseConnection:
def __init__(self, host, port, user, password):
self.host = host
self.port = port
self.user = user
self.password = password
def __enter__(self):
print(f"Connecting to {self.host}:{self.port} as {self.user}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing connection")
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
db = providers.Resource(
DatabaseConnection,
host=config.db.host,
port=config.db.port,
user=config.db.user,
password=config.db.password,
)
Generator initializer (legacy)
------------------------------
Resource provider can use 2-step generators: Resource provider can use 2-step generators:
@ -191,13 +154,8 @@ object is not mandatory. You can leave ``yield`` statement empty:
argument2=..., argument2=...,
) )
.. note:: Subclass initializer
--------------------
Generator initializers are automatically wrapped with ``contextmanager`` or ``asynccontextmanager`` decorator when
provided to a ``Resource`` provider.
Subclass initializer (legacy)
-----------------------------
You can create resource initializer by implementing a subclass of the ``resources.Resource``: You can create resource initializer by implementing a subclass of the ``resources.Resource``:
@ -252,72 +210,6 @@ first argument.
.. _resource-provider-wiring-closing: .. _resource-provider-wiring-closing:
Scoping Resources using specialized subclasses
----------------------------------------------
You can use specialized subclasses of ``Resource`` provider to initialize and shutdown resources by type.
Allowing for example to only initialize a subgroup of resources.
.. code-block:: python
class ScopedResource(resources.Resource):
pass
def init_service(name) -> Service:
print(f"Init {name}")
yield Service()
print(f"Shutdown {name}")
class Container(containers.DeclarativeContainer):
scoped = ScopedResource(
init_service,
"scoped",
)
generic = providers.Resource(
init_service,
"generic",
)
To initialize resources by type you can use ``init_resources(resource_type)`` and ``shutdown_resources(resource_type)``
methods adding the resource type as an argument:
.. code-block:: python
def main():
container = Container()
container.init_resources(ScopedResource)
# Generates:
# >>> Init scoped
container.shutdown_resources(ScopedResource)
# Generates:
# >>> Shutdown scoped
And to initialize all resources you can use ``init_resources()`` and ``shutdown_resources()`` without arguments:
.. code-block:: python
def main():
container = Container()
container.init_resources()
# Generates:
# >>> Init scoped
# >>> Init generic
container.shutdown_resources()
# Generates:
# >>> Shutdown scoped
# >>> Shutdown generic
It works using the ``traverse()`` method to find all resources of the specified type, selecting all resources
which are instances of the specified type.
Resources, wiring, and per-function execution scope Resources, wiring, and per-function execution scope
--------------------------------------------------- ---------------------------------------------------
@ -371,11 +263,10 @@ Asynchronous function initializer:
argument2=..., argument2=...,
) )
Asynchronous Context Manager initializer: Asynchronous generator initializer:
.. code-block:: python .. code-block:: python
@asynccontextmanager
async def init_async_resource(argument1=..., argument2=...): async def init_async_resource(argument1=..., argument2=...):
connection = await connect() connection = await connect()
yield connection yield connection
@ -467,54 +358,5 @@ See also:
- Wiring :ref:`async-injections-wiring` - Wiring :ref:`async-injections-wiring`
- :ref:`fastapi-redis-example` - :ref:`fastapi-redis-example`
ASGI Lifespan Protocol Support
------------------------------
The :mod:`dependency_injector.ext.starlette` module provides a :class:`~dependency_injector.ext.starlette.Lifespan`
class that integrates resource providers with ASGI applications using the `Lifespan Protocol`_. This allows resources to
be automatically initialized at application startup and properly shut down when the application stops.
.. code-block:: python
from contextlib import asynccontextmanager
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
from dependency_injector.ext.starlette import Lifespan
from fastapi import FastAPI, Request, Depends, APIRouter
class Connection: ...
@asynccontextmanager
async def init_database():
print("opening database connection")
yield Connection()
print("closing database connection")
router = APIRouter()
@router.get("/")
@inject
async def index(request: Request, db: Connection = Depends(Provide["db"])):
# use the database connection here
return "OK!"
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
db = providers.Resource(init_database)
lifespan = providers.Singleton(Lifespan, __self__)
app = providers.Singleton(FastAPI, lifespan=lifespan)
_include_router = providers.Resource(
app.provided.include_router.call(),
router,
)
if __name__ == "__main__":
import uvicorn
container = Container()
app = container.app()
uvicorn.run(app, host="localhost", port=8000)
.. _Lifespan Protocol: https://asgi.readthedocs.io/en/latest/specs/lifespan.html
.. disqus:: .. disqus::

View File

@ -24,7 +24,7 @@ returns it on the rest of the calls.
.. note:: .. note::
``Singleton`` provider makes dependencies injection only when it creates an object. When an object ``Singleton`` provider makes dependencies injection only when creates an object. When an object
is created and memorized ``Singleton`` provider just returns it without applying injections. is created and memorized ``Singleton`` provider just returns it without applying injections.
Specialization of the provided type and abstract singletons work the same like like for the Specialization of the provided type and abstract singletons work the same like like for the

View File

@ -1,7 +0,0 @@
.. list-table::
:class: no-border
:align: left
* - Sponsor the project on GitHub:
- .. raw:: html
:file: _static/sponsor.html

View File

@ -257,7 +257,7 @@ Let's check that it works. Open another terminal session and use ``httpie``:
You should see: You should see:
.. code-block:: http .. code-block:: json
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Length: 844 Content-Length: 844
@ -596,7 +596,7 @@ and make a request to the API in the terminal:
You should see: You should see:
.. code-block:: http .. code-block:: json
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Length: 492 Content-Length: 492
@ -859,6 +859,4 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -18,7 +18,7 @@ In this tutorial we will use:
- Python 3 - Python 3
- Docker - Docker
- Docker Compose - Docker-compose
Start from the scratch or jump to the section: Start from the scratch or jump to the section:
@ -47,27 +47,28 @@ response it will log:
Prerequisites Prerequisites
------------- -------------
We will use `docker compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions: We will use `Docker <https://www.docker.com/>`_ and
`docker-compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
.. code-block:: bash .. code-block:: bash
docker --version docker --version
docker compose version docker-compose --version
The output should look something like: The output should look something like:
.. code-block:: bash .. code-block:: bash
Docker version 27.3.1, build ce12230 Docker version 20.10.5, build 55c4c88
Docker Compose version v2.29.7 docker-compose version 1.29.0, build 07737305
.. note:: .. note::
If you don't have ``Docker`` or ``docker compose`` you need to install them before proceeding. If you don't have ``Docker`` or ``docker-compose`` you need to install them before proceeding.
Follow these installation guides: Follow these installation guides:
- `Install Docker <https://docs.docker.com/get-docker/>`_ - `Install Docker <https://docs.docker.com/get-docker/>`_
- `Install docker compose <https://docs.docker.com/compose/install/>`_ - `Install docker-compose <https://docs.docker.com/compose/install/>`_
The prerequisites are satisfied. Let's get started with the project layout. The prerequisites are satisfied. Let's get started with the project layout.
@ -128,13 +129,13 @@ Put next lines into the ``requirements.txt`` file:
pytest-cov pytest-cov
Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and
specify how to run it. We will use ``python:3.13-bookworm`` as a base image. specify how to run it. We will use ``python:3.9-buster`` as a base image.
Put next lines into the ``Dockerfile`` file: Put next lines into the ``Dockerfile`` file:
.. code-block:: bash .. code-block:: bash
FROM python:3.13-bookworm FROM python:3.10-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
@ -154,6 +155,8 @@ Put next lines into the ``docker-compose.yml`` file:
.. code-block:: yaml .. code-block:: yaml
version: "3.7"
services: services:
monitor: monitor:
@ -168,7 +171,7 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose build docker-compose build
The build process may take a couple of minutes. You should see something like this in the end: The build process may take a couple of minutes. You should see something like this in the end:
@ -181,7 +184,7 @@ After the build is done run the container:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should look like: The output should look like:
@ -458,7 +461,7 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should look like: The output should look like:
@ -702,7 +705,7 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
You should see: You should see:
@ -810,7 +813,7 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
You should see: You should see:
@ -962,16 +965,15 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
You should see: You should see:
.. code-block:: bash .. code-block:: bash
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0 platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /code rootdir: /code
plugins: cov-6.0.0, asyncio-0.24.0 plugins: asyncio-0.16.0, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
monitoringdaemon/tests.py .. [100%] monitoringdaemon/tests.py .. [100%]
@ -1026,6 +1028,4 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -84,7 +84,7 @@ Create next structure in the project root directory. All files are empty. That's
Initial project layout: Initial project layout:
.. code-block:: text .. code-block:: bash
./ ./
├── movies/ ├── movies/
@ -109,7 +109,7 @@ Now it's time to install the project requirements. We will use next packages:
Put next lines into the ``requirements.txt`` file: Put next lines into the ``requirements.txt`` file:
.. code-block:: text .. code-block:: bash
dependency-injector dependency-injector
pyyaml pyyaml
@ -134,7 +134,7 @@ We will create a script that creates database files.
First add the folder ``data/`` in the root of the project and then add the file First add the folder ``data/`` in the root of the project and then add the file
``fixtures.py`` inside of it: ``fixtures.py`` inside of it:
.. code-block:: text .. code-block:: bash
:emphasize-lines: 2-3 :emphasize-lines: 2-3
./ ./
@ -205,13 +205,13 @@ Now run in the terminal:
You should see: You should see:
.. code-block:: text .. code-block:: bash
OK OK
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder: Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
.. code-block:: text .. code-block:: bash
:emphasize-lines: 4-5 :emphasize-lines: 4-5
./ ./
@ -289,7 +289,7 @@ After each step we will add the provider to the container.
Create the ``entities.py`` in the ``movies`` package: Create the ``entities.py`` in the ``movies`` package:
.. code-block:: text .. code-block:: bash
:emphasize-lines: 10 :emphasize-lines: 10
./ ./
@ -356,7 +356,7 @@ Let's move on to the finders.
Create the ``finders.py`` in the ``movies`` package: Create the ``finders.py`` in the ``movies`` package:
.. code-block:: text .. code-block:: bash
:emphasize-lines: 11 :emphasize-lines: 11
./ ./
@ -465,7 +465,7 @@ The configuration file is ready. Move on to the lister.
Create the ``listers.py`` in the ``movies`` package: Create the ``listers.py`` in the ``movies`` package:
.. code-block:: text .. code-block:: bash
:emphasize-lines: 12 :emphasize-lines: 12
./ ./
@ -613,7 +613,7 @@ Run in the terminal:
You should see: You should see:
.. code-block:: text .. code-block:: plain
Francis Lawrence movies: Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence') - Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
@ -752,7 +752,7 @@ Run in the terminal:
You should see: You should see:
.. code-block:: text .. code-block:: plain
Francis Lawrence movies: Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence') - Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
@ -868,7 +868,7 @@ Run in the terminal line by line:
The output should be similar for each command: The output should be similar for each command:
.. code-block:: text .. code-block:: plain
Francis Lawrence movies: Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence') - Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
@ -888,7 +888,7 @@ We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
Create ``tests.py`` in the ``movies`` package: Create ``tests.py`` in the ``movies`` package:
.. code-block:: text .. code-block:: bash
:emphasize-lines: 13 :emphasize-lines: 13
./ ./
@ -911,7 +911,7 @@ Create ``tests.py`` in the ``movies`` package:
and put next into it: and put next into it:
.. code-block:: python .. code-block:: python
:emphasize-lines: 41,50 :emphasize-lines: 36,51
"""Tests module.""" """Tests module."""
@ -941,18 +941,13 @@ and put next into it:
return container return container
@pytest.fixture def test_movies_directed_by(container):
def finder_mock(container):
finder_mock = mock.Mock() finder_mock = mock.Mock()
finder_mock.find_all.return_value = [ finder_mock.find_all.return_value = [
container.movie("The 33", 2015, "Patricia Riggen"), container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"), container.movie("The Jungle Book", 2016, "Jon Favreau"),
] ]
return finder_mock
def test_movies_directed_by(container, finder_mock):
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_directed_by("Jon Favreau") movies = lister.movies_directed_by("Jon Favreau")
@ -961,7 +956,13 @@ and put next into it:
assert movies[0].title == "The Jungle Book" assert movies[0].title == "The Jungle Book"
def test_movies_released_in(container, finder_mock): def test_movies_released_in(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"),
]
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_released_in(2015) movies = lister.movies_released_in(2015)
@ -977,7 +978,7 @@ Run in the terminal:
You should see: You should see:
.. code-block:: text .. code-block::
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-3.0.0 plugins: cov-3.0.0
@ -994,9 +995,9 @@ You should see:
movies/entities.py 7 1 86% movies/entities.py 7 1 86%
movies/finders.py 26 13 50% movies/finders.py 26 13 50%
movies/listers.py 8 0 100% movies/listers.py 8 0 100%
movies/tests.py 24 0 100% movies/tests.py 23 0 100%
------------------------------------------ ------------------------------------------
TOTAL 90 30 67% TOTAL 89 30 66%
.. note:: .. note::
@ -1033,6 +1034,4 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -280,7 +280,7 @@ Now let's fill in the layout.
Put next into the ``base.html``: Put next into the ``base.html``:
.. code-block:: jinja .. code-block:: html
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
@ -313,7 +313,7 @@ And put something to the index page.
Put next into the ``index.html``: Put next into the ``index.html``:
.. code-block:: jinja .. code-block:: html
{% extends "base.html" %} {% extends "base.html" %}
@ -998,6 +998,5 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -22,82 +22,6 @@ To use wiring you need:
:local: :local:
:backlinks: none :backlinks: none
Decorator @inject
-----------------
Decorator ``@inject`` injects the dependencies. Use it to decorate all functions and methods
with the injections.
.. code-block:: python
from dependency_injector.wiring import inject, Provide
@inject
def foo(bar: Bar = Provide[Container.bar]):
...
Decorator ``@inject`` must be specified as a very first decorator of a function to ensure that
the wiring works appropriately. This will also contribute to the performance of the wiring process.
.. code-block:: python
from dependency_injector.wiring import inject, Provide
@decorator_etc
@decorator_2
@decorator_1
@inject
def foo(bar: Bar = Provide[Container.bar]):
...
Specifying the ``@inject`` as a first decorator is also crucial for FastAPI, other frameworks
using decorators similarly, for closures, and for any types of custom decorators with the injections.
FastAPI example:
.. code-block:: python
app = FastAPI()
@app.api_route("/")
@inject
async def index(service: Annotated[Service, Depends(Provide[Container.service])]):
value = await service.process()
return {"result": value}
Decorators example:
.. code-block:: python
def decorator1(func):
@functools.wraps(func)
@inject
def wrapper(value1: int = Provide[Container.config.value1]):
result = func()
return result + value1
return wrapper
def decorator2(func):
@functools.wraps(func)
@inject
def wrapper(value2: int = Provide[Container.config.value2]):
result = func()
return result + value2
return wrapper
@decorator1
@decorator2
def sample():
...
.. seealso::
`Issue #404 <https://github.com/ets-labs/python-dependency-injector/issues/404#issuecomment-785216978>`_
explains ``@inject`` decorator in a few more details.
Markers Markers
------- -------
@ -127,7 +51,6 @@ To inject the provider itself use ``Provide[foo.provider]``:
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]): def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
bar = bar_provider(argument="baz") bar = bar_provider(argument="baz")
... ...
You can also use ``Provider[foo]`` for injecting the provider itself: You can also use ``Provider[foo]`` for injecting the provider itself:
.. code-block:: python .. code-block:: python
@ -255,43 +178,13 @@ To inject a container use special identifier ``<container>``:
Making injections into modules and class attributes Making injections into modules and class attributes
--------------------------------------------------- ---------------------------------------------------
You can use wiring to make injections into modules and class attributes. Both the classic marker You can use wiring to make injections into modules and class attributes.
syntax and the ``Annotated`` form are supported.
Classic marker syntax:
.. code-block:: python
service: Service = Provide[Container.service]
class Main:
service: Service = Provide[Container.service]
Full example of the classic marker syntax:
.. literalinclude:: ../examples/wiring/example_attribute.py .. literalinclude:: ../examples/wiring/example_attribute.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 14,19 :emphasize-lines: 14,19
Annotated form (Python 3.9+):
.. code-block:: python
from typing import Annotated
service: Annotated[Service, Provide[Container.service]]
class Main:
service: Annotated[Service, Provide[Container.service]]
Full example of the annotated form:
.. literalinclude:: ../examples/wiring/example_attribute_annotated.py
:language: python
:lines: 3-
:emphasize-lines: 16,21
You could also use string identifiers to avoid a dependency on a container: You could also use string identifiers to avoid a dependency on a container:
.. code-block:: python .. code-block:: python
@ -632,36 +525,6 @@ or with a single container ``register_loader_containers(container)`` multiple ti
To unregister a container use ``unregister_loader_containers(container)``. To unregister a container use ``unregister_loader_containers(container)``.
Wiring module will uninstall the import hook when unregister last container. Wiring module will uninstall the import hook when unregister last container.
Few notes on performance
------------------------
``.wire()`` utilize caching to speed up the wiring process. At the end it clears the cache to avoid memory leaks.
But this may not always be desirable, when you want to keep the cache for the next wiring
(e.g. due to usage of multiple containers or during unit tests).
To keep the cache after wiring, you can set flag ``keep_cache=True`` (works with ``WiringConfiguration`` too):
.. code-block:: python
container1.wire(
modules=["yourapp.module1", "yourapp.module2"],
keep_cache=True,
)
container2.wire(
modules=["yourapp.module2", "yourapp.module3"],
keep_cache=True,
)
...
and then clear it manually when you need it:
.. code-block:: python
from dependency_injector.wiring import clear_cache
clear_cache()
Integration with other frameworks Integration with other frameworks
--------------------------------- ---------------------------------
@ -693,6 +556,5 @@ Take a look at other application examples:
- :ref:`fastapi-example` - :ref:`fastapi-example`
- :ref:`fastapi-redis-example` - :ref:`fastapi-redis-example`
- :ref:`fastapi-sqlalchemy-example` - :ref:`fastapi-sqlalchemy-example`
- :ref:`fastdepends-example`
.. disqus:: .. disqus::

View File

@ -3,18 +3,18 @@ import os
class ApiClient: class ApiClient:
def __init__(self, api_key: str, timeout: int) -> None: def __init__(self, api_key: str, timeout: int):
self.api_key = api_key # <-- dependency is injected self.api_key = api_key # <-- dependency is injected
self.timeout = timeout # <-- dependency is injected self.timeout = timeout # <-- dependency is injected
class Service: class Service:
def __init__(self, api_client: ApiClient) -> None: def __init__(self, api_client: ApiClient):
self.api_client = api_client # <-- dependency is injected self.api_client = api_client # <-- dependency is injected
def main(service: Service) -> None: # <-- dependency is injected def main(service: Service): # <-- dependency is injected
... ...
@ -23,7 +23,7 @@ if __name__ == "__main__":
service=Service( service=Service(
api_client=ApiClient( api_client=ApiClient(
api_key=os.getenv("API_KEY"), api_key=os.getenv("API_KEY"),
timeout=int(os.getenv("TIMEOUT")), timeout=os.getenv("TIMEOUT"),
), ),
), ),
) )

View File

@ -23,7 +23,7 @@ class Container(containers.DeclarativeContainer):
@inject @inject
def main(service: Service = Provide[Container.service]) -> None: def main(service: Service = Provide[Container.service]):
... ...

View File

@ -98,9 +98,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0, aiohttp-1.0.5 plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 3 items collected 3 items
giphynavigator/tests.py ... [100%] giphynavigator/tests.py ... [100%]

View File

@ -3,15 +3,11 @@
from unittest import mock from unittest import mock
import pytest import pytest
import pytest_asyncio
from giphynavigator.application import create_app from giphynavigator.application import create_app
from giphynavigator.giphy import GiphyClient from giphynavigator.giphy import GiphyClient
pytestmark = pytest.mark.asyncio
@pytest.fixture @pytest.fixture
def app(): def app():
app = create_app() app = create_app()
@ -19,9 +15,9 @@ def app():
app.container.unwire() app.container.unwire()
@pytest_asyncio.fixture @pytest.fixture
async def client(app, aiohttp_client): def client(app, aiohttp_client, loop):
return await aiohttp_client(app) return loop.run_until_complete(aiohttp_client(app))
async def test_index(client, app): async def test_index(client, app):

View File

@ -2,5 +2,4 @@ dependency-injector
aiohttp aiohttp
pyyaml pyyaml
pytest-aiohttp pytest-aiohttp
pytest-asyncio
pytest-cov pytest-cov

View File

@ -1,4 +1,4 @@
FROM python:3.13-bookworm FROM python:3.10-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1

View File

@ -13,13 +13,13 @@ Build the Docker image:
.. code-block:: bash .. code-block:: bash
docker compose build docker-compose build
Run the docker-compose environment: Run the docker-compose environment:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should be something like: The output should be something like:
@ -59,16 +59,15 @@ To run the tests do:
.. code-block:: bash .. code-block:: bash
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
The output should be something like: The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0 platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /code rootdir: /code
plugins: cov-6.0.0, asyncio-0.24.0 plugins: asyncio-0.16.0, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
monitoringdaemon/tests.py .. [100%] monitoringdaemon/tests.py .. [100%]

View File

@ -61,7 +61,7 @@ async def test_example_monitor(container, caplog):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_dispatcher(container, caplog): async def test_dispatcher(container, caplog, event_loop):
caplog.set_level("INFO") caplog.set_level("INFO")
example_monitor_mock = mock.AsyncMock() example_monitor_mock = mock.AsyncMock()
@ -72,7 +72,6 @@ async def test_dispatcher(container, caplog):
httpbin_monitor=httpbin_monitor_mock, httpbin_monitor=httpbin_monitor_mock,
): ):
dispatcher = container.dispatcher() dispatcher = container.dispatcher()
event_loop = asyncio.get_running_loop()
event_loop.create_task(dispatcher.start()) event_loop.create_task(dispatcher.start())
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
dispatcher.stop() dispatcher.stop()

View File

@ -1,4 +1,4 @@
FROM python:3.13-bookworm FROM python:3.9-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1

View File

@ -12,38 +12,47 @@ Build the Docker image:
.. code-block:: bash .. code-block:: bash
docker compose build docker-compose build
Run the docker-compose environment: Run the docker-compose environment:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should be something like: The output should be something like:
.. code-block:: .. code-block::
fastapi-redis-redis-1 | 1:C 19 Dec 2022 02:33:02.484 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo redis_1 | 1:C 04 Jan 2021 02:42:14.115 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
fastapi-redis-redis-1 | 1:C 19 Dec 2022 02:33:02.484 # Redis version=7.0.5, bits=64, commit=00000000, modified=0, pid=1, just started redis_1 | 1:C 04 Jan 2021 02:42:14.115 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
fastapi-redis-redis-1 | 1:C 19 Dec 2022 02:33:02.484 # Configuration loaded redis_1 | 1:C 04 Jan 2021 02:42:14.115 # Configuration loaded
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.485 * monotonic clock: POSIX clock_gettime redis_1 | 1:M 04 Jan 2021 02:42:14.116 * Running mode=standalone, port=6379.
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.485 * Running mode=standalone, port=6379. redis_1 | 1:M 04 Jan 2021 02:42:14.116 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.485 # Server initialized redis_1 | 1:M 04 Jan 2021 02:42:14.116 # Server initialized
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * Loading RDB produced by version 7.0.5 redis_1 | 1:M 04 Jan 2021 02:42:14.117 * Loading RDB produced by version 6.0.9
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * RDB age 58 seconds redis_1 | 1:M 04 Jan 2021 02:42:14.117 * RDB age 1 seconds
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * RDB memory usage when created 0.85 Mb redis_1 | 1:M 04 Jan 2021 02:42:14.117 * RDB memory usage when created 0.77 Mb
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * Done loading RDB, keys loaded: 0, keys expired: 0. redis_1 | 1:M 04 Jan 2021 02:42:14.117 * DB loaded from disk: 0.000 seconds
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * DB loaded from disk: 0.000 seconds redis_1 | 1:M 04 Jan 2021 02:42:14.117 * Ready to accept connections
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.488 * Ready to accept connections redis_1 | 1:signal-handler (1609728137) Received SIGTERM scheduling shutdown...
fastapi-redis-example-1 | INFO: Started server process [1] redis_1 | 1:M 04 Jan 2021 02:42:17.984 # User requested shutdown...
fastapi-redis-example-1 | INFO: Waiting for application startup. redis_1 | 1:M 04 Jan 2021 02:42:17.984 # Redis is now ready to exit, bye bye...
fastapi-redis-example-1 | INFO: Application startup complete. redis_1 | 1:C 04 Jan 2021 02:42:22.035 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
fastapi-redis-example-1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) redis_1 | 1:C 04 Jan 2021 02:42:22.035 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
fastapi-redis-example-1 | INFO: 172.18.0.1:63998 - "GET / HTTP/1.1" 200 OK redis_1 | 1:C 04 Jan 2021 02:42:22.035 # Configuration loaded
fastapi-redis-example-1 | INFO: 172.18.0.1:63998 - "GET /favicon.ico HTTP/1.1" 404 Not Found redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Running mode=standalone, port=6379.
fastapi-redis-example-1 | INFO: 172.18.0.1:63998 - "GET / HTTP/1.1" 200 OK redis_1 | 1:M 04 Jan 2021 02:42:22.037 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
fastapi-redis-example-1 | INFO: 172.18.0.1:63998 - "GET / HTTP/1.1" 200 OK redis_1 | 1:M 04 Jan 2021 02:42:22.037 # Server initialized
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Loading RDB produced by version 6.0.9
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * RDB age 9 seconds
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * RDB memory usage when created 0.77 Mb
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * DB loaded from disk: 0.000 seconds
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Ready to accept connections
example_1 | INFO: Started server process [1]
example_1 | INFO: Waiting for application startup.
example_1 | INFO: Application startup complete.
example_1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
Test Test
---- ----
@ -54,16 +63,16 @@ To run the tests do:
.. code-block:: bash .. code-block:: bash
docker compose run --rm example py.test fastapiredis/tests.py --cov=fastapiredis docker-compose run --rm example py.test fastapiredis/tests.py --cov=fastapiredis
The output should be something like: The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0 platform linux -- Python 3.9, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: /code rootdir: /code
plugins: cov-6.0.0, asyncio-0.24.0, anyio-4.7.0 plugins: cov-2.10.1, asyncio-0.14.0
asyncio: mode=Mode.STRICT, default_loop_scope=None collected 1 item
fastapiredis/tests.py . [100%] fastapiredis/tests.py . [100%]
@ -71,10 +80,10 @@ The output should be something like:
Name Stmts Miss Cover Name Stmts Miss Cover
------------------------------------------------- -------------------------------------------------
fastapiredis/__init__.py 0 0 100% fastapiredis/__init__.py 0 0 100%
fastapiredis/application.py 14 0 100% fastapiredis/application.py 15 0 100%
fastapiredis/containers.py 6 0 100% fastapiredis/containers.py 6 0 100%
fastapiredis/redis.py 7 4 43% fastapiredis/redis.py 7 4 43%
fastapiredis/services.py 7 3 57% fastapiredis/services.py 7 3 57%
fastapiredis/tests.py 18 0 100% fastapiredis/tests.py 18 0 100%
------------------------------------------------- -------------------------------------------------
TOTAL 52 7 87% TOTAL 53 7 87%

View File

@ -1,22 +1,18 @@
"""Application module.""" """Application module."""
from typing import Annotated from dependency_injector.wiring import inject, Provide
from fastapi import FastAPI, Depends
from fastapi import Depends, FastAPI
from dependency_injector.wiring import Provide, inject
from .containers import Container from .containers import Container
from .services import Service from .services import Service
app = FastAPI() app = FastAPI()
@app.api_route("/") @app.api_route("/")
@inject @inject
async def index( async def index(service: Service = Depends(Provide[Container.service])):
service: Annotated[Service, Depends(Provide[Container.service])]
) -> dict[str, str]:
value = await service.process() value = await service.process()
return {"result": value} return {"result": value}

View File

@ -1,10 +1,12 @@
"""Redis client module."""
from typing import AsyncIterator from typing import AsyncIterator
from redis.asyncio import from_url, Redis from aioredis import create_redis_pool, Redis
async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]: async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]:
session = from_url(f"redis://{host}", password=password, encoding="utf-8", decode_responses=True) pool = await create_redis_pool(f"redis://{host}", password=password)
yield session yield pool
session.close() pool.close()
await session.wait_closed() await pool.wait_closed()

View File

@ -1,6 +1,6 @@
"""Services module.""" """Services module."""
from redis.asyncio import Redis from aioredis import Redis
class Service: class Service:
@ -9,4 +9,4 @@ class Service:
async def process(self) -> str: async def process(self) -> str:
await self._redis.set("my-key", "value") await self._redis.set("my-key", "value")
return await self._redis.get("my-key") return await self._redis.get("my-key", encoding="utf-8")

View File

@ -3,7 +3,7 @@
from unittest import mock from unittest import mock
import pytest import pytest
from httpx import ASGITransport, AsyncClient from httpx import AsyncClient
from .application import app, container from .application import app, container
from .services import Service from .services import Service
@ -11,10 +11,7 @@ from .services import Service
@pytest.fixture @pytest.fixture
def client(event_loop): def client(event_loop):
client = AsyncClient( client = AsyncClient(app=app, base_url="http://test")
transport=ASGITransport(app=app),
base_url="http://test",
)
yield client yield client
event_loop.run_until_complete(client.aclose()) event_loop.run_until_complete(client.aclose())

View File

@ -1,7 +1,7 @@
dependency-injector dependency-injector
fastapi fastapi
uvicorn uvicorn
redis>=4.2 aioredis<2 # TODO: Update example to work with aioredis >= 2.0
# For testing: # For testing:
pytest pytest

View File

@ -1,7 +1,4 @@
from typing import Annotated from fastapi import FastAPI, Depends
from fastapi import Depends, FastAPI
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject from dependency_injector.wiring import Provide, inject
@ -21,9 +18,7 @@ app = FastAPI()
@app.api_route("/") @app.api_route("/")
@inject @inject
async def index( async def index(service: Service = Depends(Provide[Container.service])):
service: Annotated[Service, Depends(Provide[Container.service])]
) -> dict[str, str]:
result = await service.process() result = await service.process()
return {"result": result} return {"result": result}

View File

@ -1,18 +1,14 @@
from unittest import mock from unittest import mock
import pytest import pytest
import pytest_asyncio from httpx import AsyncClient
from httpx import ASGITransport, AsyncClient
from fastapi_di_example import app, container, Service from fastapi_di_example import app, container, Service
@pytest_asyncio.fixture @pytest.fixture
async def client(): async def client(event_loop):
async with AsyncClient( async with AsyncClient(app=app, base_url="http://test") as client:
transport=ASGITransport(app=app),
base_url="http://test",
) as client:
yield client yield client

View File

@ -1,4 +1,4 @@
FROM python:3.13-bookworm FROM python:3.10-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0

View File

@ -15,13 +15,13 @@ Build the Docker image:
.. code-block:: bash .. code-block:: bash
docker compose build docker-compose build
Run the docker-compose environment: Run the docker-compose environment:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should be something like: The output should be something like:
@ -29,15 +29,15 @@ The output should be something like:
Starting fastapi-sqlalchemy_webapp_1 ... done Starting fastapi-sqlalchemy_webapp_1 ... done
Attaching to fastapi-sqlalchemy_webapp_1 Attaching to fastapi-sqlalchemy_webapp_1
webapp_1 | 2022-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
webapp_1 | 2022-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine () webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine ()
webapp_1 | 2022-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
webapp_1 | 2022-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine () webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine ()
webapp_1 | 2022-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine PRAGMA main.table_info("users") webapp_1 | 2021-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine PRAGMA main.table_info("users")
webapp_1 | 2022-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine () webapp_1 | 2021-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine ()
webapp_1 | 2022-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine PRAGMA temp.table_info("users") webapp_1 | 2021-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine PRAGMA temp.table_info("users")
webapp_1 | 2022-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine () webapp_1 | 2021-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine ()
webapp_1 | 2022-02-04 22:07:19,809 INFO sqlalchemy.engine.base.Engine webapp_1 | 2021-02-04 22:07:19,809 INFO sqlalchemy.engine.base.Engine
webapp_1 | CREATE TABLE users ( webapp_1 | CREATE TABLE users (
webapp_1 | id INTEGER NOT NULL, webapp_1 | id INTEGER NOT NULL,
webapp_1 | email VARCHAR, webapp_1 | email VARCHAR,
@ -49,8 +49,8 @@ The output should be something like:
webapp_1 | ) webapp_1 | )
webapp_1 | webapp_1 |
webapp_1 | webapp_1 |
webapp_1 | 2022-02-04 22:07:19,810 INFO sqlalchemy.engine.base.Engine () webapp_1 | 2021-02-04 22:07:19,810 INFO sqlalchemy.engine.base.Engine ()
webapp_1 | 2022-02-04 22:07:19,821 INFO sqlalchemy.engine.base.Engine COMMIT webapp_1 | 2021-02-04 22:07:19,821 INFO sqlalchemy.engine.base.Engine COMMIT
webapp_1 | INFO: Started server process [8] webapp_1 | INFO: Started server process [8]
webapp_1 | INFO: Waiting for application startup. webapp_1 | INFO: Waiting for application startup.
webapp_1 | INFO: Application startup complete. webapp_1 | INFO: Application startup complete.
@ -67,15 +67,15 @@ To run the tests do:
.. code-block:: bash .. code-block:: bash
docker compose run --rm webapp py.test webapp/tests.py --cov=webapp docker-compose run --rm webapp py.test webapp/tests.py --cov=webapp
The output should be something like: The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0 platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /code rootdir: /code
plugins: cov-6.0.0, anyio-4.7.0 plugins: cov-3.0.0
collected 7 items collected 7 items
webapp/tests.py ....... [100%] webapp/tests.py ....... [100%]

View File

@ -1,5 +1,5 @@
dependency-injector dependency-injector
fastapi[standard] fastapi
uvicorn uvicorn
pyyaml pyyaml
sqlalchemy sqlalchemy

View File

@ -1,14 +1,11 @@
"""Endpoints module.""" """Endpoints module."""
from typing import Annotated
from fastapi import APIRouter, Depends, Response, status from fastapi import APIRouter, Depends, Response, status
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .containers import Container from .containers import Container
from .repositories import NotFoundError
from .services import UserService from .services import UserService
from .repositories import NotFoundError
router = APIRouter() router = APIRouter()
@ -16,7 +13,7 @@ router = APIRouter()
@router.get("/users") @router.get("/users")
@inject @inject
def get_list( def get_list(
user_service: Annotated[UserService, Depends(Provide[Container.user_service])], user_service: UserService = Depends(Provide[Container.user_service]),
): ):
return user_service.get_users() return user_service.get_users()
@ -24,8 +21,8 @@ def get_list(
@router.get("/users/{user_id}") @router.get("/users/{user_id}")
@inject @inject
def get_by_id( def get_by_id(
user_id: int, user_id: int,
user_service: Annotated[UserService, Depends(Provide[Container.user_service])], user_service: UserService = Depends(Provide[Container.user_service]),
): ):
try: try:
return user_service.get_user_by_id(user_id) return user_service.get_user_by_id(user_id)
@ -36,7 +33,7 @@ def get_by_id(
@router.post("/users", status_code=status.HTTP_201_CREATED) @router.post("/users", status_code=status.HTTP_201_CREATED)
@inject @inject
def add( def add(
user_service: Annotated[UserService, Depends(Provide[Container.user_service])], user_service: UserService = Depends(Provide[Container.user_service]),
): ):
return user_service.create_user() return user_service.create_user()
@ -44,9 +41,9 @@ def add(
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
@inject @inject
def remove( def remove(
user_id: int, user_id: int,
user_service: Annotated[UserService, Depends(Provide[Container.user_service])], user_service: UserService = Depends(Provide[Container.user_service]),
) -> Response: ):
try: try:
user_service.delete_user_by_id(user_id) user_service.delete_user_by_id(user_id)
except NotFoundError: except NotFoundError:

View File

@ -101,9 +101,9 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0, aiohttp-1.0.5 plugins: asyncio-0.16.0, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None collected 3 items
giphynavigator/tests.py ... [100%] giphynavigator/tests.py ... [100%]

View File

@ -1,14 +1,13 @@
"""Endpoints module.""" """Endpoints module."""
from typing import Annotated, List from typing import Optional, List
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from pydantic import BaseModel from pydantic import BaseModel
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .containers import Container
from .services import SearchService from .services import SearchService
from .containers import Container
class Gif(BaseModel): class Gif(BaseModel):
@ -27,15 +26,11 @@ router = APIRouter()
@router.get("/", response_model=Response) @router.get("/", response_model=Response)
@inject @inject
async def index( async def index(
default_query: Annotated[str, Depends(Provide[Container.config.default.query])], query: Optional[str] = None,
default_limit: Annotated[ limit: Optional[str] = None,
int, Depends(Provide[Container.config.default.limit.as_int()]) default_query: str = Depends(Provide[Container.config.default.query]),
], default_limit: int = Depends(Provide[Container.config.default.limit.as_int()]),
search_service: Annotated[ search_service: SearchService = Depends(Provide[Container.search_service]),
SearchService, Depends(Provide[Container.search_service])
],
query: str | None = None,
limit: int | None = None,
): ):
query = query or default_query query = query or default_query
limit = limit or default_limit limit = limit or default_limit

View File

@ -3,19 +3,15 @@
from unittest import mock from unittest import mock
import pytest import pytest
import pytest_asyncio from httpx import AsyncClient
from httpx import ASGITransport, AsyncClient
from giphynavigator.application import app from giphynavigator.application import app
from giphynavigator.giphy import GiphyClient from giphynavigator.giphy import GiphyClient
@pytest_asyncio.fixture @pytest.fixture
async def client(): async def client():
async with AsyncClient( async with AsyncClient(app=app, base_url="http://test") as client:
transport=ASGITransport(app=app),
base_url="http://test",
) as client:
yield client yield client

View File

@ -81,9 +81,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, flask-1.3.0 plugins: cov-3.0.0, flask-1.2.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
githubnavigator/tests.py .. [100%] githubnavigator/tests.py .. [100%]

View File

@ -1,7 +1,7 @@
"""Application module.""" """Application module."""
from flask import Flask from flask import Flask
from flask_bootstrap import Bootstrap4 from flask_bootstrap import Bootstrap
from .containers import Container from .containers import Container
from .blueprints import example from .blueprints import example
@ -15,7 +15,7 @@ def create_app() -> Flask:
app.container = container app.container = container
app.register_blueprint(example.blueprint) app.register_blueprint(example.blueprint)
bootstrap = Bootstrap4() bootstrap = Bootstrap()
bootstrap.init_app(app) bootstrap.init_app(app)
return app return app

View File

@ -81,9 +81,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, flask-1.3.0 plugins: cov-3.0.0, flask-1.2.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
githubnavigator/tests.py .. [100%] githubnavigator/tests.py .. [100%]

View File

@ -1,7 +1,7 @@
"""Application module.""" """Application module."""
from flask import Flask from flask import Flask
from flask_bootstrap import Bootstrap4 from flask_bootstrap import Bootstrap
from .containers import Container from .containers import Container
from . import views from . import views
@ -15,7 +15,7 @@ def create_app() -> Flask:
app.container = container app.container = container
app.add_url_rule("/", "index", views.index) app.add_url_rule("/", "index", views.index)
bootstrap = Bootstrap4() bootstrap = Bootstrap()
bootstrap.init_app(app) bootstrap.init_app(app)
return app return app

View File

@ -58,8 +58,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0 plugins: cov-3.0.0
collected 2 items collected 2 items
movies/tests.py .. [100%] movies/tests.py .. [100%]

View File

@ -18,9 +18,10 @@ SQLITE_FILE = DIR / "movies.db"
def create_csv(movies_data, path): def create_csv(movies_data, path):
with open(path, "w", newline="") as opened_file: with open(path, "w") as opened_file:
writer = csv.writer(opened_file) writer = csv.writer(opened_file)
writer.writerows(movies_data) for row in movies_data:
writer.writerow(row)
def create_sqlite(movies_data, path): def create_sqlite(movies_data, path):

View File

@ -29,7 +29,7 @@ class CsvMovieFinder(MovieFinder):
super().__init__(movie_factory) super().__init__(movie_factory)
def find_all(self) -> List[Movie]: def find_all(self) -> List[Movie]:
with open(self._csv_file_path, newline="") as csv_file: with open(self._csv_file_path) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=self._delimiter) csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
return [self._movie_factory(*row) for row in csv_reader] return [self._movie_factory(*row) for row in csv_reader]

View File

@ -26,18 +26,13 @@ def container():
return container return container
@pytest.fixture def test_movies_directed_by(container):
def finder_mock(container):
finder_mock = mock.Mock() finder_mock = mock.Mock()
finder_mock.find_all.return_value = [ finder_mock.find_all.return_value = [
container.movie("The 33", 2015, "Patricia Riggen"), container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"), container.movie("The Jungle Book", 2016, "Jon Favreau"),
] ]
return finder_mock
def test_movies_directed_by(container, finder_mock):
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_directed_by("Jon Favreau") movies = lister.movies_directed_by("Jon Favreau")
@ -46,7 +41,13 @@ def test_movies_directed_by(container, finder_mock):
assert movies[0].title == "The Jungle Book" assert movies[0].title == "The Jungle Book"
def test_movies_released_in(container, finder_mock): def test_movies_released_in(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"),
]
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_released_in(2015) movies = lister.movies_released_in(2015)

View File

@ -27,7 +27,7 @@ To run the application do:
.. code-block:: bash .. code-block:: bash
export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0 export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0
sanic giphynavigator.application:create_app python -m giphynavigator
The output should be something like: The output should be something like:
@ -98,9 +98,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0 plugins: sanic-1.9.1, anyio-3.3.4, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 3 items collected 3 items
giphynavigator/tests.py ... [100%] giphynavigator/tests.py ... [100%]

View File

@ -8,8 +8,6 @@ from sanic import Sanic
from giphynavigator.application import create_app from giphynavigator.application import create_app
from giphynavigator.giphy import GiphyClient from giphynavigator.giphy import GiphyClient
pytestmark = pytest.mark.asyncio
@pytest.fixture @pytest.fixture
def app(): def app():
@ -19,7 +17,12 @@ def app():
app.ctx.container.unwire() app.ctx.container.unwire()
async def test_index(app): @pytest.fixture
def test_client(loop, app, sanic_client):
return loop.run_until_complete(sanic_client(app))
async def test_index(app, test_client):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient) giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [ "data": [
@ -29,7 +32,7 @@ async def test_index(app):
} }
with app.ctx.container.giphy_client.override(giphy_client_mock): with app.ctx.container.giphy_client.override(giphy_client_mock):
_, response = await app.asgi_client.get( response = await test_client.get(
"/", "/",
params={ params={
"query": "test", "query": "test",
@ -38,7 +41,7 @@ async def test_index(app):
) )
assert response.status_code == 200 assert response.status_code == 200
data = response.json data = response.json()
assert data == { assert data == {
"query": "test", "query": "test",
"limit": 10, "limit": 10,
@ -49,30 +52,30 @@ async def test_index(app):
} }
async def test_index_no_data(app): async def test_index_no_data(app, test_client):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient) giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [], "data": [],
} }
with app.ctx.container.giphy_client.override(giphy_client_mock): with app.ctx.container.giphy_client.override(giphy_client_mock):
_, response = await app.asgi_client.get("/") response = await test_client.get("/")
assert response.status_code == 200 assert response.status_code == 200
data = response.json data = response.json()
assert data["gifs"] == [] assert data["gifs"] == []
async def test_index_default_params(app): async def test_index_default_params(app, test_client):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient) giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [], "data": [],
} }
with app.ctx.container.giphy_client.override(giphy_client_mock): with app.ctx.container.giphy_client.override(giphy_client_mock):
_, response = await app.asgi_client.get("/") response = await test_client.get("/")
assert response.status_code == 200 assert response.status_code == 200
data = response.json data = response.json()
assert data["query"] == app.ctx.container.config.default.query() assert data["query"] == app.ctx.container.config.default.query()
assert data["limit"] == app.ctx.container.config.default.limit() assert data["limit"] == app.ctx.container.config.default.limit()

View File

@ -1,6 +1,6 @@
dependency-injector dependency-injector
sanic sanic<=21.6
sanic-testing
aiohttp aiohttp
pyyaml pyyaml
pytest-sanic
pytest-cov pytest-cov

View File

@ -1,39 +0,0 @@
Integration With Starlette-based Frameworks
===========================================
This is a `Starlette <https://www.starlette.io/>`_ +
`Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application
utilizing `lifespan API <https://www.starlette.io/lifespan/>`_.
.. note::
Pretty much `any framework built on top of Starlette <https://www.starlette.io/third-party-packages/#frameworks>`_
supports this feature (`FastAPI <https://fastapi.tiangolo.com/advanced/events/#lifespan>`_,
`Xpresso <https://xpresso-api.dev/latest/tutorial/lifespan/>`_, etc...).
Run
---
Create virtual environment:
.. code-block:: bash
python -m venv env
. env/bin/activate
Install requirements:
.. code-block:: bash
pip install -r requirements.txt
To run the application do:
.. code-block:: bash
python example.py
# or (logging won't be configured):
uvicorn --factory example:container.app
After that visit http://127.0.0.1:8000/ in your browser or use CLI command (``curl``, ``httpie``,
etc).

View File

@ -1,59 +0,0 @@
#!/usr/bin/env python
from logging import basicConfig, getLogger
from dependency_injector.containers import DeclarativeContainer
from dependency_injector.ext.starlette import Lifespan
from dependency_injector.providers import Factory, Resource, Self, Singleton
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
count = 0
def init():
log = getLogger(__name__)
log.info("Inittializing resources")
yield
log.info("Cleaning up resources")
async def homepage(request: Request) -> JSONResponse:
global count
response = JSONResponse({"hello": "world", "count": count})
count += 1
return response
class Container(DeclarativeContainer):
__self__ = Self()
lifespan = Singleton(Lifespan, __self__)
logging = Resource(
basicConfig,
level="DEBUG",
datefmt="%Y-%m-%d %H:%M",
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
init = Resource(init)
app = Factory(
Starlette,
debug=True,
lifespan=lifespan,
routes=[Route("/", homepage)],
)
container = Container()
if __name__ == "__main__":
import uvicorn
uvicorn.run(
container.app,
factory=True,
# NOTE: `None` prevents uvicorn from configuring logging, which is
# impossible via CLI
log_config=None,
)

View File

@ -1,3 +0,0 @@
dependency-injector
starlette
uvicorn

View File

@ -1,6 +0,0 @@
{
"aws": {
"access_key_id": "KEY",
"secret_access_key": "SECRET"
}
}

View File

@ -1,27 +0,0 @@
"""`Configuration` provider values loading example."""
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
if __name__ == "__main__":
container = Container()
container.config.from_json("./config.json")
assert container.config() == {
"aws": {
"access_key_id": "KEY",
"secret_access_key": "SECRET",
},
}
assert container.config.aws() == {
"access_key_id": "KEY",
"secret_access_key": "SECRET",
}
assert container.config.aws.access_key_id() == "KEY"
assert container.config.aws.secret_access_key() == "SECRET"

View File

@ -1,25 +0,0 @@
"""`Configuration` provider values loading example."""
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
config = providers.Configuration(json_files=["./config.json"])
if __name__ == "__main__":
container = Container()
assert container.config() == {
"aws": {
"access_key_id": "KEY",
"secret_access_key": "SECRET",
},
}
assert container.config.aws() == {
"access_key_id": "KEY",
"secret_access_key": "SECRET",
}
assert container.config.aws.access_key_id() == "KEY"
assert container.config.aws.secret_access_key() == "SECRET"

View File

@ -3,7 +3,7 @@
import os import os
from dependency_injector import containers, providers from dependency_injector import containers, providers
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import BaseSettings, Field
# Emulate environment variables # Emulate environment variables
os.environ["AWS_ACCESS_KEY_ID"] = "KEY" os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
@ -11,16 +11,15 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
class AwsSettings(BaseSettings): class AwsSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="aws_")
access_key_id: str access_key_id: str = Field(env="aws_access_key_id")
secret_access_key: str secret_access_key: str = Field(env="aws_secret_access_key")
class Settings(BaseSettings): class Settings(BaseSettings):
aws: AwsSettings = AwsSettings() aws: AwsSettings = AwsSettings()
optional: str = "default_value" optional: str = Field(default="default_value")
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):

View File

@ -3,7 +3,7 @@
import os import os
from dependency_injector import containers, providers from dependency_injector import containers, providers
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import BaseSettings, Field
# Emulate environment variables # Emulate environment variables
os.environ["AWS_ACCESS_KEY_ID"] = "KEY" os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
@ -11,16 +11,15 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
class AwsSettings(BaseSettings): class AwsSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="aws_")
access_key_id: str access_key_id: str = Field(env="aws_access_key_id")
secret_access_key: str secret_access_key: str = Field(env="aws_secret_access_key")
class Settings(BaseSettings): class Settings(BaseSettings):
aws: AwsSettings = AwsSettings() aws: AwsSettings = AwsSettings()
optional: str = "default_value" optional: str = Field(default="default_value")
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):

View File

@ -13,12 +13,12 @@ class Container(containers.DeclarativeContainer):
if __name__ == "__main__": if __name__ == "__main__":
container = Container() container = Container()
container.config.option1.from_value(date(2022, 3, 13)) container.config.option1.from_value(date(2021, 6, 13))
container.config.option2.from_value(date(2022, 3, 14)) container.config.option2.from_value(date(2021, 6, 14))
assert container.config() == { assert container.config() == {
"option1": date(2022, 3, 13), "option1": date(2021, 6, 13),
"option2": date(2022, 3, 14), "option2": date(2021, 6, 14),
} }
assert container.config.option1() == date(2022, 3, 13) assert container.config.option1() == date(2021, 6, 13)
assert container.config.option2() == date(2022, 3, 14) assert container.config.option2() == date(2021, 6, 14)

View File

@ -3,12 +3,10 @@
import sys import sys
import logging import logging
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager
from dependency_injector import containers, providers from dependency_injector import containers, providers
@contextmanager
def init_thread_pool(max_workers: int): def init_thread_pool(max_workers: int):
thread_pool = ThreadPoolExecutor(max_workers=max_workers) thread_pool = ThreadPoolExecutor(max_workers=max_workers)
yield thread_pool yield thread_pool

View File

@ -2,10 +2,10 @@
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject from dependency_injector.wiring import Provide, inject
from typing import Annotated
class Service: ... class Service:
...
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -13,16 +13,9 @@ class Container(containers.DeclarativeContainer):
service = providers.Factory(Service) service = providers.Factory(Service)
# You can place marker on parameter default value
@inject @inject
def main(service: Service = Provide[Container.service]) -> None: ... def main(service: Service = Provide[Container.service]) -> None:
...
# Also, you can place marker with typing.Annotated
@inject
def main_with_annotated(
service: Annotated[Service, Provide[Container.service]]
) -> None: ...
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,31 +0,0 @@
"""Wiring attribute example with Annotated."""
from typing import Annotated
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide
class Service:
...
class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
service: Annotated[Service, Provide[Container.service]]
class Main:
service: Annotated[Service, Provide[Container.service]]
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__])
assert isinstance(service, Service)
assert isinstance(Main.service, Service)

View File

@ -1,122 +0,0 @@
[build-system]
requires = ["setuptools", "Cython>=3.1.1"]
build-backend = "setuptools.build_meta"
[project]
name = "dependency-injector"
authors = [
{name = "Roman Mogylatov", email = "rmogilatov@gmail.com"},
]
maintainers = [
{name = "Roman Mogylatov", email = "rmogilatov@gmail.com"},
]
description = "Dependency injection framework for Python"
readme = {file = "README.rst", content-type = "text/x-rst"}
license = {file = "LICENSE.rst", content-type = "text/x-rst"}
requires-python = ">=3.8"
keywords = [
"Dependency injection",
"DI",
"Inversion of Control",
"IoC",
"Factory",
"Singleton",
"Design patterns",
"Flask",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Framework :: AsyncIO",
"Framework :: Bottle",
"Framework :: Django",
"Framework :: Flask",
"Framework :: Pylons",
"Framework :: Pyramid",
"Framework :: Pytest",
"Framework :: TurboGears",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dynamic = ["version"]
dependencies = [
# typing.Annotated since v3.9
# typing.Self since v3.11
"typing-extensions; python_version<'3.11'",
]
[project.optional-dependencies]
yaml = ["pyyaml"]
pydantic = ["pydantic"]
pydantic2 = ["pydantic-settings"]
flask = ["flask"]
aiohttp = ["aiohttp"]
[project.urls]
Homepage = "https://github.com/ets-labs/python-dependency-injector"
Documentation = "https://python-dependency-injector.ets-labs.org/"
Download = "https://pypi.python.org/pypi/dependency_injector"
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
dependency_injector = ["*.pxd", "*.pyi", "py.typed"]
[tool.setuptools.dynamic]
version = {attr = "dependency_injector.__version__"}
[tool.coverage.run]
branch = false
relative_files = true
source_pkgs = ["dependency_injector"]
plugins = ["Cython.Coverage"]
[tool.coverage.html]
directory = "reports/unittests/"
[tool.coverage.report]
show_missing = true
[tool.isort]
profile = "black"
combine_as_imports = true
[tool.pylint.main]
ignore = ["tests"]
[tool.pylint.design]
min-public-methods = 0
max-public-methods = 30
[tool.pytest.ini_options]
testpaths = ["tests/unit/"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
markers = [
"pydantic: Tests with Pydantic as a dependency",
]
filterwarnings = [
"ignore::dependency_injector.wiring.DIWiringWarning",
"ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
"ignore:Module \"dependency_injector.ext.flask\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
"ignore:Please use \\`.*?\\` from the \\`scipy.*?\\`(.*?)namespace is deprecated\\.:DeprecationWarning",
"ignore:Please import \\`.*?\\` from the \\`scipy(.*?)\\` namespace(.*):DeprecationWarning",
"ignore:\\`scipy(.*?)\\` is deprecated(.*):DeprecationWarning",
]

View File

@ -1,11 +1,9 @@
cython==3.1.1 cython==0.29.24
setuptools
pytest pytest
pytest-asyncio pytest-asyncio
tox tox
coverage coverage
flake8 flake8
flake8-pyproject
pydocstyle pydocstyle
sphinx_autobuild sphinx_autobuild
pip pip
@ -14,12 +12,9 @@ pyyaml
httpx httpx
fastapi fastapi
pydantic pydantic
pydantic-settings
numpy numpy
scipy scipy
boto3 boto3
mypy_boto3_s3 mypy_boto3_s3
typing_extensions
fast-depends
-r requirements-ext.txt -r requirements-ext.txt

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