mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-10-31 07:57:43 +03:00 
			
		
		
		
	Merge branch 'release/4.44.0' into master
This commit is contained in:
		
						commit
						704e36a642
					
				
							
								
								
									
										10
									
								
								.coveragerc
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.coveragerc
									
									
									
									
									
								
							|  | @ -1,10 +0,0 @@ | ||||||
| [run] |  | ||||||
| source = dependency_injector |  | ||||||
| omit = tests/unit |  | ||||||
| plugins = Cython.Coverage |  | ||||||
| 
 |  | ||||||
| [report] |  | ||||||
| show_missing = true |  | ||||||
| 
 |  | ||||||
| [html] |  | ||||||
| directory=reports/unittests/ |  | ||||||
							
								
								
									
										12
									
								
								.github/workflows/tests-and-linters.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/tests-and-linters.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -36,6 +36,17 @@ jobs: | ||||||
|         env: |         env: | ||||||
|           TOXENV: ${{ matrix.python-version }} |           TOXENV: ${{ matrix.python-version }} | ||||||
| 
 | 
 | ||||||
|  |   test-different-pydantic-versions: | ||||||
|  |     name: Run tests with different pydantic versions | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v3 | ||||||
|  |       - uses: actions/setup-python@v4 | ||||||
|  |         with: | ||||||
|  |           python-version: "3.12" | ||||||
|  |       - run: pip install tox | ||||||
|  |       - run: tox -e pydantic-v1,pydantic-v2 | ||||||
|  | 
 | ||||||
|   test-coverage: |   test-coverage: | ||||||
|     name: Run tests with coverage |     name: Run tests with coverage | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  | @ -50,7 +61,6 @@ jobs: | ||||||
|         with: |         with: | ||||||
|           python-version: 3.12 |           python-version: 3.12 | ||||||
|       - run: pip install tox 'cython>=3,<4' |       - run: pip install tox 'cython>=3,<4' | ||||||
|       - run: make cythonize |  | ||||||
|       - run: tox -vv |       - run: tox -vv | ||||||
|         env: |         env: | ||||||
|           TOXENV: coveralls |           TOXENV: coveralls | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -63,13 +63,11 @@ venv*/ | ||||||
| # Vim Rope | # Vim Rope | ||||||
| .ropeproject/ | .ropeproject/ | ||||||
| 
 | 
 | ||||||
