mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-02-05 14:10:49 +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:
|
||||
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
|
||||
|
@ -50,7 +61,6 @@ jobs:
|
|||
with:
|
||||
python-version: 3.12
|
||||
- run: pip install tox 'cython>=3,<4'
|
||||
- run: make cythonize
|
||||
- run: tox -vv
|
||||
env:
|
||||
TOXENV: coveralls
|
||||
|
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -63,13 +63,11 @@ 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/
|
||||
|
|
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)
|
||||
|
||||
CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
|
||||
|
||||
CYTHON_DIRECTIVES = -Xlanguage_level=3
|
||||
|
||||
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 -c tests/.configs/pytest.ini
|
||||
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)*
|
||||
|
||||
|
|
|
@ -7,6 +7,14 @@ that were made in every particular version.
|
|||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
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
|
||||
--------
|
||||
- Add support for Python 3.13.
|
||||
|
|
|
@ -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
|
||||
-------------------------
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
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:
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
) as client:
|
||||
yield client
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
from giphynavigator.application import app
|
||||
from giphynavigator.giphy import GiphyClient
|
||||
|
@ -11,7 +11,10 @@ from giphynavigator.giphy import GiphyClient
|
|||
|
||||
@pytest.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
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
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
|
||||
coverage
|
||||
flake8
|
||||
flake8-pyproject
|
||||
pydocstyle
|
||||
sphinx_autobuild
|
||||
pip
|
||||
|
|
150
setup.py
150
setup.py
|
@ -1,128 +1,42 @@
|
|||
"""`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
|
||||
|
||||
|
||||
def _open(filename):
|
||||
if sys.version_info[0] == 2:
|
||||
return open(filename)
|
||||
return open(filename, encoding="utf-8")
|
||||
|
||||
|
||||
# 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"
|
||||
defined_macros = []
|
||||
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:
|
||||
defined_macros.extend(
|
||||
[
|
||||
("CYTHON_TRACE", "1"),
|
||||
("CYTHON_TRACE_NOGIL", "1"),
|
||||
("CYTHON_CLINE_IN_TRACEBACK", "1"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
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(
|
||||
ext_modules=cythonize(
|
||||
[
|
||||
Extension(
|
||||
"*",
|
||||
["src/**/*.pyx"],
|
||||
define_macros=defined_macros,
|
||||
),
|
||||
],
|
||||
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 :: 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",
|
||||
])
|
||||
annotate=debug,
|
||||
show_all_warnings=True,
|
||||
compiler_directives=compiler_directives,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Top-level package."""
|
||||
|
||||
__version__ = "4.43.0"
|
||||
__version__ = "4.44.0"
|
||||
"""Version number.
|
||||
|
||||
: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:
|
||||
yaml = None
|
||||
|
||||
has_pydantic_settings = True
|
||||
cdef bint pydantic_v1 = False
|
||||
cdef str pydantic_module = "pydantic_settings"
|
||||
cdef str pydantic_extra = "pydantic2"
|
||||
|
||||
try:
|
||||
import pydantic
|
||||
from pydantic_settings import BaseSettings as PydanticSettings
|
||||
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 (
|
||||
Error,
|
||||
|
@ -149,6 +164,31 @@ cdef int ASYNC_MODE_DISABLED = 2
|
|||
cdef set __iscoroutine_typecache = set()
|
||||
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):
|
||||
"""Base provider class.
|
||||
|
@ -1786,36 +1826,20 @@ cdef class ConfigurationOption(Provider):
|
|||
Loaded configuration is merged recursively over existing configuration.
|
||||
|
||||
: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.
|
||||
: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]
|
||||
|
||||
: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):
|
||||
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)
|
||||
self.from_dict(pydantic_settings_to_dict(settings, kwargs), required=required)
|
||||
|
||||
def from_dict(self, options, required=UNDEFINED):
|
||||
"""Load configuration from the dictionary.
|
||||
|
@ -2355,7 +2379,8 @@ cdef class Configuration(Object):
|
|||
Loaded configuration is merged recursively over existing configuration.
|
||||
|
||||
: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.
|
||||
:type required: bool
|
||||
|
@ -2365,26 +2390,8 @@ cdef class Configuration(Object):
|
|||
|
||||
: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):
|
||||
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)
|
||||
self.from_dict(pydantic_settings_to_dict(settings, kwargs), required=required)
|
||||
|
||||
def from_dict(self, options, required=UNDEFINED):
|
||||
"""Load configuration from the dictionary.
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
testpaths = tests/unit/
|
||||
python_files = test_*_py3*.py
|
||||
asyncio_mode = auto
|
||||
markers =
|
||||
pydantic: Tests with Pydantic as a dependency
|
||||
filterwarnings =
|
||||
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
|
||||
|
|
|
@ -1,41 +1,60 @@
|
|||
"""Configuration.from_pydantic() tests."""
|
||||
|
||||
import pydantic
|
||||
from dependency_injector import providers, errors
|
||||
from pydantic import BaseModel
|
||||
|
||||
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 dependency_injector import errors, providers
|
||||
|
||||
class Section11(pydantic.BaseModel):
|
||||
value1 = 1
|
||||
pytestmark = mark.pydantic
|
||||
|
||||
|
||||
class Section12(pydantic.BaseModel):
|
||||
value2 = 2
|
||||
class Section11(BaseModel):
|
||||
value1: int = 1
|
||||
|
||||
|
||||
class Settings1(pydantic.BaseSettings):
|
||||
section1 = Section11()
|
||||
section2 = Section12()
|
||||
class Section12(BaseModel):
|
||||
value2: int = 2
|
||||
|
||||
|
||||
class Section21(pydantic.BaseModel):
|
||||
value1 = 11
|
||||
value11 = 11
|
||||
class Settings1(BaseSettings):
|
||||
section1: Section11 = Section11()
|
||||
section2: Section12 = Section12()
|
||||
|
||||
|
||||
class Section3(pydantic.BaseModel):
|
||||
value3 = 3
|
||||
class Section21(BaseModel):
|
||||
value1: int = 11
|
||||
value11: int = 11
|
||||
|
||||
|
||||
class Settings2(pydantic.BaseSettings):
|
||||
section1 = Section21()
|
||||
section3 = Section3()
|
||||
class Section3(BaseModel):
|
||||
value3: int = 3
|
||||
|
||||
|
||||
class Settings2(BaseSettings):
|
||||
section1: Section21 = Section21()
|
||||
section3: Section3 = Section3()
|
||||
|
||||
|
||||
@fixture
|
||||
def no_pydantic_module_installed():
|
||||
providers.pydantic = None
|
||||
has_pydantic_settings = providers.has_pydantic_settings
|
||||
providers.has_pydantic_settings = False
|
||||
yield
|
||||
providers.pydantic = pydantic
|
||||
providers.has_pydantic_settings = has_pydantic_settings
|
||||
|
||||
|
||||
def test(config):
|
||||
|
@ -82,66 +101,70 @@ def test_merge(config):
|
|||
|
||||
|
||||
def test_empty_settings(config):
|
||||
config.from_pydantic(pydantic.BaseSettings())
|
||||
config.from_pydantic(BaseSettings())
|
||||
assert config() == {}
|
||||
|
||||
|
||||
@mark.parametrize("config_type", ["strict"])
|
||||
def test_empty_settings_strict_mode(config):
|
||||
with raises(ValueError):
|
||||
config.from_pydantic(pydantic.BaseSettings())
|
||||
config.from_pydantic(BaseSettings())
|
||||
|
||||
|
||||
def test_option_empty_settings(config):
|
||||
config.option.from_pydantic(pydantic.BaseSettings())
|
||||
config.option.from_pydantic(BaseSettings())
|
||||
assert config.option() == {}
|
||||
|
||||
|
||||
@mark.parametrize("config_type", ["strict"])
|
||||
def test_option_empty_settings_strict_mode(config):
|
||||
with raises(ValueError):
|
||||
config.option.from_pydantic(pydantic.BaseSettings())
|
||||
config.option.from_pydantic(BaseSettings())
|
||||
|
||||
|
||||
def test_required_empty_settings(config):
|
||||
with raises(ValueError):
|
||||
config.from_pydantic(pydantic.BaseSettings(), required=True)
|
||||
config.from_pydantic(BaseSettings(), required=True)
|
||||
|
||||
|
||||
def test_required_option_empty_settings(config):
|
||||
with raises(ValueError):
|
||||
config.option.from_pydantic(pydantic.BaseSettings(), required=True)
|
||||
config.option.from_pydantic(BaseSettings(), required=True)
|
||||
|
||||
|
||||
@mark.parametrize("config_type", ["strict"])
|
||||
def test_not_required_empty_settings_strict_mode(config):
|
||||
config.from_pydantic(pydantic.BaseSettings(), required=False)
|
||||
config.from_pydantic(BaseSettings(), required=False)
|
||||
assert config() == {}
|
||||
|
||||
|
||||
@mark.parametrize("config_type", ["strict"])
|
||||
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": {}}
|
||||
|
||||
|
||||
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({})
|
||||
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):
|
||||
with raises(errors.Error) as error:
|
||||
config.option.from_pydantic({})
|
||||
assert error.value.args[0] == (
|
||||
"Unable to recognize settings instance, expect \"pydantic.BaseSettings\", "
|
||||
with raises(
|
||||
errors.Error,
|
||||
match=(
|
||||
r"Unable to recognize settings instance, expect \"pydantic(?:_settings)?\.BaseSettings\", "
|
||||
"got {0} instead".format({})
|
||||
)
|
||||
),
|
||||
):
|
||||
config.option.from_pydantic({})
|
||||
|
||||
|
||||
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")
|
||||
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())
|
||||
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")
|
||||
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())
|
||||
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,35 +1,52 @@
|
|||
"""Configuration.from_pydantic() tests."""
|
||||
|
||||
import pydantic
|
||||
from dependency_injector import providers
|
||||
from pydantic import BaseModel
|
||||
|
||||
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 dependency_injector import providers
|
||||
|
||||
class Section11(pydantic.BaseModel):
|
||||
pytestmark = mark.pydantic
|
||||
|
||||
|
||||
class Section11(BaseModel):
|
||||
value1: int = 1
|
||||
|
||||
|
||||
class Section12(pydantic.BaseModel):
|
||||
class Section12(BaseModel):
|
||||
value2: int = 2
|
||||
|
||||
|
||||
class Settings1(pydantic.BaseSettings):
|
||||
class Settings1(BaseSettings):
|
||||
section1: Section11 = Section11()
|
||||
section2: Section12 = Section12()
|
||||
|
||||
|
||||
class Section21(pydantic.BaseModel):
|
||||
class Section21(BaseModel):
|
||||
value1: int = 11
|
||||
value11: int = 11
|
||||
|
||||
|
||||
class Section3(pydantic.BaseModel):
|
||||
class Section3(BaseModel):
|
||||
value3: int = 3
|
||||
|
||||
|
||||
class Settings2(pydantic.BaseSettings):
|
||||
class Settings2(BaseSettings):
|
||||
section1: Section21 = Section21()
|
||||
section3: Section3= Section3()
|
||||
section3: Section3 = Section3()
|
||||
|
||||
|
||||
@fixture
|
||||
|
@ -86,10 +103,10 @@ def test_copy(config, pydantic_settings_1, pydantic_settings_2):
|
|||
|
||||
|
||||
def test_set_pydantic_settings(config):
|
||||
class Settings3(pydantic.BaseSettings):
|
||||
class Settings3(BaseSettings):
|
||||
...
|
||||
|
||||
class Settings4(pydantic.BaseSettings):
|
||||
class Settings4(BaseSettings):
|
||||
...
|
||||
|
||||
settings_3 = Settings3()
|
||||
|
@ -100,27 +117,27 @@ def test_set_pydantic_settings(config):
|
|||
|
||||
|
||||
def test_file_does_not_exist(config):
|
||||
config.set_pydantic_settings([pydantic.BaseSettings()])
|
||||
config.set_pydantic_settings([BaseSettings()])
|
||||
config.load()
|
||||
assert config() == {}
|
||||
|
||||
|
||||
@mark.parametrize("config_type", ["strict"])
|
||||
def test_file_does_not_exist_strict_mode(config):
|
||||
config.set_pydantic_settings([pydantic.BaseSettings()])
|
||||
config.set_pydantic_settings([BaseSettings()])
|
||||
with raises(ValueError):
|
||||
config.load()
|
||||
assert config() == {}
|
||||
|
||||
|
||||
def test_required_file_does_not_exist(config):
|
||||
config.set_pydantic_settings([pydantic.BaseSettings()])
|
||||
config.set_pydantic_settings([BaseSettings()])
|
||||
with raises(ValueError):
|
||||
config.load(required=True)
|
||||
|
||||
|
||||
@mark.parametrize("config_type", ["strict"])
|
||||
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)
|
||||
assert config() == {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from httpx import AsyncClient
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from pytest import fixture, mark
|
||||
from pytest_asyncio import fixture as aio_fixture
|
||||
|
||||
|
@ -19,7 +19,7 @@ from wiringfastapi import web
|
|||
|
||||
@aio_fixture
|
||||
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
|
||||
await client.aclose()
|
||||
|
||||
|
|
34
tox.ini
34
tox.ini
|
@ -1,7 +1,7 @@
|
|||
[tox]
|
||||
parallel_show_output = true
|
||||
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]
|
||||
deps=
|
||||
|
@ -23,6 +23,32 @@ extras=
|
|||
yaml
|
||||
commands = pytest -c tests/.configs/pytest.ini
|
||||
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]
|
||||
passenv = GITHUB_*, COVERALLS_*, DEPENDENCY_INJECTOR_*
|
||||
|
@ -34,8 +60,8 @@ deps=
|
|||
coveralls>=4
|
||||
commands=
|
||||
coverage erase
|
||||
coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini
|
||||
coverage report --rcfile=./.coveragerc
|
||||
coverage run -m pytest -c tests/.configs/pytest.ini
|
||||
coverage report
|
||||
coveralls
|
||||
|
||||
[testenv:pypy3.9]
|
||||
|
@ -60,7 +86,7 @@ deps=
|
|||
flask<2.2
|
||||
werkzeug<=2.2.2
|
||||
commands=
|
||||
- pylint -f colorized --rcfile=./.pylintrc src/dependency_injector
|
||||
- pylint -f colorized src/dependency_injector
|
||||
|
||||
[testenv:flake8]
|
||||
deps=
|
||||
|
|
Loading…
Reference in New Issue
Block a user