Compare commits

..

138 Commits

Author SHA1 Message Date
ZipFile
2c0aede4aa Merge branch 'release/4.48.1' 2025-06-20 10:49:54 +00:00
ZipFile
84a14f2ca7 Update changelog 2025-06-20 10:03:00 +00:00
ZipFile
0c58064a36 Make wiring inspect exclsuions extensible 2025-06-20 10:03:00 +00:00
ZipFile
eb74b1e9d0 Minor improvements for _cwiring.DependencyResolver code generation
* Remove KWPair
* Avoid type checks around _is_injectable
2025-06-20 10:03:00 +00:00
ZipFile
be7d25518d Add typing-extensions as a dependency for older Python versions 2025-06-20 10:03:00 +00:00
ZipFile
04b5907f21 Add warning on extra @inject 2025-06-20 10:03:00 +00:00
ZipFile
e6cc12762f Add support for resource_type in Lifespans 2025-06-18 21:58:00 +00:00
ZipFile
bf2ddbce32 Upgrade cibuildwheel 2025-06-16 10:51:39 +00:00
ZipFile
9d6994391f Merge branch 'release/4.48.0' 2025-06-16 09:03:49 +00:00
ZipFile
dd84a1b5d6 Bump version 2025-06-16 08:53:36 +00:00
ZipFile
31a98f7731 Update changelog 2025-06-16 08:51:10 +00:00
ZipFile
b261251b34 Fix Sphinx warning 2025-06-16 08:50:00 +00:00
Aran Moncusí Ramírez
4bfe64563e
Add resource type parameter to init and shutdown resources using specialized providers (#858) 2025-06-16 11:34:02 +03:00
AndrianEquestrian
b411807572
Add support for Fast Stream Depends (#898) 2025-06-16 10:37:31 +03:00
ZipFile
f2da51e0d4 Use typing_extensions.Self as fallback (fixes #902) 2025-06-05 16:26:40 +00:00
ZipFile
2293251986 Add docs for ASGI Lifespan support 2025-06-03 20:43:06 +00:00
ZipFile
1b4b3d349f Fix some more Sphinx warnings 2025-06-03 20:33:13 +00:00
ZipFile
d8e49f7dd5
Add support for async generator injections (#900) 2025-06-03 21:45:43 +03:00
ZipFile
c1f14a876a Expose null awaitables 2025-06-02 22:46:57 +00:00
ZipFile
c97a0cc515 Fix mypy warnings in dependency_injector.ext 2025-06-01 18:57:47 +00:00
ZipFile
0ada62acbf Add .editorconfig 2025-06-01 18:50:07 +00:00
ZipFile
67827a36d1 Fix mypy warnigns in containers.pyi 2025-06-01 18:46:30 +00:00
ZipFile
ceed6a8222 Add combine_as_imports = true isort option 2025-06-01 18:45:47 +00:00
ZipFile
6766ef3eba Remove __init__.pyi 2025-06-01 18:45:12 +00:00
ZipFile
a8914e54e0 Fix Sphinx warnings 2025-06-01 18:08:37 +00:00
ZipFile
cdd9ce5048 Add doc section on wire() caching 2025-06-01 17:51:04 +00:00
ZipFile
51c7db771d Fix csv newline handling in movie-lister example 2025-06-01 17:35:32 +00:00
ZipFile
a322584308 Add context manager support to Resource provider 2025-06-01 15:48:57 +00:00
ZipFile
4b3476cb48 Use cache in _fetch_reference_injections() 2025-05-31 12:31:54 +00:00
ZipFile
8460465b5e Merge branch 'release/4.47.1' 2025-05-30 19:52:18 +00:00
ZipFile
16f444b230 Bump version 2025-05-30 19:50:21 +00:00
ZipFile
1ae96e3eeb Use windows-2022 GHA image for building wheels 2025-05-30 19:44:47 +00:00
ZipFile
7fcf1ac7ad Make mypy --strict tests/typing passable 2025-05-30 19:31:44 +00:00
ZipFile
1271d0fa79 Add type info for _cwiring module 2025-05-30 19:29:11 +00:00
ZipFile
b9df88eea7 Fix typing for wiring marker 2025-05-30 19:28:49 +00:00
ZipFile
99489afa3f Strip debug symbols when building wheels 2025-05-28 20:35:04 +00:00
ZipFile
193249f7ec Merge branch 'release/4.47.0' 2025-05-28 19:09:56 +00:00
ZipFile
01349c43e1 Bump version 2025-05-28 19:05:05 +00:00
ZipFile
41ed07a210 Update changelog 2025-05-28 19:03:52 +00:00
ZipFile
8be79126ad Enable ABI3 for regular tests 2025-05-28 16:51:39 +00:00
ZipFile
dfee54932b Migrate to OIDC publishing 2025-05-28 13:50:02 +00:00
ZipFile
a61749c68d Enable ABI3 builds 2025-05-28 13:50:02 +00:00
ZipFile
14be69371b Limit ABI3 builds to CPython only 2025-05-28 13:50:02 +00:00
ZipFile
cfeb018ca7 Fix pytest-asyncio warning 2025-05-28 13:50:02 +00:00
ZipFile
183b2ae7ff Require Cython>=3.1.1 2025-05-28 13:50:02 +00:00
ZipFile
561ff46658 Add wheelhouse to .gitignore 2025-05-28 13:50:02 +00:00
ZipFile
49cc8ed827 Fix PyPy test runs in tox 2025-05-28 13:50:02 +00:00
ZipFile
383e95faed Fix file inclusion warnings from MANIFEST.in 2025-05-28 13:50:02 +00:00
Roman Mogylatov
e7e64f6ae0
Update coding-guide.mdc 2025-05-22 18:29:37 -04:00
Roman Mogylatov
f50cc95405 Add Cursor rules 2025-05-21 16:19:08 -04:00
Roman Mogylatov
8814db3fb3
Fix annotated attribute injection (#889)
* Add example for Annotated attribute injection for module/class attributes

* Fix attribute injection with Annotated types

* Add unit tests for Annotated attribute and argument injection in wiring

* Add .cursor to .gitignore

* Style: add blank lines between class definitions and attributes in annotated attribute example

* Docs: clarify and format module/class attribute injection for classic and Annotated forms

* Changelog: add note and discussion link for Annotated attribute injection support

* Fix nls

* Fix CI checks and Python 3.8 tests

* Fix PR issues

* Fix Python 3.8 tests

* Fix flake8 issues

* Fix: robust Annotated detection for wiring across Python versions

* Refactor: extract annotation retrieval and improve typing for Python 3.9 compatibility

* Update src/dependency_injector/wiring.py

Co-authored-by: ZipFile <zipfile.d@protonmail.com>

---------

Co-authored-by: ZipFile <zipfile.d@protonmail.com>
2025-05-21 16:13:37 -04:00
ZipFile
8bf9ed04c8
Update Cython to v3.1 (#887) 2025-05-18 13:55:06 +03:00
ZipFile
dbf86e4eb4
Do not override methods without patching (#886) 2025-05-18 12:17:54 +03:00
ZipFile
9a08bfcede
Drop Python 3.7 support (#885) 2025-05-18 12:06:33 +03:00
ZipFile
4ae79ac21f
Remove unused root property from ConfigurationOption (#875)
fixes #874
2025-04-07 14:57:45 +03:00
ZipFile
35bfafdfe2
Move pytest config to pyproject.toml (#876)
* Move pytest config to pyproject.toml

* Fix pytest warning
2025-04-07 14:57:13 +03:00
ZipFile
6685c5a141 Fix infinite loop with Closing+ConfigurationOption 2025-03-10 20:35:37 +00:00
ZipFile
57123cebaa Merge branch 'master' into develop 2025-03-02 12:37:21 +00:00
ZipFile
6e4794bab1
Remove code for EOL Python versions (#864) 2025-03-02 14:33:31 +02:00
ZipFile
f3b3b1baa4 Merge branch 'release/4.46.0' into master 2025-02-25 15:14:15 +00:00
ZipFile
9b66d4bf16 Bump version to v4.46.0 2025-02-23 17:22:13 +00:00
ZipFile
7d4ebecd19
Add option to disable env var interpolation in configs (#861) 2025-02-23 19:01:01 +02:00
ZipFile
09efbffab1
Fix Closing dependency resolution (#852)
Co-authored-by: federinik <federico.tomasi@outlook.com>
Co-authored-by: jazzthief <mynameisyegor@gmail.com>
2025-02-23 18:31:34 +02:00
ZipFile
8b625d81ad
Use Annotated for DI in FastAPI examples (#853) 2025-02-23 18:21:31 +02:00
ZipFile
23acf01c15
Add support for inspect.iscoroutinefunction() in Coroutine provider (#830) 2025-02-23 18:20:38 +02:00
Martin Lafrance
0d6fdb5b78
Fix broken wiring of sync inject-decorated methods (#673)
Co-authored-by: Martin Lafrance <mlafrance@cae.com>
Co-authored-by: ZipFile <zipfile.d@protonmail.com>
2025-02-23 18:17:45 +02:00
Taein Min
2330122de6
Add support for typing.Annotated (#721) 2025-01-20 17:37:28 +02:00
ZipFile
29ae3e1337 Make flake8 config black-compatible 2025-01-18 17:39:07 +00:00
ZipFile
50643e0dfb Run black 2025-01-18 17:02:55 +00:00
ZipFile
3893e1df81 Use native GHA ubuntu-24.04-arm image for building wheels 2025-01-16 19:15:49 +00:00
ZipFile
0fd35baee6 Use ubuntu-24.04 GHA image 2025-01-16 19:13:11 +00:00
Ilya Kazakov
3df95847d5
[movie-lister] Added test fixture and updated documentation (#747)
* test: add fixture for finder mock

* docs: update tests code example, emphasize-lines & test coverage report results
2025-01-12 15:46:29 +02:00
ZipFile
6d9d34c0f6 Add test case for Provider.provider type propagation 2025-01-12 12:18:21 +00:00
Philip Bjorge
de50666a13
fix: type provider (#744) 2025-01-12 14:14:12 +02:00
ZipFile
ccbd5bbb80 Migrate CI pipeline to actions/upload-artifact@v4 2025-01-08 13:07:04 +00:00
Philip Bjorge
00326e9a22
fix: type propogation through provided (#733)
Co-authored-by: Gonzalo Martinez <gonzarmv@gmail.com>
2025-01-08 13:31:00 +02:00
Roman Mogylatov
46646b1acf Merge branch 'release/4.45.0' into master 2025-01-05 15:20:12 -05:00
Roman Mogylatov
9f38db6ef3 Bump version to 4.45.0 2025-01-05 15:19:57 -05:00
Roman Mogylatov
9f4e2839d2
Remove unused imports from the starlette extension (#846) 2025-01-05 14:57:55 -05:00
ZipFile
41e18dfa90
Add Starlette lifespan handler implementation (#683) 2025-01-05 14:39:26 -05:00
František Trebuňa
f9db578c59
🎨 Raise exception instead of hiding it in finally (#845) 2025-01-05 14:33:09 -05:00
ZipFile
d82d9fb822
Improve debugability of deepcopy errors (#839) 2025-01-01 21:22:29 +02:00
ZipFile
3ba4704bc1 Remove six 2024-12-14 13:24:28 +00:00
JC (Jonathan Chen)
aa56b70dc8
docs: fix grammar (#709) 2024-12-09 10:54:30 +02:00
ZipFile
7f586246b4
Update examples (#838) 2024-12-08 18:53:29 +02:00
ZipFile
87741edb53
Upgrade testing deps (#837) 2024-12-08 18:53:08 +02:00
Roman Mogylatov
be7abb3ec7 Merge branch 'release/4.44.0' into master 2024-12-07 13:44:32 -05:00
ZipFile
15400dea7d
Fix sdist build for publishing (#836) 2024-12-07 13:42:27 -05:00
Roman Mogylatov
704e36a642 Merge branch 'release/4.44.0' into master 2024-12-07 11:52:05 -05:00
Roman Mogylatov
83d71acb70 Bump version to 4.44.0 2024-12-07 11:51:44 -05:00
ZipFile
c61fc16b8d
Yet another Pydantic 2 support (#832)
* Add support for Pydantic v2 settings

* Configure pipeline to run tests against different pydantic versions

* Update Pydantic docs and examples for v2

* Fix compatibility with httpx v0.27.0
2024-12-07 11:38:08 -05:00
Roman Mogylatov
cab75cb9c7 Update changelog 2024-11-10 00:05:25 -05:00
ZipFile
494c457643
PEP-517 (#829)
* Convert to PEP-517 project

* Move pylint and coverage configs to pyproject.toml

* Remove autogenerated C files
2024-11-10 00:01:30 -05:00
Roman Mogylatov
abf2a2577c Merge branch 'release/4.43.0' into master 2024-11-04 00:03:25 -05:00
Roman Mogylatov
3777a947ea Update version to 4.43.0 2024-11-04 00:02:52 -05:00
Roman Mogylatov
c92129dcb0
Add support for Python 3.13 (#828)
* Update tests pipeline and setup.py

* Update tox coverage command

* Add setuptools to the dev requirements file

* Enforce coverage version in tox

* Leave coveralls CI/CD job on Python 3.12 because coveralls 4.0.1 doesn't support Python 3.13

* Update changelog and publishing jobs
2024-11-04 00:01:28 -05:00
Roman Mogylatov
37486900cd Add ZipFile to the list of contributors and update changelog 2024-11-03 21:01:45 -05:00
Roman Mogylatov
9071583981 Pin Cython version 2024-11-03 20:55:53 -05:00
ZipFile
595daebd3a
Migrate to Cython3 (#813)
* Fix asyncio tests

* Convert class-private attributes to just private

* Upgrade to Cython 3

* Regenerate C files

* Fix tox coverage report
2024-11-03 20:48:40 -05:00
Roman Mogylatov
13a7ef609b Merge branch 'release/4.42.0' into master 2024-09-09 22:24:12 -04:00
Roman Mogylatov
7a88a8ee8d Update version 2024-09-09 22:23:27 -04:00
Roman Mogylatov
938091b6ea Add Github Sponsors button 2024-09-09 22:23:19 -04:00
Roman Mogylatov
4bda5105c2 Remove obsolete disqus javascript file 2024-09-09 22:22:09 -04:00
Roman Mogylatov
46034cbeb1 Update the copuright in the docs 2024-09-09 21:53:37 -04:00
Roman Mogylatov
39ac098ca2 Fix the Disqus comment widget 2024-09-09 21:52:11 -04:00
Roman Mogylatov
f54604fc14 Fix the bug in the docs step of the publishing job 2024-08-08 21:29:02 -04:00
Roman Mogylatov
2c998b8448 Merge branch 'release/4.42.0b1' into master 2024-08-07 22:11:07 -04:00
Roman Mogylatov
5697f1d5d8 Update macos version in the publishing job 2024-08-07 22:10:03 -04:00
Roman Mogylatov
086d82f13d Merge branch 'release/4.42.0b1' into master 2024-08-07 21:22:25 -04:00
Roman Mogylatov
3375436eb3 Remove Python 3.13 builds from the publishing job 2024-08-07 21:21:36 -04:00
Roman Mogylatov
fec2b08210 Merge branch 'release/4.42.0b1' into master 2024-08-07 21:04:56 -04:00
Roman Mogylatov
8a44027f3d Update cibuildwheel to version 2.20.0 2024-08-07 21:04:37 -04:00
Roman Mogylatov
f56453f59f Merge branch 'release/4.42.0b1' into master 2024-08-07 20:51:40 -04:00
Roman Mogylatov
1b9e079524 Add explicit setuptools installation to the publishing job 2024-08-07 20:48:41 -04:00
Roman Mogylatov
b1a3a69428 Merge branch 'release/4.42.0b1' into master 2024-08-06 22:52:17 -04:00
Roman Mogylatov
a8b54423dc Update version to 4.42.0b1 2024-08-06 22:51:59 -04:00
Roman Mogylatov
3e56fef461 Update version to 4.42.0b 2024-08-06 22:45:40 -04:00
Roman Mogylatov
5d1e5ee485 Update the year in the licensing file 2024-08-06 22:45:21 -04:00
Roman Mogylatov
f7c6cb2647 Add Anton Petrov to CONTRIBUTORS.rst 2024-08-06 22:44:57 -04:00
Roman Mogylatov
a5166bf591
Add Python 3.12 Support (#752) (#765)
* Add Python 3.12 Support (#752)

* Ignore .vscode

* Python 3.12 Support

* Change base python to 3.12 and pin pydantic to V1

* all tests passed

* ci: change default python to 3.12

* remove legacy python versions

* annotate pydantic models for tests

* Update publishing pipeline to use Python 3.12

* Test environment updates

* Update Cython to the latest prior 3.0 version and remove tracing from CI/CD

* Give up using editable tox installation in the coverage job

* Add mypy test fixes

* Remove tracing from the coverage job

* Fix typing test

* Remove PyPy 2.7

* Fix typing test

* Fix the typing issue with pydantic

* Remove pypy 3.9

* Fix the typing issue with mypy

* Update pydantic version to the latest from 1.x

* Update scipy deprecation warning filter

* Fix the tox job running coveralls

* Update changelog

---------

Co-authored-by: Anton Petrov <anton.a.petrov@gmail.com>
2024-08-06 22:41:24 -04:00
Roman Mogylatov
98d5867743 Add a link to my profile (#806) 2024-08-06 21:28:50 -04:00
Roman Mogylatov
68da747ce0
Add a link to my profile (#806) 2024-08-06 21:27:23 -04:00
Roman Mogylatov
cc2304e46e Merge branch 'release/4.41.0' into master 2022-12-18 22:14:24 -05:00
Roman Mogylatov
4bfdf89142 Bump version 2022-12-18 22:14:00 -05:00
Roman Mogylatov
659d242503 Update builds badge 2022-12-18 22:12:38 -05:00
Roman Mogylatov
6b13b6dbaf Update line length to 120 2022-12-18 22:04:03 -05:00
Roman Mogylatov
d3320f5f06 Fix typing in wiring 2022-12-18 22:02:11 -05:00
Roman Mogylatov
31c1f7c2d6 Update setup.py 2022-12-18 21:59:31 -05:00
Roman Mogylatov
d0c8f328b3 Update changelog 2022-12-18 21:52:32 -05:00
Jamie Stumme
3b76a0d091
Allow Closing to detect dependent resources (#636) 2022-12-18 21:49:23 -05:00
Roman Mogylatov
a79ea1790c Update custom provider docs formatting 2022-12-18 21:43:02 -05:00
Kirill Shershen
781d3b9c4c
fixed bug in windows build with default charset (#635)
Co-authored-by: kirill-shershen <kirill.shershen@alao.ch>
2022-12-18 21:42:23 -05:00
thatguysimon
6f491a6cae
Explicitly mention the required usage of "memo" (#598) 2022-12-18 21:38:12 -05:00
Roman Mogylatov
88a2b96102 Update FastAPI + Redis example 2022-12-18 21:35:43 -05:00
Yan
f0d9eda566
Update aioredis to 2.0.1 (#613)
* Update aioredis to 2.0.1

* Rearranged aioredis.from_url parameters
2022-12-18 21:27:36 -05:00
Roman Mogylatov
55f81bd754 Update changelog 2022-12-18 21:25:16 -05:00
Eugene Brodsky
a9cd0de886
(setup) fix install crash on non-utf8 systems (#644)
fixes https://github.com/ets-labs/python-dependency-injector/issues/643
2022-12-18 21:23:24 -05:00
Roman Mogylatov
aaff333f01
Python 3.11 Support (#647)
* Update tests

* Enable tests on 3.11

* Fix coverage config in tox.ini

* feat: re cythonize to support python 3.11 (#646)

* feat: re cythonize to support python 3.11

* misc: added tox env for python 3.11

* misc: add classifiers for python 3.11

* fix: skip tests for removed functions

* misc: CI updates for python 3.11

Co-authored-by: Roman Mogylatov <rmogilatov@gmail.com>

* Update tests and linters job

* Update test skip decorators

* Fix tox.ini

* Update 3.10 to be explicit string literal

* Move pypy3 to legacy job

* Fix error in resourse typing test

* Update publishing job

* Update actions and setup-python versions

* Update changelog

* Update pypy

* Update tox.ini with new pypy versions

* Update publishing job

* Update actions/upload-artifact@v3

* Update ubuntu to 22.04 on docs publishing job

* Update actions/download-artifact@v3 and pypa/gh-action-pypi-publish@release/v1

Co-authored-by: Gen Xu <xgbarry@gmail.com>
2022-12-18 21:09:14 -05:00
171 changed files with 4474 additions and 211322 deletions

View File

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

View File

@ -0,0 +1,29 @@
---
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

@ -0,0 +1,7 @@
---
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

@ -0,0 +1,8 @@
---
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``

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
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 Normal file
View File

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

View File

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

View File

@ -4,28 +4,12 @@ on: [push, pull_request, workflow_dispatch]
jobs:
tests-on-legacy-versions:
name: Run tests on legacy versions
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, pypy2.7, pypy3.9]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- run: pip install tox
- run: tox
env:
TOXENV: ${{ matrix.python-version }}
test-on-different-versions:
name: Run tests
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, "3.10", 3.11]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@ -34,23 +18,35 @@ jobs:
- run: pip install tox
- run: tox
env:
DEPENDENCY_INJECTOR_LIMITED_API: 1
TOXENV: ${{ matrix.python-version }}
test-different-pydantic-versions:
name: Run tests with different pydantic versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.12"
- run: pip install tox
- run: tox -e pydantic-v1,pydantic-v2
test-coverage:
name: Run tests with coverage
runs-on: ubuntu-latest
env:
DEPENDENCY_INJECTOR_DEBUG_MODE: 1
PIP_VERBOSE: 1
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.11
- run: pip install tox cython
- run: make cythonize
- run: tox
python-version: 3.12
- run: pip install tox
- run: tox -vv
env:
TOXENV: coveralls
@ -64,7 +60,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.11
python-version: 3.13
- run: pip install tox
- run: tox
env:

15
.gitignore vendored
View File

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

View File

@ -1,49 +0,0 @@
[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,3 +20,5 @@ Dependency Injector Contributors
+ Ngo Thanh Loi (Leonn) (loingo95)
+ Thiago Hiromi (thiromi)
+ Felipe Rubio (krouw)
+ Anton Petrov (anton-petrov)
+ ZipFile (ZipFile)

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@
:target: https://pypi.org/project/dependency-injector/
:alt: Wheel
.. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
:target: https://github.com/ets-labs/python-dependency-injector/actions
:alt: Build Status

9
docs/_static/custom.css vendored Normal file
View File

@ -0,0 +1,9 @@
.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;
}

View File

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

1
docs/_static/sponsor.html vendored Normal file
View File

@ -0,0 +1 @@
<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

@ -0,0 +1,9 @@
dependency_injector.ext.starlette
=================================
.. automodule:: dependency_injector.ext.starlette
:members:
:inherited-members:
:show-inheritance:
.. disqus::

View File

@ -9,3 +9,4 @@ API Documentation
containers
wiring
errors
asgi-lifespan

View File

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

View File

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

View File

@ -84,4 +84,6 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -90,4 +90,6 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

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

View File

@ -129,4 +129,6 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -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
:language: python
:emphasize-lines: 13
:emphasize-lines: 12
Tests
-----
@ -94,4 +94,6 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -50,7 +50,7 @@ Dependency Injector --- Dependency injection framework for Python
:target: https://pypi.org/project/dependency-injector/
:alt: Wheel
.. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
:target: https://github.com/ets-labs/python-dependency-injector/actions
:alt: Build Status

View File

@ -310,4 +310,6 @@ A few useful links related to a dependency injection design pattern for further
+ https://github.com/ets-labs/python-dependency-injector
+ https://pypi.org/project/dependency-injector/
.. include:: ../sponsor.rst
.. disqus::

View File

@ -31,7 +31,7 @@ Key features of the ``Dependency Injector``:
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
.. code-block:: plain
.. code-block:: text
Explicit is better than implicit

View File

@ -7,12 +7,118 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
Development version
-------------------
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``, and ``pypa/cibuildwheel@v2.11.3``.
- 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
------

View File

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

View File

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

View File

@ -61,11 +61,12 @@ 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
resource shutdown.
Resource provider supports 3 types of initializers:
Resource provider supports 4 types of initializers:
- Function
- Generator
- Subclass of ``resources.Resource``
- Context Manager
- Generator (legacy)
- Subclass of ``resources.Resource`` (legacy)
Function initializer
--------------------
@ -103,8 +104,44 @@ you configure global resource:
Function initializer does not provide a way to specify custom resource shutdown.
Generator initializer
---------------------
Context Manager 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:
@ -154,8 +191,13 @@ object is not mandatory. You can leave ``yield`` statement empty:
argument2=...,
)
Subclass initializer
--------------------
.. note::
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``:
@ -210,6 +252,72 @@ first argument.
.. _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
---------------------------------------------------
@ -263,10 +371,11 @@ Asynchronous function initializer:
argument2=...,
)
Asynchronous generator initializer:
Asynchronous Context Manager initializer:
.. code-block:: python
@asynccontextmanager
async def init_async_resource(argument1=..., argument2=...):
connection = await connect()
yield connection
@ -358,5 +467,54 @@ See also:
- Wiring :ref:`async-injections-wiring`
- :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::

View File

@ -24,7 +24,7 @@ returns it on the rest of the calls.
.. note::
``Singleton`` provider makes dependencies injection only when creates an object. When an object
``Singleton`` provider makes dependencies injection only when it creates an object. When an object
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

7
docs/sponsor.rst Normal file
View File

@ -0,0 +1,7 @@
.. 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:
.. code-block:: json
.. code-block:: http
HTTP/1.1 200 OK
Content-Length: 844
@ -596,7 +596,7 @@ and make a request to the API in the terminal:
You should see:
.. code-block:: json
.. code-block:: http
HTTP/1.1 200 OK
Content-Length: 492
@ -859,4 +859,6 @@ What's next?
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus::

View File

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

View File

@ -84,7 +84,7 @@ Create next structure in the project root directory. All files are empty. That's
Initial project layout:
.. code-block:: bash
.. code-block:: text
./
├── 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:
.. code-block:: bash
.. code-block:: text
dependency-injector
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
``fixtures.py`` inside of it:
.. code-block:: bash
.. code-block:: text
:emphasize-lines: 2-3
./
@ -205,13 +205,13 @@ Now run in the terminal:
You should see:
.. code-block:: bash
.. code-block:: text
OK
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
.. code-block:: bash
.. code-block:: text
: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:
.. code-block:: bash
.. code-block:: text
:emphasize-lines: 10
./
@ -356,7 +356,7 @@ Let's move on to the finders.
Create the ``finders.py`` in the ``movies`` package:
.. code-block:: bash
.. code-block:: text
: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:
.. code-block:: bash
.. code-block:: text
:emphasize-lines: 12
./
@ -613,7 +613,7 @@ Run in the terminal:
You should see:
.. code-block:: plain
.. code-block:: text
Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
@ -752,7 +752,7 @@ Run in the terminal:
You should see:
.. code-block:: plain
.. code-block:: text
Francis Lawrence movies:
- 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:
.. code-block:: plain
.. code-block:: text
Francis Lawrence movies:
- 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:
.. code-block:: bash
.. code-block:: text
:emphasize-lines: 13
./
@ -911,7 +911,7 @@ Create ``tests.py`` in the ``movies`` package:
and put next into it:
.. code-block:: python
:emphasize-lines: 36,51
:emphasize-lines: 41,50
"""Tests module."""
@ -941,13 +941,18 @@ and put next into it:
return container
def test_movies_directed_by(container):
@pytest.fixture
def finder_mock(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"),
]
return finder_mock
def test_movies_directed_by(container, finder_mock):
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_directed_by("Jon Favreau")
@ -956,13 +961,7 @@ and put next into it:
assert movies[0].title == "The Jungle Book"
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"),
]
def test_movies_released_in(container, finder_mock):
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_released_in(2015)
@ -978,7 +977,7 @@ Run in the terminal:
You should see:
.. code-block::
.. code-block:: text
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-3.0.0
@ -995,9 +994,9 @@ You should see:
movies/entities.py 7 1 86%
movies/finders.py 26 13 50%
movies/listers.py 8 0 100%
movies/tests.py 23 0 100%
movies/tests.py 24 0 100%
------------------------------------------
TOTAL 89 30 66%
TOTAL 90 30 67%
.. note::
@ -1034,4 +1033,6 @@ What's next?
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus::

View File

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

View File

@ -64,7 +64,7 @@ FastAPI example:
@app.api_route("/")
@inject
async def index(service: Service = Depends(Provide[Container.service])):
async def index(service: Annotated[Service, Depends(Provide[Container.service])]):
value = await service.process()
return {"result": value}
@ -127,6 +127,7 @@ To inject the provider itself use ``Provide[foo.provider]``:
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
bar = bar_provider(argument="baz")
...
You can also use ``Provider[foo]`` for injecting the provider itself:
.. code-block:: python
@ -254,13 +255,43 @@ To inject a container use special identifier ``<container>``:
Making injections into modules and class attributes
---------------------------------------------------
You can use wiring to make injections into modules and class attributes.
You can use wiring to make injections into modules and class attributes. Both the classic marker
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
:language: python
:lines: 3-
: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:
.. code-block:: python
@ -601,6 +632,36 @@ or with a single container ``register_loader_containers(container)`` multiple ti
To unregister a container use ``unregister_loader_containers(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
---------------------------------
@ -632,5 +693,6 @@ Take a look at other application examples:
- :ref:`fastapi-example`
- :ref:`fastapi-redis-example`
- :ref:`fastapi-sqlalchemy-example`
- :ref:`fastdepends-example`
.. disqus::

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,13 +15,13 @@ Build the Docker image:
.. code-block:: bash
docker-compose build
docker compose build
Run the docker-compose environment:
.. code-block:: bash
docker-compose up
docker compose up
The output should be something like:
@ -67,15 +67,15 @@ To run the tests do:
.. 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:
.. code-block::
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
rootdir: /code
plugins: cov-3.0.0
plugins: cov-6.0.0, anyio-4.7.0
collected 7 items
webapp/tests.py ....... [100%]

View File

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

View File

@ -1,11 +1,14 @@
"""Endpoints module."""
from typing import Annotated
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 .services import UserService
from .repositories import NotFoundError
from .services import UserService
router = APIRouter()
@ -13,7 +16,7 @@ router = APIRouter()
@router.get("/users")
@inject
def get_list(
user_service: UserService = Depends(Provide[Container.user_service]),
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
):
return user_service.get_users()
@ -22,7 +25,7 @@ def get_list(
@inject
def get_by_id(
user_id: int,
user_service: UserService = Depends(Provide[Container.user_service]),
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
):
try:
return user_service.get_user_by_id(user_id)
@ -33,7 +36,7 @@ def get_by_id(
@router.post("/users", status_code=status.HTTP_201_CREATED)
@inject
def add(
user_service: UserService = Depends(Provide[Container.user_service]),
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
):
return user_service.create_user()
@ -42,8 +45,8 @@ def add(
@inject
def remove(
user_id: int,
user_service: UserService = Depends(Provide[Container.user_service]),
):
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
) -> Response:
try:
user_service.delete_user_by_id(user_id)
except NotFoundError:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,13 +26,18 @@ def container():
return container
def test_movies_directed_by(container):
@pytest.fixture
def finder_mock(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"),
]
return finder_mock
def test_movies_directed_by(container, finder_mock):
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_directed_by("Jon Favreau")
@ -41,13 +46,7 @@ def test_movies_directed_by(container):
assert movies[0].title == "The Jungle Book"
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"),
]
def test_movies_released_in(container, finder_mock):
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_released_in(2015)

View File

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

View File

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

View File

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

View File

@ -0,0 +1,39 @@
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

@ -0,0 +1,59 @@
#!/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

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

View File

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

View File

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

View File

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

View File

@ -2,10 +2,10 @@
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
from typing import Annotated
class Service:
...
class Service: ...
class Container(containers.DeclarativeContainer):
@ -13,9 +13,16 @@ class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
# You can place marker on parameter default value
@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__":

View File

@ -0,0 +1,31 @@
"""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)

122
pyproject.toml Normal file
View File

@ -0,0 +1,122 @@
[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,9 +1,11 @@
cython==0.29.32
cython==3.1.1
setuptools
pytest
pytest-asyncio
tox
coverage
flake8
flake8-pyproject
pydocstyle
sphinx_autobuild
pip
@ -12,9 +14,12 @@ pyyaml
httpx
fastapi
pydantic
pydantic-settings
numpy
scipy
boto3
mypy_boto3_s3
typing_extensions
fast-depends
-r requirements-ext.txt

View File

@ -1,9 +1,9 @@
# TODO: unpin 3.5.0 when this bug is fixed: https://github.com/sphinx-doc/sphinx/issues/8885
sphinx<3.5.0
sphinx
# TODO: unpin jinja2 after sphinx update to 4+
jinja2<3.1
jinja2
-e git+https://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus
sphinx-disqus==1.3.0
-r requirements-ext.txt

View File

@ -1,2 +1,3 @@
flask
werkzeug
aiohttp

View File

@ -1 +0,0 @@
six>=1.7.0,<=1.16.0

View File

@ -1,13 +1,14 @@
[flake8]
max_line_length = 100
max_line_length = 120
max_complexity = 10
exclude = types.py
extend-ignore = E203,E701
per-file-ignores =
examples/demo/*: F841
examples/containers/traverse.py: E501
examples/providers/async.py: F841
examples/providers/async_overriding.py: F841
examples/wiring/*: F841
examples/wiring/*: F821,F841
[pydocstyle]
ignore = D100,D101,D102,D105,D106,D107,D203,D213

158
setup.py
View File

@ -1,123 +1,55 @@
"""`Dependency injector` setup script."""
import os
import re
import sys
from setuptools import setup, Extension
from Cython.Build import cythonize
from Cython.Compiler import Options
from setuptools import Extension, setup
# Defining setup variables:
defined_macros = dict()
defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 0
# Getting description:
with open("README.rst") as readme_file:
description = readme_file.read()
# Getting requirements:
with open("requirements.txt") as requirements_file:
requirements = requirements_file.readlines()
# Getting version:
with open("src/dependency_injector/__init__.py") as init_file:
version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1)
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
limited_api = (
os.environ.get("DEPENDENCY_INJECTOR_LIMITED_API") == "1"
and sys.implementation.name == "cpython"
)
defined_macros = []
options = {}
compiler_directives = {
"language_level": 3,
"profile": debug,
"linetrace": debug,
}
Options.annotate = debug
# Adding debug options:
if os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1":
defined_macros["CYTHON_TRACE"] = 1
defined_macros["CYTHON_TRACE_NOGIL"] = 1
defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 1
if debug:
limited_api = False # line tracing is not part of the Limited API
defined_macros.extend(
[
("CYTHON_TRACE", "1"),
("CYTHON_TRACE_NOGIL", "1"),
("CYTHON_CLINE_IN_TRACEBACK", "1"),
]
)
if limited_api:
options.setdefault("bdist_wheel", {})
options["bdist_wheel"]["py_limited_api"] = "cp38"
defined_macros.append(("Py_LIMITED_API", "0x03080000"))
setup(name="dependency-injector",
version=version,
description="Dependency injection framework for Python",
long_description=description,
author="Roman Mogylatov",
author_email="rmogilatov@gmail.com",
maintainer="Roman Mogylatov",
maintainer_email="rmogilatov@gmail.com",
url="https://github.com/ets-labs/python-dependency-injector",
download_url="https://pypi.python.org/pypi/dependency_injector",
packages=[
"dependency_injector",
"dependency_injector.ext",
setup(
options=options,
ext_modules=cythonize(
[
Extension(
"*",
["src/**/*.pyx"],
define_macros=defined_macros,
py_limited_api=limited_api,
),
],
package_dir={
"": "src",
},
package_data={
"dependency_injector": ["*.pxd", "*.pyi", "py.typed"],
},
ext_modules=[
Extension("dependency_injector.containers",
["src/dependency_injector/containers.c"],
define_macros=list(defined_macros.items()),
extra_compile_args=["-O2"]),
Extension("dependency_injector.providers",
["src/dependency_injector/providers.c"],
define_macros=list(defined_macros.items()),
extra_compile_args=["-O2"]),
Extension("dependency_injector._cwiring",
["src/dependency_injector/_cwiring.c"],
define_macros=list(defined_macros.items()),
extra_compile_args=["-O2"]),
],
install_requires=requirements,
extras_require={
"yaml": [
"pyyaml",
],
"pydantic": [
"pydantic",
],
"flask": [
"flask",
],
"aiohttp": [
"aiohttp",
],
},
zip_safe=True,
license="BSD New",
platforms=["any"],
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 :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"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",
])
annotate=debug,
show_all_warnings=True,
compiler_directives=compiler_directives,
),
)

View File

@ -1,6 +1,6 @@
"""Top-level package."""
__version__ = "4.40.0"
__version__ = "4.48.1"
"""Version number.
:type: str

File diff suppressed because it is too large Load Diff

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