| # C extensions | # Cython artifacts | ||||||
| src/dependency_injector/*.h | src/**/*.c | ||||||
| src/dependency_injector/*.so | src/**/*.h | ||||||
| src/dependency_injector/containers/*.h | src/**/*.so | ||||||
| src/dependency_injector/containers/*.so | src/**/*.html | ||||||
| src/dependency_injector/providers/*.h |  | ||||||
| src/dependency_injector/providers/*.so |  | ||||||
| 
 | 
 | ||||||
| # Workspace for samples | # Workspace for samples | ||||||
| .workspace/ | .workspace/ | ||||||
|  |  | ||||||
							
								
								
									
										49
									
								
								.pylintrc
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								.pylintrc
									
									
									
									
									
								
							|  | @ -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 |  | ||||||
							
								
								
									
										32
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								Makefile
									
									
									
									
									
								
							|  | @ -1,14 +1,6 @@ | ||||||
| VERSION := $(shell python setup.py --version) | VERSION := $(shell python setup.py --version) | ||||||
| 
 | 
 | ||||||
| CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx') | export COVERAGE_RCFILE := pyproject.toml | ||||||
| 
 |  | ||||||
| CYTHON_DIRECTIVES = -Xlanguage_level=3 |  | ||||||
| 
 |  | ||||||
| ifdef DEPENDENCY_INJECTOR_DEBUG_MODE |  | ||||||
| 	CYTHON_DIRECTIVES += -Xprofile=True |  | ||||||
| 	CYTHON_DIRECTIVES += -Xlinetrace=True |  | ||||||
| endif |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| clean: | clean: | ||||||
| 	# Clean sources | 	# Clean sources | ||||||
|  | @ -25,21 +17,17 @@ clean: | ||||||
| 	find examples -name '*.py[co]' -delete | 	find examples -name '*.py[co]' -delete | ||||||
| 	find examples -name '__pycache__' -delete | 	find examples -name '__pycache__' -delete | ||||||
| 
 | 
 | ||||||
| cythonize: | build: clean | ||||||
| 	# Compile Cython to C | 	# Compile C extensions | ||||||
| 	cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC) | 	python setup.py build_ext --inplace | ||||||
| 	# Move all Cython html reports | 	# Move all Cython html reports | ||||||
| 	mkdir -p reports/cython/ | 	mkdir -p reports/cython/ | ||||||
| 	find src -name '*.html' -exec mv {}  reports/cython/  \; | 	find src -name '*.html' -exec mv {}  reports/cython/  \; | ||||||
| 
 | 
 | ||||||
| build: clean cythonize |  | ||||||
| 	# Compile C extensions |  | ||||||
| 	python setup.py build_ext --inplace |  | ||||||
| 
 |  | ||||||
| docs-live: | docs-live: | ||||||
| 	sphinx-autobuild docs docs/_build/html | 	sphinx-autobuild docs docs/_build/html | ||||||
| 
 | 
 | ||||||
| install: uninstall clean cythonize | install: uninstall clean build | ||||||
| 	pip install -ve . | 	pip install -ve . | ||||||
| 
 | 
 | ||||||
| uninstall: | uninstall: | ||||||
|  | @ -48,9 +36,9 @@ uninstall: | ||||||
| test: | test: | ||||||
| 	# Unit tests with coverage report | 	# Unit tests with coverage report | ||||||
| 	coverage erase | 	coverage erase | ||||||
| 	coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini | 	coverage run -m pytest -c tests/.configs/pytest.ini | ||||||
| 	coverage report --rcfile=./.coveragerc | 	coverage report | ||||||
| 	coverage html --rcfile=./.coveragerc | 	coverage html | ||||||
| 
 | 
 | ||||||
| check: | check: | ||||||
| 	flake8 src/dependency_injector/ | 	flake8 src/dependency_injector/ | ||||||
|  | @ -61,9 +49,9 @@ check: | ||||||
| 
 | 
 | ||||||
| 	mypy tests/typing | 	mypy tests/typing | ||||||
| 
 | 
 | ||||||
| test-publish: cythonize | test-publish: build | ||||||
| 	# Create distributions | 	# Create distributions | ||||||
| 	python setup.py sdist | 	python -m build --sdist | ||||||
| 	# Upload distributions to PyPI | 	# Upload distributions to PyPI | ||||||
| 	twine upload --repository testpypi dist/dependency-injector-$(VERSION)* | 	twine upload --repository testpypi dist/dependency-injector-$(VERSION)* | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,6 +7,14 @@ that were made in every particular version. | ||||||
| From version 0.7.6 *Dependency Injector* framework strictly | From version 0.7.6 *Dependency Injector* framework strictly | ||||||
| follows `Semantic versioning`_ | follows `Semantic versioning`_ | ||||||
| 
 | 
 | ||||||
|  | 4.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 | 4.43.0 | ||||||
| -------- | -------- | ||||||
| - Add support for Python 3.13. | - Add support for Python 3.13. | ||||||
|  |  | ||||||
|  | @ -183,22 +183,22 @@ See also: :ref:`configuration-envs-interpolation`. | ||||||
| Loading from a Pydantic settings | Loading from a Pydantic settings | ||||||
| -------------------------------- | -------------------------------- | ||||||
| 
 | 
 | ||||||
| ``Configuration`` provider can load configuration from a ``pydantic`` settings object using the | ``Configuration`` provider can load configuration from a ``pydantic_settings.BaseSettings`` object using the | ||||||
| :py:meth:`Configuration.from_pydantic` method: | :py:meth:`Configuration.from_pydantic` method: | ||||||
| 
 | 
 | ||||||
| .. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py | .. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py | ||||||
|    :language: python |    :language: python | ||||||
|    :lines: 3- |    :lines: 3- | ||||||
|    :emphasize-lines: 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. | If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments. | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    container.config.from_pydantic(Settings(), exclude={"optional"}) |    container.config.from_pydantic(Settings(), exclude={"optional"}) | ||||||
| 
 | 
 | ||||||
| Alternatively, you can provide a ``pydantic`` settings 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: | the container will call ``config.from_pydantic()`` automatically: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|  | @ -215,18 +215,23 @@ the container will call ``config.from_pydantic()`` automatically: | ||||||
| 
 | 
 | ||||||
| .. note:: | .. 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:: |    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.* |    *Don't forget to mirror the changes in the requirements file.* | ||||||
| 
 | 
 | ||||||
|  | .. note:: | ||||||
|  | 
 | ||||||
|  |    For backward-compatibility, Pydantic v1 is still supported. | ||||||
|  |    Passing ``pydantic.BaseSettings`` instances will work just as fine as ``pydantic_settings.BaseSettings``. | ||||||
|  | 
 | ||||||
| Loading from a dictionary | Loading from a dictionary | ||||||
| ------------------------- | ------------------------- | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| from unittest import mock | from unittest import mock | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
| from httpx import AsyncClient | from httpx import ASGITransport, AsyncClient | ||||||
| 
 | 
 | ||||||
| from .application import app, container | from .application import app, container | ||||||
| from .services import Service | from .services import Service | ||||||
|  | @ -11,7 +11,10 @@ from .services import Service | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def client(event_loop): | def client(event_loop): | ||||||
|     client = AsyncClient(app=app, base_url="http://test") |     client = AsyncClient( | ||||||
|  |         transport=ASGITransport(app=app), | ||||||
|  |         base_url="http://test", | ||||||
|  |     ) | ||||||
|     yield client |     yield client | ||||||
|     event_loop.run_until_complete(client.aclose()) |     event_loop.run_until_complete(client.aclose()) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,14 +1,17 @@ | ||||||
| from unittest import mock | from unittest import mock | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
| from httpx import AsyncClient | from httpx import ASGITransport, AsyncClient | ||||||
| 
 | 
 | ||||||
| from fastapi_di_example import app, container, Service | from fastapi_di_example import app, container, Service | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| async def client(event_loop): | async def client(event_loop): | ||||||
|     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 |         yield client | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| from unittest import mock | from unittest import mock | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
| from httpx import AsyncClient | from httpx import ASGITransport, AsyncClient | ||||||
| 
 | 
 | ||||||
| from giphynavigator.application import app | from giphynavigator.application import app | ||||||
| from giphynavigator.giphy import GiphyClient | from giphynavigator.giphy import GiphyClient | ||||||
|  | @ -11,7 +11,10 @@ from giphynavigator.giphy import GiphyClient | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| async def client(): | 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 |         yield client | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| import os | import os | ||||||
| 
 | 
 | ||||||
| from dependency_injector import containers, providers | from dependency_injector import containers, providers | ||||||
| from pydantic import BaseSettings, Field | from pydantic_settings import BaseSettings, SettingsConfigDict | ||||||
| 
 | 
 | ||||||
| # Emulate environment variables | # Emulate environment variables | ||||||
| os.environ["AWS_ACCESS_KEY_ID"] = "KEY" | os.environ["AWS_ACCESS_KEY_ID"] = "KEY" | ||||||
|  | @ -11,15 +11,16 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AwsSettings(BaseSettings): | class AwsSettings(BaseSettings): | ||||||
|  |     model_config = SettingsConfigDict(env_prefix="aws_") | ||||||
| 
 | 
 | ||||||
|     access_key_id: str = Field(env="aws_access_key_id") |     access_key_id: str | ||||||
|     secret_access_key: str = Field(env="aws_secret_access_key") |     secret_access_key: str | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Settings(BaseSettings): | class Settings(BaseSettings): | ||||||
| 
 | 
 | ||||||
|     aws: AwsSettings = AwsSettings() |     aws: AwsSettings = AwsSettings() | ||||||
|     optional: str = Field(default="default_value") |     optional: str = "default_value" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| import os | import os | ||||||
| 
 | 
 | ||||||
| from dependency_injector import containers, providers | from dependency_injector import containers, providers | ||||||
| from pydantic import BaseSettings, Field | from pydantic_settings import BaseSettings, SettingsConfigDict | ||||||
| 
 | 
 | ||||||
| # Emulate environment variables | # Emulate environment variables | ||||||
| os.environ["AWS_ACCESS_KEY_ID"] = "KEY" | os.environ["AWS_ACCESS_KEY_ID"] = "KEY" | ||||||
|  | @ -11,15 +11,16 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AwsSettings(BaseSettings): | class AwsSettings(BaseSettings): | ||||||
|  |     model_config = SettingsConfigDict(env_prefix="aws_") | ||||||
| 
 | 
 | ||||||
|     access_key_id: str = Field(env="aws_access_key_id") |     access_key_id: str | ||||||
|     secret_access_key: str = Field(env="aws_secret_access_key") |     secret_access_key: str | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Settings(BaseSettings): | class Settings(BaseSettings): | ||||||
| 
 | 
 | ||||||
|     aws: AwsSettings = AwsSettings() |     aws: AwsSettings = AwsSettings() | ||||||
|     optional: str = Field(default="default_value") |     optional: str = "default_value" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
|  |  | ||||||
							
								
								
									
										102
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | ||||||
|  | [build-system] | ||||||
|  | requires = ["setuptools", "Cython"] | ||||||
|  | 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.7" | ||||||
|  | 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.7", | ||||||
|  |     "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 = ["six"] | ||||||
|  | 
 | ||||||
|  | [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" | ||||||
|  | 
 | ||||||
|  | [tool.pylint.main] | ||||||
|  | ignore = ["tests"] | ||||||
|  | 
 | ||||||
|  | [tool.pylint.design] | ||||||
|  | min-public-methods = 0 | ||||||
|  | max-public-methods = 30 | ||||||
|  | @ -5,6 +5,7 @@ pytest-asyncio | ||||||
| tox | tox | ||||||
| coverage | coverage | ||||||
| flake8 | flake8 | ||||||
|  | flake8-pyproject | ||||||
| pydocstyle | pydocstyle | ||||||
| sphinx_autobuild | sphinx_autobuild | ||||||
| pip | pip | ||||||
|  |  | ||||||
							
								
								
									
										150
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								setup.py
									
									
									
									
									
								
							|  | @ -1,128 +1,42 @@ | ||||||
| """`Dependency injector` setup script.""" | """`Dependency injector` setup script.""" | ||||||
| 
 | 
 | ||||||
| import os | 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 | ||||||
| 
 | 
 | ||||||
| 
 | debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1" | ||||||
| def _open(filename): | defined_macros = [] | ||||||
|     if sys.version_info[0] == 2: | compiler_directives = { | ||||||
|         return open(filename) |     "language_level": 3, | ||||||
|     return open(filename, encoding="utf-8") |     "profile": debug, | ||||||
| 
 |     "linetrace": debug, | ||||||
| 
 | } | ||||||
| # Defining setup variables: | Options.annotate = debug | ||||||
| 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) |  | ||||||
| 
 | 
 | ||||||
| # Adding debug options: | # Adding debug options: | ||||||
| if os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1": | if debug: | ||||||
|     defined_macros["CYTHON_TRACE"] = 1 |     defined_macros.extend( | ||||||
|     defined_macros["CYTHON_TRACE_NOGIL"] = 1 |         [ | ||||||
|     defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 1 |             ("CYTHON_TRACE", "1"), | ||||||
|  |             ("CYTHON_TRACE_NOGIL", "1"), | ||||||
|  |             ("CYTHON_CLINE_IN_TRACEBACK", "1"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| setup(name="dependency-injector", | setup( | ||||||
|       version=version, |     ext_modules=cythonize( | ||||||
|       description="Dependency injection framework for Python", |         [ | ||||||
|       long_description=description, |             Extension( | ||||||
|       author="Roman Mogylatov", |                 "*", | ||||||
|       author_email="rmogilatov@gmail.com", |                 ["src/**/*.pyx"], | ||||||
|       maintainer="Roman Mogylatov", |                 define_macros=defined_macros, | ||||||
|       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", |  | ||||||
|         ], |         ], | ||||||
|       package_dir={ |         annotate=debug, | ||||||
|           "": "src", |         show_all_warnings=True, | ||||||
|       }, |         compiler_directives=compiler_directives, | ||||||
|       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 :: 3", |  | ||||||
|           "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 :: 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", |  | ||||||
|       ]) |  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| """Top-level package.""" | """Top-level package.""" | ||||||
| 
 | 
 | ||||||
| __version__ = "4.43.0" | __version__ = "4.44.0" | ||||||
| """Version number. | """Version number. | ||||||
| 
 | 
 | ||||||
| :type: str | :type: str | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -48,10 +48,25 @@ try: | ||||||
| except ImportError: | except ImportError: | ||||||
|     yaml = None |     yaml = None | ||||||
| 
 | 
 | ||||||
|  | has_pydantic_settings = True | ||||||
|  | cdef bint pydantic_v1 = False | ||||||
|  | cdef str pydantic_module = "pydantic_settings" | ||||||
|  | cdef str pydantic_extra = "pydantic2" | ||||||
|  | 
 | ||||||
| try: | try: | ||||||
|     import pydantic |     from pydantic_settings import BaseSettings as PydanticSettings | ||||||
| except ImportError: | except ImportError: | ||||||
|     pydantic = None |     try: | ||||||
|  |         # pydantic-settings requires pydantic v2, | ||||||
|  |         # so it is safe to assume that we're dealing with v1: | ||||||
|  |         from pydantic import BaseSettings as PydanticSettings | ||||||
|  |         pydantic_v1 = True | ||||||
|  |         pydantic_module = "pydantic" | ||||||
|  |         pydantic_extra = "pydantic" | ||||||
|  |     except ImportError: | ||||||
|  |         # if it is present, ofc | ||||||
|  |         has_pydantic_settings = False | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| from .errors import ( | from .errors import ( | ||||||
|     Error, |     Error, | ||||||
|  | @ -149,6 +164,31 @@ cdef int ASYNC_MODE_DISABLED = 2 | ||||||
| cdef set __iscoroutine_typecache = set() | cdef set __iscoroutine_typecache = set() | ||||||
| cdef tuple __COROUTINE_TYPES = asyncio.coroutines._COROUTINE_TYPES if asyncio else tuple() | cdef tuple __COROUTINE_TYPES = asyncio.coroutines._COROUTINE_TYPES if asyncio else tuple() | ||||||
| 
 | 
 | ||||||
|  | cdef dict pydantic_settings_to_dict(settings, dict kwargs): | ||||||
|  |     if not has_pydantic_settings: | ||||||
|  |         raise Error( | ||||||
|  |             f"Unable to load pydantic configuration - {pydantic_module} is not installed. " | ||||||
|  |             "Install pydantic or install Dependency Injector with pydantic extras: " | ||||||
|  |             f"\"pip install dependency-injector[{pydantic_extra}]\"" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     if isinstance(settings, CLASS_TYPES) and issubclass(settings, PydanticSettings): | ||||||
|  |         raise Error( | ||||||
|  |             "Got settings class, but expect instance: " | ||||||
|  |             "instead \"{0}\" use \"{0}()\"".format(settings.__name__) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     if not isinstance(settings, PydanticSettings): | ||||||
|  |         raise Error( | ||||||
|  |             f"Unable to recognize settings instance, expect \"{pydantic_module}.BaseSettings\", " | ||||||
|  |             f"got {settings} instead" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     if pydantic_v1: | ||||||
|  |         return settings.dict(**kwargs) | ||||||
|  | 
 | ||||||
|  |     return settings.model_dump(mode="python", **kwargs) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| cdef class Provider(object): | cdef class Provider(object): | ||||||
|     """Base provider class. |     """Base provider class. | ||||||
|  | @ -1786,36 +1826,20 @@ cdef class ConfigurationOption(Provider): | ||||||
|         Loaded configuration is merged recursively over existing configuration. |         Loaded configuration is merged recursively over existing configuration. | ||||||
| 
 | 
 | ||||||
|         :param settings: Pydantic settings instances. |         :param settings: Pydantic settings instances. | ||||||
|         :type settings: :py:class:`pydantic.BaseSettings` |         :type settings: :py:class:`pydantic.BaseSettings` (pydantic v1) or | ||||||
|  |             :py:class:`pydantic_settings.BaseSettings` (pydantic v2 and onwards) | ||||||
| 
 | 
 | ||||||
|         :param required: When required is True, raise an exception if settings dict is empty. |         :param required: When required is True, raise an exception if settings dict is empty. | ||||||
|         :type required: bool |         :type required: bool | ||||||
| 
 | 
 | ||||||
|         :param kwargs: Keyword arguments forwarded to ``pydantic.BaseSettings.dict()`` call. |         :param kwargs: Keyword arguments forwarded to ``pydantic.BaseSettings.dict()`` or | ||||||
|  |             ``pydantic_settings.BaseSettings.model_dump()`` call (based on pydantic version). | ||||||
|         :type kwargs: Dict[Any, Any] |         :type kwargs: Dict[Any, Any] | ||||||
| 
 | 
 | ||||||
|         :rtype: None |         :rtype: None | ||||||
|         """ |         """ | ||||||
|         if pydantic is None: |  | ||||||
|             raise Error( |  | ||||||
|                 "Unable to load pydantic configuration - pydantic is not installed. " |  | ||||||
|                 "Install pydantic or install Dependency Injector with pydantic extras: " |  | ||||||
|                 "\"pip install dependency-injector[pydantic]\"" |  | ||||||
|             ) |  | ||||||
| 
 | 
 | ||||||
|         if isinstance(settings, CLASS_TYPES) and issubclass(settings, pydantic.BaseSettings): |         self.from_dict(pydantic_settings_to_dict(settings, kwargs), required=required) | ||||||
|             raise Error( |  | ||||||
|                 "Got settings class, but expect instance: " |  | ||||||
|                 "instead \"{0}\" use \"{0}()\"".format(settings.__name__) |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|         if not isinstance(settings, pydantic.BaseSettings): |  | ||||||
|             raise Error( |  | ||||||
|                 "Unable to recognize settings instance, expect \"pydantic.BaseSettings\", " |  | ||||||
|                 "got {0} instead".format(settings) |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|         self.from_dict(settings.dict(**kwargs), required=required) |  | ||||||
| 
 | 
 | ||||||
|     def from_dict(self, options, required=UNDEFINED): |     def from_dict(self, options, required=UNDEFINED): | ||||||
|         """Load configuration from the dictionary. |         """Load configuration from the dictionary. | ||||||
|  | @ -2355,7 +2379,8 @@ cdef class Configuration(Object): | ||||||
|         Loaded configuration is merged recursively over existing configuration. |         Loaded configuration is merged recursively over existing configuration. | ||||||
| 
 | 
 | ||||||
|         :param settings: Pydantic settings instances. |         :param settings: Pydantic settings instances. | ||||||
|         :type settings: :py:class:`pydantic.BaseSettings` |         :type settings: :py:class:`pydantic.BaseSettings` (pydantic v1) or | ||||||
|  |             :py:class:`pydantic_settings.BaseSettings` (pydantic v2 and onwards) | ||||||
| 
 | 
 | ||||||
|         :param required: When required is True, raise an exception if settings dict is empty. |         :param required: When required is True, raise an exception if settings dict is empty. | ||||||
|         :type required: bool |         :type required: bool | ||||||
|  | @ -2365,26 +2390,8 @@ cdef class Configuration(Object): | ||||||
| 
 | 
 | ||||||
|         :rtype: None |         :rtype: None | ||||||
|         """ |         """ | ||||||
|         if pydantic is None: |  | ||||||
|             raise Error( |  | ||||||
|                 "Unable to load pydantic configuration - pydantic is not installed. " |  | ||||||
|                 "Install pydantic or install Dependency Injector with pydantic extras: " |  | ||||||
|                 "\"pip install dependency-injector[pydantic]\"" |  | ||||||
|             ) |  | ||||||
| 
 | 
 | ||||||
|         if isinstance(settings, CLASS_TYPES) and issubclass(settings, pydantic.BaseSettings): |         self.from_dict(pydantic_settings_to_dict(settings, kwargs), required=required) | ||||||
|             raise Error( |  | ||||||
|                 "Got settings class, but expect instance: " |  | ||||||
|                 "instead \"{0}\" use \"{0}()\"".format(settings.__name__) |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|         if not isinstance(settings, pydantic.BaseSettings): |  | ||||||
|             raise Error( |  | ||||||
|                 "Unable to recognize settings instance, expect \"pydantic.BaseSettings\", " |  | ||||||
|                 "got {0} instead".format(settings) |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|         self.from_dict(settings.dict(**kwargs), required=required) |  | ||||||
| 
 | 
 | ||||||
|     def from_dict(self, options, required=UNDEFINED): |     def from_dict(self, options, required=UNDEFINED): | ||||||
|         """Load configuration from the dictionary. |         """Load configuration from the dictionary. | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ | ||||||
| testpaths = tests/unit/ | testpaths = tests/unit/ | ||||||
| python_files = test_*_py3*.py | python_files = test_*_py3*.py | ||||||
| asyncio_mode = auto | asyncio_mode = auto | ||||||
|  | markers = | ||||||
|  |     pydantic: Tests with Pydantic as a dependency | ||||||
| filterwarnings = | filterwarnings = | ||||||
|     ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\.0\.0:DeprecationWarning |     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:Module \"dependency_injector.ext.flask\" is deprecated since version 4\.0\.0:DeprecationWarning | ||||||
|  |  | ||||||
|  | @ -1,41 +1,60 @@ | ||||||
| """Configuration.from_pydantic() tests.""" | """Configuration.from_pydantic() tests.""" | ||||||
| 
 | 
 | ||||||
| import pydantic | from pydantic import BaseModel | ||||||
| from dependency_injector import providers, errors | 
 | ||||||
|  | try: | ||||||
|  |     from pydantic_settings import ( | ||||||
|  |         BaseSettings,  # type: ignore[import-not-found,unused-ignore] | ||||||
|  |     ) | ||||||
|  | except ImportError: | ||||||
|  |     try: | ||||||
|  |         from pydantic import BaseSettings  # type: ignore[no-redef,unused-ignore] | ||||||
|  |     except ImportError: | ||||||
|  | 
 | ||||||
|  |         class BaseSettings:  # type: ignore[no-redef] | ||||||
|  |             """No-op fallback""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| from pytest import fixture, mark, raises | from pytest import fixture, mark, raises | ||||||
| 
 | 
 | ||||||
|  | from dependency_injector import errors, providers | ||||||
| 
 | 
 | ||||||
| class Section11(pydantic.BaseModel): | pytestmark = mark.pydantic | ||||||
|     value1 = 1 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Section12(pydantic.BaseModel): | class Section11(BaseModel): | ||||||
|     value2 = 2 |     value1: int = 1 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Settings1(pydantic.BaseSettings): | class Section12(BaseModel): | ||||||
|     section1 = Section11() |     value2: int = 2 | ||||||
|     section2 = Section12() |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Section21(pydantic.BaseModel): | class Settings1(BaseSettings): | ||||||
|     value1 = 11 |     section1: Section11 = Section11() | ||||||
|     value11 = 11 |     section2: Section12 = Section12() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Section3(pydantic.BaseModel): | class Section21(BaseModel): | ||||||
|     value3 = 3 |     value1: int = 11 | ||||||
|  |     value11: int = 11 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Settings2(pydantic.BaseSettings): | class Section3(BaseModel): | ||||||
|     section1 = Section21() |     value3: int = 3 | ||||||
|     section3 = Section3() | 
 | ||||||
|  | 
 | ||||||
|  | class Settings2(BaseSettings): | ||||||
|  |     section1: Section21 = Section21() | ||||||
|  |     section3: Section3 = Section3() | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| @fixture | @fixture | ||||||
| def no_pydantic_module_installed(): | def no_pydantic_module_installed(): | ||||||
|     providers.pydantic = None |     has_pydantic_settings = providers.has_pydantic_settings | ||||||
|  |     providers.has_pydantic_settings = False | ||||||
|     yield |     yield | ||||||
|     providers.pydantic = pydantic |     providers.has_pydantic_settings = has_pydantic_settings | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test(config): | def test(config): | ||||||
|  | @ -82,66 +101,70 @@ def test_merge(config): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_empty_settings(config): | def test_empty_settings(config): | ||||||
|     config.from_pydantic(pydantic.BaseSettings()) |     config.from_pydantic(BaseSettings()) | ||||||
|     assert config() == {} |     assert config() == {} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mark.parametrize("config_type", ["strict"]) | @mark.parametrize("config_type", ["strict"]) | ||||||
| def test_empty_settings_strict_mode(config): | def test_empty_settings_strict_mode(config): | ||||||
|     with raises(ValueError): |     with raises(ValueError): | ||||||
|         config.from_pydantic(pydantic.BaseSettings()) |         config.from_pydantic(BaseSettings()) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_option_empty_settings(config): | def test_option_empty_settings(config): | ||||||
|     config.option.from_pydantic(pydantic.BaseSettings()) |     config.option.from_pydantic(BaseSettings()) | ||||||
|     assert config.option() == {} |     assert config.option() == {} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mark.parametrize("config_type", ["strict"]) | @mark.parametrize("config_type", ["strict"]) | ||||||
| def test_option_empty_settings_strict_mode(config): | def test_option_empty_settings_strict_mode(config): | ||||||
|     with raises(ValueError): |     with raises(ValueError): | ||||||
|         config.option.from_pydantic(pydantic.BaseSettings()) |         config.option.from_pydantic(BaseSettings()) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_required_empty_settings(config): | def test_required_empty_settings(config): | ||||||
|     with raises(ValueError): |     with raises(ValueError): | ||||||
|         config.from_pydantic(pydantic.BaseSettings(), required=True) |         config.from_pydantic(BaseSettings(), required=True) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_required_option_empty_settings(config): | def test_required_option_empty_settings(config): | ||||||
|     with raises(ValueError): |     with raises(ValueError): | ||||||
|         config.option.from_pydantic(pydantic.BaseSettings(), required=True) |         config.option.from_pydantic(BaseSettings(), required=True) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mark.parametrize("config_type", ["strict"]) | @mark.parametrize("config_type", ["strict"]) | ||||||
| def test_not_required_empty_settings_strict_mode(config): | def test_not_required_empty_settings_strict_mode(config): | ||||||
|     config.from_pydantic(pydantic.BaseSettings(), required=False) |     config.from_pydantic(BaseSettings(), required=False) | ||||||
|     assert config() == {} |     assert config() == {} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mark.parametrize("config_type", ["strict"]) | @mark.parametrize("config_type", ["strict"]) | ||||||
| def test_not_required_option_empty_settings_strict_mode(config): | def test_not_required_option_empty_settings_strict_mode(config): | ||||||
|     config.option.from_pydantic(pydantic.BaseSettings(), required=False) |     config.option.from_pydantic(BaseSettings(), required=False) | ||||||
|     assert config.option() == {} |     assert config.option() == {} | ||||||
|     assert config() == {"option": {}} |     assert config() == {"option": {}} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_not_instance_of_settings(config): | def test_not_instance_of_settings(config): | ||||||
|     with raises(errors.Error) as error: |     with raises( | ||||||
|  |         errors.Error, | ||||||
|  |         match=( | ||||||
|  |             r"Unable to recognize settings instance, expect \"pydantic(?:_settings)?\.BaseSettings\", " | ||||||
|  |             r"got {0} instead".format({}) | ||||||
|  |         ), | ||||||
|  |     ): | ||||||
|         config.from_pydantic({}) |         config.from_pydantic({}) | ||||||
|     assert error.value.args[0] == ( |  | ||||||
|         "Unable to recognize settings instance, expect \"pydantic.BaseSettings\", " |  | ||||||
|         "got {0} instead".format({}) |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_option_not_instance_of_settings(config): | def test_option_not_instance_of_settings(config): | ||||||
|     with raises(errors.Error) as error: |     with raises( | ||||||
|         config.option.from_pydantic({}) |         errors.Error, | ||||||
|     assert error.value.args[0] == ( |         match=( | ||||||
|         "Unable to recognize settings instance, expect \"pydantic.BaseSettings\", " |             r"Unable to recognize settings instance, expect \"pydantic(?:_settings)?\.BaseSettings\", " | ||||||
|             "got {0} instead".format({}) |             "got {0} instead".format({}) | ||||||
|     ) |         ), | ||||||
|  |     ): | ||||||
|  |         config.option.from_pydantic({}) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_subclass_instead_of_instance(config): | def test_subclass_instead_of_instance(config): | ||||||
|  | @ -164,21 +187,25 @@ def test_option_subclass_instead_of_instance(config): | ||||||
| 
 | 
 | ||||||
| @mark.usefixtures("no_pydantic_module_installed") | @mark.usefixtures("no_pydantic_module_installed") | ||||||
| def test_no_pydantic_installed(config): | def test_no_pydantic_installed(config): | ||||||
|     with raises(errors.Error) as error: |     with raises( | ||||||
|  |         errors.Error, | ||||||
|  |         match=( | ||||||
|  |             r"Unable to load pydantic configuration - pydantic(?:_settings)? is not installed\. " | ||||||
|  |             r"Install pydantic or install Dependency Injector with pydantic extras: " | ||||||
|  |             r"\"pip install dependency-injector\[pydantic2?\]\"" | ||||||
|  |         ), | ||||||
|  |     ): | ||||||
|         config.from_pydantic(Settings1()) |         config.from_pydantic(Settings1()) | ||||||
|     assert error.value.args[0] == ( |  | ||||||
|         "Unable to load pydantic configuration - pydantic is not installed. " |  | ||||||
|         "Install pydantic or install Dependency Injector with pydantic extras: " |  | ||||||
|         "\"pip install dependency-injector[pydantic]\"" |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mark.usefixtures("no_pydantic_module_installed") | @mark.usefixtures("no_pydantic_module_installed") | ||||||
| def test_option_no_pydantic_installed(config): | def test_option_no_pydantic_installed(config): | ||||||
|     with raises(errors.Error) as error: |     with raises( | ||||||
|  |         errors.Error, | ||||||
|  |         match=( | ||||||
|  |             r"Unable to load pydantic configuration - pydantic(?:_settings)? is not installed\. " | ||||||
|  |             r"Install pydantic or install Dependency Injector with pydantic extras: " | ||||||
|  |             r"\"pip install dependency-injector\[pydantic2?\]\"" | ||||||
|  |         ), | ||||||
|  |     ): | ||||||
|         config.option.from_pydantic(Settings1()) |         config.option.from_pydantic(Settings1()) | ||||||
|     assert error.value.args[0] == ( |  | ||||||
|         "Unable to load pydantic configuration - pydantic is not installed. " |  | ||||||
|         "Install pydantic or install Dependency Injector with pydantic extras: " |  | ||||||
|         "\"pip install dependency-injector[pydantic]\"" |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|  | @ -1,33 +1,50 @@ | ||||||
| """Configuration.from_pydantic() tests.""" | """Configuration.from_pydantic() tests.""" | ||||||
| 
 | 
 | ||||||
| import pydantic | from pydantic import BaseModel | ||||||
| from dependency_injector import providers | 
 | ||||||
|  | try: | ||||||
|  |     from pydantic_settings import ( | ||||||
|  |         BaseSettings,  # type: ignore[import-not-found,unused-ignore] | ||||||
|  |     ) | ||||||
|  | except ImportError: | ||||||
|  |     try: | ||||||
|  |         from pydantic import BaseSettings  # type: ignore[no-redef,unused-ignore] | ||||||
|  |     except ImportError: | ||||||
|  | 
 | ||||||
|  |         class BaseSettings:  # type: ignore[no-redef] | ||||||
|  |             """No-op fallback""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| from pytest import fixture, mark, raises | from pytest import fixture, mark, raises | ||||||
| 
 | 
 | ||||||
|  | from dependency_injector import providers | ||||||
| 
 | 
 | ||||||
| class Section11(pydantic.BaseModel): | pytestmark = mark.pydantic | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Section11(BaseModel): | ||||||
|     value1: int = 1 |     value1: int = 1 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Section12(pydantic.BaseModel): | class Section12(BaseModel): | ||||||
|     value2: int = 2 |     value2: int = 2 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Settings1(pydantic.BaseSettings): | class Settings1(BaseSettings): | ||||||
|     section1: Section11 = Section11() |     section1: Section11 = Section11() | ||||||
|     section2: Section12 = Section12() |     section2: Section12 = Section12() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Section21(pydantic.BaseModel): | class Section21(BaseModel): | ||||||
|     value1: int = 11 |     value1: int = 11 | ||||||
|     value11: int = 11 |     value11: int = 11 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Section3(pydantic.BaseModel): | class Section3(BaseModel): | ||||||
|     value3: int = 3 |     value3: int = 3 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Settings2(pydantic.BaseSettings): | class Settings2(BaseSettings): | ||||||
|     section1: Section21 = Section21() |     section1: Section21 = Section21() | ||||||
|     section3: Section3 = Section3() |     section3: Section3 = Section3() | ||||||
| 
 | 
 | ||||||
|  | @ -86,10 +103,10 @@ def test_copy(config, pydantic_settings_1, pydantic_settings_2): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_set_pydantic_settings(config): | def test_set_pydantic_settings(config): | ||||||
|     class Settings3(pydantic.BaseSettings): |     class Settings3(BaseSettings): | ||||||
|         ... |         ... | ||||||
| 
 | 
 | ||||||
|     class Settings4(pydantic.BaseSettings): |     class Settings4(BaseSettings): | ||||||
|         ... |         ... | ||||||
| 
 | 
 | ||||||
|     settings_3 = Settings3() |     settings_3 = Settings3() | ||||||
|  | @ -100,27 +117,27 @@ def test_set_pydantic_settings(config): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_file_does_not_exist(config): | def test_file_does_not_exist(config): | ||||||
|     config.set_pydantic_settings([pydantic.BaseSettings()]) |     config.set_pydantic_settings([BaseSettings()]) | ||||||
|     config.load() |     config.load() | ||||||
|     assert config() == {} |     assert config() == {} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mark.parametrize("config_type", ["strict"]) | @mark.parametrize("config_type", ["strict"]) | ||||||
| def test_file_does_not_exist_strict_mode(config): | def test_file_does_not_exist_strict_mode(config): | ||||||
|     config.set_pydantic_settings([pydantic.BaseSettings()]) |     config.set_pydantic_settings([BaseSettings()]) | ||||||
|     with raises(ValueError): |     with raises(ValueError): | ||||||
|         config.load() |         config.load() | ||||||
|     assert config() == {} |     assert config() == {} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_required_file_does_not_exist(config): | def test_required_file_does_not_exist(config): | ||||||
|     config.set_pydantic_settings([pydantic.BaseSettings()]) |     config.set_pydantic_settings([BaseSettings()]) | ||||||
|     with raises(ValueError): |     with raises(ValueError): | ||||||
|         config.load(required=True) |         config.load(required=True) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mark.parametrize("config_type", ["strict"]) | @mark.parametrize("config_type", ["strict"]) | ||||||
| def test_not_required_file_does_not_exist_strict_mode(config): | def test_not_required_file_does_not_exist_strict_mode(config): | ||||||
|     config.set_pydantic_settings([pydantic.BaseSettings()]) |     config.set_pydantic_settings([BaseSettings()]) | ||||||
|     config.load(required=False) |     config.load(required=False) | ||||||
|     assert config() == {} |     assert config() == {} | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| from httpx import AsyncClient | from httpx import ASGITransport, AsyncClient | ||||||
| from pytest import fixture, mark | from pytest import fixture, mark | ||||||
| from pytest_asyncio import fixture as aio_fixture | from pytest_asyncio import fixture as aio_fixture | ||||||
| 
 | 
 | ||||||
|  | @ -19,7 +19,7 @@ from wiringfastapi import web | ||||||
| 
 | 
 | ||||||
| @aio_fixture | @aio_fixture | ||||||
| async def async_client(): | async def async_client(): | ||||||
|     client = AsyncClient(app=web.app, base_url="http://test") |     client = AsyncClient(transport=ASGITransport(app=web.app), base_url="http://test") | ||||||
|     yield client |     yield client | ||||||
|     await client.aclose() |     await client.aclose() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										34
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								tox.ini
									
									
									
									
									
								
							|  | @ -1,7 +1,7 @@ | ||||||
| [tox] | [tox] | ||||||
| parallel_show_output = true | parallel_show_output = true | ||||||
| envlist= | envlist= | ||||||
|     coveralls, pylint, flake8, pydocstyle, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, pypy3.9, pypy3.10 |     coveralls, pylint, flake8, pydocstyle, pydantic-v1, pydantic-v2, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, pypy3.9, pypy3.10 | ||||||
| 
 | 
 | ||||||
| [testenv] | [testenv] | ||||||
| deps= | deps= | ||||||
|  | @ -23,6 +23,32 @@ extras= | ||||||
|     yaml |     yaml | ||||||
| commands = pytest -c tests/.configs/pytest.ini | commands = pytest -c tests/.configs/pytest.ini | ||||||
| python_files = test_*_py3*.py | python_files = test_*_py3*.py | ||||||
|  | setenv = | ||||||
|  |     COVERAGE_RCFILE = pyproject.toml | ||||||
|  | 
 | ||||||
|  | [testenv:.pkg] | ||||||
|  | passenv = DEPENDENCY_INJECTOR_* | ||||||
|  | 
 | ||||||
|  | [testenv:pydantic-{v1,v2}] | ||||||
|  | description = run tests with different pydantic versions | ||||||
|  | base_python = python3.12 | ||||||
|  | deps = | ||||||
|  |     v1: pydantic<2 | ||||||
|  |     v2: pydantic-settings | ||||||
|  |     pytest | ||||||
|  |     pytest-asyncio | ||||||
|  |     -rrequirements.txt | ||||||
|  |     typing_extensions | ||||||
|  |     httpx | ||||||
|  |     fastapi | ||||||
|  |     flask<2.2 | ||||||
|  |     aiohttp<=3.9.0b1 | ||||||
|  |     numpy | ||||||
|  |     scipy | ||||||
|  |     boto3 | ||||||
|  |     mypy_boto3_s3 | ||||||
|  |     werkzeug<=2.2.2 | ||||||
|  | commands = pytest -c tests/.configs/pytest.ini -m pydantic | ||||||
| 
 | 
 | ||||||
| [testenv:coveralls] | [testenv:coveralls] | ||||||
| passenv = GITHUB_*, COVERALLS_*, DEPENDENCY_INJECTOR_* | passenv = GITHUB_*, COVERALLS_*, DEPENDENCY_INJECTOR_* | ||||||
|  | @ -34,8 +60,8 @@ deps= | ||||||
|     coveralls>=4 |     coveralls>=4 | ||||||
| commands= | commands= | ||||||
|     coverage erase |     coverage erase | ||||||
|     coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini |     coverage run -m pytest -c tests/.configs/pytest.ini | ||||||
|     coverage report --rcfile=./.coveragerc |     coverage report | ||||||
|     coveralls |     coveralls | ||||||
| 
 | 
 | ||||||
| [testenv:pypy3.9] | [testenv:pypy3.9] | ||||||
|  | @ -60,7 +86,7 @@ deps= | ||||||
|     flask<2.2 |     flask<2.2 | ||||||
|     werkzeug<=2.2.2 |     werkzeug<=2.2.2 | ||||||
| commands= | commands= | ||||||
|     - pylint -f colorized --rcfile=./.pylintrc src/dependency_injector |     - pylint -f colorized src/dependency_injector | ||||||
| 
 | 
 | ||||||
| [testenv:flake8] | [testenv:flake8] | ||||||
| deps= | deps= | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user