mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-03-03 11:15:47 +03:00
Merge branch 'release/4.11.0' into master
This commit is contained in:
commit
78479c65e6
126
.github/workflows/publishing.yml
vendored
Normal file
126
.github/workflows/publishing.yml
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
name: Publishing
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
|
||||
tests:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: 3.9
|
||||
|
||||
linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: ${{ matrix.toxenv }}
|
||||
|
||||
build-sdist:
|
||||
name: Build source tarball
|
||||
needs: [tests, linters]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: python setup.py sdist
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: ./dist/*
|
||||
|
||||
build-wheels:
|
||||
name: Build wheels
|
||||
needs: [tests, linters]
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install cibuildwheel==1.8.0
|
||||
- name: Install Visual C++ for Python 2.7 on Windows
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
choco install vcpython27 -f -y
|
||||
- run: cibuildwheel --output-dir wheelhouse
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
build-wheels-linux-aarch64:
|
||||
name: Build wheels (ubuntu-latest-aarch64)
|
||||
needs: [tests, linters]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install cibuildwheel==1.8.0
|
||||
- run: cibuildwheel --archs aarch64 --output-dir wheelhouse
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
publish:
|
||||
name: Publish on PyPI
|
||||
needs: [build-sdist, build-wheels, build-wheels-linux-aarch64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: artifact
|
||||
path: dist
|
||||
- uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
publish-docs:
|
||||
name: Publish docs
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install -r requirements-doc.txt
|
||||
- run: pip install awscli
|
||||
- run: pip install -e .
|
||||
- run: (cd docs && make clean html)
|
||||
- run: |
|
||||
aws s3 sync docs/_build/html s3://python-dependency-injector-docs --delete
|
||||
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --path "/*" > /dev/null
|
||||
echo "Cache invalidation triggered"
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
|
55
.github/workflows/tests-and-linters.yml
vendored
Normal file
55
.github/workflows/tests-and-linters.yml
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
name: Tests and linters
|
||||
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
|
||||
test-on-different-versions:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: ${{ matrix.python-version }}
|
||||
|
||||
test-coverage:
|
||||
name: Run tests with coverage
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DEPENDENCY_INJECTOR_DEBUG_MODE: 1
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install tox cython
|
||||
- run: make cythonize
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: coveralls
|
||||
|
||||
linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: ${{ matrix.toxenv }}
|
132
.travis.yml
132
.travis.yml
|
@ -1,132 +0,0 @@
|
|||
os: linux
|
||||
dist: xenial
|
||||
language: python
|
||||
jobs:
|
||||
include:
|
||||
- python: 3.9
|
||||
env: TOXENV=coveralls DEPENDENCY_INJECTOR_DEBUG_MODE=1
|
||||
install:
|
||||
- pip install tox
|
||||
- pip install cython
|
||||
- make cythonize
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=pylint
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=flake8
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=pydocstyle
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=mypy
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 2.7
|
||||
env: TOXENV=py27
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.4
|
||||
env: TOXENV=py34
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.5
|
||||
env: TOXENV=py35
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.6
|
||||
env: TOXENV=py36
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.7
|
||||
env: TOXENV=py37
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.8
|
||||
env: TOXENV=py38
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.9
|
||||
env: TOXENV=py39
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: pypy
|
||||
env: TOXENV=pypy
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: pypy3
|
||||
env: TOXENV=pypy3
|
||||
install: pip install tox
|
||||
script: tox
|
||||
- python: 3.8
|
||||
if: tag IS present
|
||||
env: TWINE_USERNAME=__token__
|
||||
install: pip install pip --upgrade
|
||||
script: python setup.py sdist
|
||||
after_success:
|
||||
- python3 -m pip install twine
|
||||
- python3 -m twine upload dist/*
|
||||
- services: docker
|
||||
if: tag IS present
|
||||
env: TWINE_USERNAME=__token__
|
||||
install: python3 -m pip install cibuildwheel==1.6.3
|
||||
script: python3 -m cibuildwheel --output-dir wheelhouse
|
||||
after_success:
|
||||
- python3 -m pip install --upgrade --upgrade-strategy eager twine
|
||||
- python3 -m twine upload wheelhouse/*.whl
|
||||
- services: docker
|
||||
arch: arm64
|
||||
if: tag IS present
|
||||
env: TWINE_USERNAME=__token__
|
||||
install: python3 -m pip install cibuildwheel==1.6.3
|
||||
script: python3 -m cibuildwheel --output-dir wheelhouse
|
||||
after_success:
|
||||
- python3 -m pip install --upgrade --upgrade-strategy eager twine
|
||||
- python3 -m twine upload wheelhouse/*.whl
|
||||
- os: osx
|
||||
if: tag IS present
|
||||
language: shell
|
||||
osx_image: xcode10.2
|
||||
env: TWINE_USERNAME=__token__
|
||||
install: python3 -m pip install cibuildwheel==1.6.3
|
||||
script: python3 -m cibuildwheel --output-dir wheelhouse
|
||||
after_success:
|
||||
- python3 -m pip install --upgrade --upgrade-strategy eager twine
|
||||
- python3 -m twine upload wheelhouse/*.whl
|
||||
- os: windows
|
||||
if: tag IS present
|
||||
language: shell
|
||||
env: TWINE_USERNAME=__token__
|
||||
before_install:
|
||||
- choco install python --version 3.8.6
|
||||
- export PATH="/c/Python38:/c/Python38/Scripts:$PATH"
|
||||
- ln -s /c/Python38/python.exe /c/Python38/python3.exe
|
||||
install:
|
||||
- python3 -m pip install certifi cibuildwheel==1.6.3
|
||||
- export SSL_CERT_FILE=`python3 -c "import certifi;print(certifi.where())"`
|
||||
- echo $SSL_CERT_FILE
|
||||
script: python -m cibuildwheel --output-dir wheelhouse
|
||||
after_success:
|
||||
- python -m pip install --upgrade --upgrade-strategy eager twine
|
||||
- python -m twine upload wheelhouse/*.whl
|
||||
- python: 3.8
|
||||
if: branch = master
|
||||
install:
|
||||
- pip install -r requirements-doc.txt
|
||||
- pip install awscli
|
||||
- pip install -e .
|
||||
script: (cd docs && make clean html)
|
||||
after_success:
|
||||
- aws s3 sync docs/_build/html s3://python-dependency-injector-docs --delete
|
||||
- aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DISTRIBUTION_ID} --path "/*" > /dev/null
|
||||
- echo "Cache invalidation triggered"
|
||||
echo "Result: OK"
|
||||
- python -m twine upload wheelhouse/*.whl
|
||||
notifications:
|
||||
slack:
|
||||
rooms:
|
||||
secure: CdWDgKnfYW7vvvoH3nS3yg3TcNZiYLRUyEp6ukQ4rQiiuR4+ltuvyGyFJWgP8r7VVJ9yHkB0jebCKWLUMsAEt1my33B6eMDEVefovpkdh2eJjGswmm80brt0EJULpgwPOtB1U47Mwca8L5jDW4KSv9RypUFRgn8eHDoWw6LKf5g=
|
|
@ -35,8 +35,8 @@
|
|||
:target: https://pypi.org/project/dependency-injector/
|
||||
:alt: Wheel
|
||||
|
||||
.. image:: https://api.travis-ci.com/ets-labs/python-dependency-injector.svg?branch=master
|
||||
:target: https://travis-ci.com/github/ets-labs/python-dependency-injector
|
||||
.. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
|
||||
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||
:alt: Build Status
|
||||
|
||||
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
||||
|
|
|
@ -50,8 +50,8 @@ Dependency Injector --- Dependency injection framework for Python
|
|||
:target: https://pypi.org/project/dependency-injector/
|
||||
:alt: Wheel
|
||||
|
||||
.. image:: https://api.travis-ci.com/ets-labs/python-dependency-injector.svg?branch=master
|
||||
:target: https://travis-ci.com/github/ets-labs/python-dependency-injector
|
||||
.. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
|
||||
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||
:alt: Build Status
|
||||
|
||||
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
||||
|
|
|
@ -7,6 +7,27 @@ that were made in every particular version.
|
|||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
follows `Semantic versioning`_
|
||||
|
||||
4.11.0
|
||||
------
|
||||
- Add ``loader`` argument to the configuration provider ``Configuration.from_yaml(..., loader=...)``
|
||||
to override the default YAML loader.
|
||||
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for suggesting an improvement.
|
||||
- Make security improvement: change default YAML loader to the custom ``yaml.SafeLoader`` with a support
|
||||
of environment variables interpolation.
|
||||
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for suggesting an improvement.
|
||||
- Update configuration provider ``.from_*()`` methods to raise an exception in strict mode if
|
||||
configuration file does not exist or configuration data is undefined.
|
||||
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for suggesting an improvement.
|
||||
- Add ``required`` argument to the configuration provider ``.from_*()`` methods to specify
|
||||
mandatory configuration sources.
|
||||
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for suggesting an improvement.
|
||||
- Fix a bug with asynchronous injections: async providers do not work with async dependencies.
|
||||
See issue: `#368 <https://github.com/ets-labs/python-dependency-injector/issues/368>`_.
|
||||
Thanks `@kolypto <https://github.com/kolypto>`_ for the bug report.
|
||||
- Refactor asynchronous injections.
|
||||
- Add extra tests for asynchronous injections.
|
||||
- Migrate CI to Github Actions.
|
||||
|
||||
4.10.3
|
||||
------
|
||||
- Fix a bug in the ``Configuration`` provider: strict mode didn't work when provider
|
||||
|
|
|
@ -21,6 +21,10 @@ Configuration provider
|
|||
|
||||
It implements the principle "use first, define later".
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:backlinks: none
|
||||
|
||||
Loading from an INI file
|
||||
------------------------
|
||||
|
||||
|
@ -57,9 +61,19 @@ where ``examples/providers/configuration/config.yml`` is:
|
|||
.. literalinclude:: ../../examples/providers/configuration/config.yml
|
||||
:language: ini
|
||||
|
||||
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation. Use
|
||||
``${ENV_NAME}`` format in the configuration file to substitute value of the environment
|
||||
variable ``ENV_NAME``.
|
||||
:py:meth:`Configuration.from_yaml` method uses custom version of ``yaml.SafeLoader``.
|
||||
|
||||
The loader supports environment variables interpolation. Use ``${ENV_NAME}`` format
|
||||
in the configuration file to substitute value of the environment variable ``ENV_NAME``.
|
||||
|
||||
You can also specify a YAML loader as an argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
container.config.from_yaml('config.yml', loader=yaml.UnsafeLoader)
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -113,6 +127,43 @@ where ``examples/providers/configuration/config.local.yml`` is:
|
|||
.. literalinclude:: ../../examples/providers/configuration/config.local.yml
|
||||
:language: ini
|
||||
|
||||
Mandatory and optional sources
|
||||
------------------------------
|
||||
|
||||
By default, methods ``.from_yaml()`` and ``.from_ini()`` ignore errors if configuration file does not exist.
|
||||
You can use this to specify optional configuration files.
|
||||
|
||||
If configuration file is mandatory, use ``required`` argument. Configuration provider will raise an error
|
||||
if required file does not exist.
|
||||
|
||||
You can also use ``required`` argument when loading configuration from dictionaries and environment variables.
|
||||
|
||||
Mandatory YAML file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_yaml('config.yaml', required=True)
|
||||
|
||||
Mandatory INI file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_ini('config.ini', required=True)
|
||||
|
||||
Mandatory dictionary:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_dict(config_dict, required=True)
|
||||
|
||||
Mandatory environment variable:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.api_key.from_env('API_KEY', required=True)
|
||||
|
||||
See also: :ref:`configuration-strict-mode`.
|
||||
|
||||
Specifying the value type
|
||||
-------------------------
|
||||
|
||||
|
@ -143,6 +194,8 @@ With the ``.as_(callback, *args, **kwargs)`` you can specify a function that wil
|
|||
before the injection. The value from the config will be passed as a first argument. The returned
|
||||
value will be injected. Parameters ``*args`` and ``**kwargs`` are handled as any other injections.
|
||||
|
||||
.. _configuration-strict-mode:
|
||||
|
||||
Strict mode and required options
|
||||
--------------------------------
|
||||
|
||||
|
@ -154,7 +207,57 @@ on access to any undefined option.
|
|||
:lines: 3-
|
||||
:emphasize-lines: 12
|
||||
|
||||
You can also use ``.required()`` option modifier when making an injection.
|
||||
Methods ``.from_*()`` in strict mode raise an exception if configuration file does not exist or
|
||||
configuration data is undefined:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10,15,20,25
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(strict=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
|
||||
try:
|
||||
container.config.from_yaml('does-not_exist.yml') # raise exception
|
||||
except FileNotFoundError:
|
||||
...
|
||||
|
||||
try:
|
||||
container.config.from_ini('does-not_exist.ini') # raise exception
|
||||
except FileNotFoundError:
|
||||
...
|
||||
|
||||
try:
|
||||
container.config.from_env('UNDEFINED_ENV_VAR') # raise exception
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
try:
|
||||
container.config.from_dict({}) # raise exception
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
You can override ``.from_*()`` methods behaviour in strict mode using ``required`` argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(strict=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.from_yaml('config.local.yml', required=False)
|
||||
|
||||
You can also use ``.required()`` option modifier when making an injection. It does not require to switch
|
||||
configuration provider to strict mode.
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_required.py
|
||||
:language: python
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Top-level package."""
|
||||
|
||||
__version__ = '4.10.3'
|
||||
__version__ = '4.11.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
|
@ -5,6 +5,7 @@ try:
|
|||
except ImportError:
|
||||
asyncio = None
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
|
||||
cimport cython
|
||||
|
@ -419,18 +420,27 @@ cdef inline object __provide_keyword_args(
|
|||
cdef inline object __awaitable_args_kwargs_future(object args, list awaitables):
|
||||
future_result = asyncio.Future()
|
||||
|
||||
args_future = asyncio.Future()
|
||||
args_future.set_result((future_result, args, awaitables))
|
||||
|
||||
args_ready = asyncio.gather(args_future, *[value for _, value in awaitables])
|
||||
args_ready.add_done_callback(__async_prepare_args_kwargs_callback)
|
||||
args_ready = asyncio.gather(*[value for _, value in awaitables])
|
||||
args_ready.add_done_callback(
|
||||
functools.partial(
|
||||
__async_prepare_args_kwargs_callback,
|
||||
future_result,
|
||||
args,
|
||||
awaitables,
|
||||
),
|
||||
)
|
||||
asyncio.ensure_future(args_ready)
|
||||
|
||||
return future_result
|
||||
|
||||
|
||||
cdef inline void __async_prepare_args_kwargs_callback(object future):
|
||||
(future_result, args, awaitables), *awaited = future.result()
|
||||
cdef inline void __async_prepare_args_kwargs_callback(
|
||||
object future_result,
|
||||
object args,
|
||||
object awaitables,
|
||||
object future,
|
||||
):
|
||||
awaited = future.result()
|
||||
for value, (key, _) in zip(awaited, awaitables):
|
||||
args[key] = value
|
||||
future_result.set_result(args)
|
||||
|
@ -460,17 +470,20 @@ cdef inline object __provide_attributes(tuple attributes, int attributes_len):
|
|||
cdef inline object __async_inject_attributes(future_instance, future_attributes):
|
||||
future_result = asyncio.Future()
|
||||
|
||||
future = asyncio.Future()
|
||||
future.set_result(future_result)
|
||||
|
||||
attributes_ready = asyncio.gather(future, future_instance, future_attributes)
|
||||
attributes_ready.add_done_callback(__async_inject_attributes_callback)
|
||||
attributes_ready = asyncio.gather(future_instance, future_attributes)
|
||||
attributes_ready.add_done_callback(
|
||||
functools.partial(
|
||||
__async_inject_attributes_callback,
|
||||
future_result,
|
||||
),
|
||||
)
|
||||
asyncio.ensure_future(attributes_ready)
|
||||
|
||||
return future_result
|
||||
|
||||
cdef inline void __async_inject_attributes_callback(future):
|
||||
future_result, instance, attributes = future.result()
|
||||
|
||||
cdef inline void __async_inject_attributes_callback(object future_result, object future):
|
||||
instance, attributes = future.result()
|
||||
__inject_attributes(instance, attributes)
|
||||
future_result.set_result(instance)
|
||||
|
||||
|
@ -516,11 +529,14 @@ cdef inline object __call(
|
|||
|
||||
future_result = asyncio.Future()
|
||||
|
||||
future = asyncio.Future()
|
||||
future.set_result((future_result, call))
|
||||
|
||||
args_kwargs_ready = asyncio.gather(future, args, kwargs)
|
||||
args_kwargs_ready.add_done_callback(__async_call_callback)
|
||||
args_kwargs_ready = asyncio.gather(args, kwargs)
|
||||
args_kwargs_ready.add_done_callback(
|
||||
functools.partial(
|
||||
__async_call_callback,
|
||||
future_result,
|
||||
call,
|
||||
),
|
||||
)
|
||||
asyncio.ensure_future(args_kwargs_ready)
|
||||
|
||||
return future_result
|
||||
|
@ -528,12 +544,22 @@ cdef inline object __call(
|
|||
return call(*args, **kwargs)
|
||||
|
||||
|
||||
cdef inline void __async_call_callback(object future):
|
||||
(future_result, call), args, kwargs = future.result()
|
||||
cdef inline void __async_call_callback(object future_result, object call, object future):
|
||||
args, kwargs = future.result()
|
||||
result = call(*args, **kwargs)
|
||||
|
||||
if __isawaitable(result):
|
||||
result = asyncio.ensure_future(result)
|
||||
result.add_done_callback(functools.partial(__async_result_callback, future_result))
|
||||
return
|
||||
|
||||
future_result.set_result(result)
|
||||
|
||||
|
||||
cdef inline object __async_result_callback(object future_result, object future):
|
||||
future_result.set_result(future.result())
|
||||
|
||||
|
||||
cdef inline object __callable_call(Callable self, tuple args, dict kwargs):
|
||||
return __call(
|
||||
self.__provides,
|
||||
|
|
|
@ -20,6 +20,11 @@ from typing import (
|
|||
overload,
|
||||
)
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
from . import resources
|
||||
|
||||
|
||||
|
@ -149,10 +154,10 @@ class ConfigurationOption(Provider[Any]):
|
|||
def required(self) -> ConfigurationOption: ...
|
||||
def is_required(self) -> bool: ...
|
||||
def update(self, value: Any) -> None: ...
|
||||
def from_ini(self, filepath: Union[Path, str]) -> None: ...
|
||||
def from_yaml(self, filepath: Union[Path, str]) -> None: ...
|
||||
def from_dict(self, options: _Dict[str, Any]) -> None: ...
|
||||
def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
|
||||
def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
|
||||
def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any]=None) -> None: ...
|
||||
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
|
||||
def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
|
||||
|
||||
|
||||
class TypedConfigurationOption(Callable[T]):
|
||||
|
@ -170,10 +175,10 @@ class Configuration(Object[Any]):
|
|||
def set(self, selector: str, value: Any) -> OverridingContext: ...
|
||||
def reset_cache(self) -> None: ...
|
||||
def update(self, value: Any) -> None: ...
|
||||
def from_ini(self, filepath: Union[Path, str]) -> None: ...
|
||||
def from_yaml(self, filepath: Union[Path, str]) -> None: ...
|
||||
def from_dict(self, options: _Dict[str, Any]) -> None: ...
|
||||
def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
|
||||
def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
|
||||
def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any]=None) -> None: ...
|
||||
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
|
||||
def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
|
||||
|
||||
|
||||
class Factory(Provider[T]):
|
||||
|
@ -373,3 +378,9 @@ def deepcopy(instance: Any, memo: Optional[_Dict[Any, Any]] = None): Any: ...
|
|||
|
||||
|
||||
def merge_dicts(dict1: _Dict[Any, Any], dict2: _Dict[Any, Any]) -> _Dict[Any, Any]: ...
|
||||
|
||||
|
||||
if yaml:
|
||||
class YamlLoader(yaml.SafeLoader): ...
|
||||
else:
|
||||
class YamlLoader: ...
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
|
@ -53,14 +54,6 @@ else: # pragma: no cover
|
|||
copy.deepcopy(obj.im_self, memo),
|
||||
obj.im_class)
|
||||
|
||||
if yaml:
|
||||
yaml_env_marker_pattern = re.compile(r'\$\{([^}^{]+)\}')
|
||||
def yaml_env_marker_constructor(_, node):
|
||||
""""Replace environment variable marker with its value."""
|
||||
return os.path.expandvars(node.value)
|
||||
|
||||
yaml.add_implicit_resolver('!path', yaml_env_marker_pattern)
|
||||
yaml.add_constructor('!path', yaml_env_marker_constructor)
|
||||
|
||||
if sys.version_info[0] == 3:
|
||||
class EnvInterpolation(iniconfigparser.BasicInterpolation):
|
||||
|
@ -72,23 +65,48 @@ if sys.version_info[0] == 3:
|
|||
|
||||
def _parse_ini_file(filepath):
|
||||
parser = iniconfigparser.ConfigParser(interpolation=EnvInterpolation())
|
||||
parser.read(filepath)
|
||||
with open(filepath) as config_file:
|
||||
parser.read_file(config_file)
|
||||
return parser
|
||||
else:
|
||||
import StringIO
|
||||
|
||||
def _parse_ini_file(filepath):
|
||||
parser = iniconfigparser.ConfigParser()
|
||||
try:
|
||||
with open(filepath) as config_file:
|
||||
config_string = os.path.expandvars(config_file.read())
|
||||
except IOError:
|
||||
return parser
|
||||
else:
|
||||
parser.readfp(StringIO.StringIO(config_string))
|
||||
return parser
|
||||
with open(filepath) as config_file:
|
||||
config_string = os.path.expandvars(config_file.read())
|
||||
parser.readfp(StringIO.StringIO(config_string))
|
||||
return parser
|
||||
|
||||
|
||||
if yaml:
|
||||
# TODO: use SafeLoader without env interpolation by default in version 5.*
|
||||
yaml_env_marker_pattern = re.compile(r'\$\{([^}^{]+)\}')
|
||||
def yaml_env_marker_constructor(_, node):
|
||||
""""Replace environment variable marker with its value."""
|
||||
return os.path.expandvars(node.value)
|
||||
|
||||
yaml.add_implicit_resolver('!path', yaml_env_marker_pattern)
|
||||
yaml.add_constructor('!path', yaml_env_marker_constructor)
|
||||
|
||||
class YamlLoader(yaml.SafeLoader):
|
||||
"""Custom YAML loader.
|
||||
|
||||
Inherits ``yaml.SafeLoader`` and add environment variables interpolation.
|
||||
"""
|
||||
|
||||
YamlLoader.add_implicit_resolver('!path', yaml_env_marker_pattern, None)
|
||||
YamlLoader.add_constructor('!path', yaml_env_marker_constructor)
|
||||
else:
|
||||
class YamlLoader:
|
||||
"""Custom YAML loader.
|
||||
|
||||
Inherits ``yaml.SafeLoader`` and add environment variables interpolation.
|
||||
"""
|
||||
|
||||
|
||||
UNDEFINED = object()
|
||||
|
||||
cdef int ASYNC_MODE_UNDEFINED = 0
|
||||
cdef int ASYNC_MODE_ENABLED = 1
|
||||
cdef int ASYNC_MODE_DISABLED = 2
|
||||
|
@ -1170,14 +1188,12 @@ cdef class ConfigurationOption(Provider):
|
|||
:py:class:`Configuration` provider.
|
||||
"""
|
||||
|
||||
UNDEFINED = object()
|
||||
|
||||
def __init__(self, name, root, required=False):
|
||||
self.__name = name
|
||||
self.__root_ref = weakref.ref(root)
|
||||
self.__children = {}
|
||||
self.__required = required
|
||||
self.__cache = self.UNDEFINED
|
||||
self.__cache = UNDEFINED
|
||||
super().__init__()
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
|
@ -1226,7 +1242,7 @@ cdef class ConfigurationOption(Provider):
|
|||
|
||||
cpdef object _provide(self, tuple args, dict kwargs):
|
||||
"""Return new instance."""
|
||||
if self.__cache is not self.UNDEFINED:
|
||||
if self.__cache is not UNDEFINED:
|
||||
return self.__cache
|
||||
|
||||
root = self.__root_ref()
|
||||
|
@ -1278,7 +1294,7 @@ cdef class ConfigurationOption(Provider):
|
|||
raise Error('Configuration option does not support this method')
|
||||
|
||||
def reset_cache(self):
|
||||
self.__cache = self.UNDEFINED
|
||||
self.__cache = UNDEFINED
|
||||
for child in self.__children.values():
|
||||
child.reset_cache()
|
||||
|
||||
|
@ -1296,7 +1312,7 @@ cdef class ConfigurationOption(Provider):
|
|||
"""
|
||||
self.override(value)
|
||||
|
||||
def from_ini(self, filepath):
|
||||
def from_ini(self, filepath, required=UNDEFINED):
|
||||
"""Load configuration from the ini file.
|
||||
|
||||
Loaded configuration is merged recursively over existing configuration.
|
||||
|
@ -1304,9 +1320,20 @@ cdef class ConfigurationOption(Provider):
|
|||
:param filepath: Path to the configuration file.
|
||||
:type filepath: str
|
||||
|
||||
:param required: When required is True, raise an exception if file does not exist.
|
||||
:type required: bool
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
parser = _parse_ini_file(filepath)
|
||||
try:
|
||||
parser = _parse_ini_file(filepath)
|
||||
except IOError as exception:
|
||||
if required is not False \
|
||||
and (self._is_strict_mode_enabled() or required is True) \
|
||||
and exception.errno in (errno.ENOENT, errno.EISDIR):
|
||||
exception.strerror = 'Unable to load configuration file {0}'.format(exception.strerror)
|
||||
raise
|
||||
return
|
||||
|
||||
config = {}
|
||||
for section in parser.sections():
|
||||
|
@ -1317,7 +1344,7 @@ cdef class ConfigurationOption(Provider):
|
|||
current_config = {}
|
||||
self.override(merge_dicts(current_config, config))
|
||||
|
||||
def from_yaml(self, filepath):
|
||||
def from_yaml(self, filepath, required=UNDEFINED, loader=None):
|
||||
"""Load configuration from the yaml file.
|
||||
|
||||
Loaded configuration is merged recursively over existing configuration.
|
||||
|
@ -1325,6 +1352,12 @@ cdef class ConfigurationOption(Provider):
|
|||
:param filepath: Path to the configuration file.
|
||||
:type filepath: str
|
||||
|
||||
:param required: When required is True, raise an exception if file does not exist.
|
||||
:type required: bool
|
||||
|
||||
:param loader: YAML loader, :py:class:`YamlLoader` is used if not specified.
|
||||
:type loader: ``yaml.Loader``
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
if yaml is None:
|
||||
|
@ -1334,10 +1367,19 @@ cdef class ConfigurationOption(Provider):
|
|||
'"pip install dependency-injector[yaml]"'
|
||||
)
|
||||
|
||||
|
||||
if loader is None:
|
||||
loader = YamlLoader
|
||||
|
||||
try:
|
||||
with open(filepath) as opened_file:
|
||||
config = yaml.load(opened_file, yaml.Loader)
|
||||
except IOError:
|
||||
config = yaml.load(opened_file, loader)
|
||||
except IOError as exception:
|
||||
if required is not False \
|
||||
and (self._is_strict_mode_enabled() or required is True) \
|
||||
and exception.errno in (errno.ENOENT, errno.EISDIR):
|
||||
exception.strerror = 'Unable to load configuration file {0}'.format(exception.strerror)
|
||||
raise
|
||||
return
|
||||
|
||||
current_config = self.__call__()
|
||||
|
@ -1345,7 +1387,7 @@ cdef class ConfigurationOption(Provider):
|
|||
current_config = {}
|
||||
self.override(merge_dicts(current_config, config))
|
||||
|
||||
def from_dict(self, options):
|
||||
def from_dict(self, options, required=UNDEFINED):
|
||||
"""Load configuration from the dictionary.
|
||||
|
||||
Loaded configuration is merged recursively over existing configuration.
|
||||
|
@ -1353,27 +1395,59 @@ cdef class ConfigurationOption(Provider):
|
|||
:param options: Configuration options.
|
||||
:type options: dict
|
||||
|
||||
:param required: When required is True, raise an exception if dictionary is empty.
|
||||
:type required: bool
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
current_config = self.__call__()
|
||||
if not current_config:
|
||||
if required is not False \
|
||||
and (self._is_strict_mode_enabled() or required is True) \
|
||||
and not options:
|
||||
raise ValueError('Can not use empty dictionary')
|
||||
|
||||
try:
|
||||
current_config = self.__call__()
|
||||
except Error:
|
||||
current_config = {}
|
||||
else:
|
||||
if not current_config:
|
||||
current_config = {}
|
||||
|
||||
self.override(merge_dicts(current_config, options))
|
||||
|
||||
def from_env(self, name, default=None):
|
||||
def from_env(self, name, default=UNDEFINED, required=UNDEFINED):
|
||||
"""Load configuration value from the environment variable.
|
||||
|
||||
:param name: Name of the environment variable.
|
||||
:type name: str
|
||||
|
||||
:param default: Default value that is used if environment variable does not exist.
|
||||
:type default: str
|
||||
:type default: object
|
||||
|
||||
:param required: When required is True, raise an exception if environment variable is undefined.
|
||||
:type required: bool
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
value = os.getenv(name, default)
|
||||
value = os.environ.get(name, default)
|
||||
|
||||
if value is UNDEFINED:
|
||||
if required is not False \
|
||||
and (self._is_strict_mode_enabled() or required is True):
|
||||
raise ValueError('Environment variable "{0}" is undefined'.format(name))
|
||||
value = None
|
||||
|
||||
self.override(value)
|
||||
|
||||
def _is_strict_mode_enabled(self):
|
||||
cdef Configuration root
|
||||
|
||||
root = self.__root_ref()
|
||||
if not root:
|
||||
return False
|
||||
|
||||
return root.__strict
|
||||
|
||||
|
||||
cdef class TypedConfigurationOption(Callable):
|
||||
|
||||
|
@ -1403,7 +1477,6 @@ cdef class Configuration(Object):
|
|||
"""
|
||||
|
||||
DEFAULT_NAME = 'config'
|
||||
UNDEFINED = object()
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, default=None, strict=False):
|
||||
self.__name = name
|
||||
|
@ -1474,17 +1547,17 @@ cdef class Configuration(Object):
|
|||
value = self.__call__()
|
||||
|
||||
if value is None:
|
||||
if self.__strict or required:
|
||||
if self._is_strict_mode_enabled() or required:
|
||||
raise Error('Undefined configuration option "{0}.{1}"'.format(self.__name, selector))
|
||||
return None
|
||||
|
||||
keys = selector.split('.')
|
||||
while len(keys) > 0:
|
||||
key = keys.pop(0)
|
||||
value = value.get(key, self.UNDEFINED)
|
||||
value = value.get(key, UNDEFINED)
|
||||
|
||||
if value is self.UNDEFINED:
|
||||
if self.__strict or required:
|
||||
if value is UNDEFINED:
|
||||
if self._is_strict_mode_enabled() or required:
|
||||
raise Error('Undefined configuration option "{0}.{1}"'.format(self.__name, selector))
|
||||
return None
|
||||
|
||||
|
@ -1572,7 +1645,7 @@ cdef class Configuration(Object):
|
|||
"""
|
||||
self.override(value)
|
||||
|
||||
def from_ini(self, filepath):
|
||||
def from_ini(self, filepath, required=UNDEFINED):
|
||||
"""Load configuration from the ini file.
|
||||
|
||||
Loaded configuration is merged recursively over existing configuration.
|
||||
|
@ -1580,9 +1653,20 @@ cdef class Configuration(Object):
|
|||
:param filepath: Path to the configuration file.
|
||||
:type filepath: str
|
||||
|
||||
:param required: When required is True, raise an exception if file does not exist.
|
||||
:type required: bool
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
parser = _parse_ini_file(filepath)
|
||||
try:
|
||||
parser = _parse_ini_file(filepath)
|
||||
except IOError as exception:
|
||||
if required is not False \
|
||||
and (self._is_strict_mode_enabled() or required is True) \
|
||||
and exception.errno in (errno.ENOENT, errno.EISDIR):
|
||||
exception.strerror = 'Unable to load configuration file {0}'.format(exception.strerror)
|
||||
raise
|
||||
return
|
||||
|
||||
config = {}
|
||||
for section in parser.sections():
|
||||
|
@ -1593,7 +1677,7 @@ cdef class Configuration(Object):
|
|||
current_config = {}
|
||||
self.override(merge_dicts(current_config, config))
|
||||
|
||||
def from_yaml(self, filepath):
|
||||
def from_yaml(self, filepath, required=UNDEFINED, loader=None):
|
||||
"""Load configuration from the yaml file.
|
||||
|
||||
Loaded configuration is merged recursively over existing configuration.
|
||||
|
@ -1601,6 +1685,12 @@ cdef class Configuration(Object):
|
|||
:param filepath: Path to the configuration file.
|
||||
:type filepath: str
|
||||
|
||||
:param required: When required is True, raise an exception if file does not exist.
|
||||
:type required: bool
|
||||
|
||||
:param loader: YAML loader, :py:class:`YamlLoader` is used if not specified.
|
||||
:type loader: ``yaml.Loader``
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
if yaml is None:
|
||||
|
@ -1610,10 +1700,18 @@ cdef class Configuration(Object):
|
|||
'"pip install dependency-injector[yaml]"'
|
||||
)
|
||||
|
||||
if loader is None:
|
||||
loader = YamlLoader
|
||||
|
||||
try:
|
||||
with open(filepath) as opened_file:
|
||||
config = yaml.load(opened_file, yaml.Loader)
|
||||
except IOError:
|
||||
config = yaml.load(opened_file, loader)
|
||||
except IOError as exception:
|
||||
if required is not False \
|
||||
and (self._is_strict_mode_enabled() or required is True) \
|
||||
and exception.errno in (errno.ENOENT, errno.EISDIR):
|
||||
exception.strerror = 'Unable to load configuration file {0}'.format(exception.strerror)
|
||||
raise
|
||||
return
|
||||
|
||||
current_config = self.__call__()
|
||||
|
@ -1621,7 +1719,7 @@ cdef class Configuration(Object):
|
|||
current_config = {}
|
||||
self.override(merge_dicts(current_config, config))
|
||||
|
||||
def from_dict(self, options):
|
||||
def from_dict(self, options, required=UNDEFINED):
|
||||
"""Load configuration from the dictionary.
|
||||
|
||||
Loaded configuration is merged recursively over existing configuration.
|
||||
|
@ -1629,27 +1727,48 @@ cdef class Configuration(Object):
|
|||
:param options: Configuration options.
|
||||
:type options: dict
|
||||
|
||||
:param required: When required is True, raise an exception if dictionary is empty.
|
||||
:type required: bool
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
if required is not False \
|
||||
and (self._is_strict_mode_enabled() or required is True) \
|
||||
and not options:
|
||||
raise ValueError('Can not use empty dictionary')
|
||||
|
||||
current_config = self.__call__()
|
||||
if not current_config:
|
||||
current_config = {}
|
||||
self.override(merge_dicts(current_config, options))
|
||||
|
||||
def from_env(self, name, default=None):
|
||||
def from_env(self, name, default=UNDEFINED, required=UNDEFINED):
|
||||
"""Load configuration value from the environment variable.
|
||||
|
||||
:param name: Name of the environment variable.
|
||||
:type name: str
|
||||
|
||||
:param default: Default value that is used if environment variable does not exist.
|
||||
:type default: str
|
||||
:type default: object
|
||||
|
||||
:param required: When required is True, raise an exception if environment variable is undefined.
|
||||
:type required: bool
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
value = os.getenv(name, default)
|
||||
value = os.environ.get(name, default)
|
||||
|
||||
if value is UNDEFINED:
|
||||
if required is not False \
|
||||
and (self._is_strict_mode_enabled() or required is True):
|
||||
raise ValueError('Environment variable "{0}" is undefined'.format(name))
|
||||
value = None
|
||||
|
||||
self.override(value)
|
||||
|
||||
def _is_strict_mode_enabled(self):
|
||||
return self.__strict
|
||||
|
||||
|
||||
cdef class Factory(Provider):
|
||||
r"""Factory provider creates new instance on every call.
|
||||
|
|
|
@ -236,6 +236,51 @@ class FactoryTests(AsyncTestCase):
|
|||
|
||||
self.assertIsNot(service1.client, service2.client)
|
||||
|
||||
def test_async_instance_and_sync_attributes_injection(self):
|
||||
class ContainerWithAttributes(containers.DeclarativeContainer):
|
||||
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||
|
||||
client = providers.Factory(
|
||||
Client,
|
||||
resource1,
|
||||
resource2=None,
|
||||
)
|
||||
client.add_attributes(resource2=providers.Object(RESOURCE2))
|
||||
|
||||
service = providers.Factory(
|
||||
Service,
|
||||
client=None,
|
||||
)
|
||||
service.add_attributes(client=client)
|
||||
|
||||
container = ContainerWithAttributes()
|
||||
|
||||
client1 = self._run(container.client())
|
||||
client2 = self._run(container.client())
|
||||
|
||||
self.assertIsInstance(client1, Client)
|
||||
self.assertIs(client1.resource1, RESOURCE1)
|
||||
self.assertIs(client1.resource2, RESOURCE2)
|
||||
|
||||
self.assertIsInstance(client2, Client)
|
||||
self.assertIs(client2.resource1, RESOURCE1)
|
||||
self.assertIs(client2.resource2, RESOURCE2)
|
||||
|
||||
service1 = self._run(container.service())
|
||||
service2 = self._run(container.service())
|
||||
|
||||
self.assertIsInstance(service1, Service)
|
||||
self.assertIsInstance(service1.client, Client)
|
||||
self.assertIs(service1.client.resource1, RESOURCE1)
|
||||
self.assertIs(service1.client.resource2, RESOURCE2)
|
||||
|
||||
self.assertIsInstance(service2, Service)
|
||||
self.assertIsInstance(service2.client, Client)
|
||||
self.assertIs(service2.client.resource1, RESOURCE1)
|
||||
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||
|
||||
self.assertIsNot(service1.client, service2.client)
|
||||
|
||||
|
||||
class FactoryAggregateTests(AsyncTestCase):
|
||||
|
||||
|
@ -816,3 +861,24 @@ class AsyncTypingStubTests(AsyncTestCase):
|
|||
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||
|
||||
self.assertIsNot(service1.client, service2.client)
|
||||
|
||||
|
||||
class AsyncProvidersWithAsyncDependenciesTests(AsyncTestCase):
|
||||
|
||||
def test_injections(self):
|
||||
# See: https://github.com/ets-labs/python-dependency-injector/issues/368
|
||||
async def async_db_provider():
|
||||
return {'db': 'ok'}
|
||||
|
||||
async def async_service(db=None):
|
||||
return {'service': 'ok', 'db': db}
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
db = providers.Factory(async_db_provider)
|
||||
service = providers.Singleton(async_service, db=db)
|
||||
|
||||
container = Container()
|
||||
service = self._run(container.service())
|
||||
|
||||
self.assertEquals(service, {'service': 'ok', 'db': {'db': 'ok'}})
|
||||
|
|
|
@ -9,6 +9,10 @@ import tempfile
|
|||
import unittest2 as unittest
|
||||
|
||||
from dependency_injector import containers, providers, errors
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
|
||||
class ConfigTests(unittest.TestCase):
|
||||
|
@ -395,6 +399,16 @@ class ConfigFromIniTests(unittest.TestCase):
|
|||
self.assertEqual(self.config.section2(), {'value2': '2'})
|
||||
self.assertEqual(self.config.section2.value2(), '2')
|
||||
|
||||
def test_option(self):
|
||||
self.config.option.from_ini(self.config_file_1)
|
||||
|
||||
self.assertEqual(self.config(), {'option': {'section1': {'value1': '1'}, 'section2': {'value2': '2'}}})
|
||||
self.assertEqual(self.config.option(), {'section1': {'value1': '1'}, 'section2': {'value2': '2'}})
|
||||
self.assertEqual(self.config.option.section1(), {'value1': '1'})
|
||||
self.assertEqual(self.config.option.section1.value1(), '1')
|
||||
self.assertEqual(self.config.option.section2(), {'value2': '2'})
|
||||
self.assertEqual(self.config.option.section2.value2(), '2')
|
||||
|
||||
def test_merge(self):
|
||||
self.config.from_ini(self.config_file_1)
|
||||
self.config.from_ini(self.config_file_2)
|
||||
|
@ -422,6 +436,43 @@ class ConfigFromIniTests(unittest.TestCase):
|
|||
self.assertEqual(self.config.section3(), {'value3': '3'})
|
||||
self.assertEqual(self.config.section3.value3(), '3')
|
||||
|
||||
def test_file_does_not_exist(self):
|
||||
self.config.from_ini('./does_not_exist.ini')
|
||||
self.assertEqual(self.config(), {})
|
||||
|
||||
def test_file_does_not_exist_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
with self.assertRaises(IOError):
|
||||
self.config.from_ini('./does_not_exist.ini')
|
||||
|
||||
def test_option_file_does_not_exist(self):
|
||||
self.config.option.from_ini('does_not_exist.ini')
|
||||
self.assertIsNone(self.config.option.undefined())
|
||||
|
||||
def test_option_file_does_not_exist_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
with self.assertRaises(IOError):
|
||||
self.config.option.from_ini('./does_not_exist.ini')
|
||||
|
||||
def test_required_file_does_not_exist(self):
|
||||
with self.assertRaises(IOError):
|
||||
self.config.from_ini('./does_not_exist.ini', required=True)
|
||||
|
||||
def test_required_option_file_does_not_exist(self):
|
||||
with self.assertRaises(IOError):
|
||||
self.config.option.from_ini('./does_not_exist.ini', required=True)
|
||||
|
||||
def test_not_required_file_does_not_exist_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.from_ini('./does_not_exist.ini', required=False)
|
||||
self.assertEqual(self.config(), {})
|
||||
|
||||
def test_not_required_option_file_does_not_exist_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.option.from_ini('./does_not_exist.ini', required=False)
|
||||
with self.assertRaises(errors.Error):
|
||||
self.config.option()
|
||||
|
||||
|
||||
class ConfigFromIniWithEnvInterpolationTests(unittest.TestCase):
|
||||
|
||||
|
@ -525,6 +576,51 @@ class ConfigFromYamlTests(unittest.TestCase):
|
|||
self.assertEqual(self.config.section3(), {'value3': 3})
|
||||
self.assertEqual(self.config.section3.value3(), 3)
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_file_does_not_exist(self):
|
||||
self.config.from_yaml('./does_not_exist.yml')
|
||||
self.assertEqual(self.config(), {})
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_file_does_not_exist_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
with self.assertRaises(IOError):
|
||||
self.config.from_yaml('./does_not_exist.yml')
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_option_file_does_not_exist(self):
|
||||
self.config.option.from_yaml('./does_not_exist.yml')
|
||||
self.assertIsNone(self.config.option())
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_option_file_does_not_exist_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
with self.assertRaises(IOError):
|
||||
self.config.option.from_yaml('./does_not_exist.yml')
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_required_file_does_not_exist(self):
|
||||
with self.assertRaises(IOError):
|
||||
self.config.from_yaml('./does_not_exist.yml', required=True)
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_required_option_file_does_not_exist(self):
|
||||
with self.assertRaises(IOError):
|
||||
self.config.option.from_yaml('./does_not_exist.yml', required=True)
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_not_required_file_does_not_exist_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.from_yaml('./does_not_exist.yml', required=False)
|
||||
self.assertEqual(self.config(), {})
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_not_required_option_file_does_not_exist_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.option.from_yaml('./does_not_exist.yml', required=False)
|
||||
with self.assertRaises(errors.Error):
|
||||
self.config.option()
|
||||
|
||||
def test_no_yaml_installed(self):
|
||||
@contextlib.contextmanager
|
||||
def no_yaml_module():
|
||||
|
@ -581,6 +677,51 @@ class ConfigFromYamlWithEnvInterpolationTests(unittest.TestCase):
|
|||
self.assertEqual(self.config.section1(), {'value1': 'test-value'})
|
||||
self.assertEqual(self.config.section1.value1(), 'test-value')
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_option_env_variable_interpolation(self):
|
||||
self.config.option.from_yaml(self.config_file)
|
||||
|
||||
self.assertEqual(
|
||||
self.config.option(),
|
||||
{
|
||||
'section1': {
|
||||
'value1': 'test-value',
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(self.config.option.section1(), {'value1': 'test-value'})
|
||||
self.assertEqual(self.config.option.section1.value1(), 'test-value')
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_env_variable_interpolation_custom_loader(self):
|
||||
self.config.from_yaml(self.config_file, loader=yaml.UnsafeLoader)
|
||||
|
||||
self.assertEqual(
|
||||
self.config(),
|
||||
{
|
||||
'section1': {
|
||||
'value1': 'test-value',
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(self.config.section1(), {'value1': 'test-value'})
|
||||
self.assertEqual(self.config.section1.value1(), 'test-value')
|
||||
|
||||
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
|
||||
def test_option_env_variable_interpolation_custom_loader(self):
|
||||
self.config.option.from_yaml(self.config_file, loader=yaml.UnsafeLoader)
|
||||
|
||||
self.assertEqual(
|
||||
self.config.option(),
|
||||
{
|
||||
'section1': {
|
||||
'value1': 'test-value',
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(self.config.option.section1(), {'value1': 'test-value'})
|
||||
self.assertEqual(self.config.option.section1.value1(), 'test-value')
|
||||
|
||||
|
||||
class ConfigFromDict(unittest.TestCase):
|
||||
|
||||
|
@ -641,6 +782,43 @@ class ConfigFromDict(unittest.TestCase):
|
|||
self.assertEqual(self.config.section3(), {'value3': '3'})
|
||||
self.assertEqual(self.config.section3.value3(), '3')
|
||||
|
||||
def test_empty_dict(self):
|
||||
self.config.from_dict({})
|
||||
self.assertEqual(self.config(), {})
|
||||
|
||||
def test_option_empty_dict(self):
|
||||
self.config.option.from_dict({})
|
||||
self.assertEqual(self.config.option(), {})
|
||||
|
||||
def test_empty_dict_in_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.from_dict({})
|
||||
|
||||
def test_option_empty_dict_in_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.option.from_dict({})
|
||||
|
||||
def test_required_empty_dict(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.from_dict({}, required=True)
|
||||
|
||||
def test_required_option_empty_dict(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.option.from_dict({}, required=True)
|
||||
|
||||
def test_not_required_empty_dict_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.from_dict({}, required=False)
|
||||
self.assertEqual(self.config(), {})
|
||||
|
||||
def test_not_required_option_empty_dict_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.option.from_dict({}, required=False)
|
||||
self.assertEqual(self.config.option(), {})
|
||||
self.assertEqual(self.config(), {'option': {}})
|
||||
|
||||
|
||||
class ConfigFromEnvTests(unittest.TestCase):
|
||||
|
||||
|
@ -656,13 +834,77 @@ class ConfigFromEnvTests(unittest.TestCase):
|
|||
self.config.from_env('CONFIG_TEST_ENV')
|
||||
self.assertEqual(self.config(), 'test-value')
|
||||
|
||||
def test_default(self):
|
||||
self.config.from_env('UNDEFINED_ENV', 'default-value')
|
||||
self.assertEqual(self.config(), 'default-value')
|
||||
|
||||
def test_with_children(self):
|
||||
self.config.section1.value1.from_env('CONFIG_TEST_ENV')
|
||||
|
||||
self.assertEqual(self.config(), {'section1': {'value1': 'test-value'}})
|
||||
self.assertEqual(self.config.section1(), {'value1': 'test-value'})
|
||||
self.assertEqual(self.config.section1.value1(), 'test-value')
|
||||
|
||||
def test_default(self):
|
||||
self.config.from_env('UNDEFINED_ENV', 'default-value')
|
||||
self.assertEqual(self.config(), 'default-value')
|
||||
|
||||
def test_default_none(self):
|
||||
self.config.from_env('UNDEFINED_ENV')
|
||||
self.assertIsNone(self.config())
|
||||
|
||||
def test_option_default_none(self):
|
||||
self.config.option.from_env('UNDEFINED_ENV')
|
||||
self.assertIsNone(self.config.option())
|
||||
|
||||
def test_undefined_in_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.from_env('UNDEFINED_ENV')
|
||||
|
||||
def test_option_undefined_in_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.option.from_env('UNDEFINED_ENV')
|
||||
|
||||
def test_undefined_in_strict_mode_with_default(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.from_env('UNDEFINED_ENV', 'default-value')
|
||||
self.assertEqual(self.config(), 'default-value')
|
||||
|
||||
def test_option_undefined_in_strict_mode_with_default(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.option.from_env('UNDEFINED_ENV', 'default-value')
|
||||
self.assertEqual(self.config.option(), 'default-value')
|
||||
|
||||
def test_required_undefined(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.from_env('UNDEFINED_ENV', required=True)
|
||||
|
||||
def test_required_undefined_with_default(self):
|
||||
self.config.from_env('UNDEFINED_ENV', default='default-value', required=True)
|
||||
self.assertEqual(self.config(), 'default-value')
|
||||
|
||||
def test_option_required_undefined(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.option.from_env('UNDEFINED_ENV', required=True)
|
||||
|
||||
def test_option_required_undefined_with_default(self):
|
||||
self.config.option.from_env('UNDEFINED_ENV', default='default-value', required=True)
|
||||
self.assertEqual(self.config.option(), 'default-value')
|
||||
|
||||
def test_not_required_undefined_in_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.from_env('UNDEFINED_ENV', required=False)
|
||||
self.assertIsNone(self.config())
|
||||
|
||||
def test_option_not_required_undefined_in_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.option.from_env('UNDEFINED_ENV', required=False)
|
||||
self.assertIsNone(self.config.option())
|
||||
|
||||
def test_not_required_undefined_with_default_in_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.from_env('UNDEFINED_ENV', default='default-value', required=False)
|
||||
self.assertEqual(self.config(), 'default-value')
|
||||
|
||||
def test_option_not_required_undefined_with_default_in_strict_mode(self):
|
||||
self.config = providers.Configuration(strict=True)
|
||||
self.config.option.from_env('UNDEFINED_ENV', default='default-value', required=False)
|
||||
self.assertEqual(self.config.option(), 'default-value')
|
||||
|
|
12
tox.ini
12
tox.ini
|
@ -1,6 +1,6 @@
|
|||
[tox]
|
||||
envlist=
|
||||
coveralls, pylint, flake8, pydocstyle, py27, py34, py35, py36, py37, py38, pypy, pypy3
|
||||
coveralls, pylint, flake8, pydocstyle, 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3
|
||||
|
||||
[testenv]
|
||||
deps=
|
||||
|
@ -17,7 +17,7 @@ commands=
|
|||
unit2 discover -s tests/unit -p test_*_py3*.py
|
||||
|
||||
[testenv:coveralls]
|
||||
passenv=TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH DEPENDENCY_INJECTOR_DEBUG_MODE
|
||||
passenv = GITHUB_* COVERALLS_*
|
||||
basepython=python3.9
|
||||
usedevelop=True
|
||||
deps=
|
||||
|
@ -31,7 +31,7 @@ commands=
|
|||
coverage report --rcfile=./.coveragerc
|
||||
coveralls
|
||||
|
||||
[testenv:py27]
|
||||
[testenv:2.7]
|
||||
deps=
|
||||
unittest2
|
||||
extras=
|
||||
|
@ -40,7 +40,7 @@ extras=
|
|||
commands=
|
||||
unit2 discover -s tests/unit -p test_*_py2_py3.py
|
||||
|
||||
[testenv:py34]
|
||||
[testenv:3.4]
|
||||
deps=
|
||||
unittest2
|
||||
extras=
|
||||
|
@ -48,7 +48,7 @@ extras=
|
|||
commands=
|
||||
unit2 discover -s tests/unit -p test_*_py3.py
|
||||
|
||||
[testenv:py35]
|
||||
[testenv:3.5]
|
||||
deps=
|
||||
unittest2
|
||||
extras=
|
||||
|
@ -57,7 +57,7 @@ extras=
|
|||
commands=
|
||||
unit2 discover -s tests/unit -p test_*_py3.py
|
||||
|
||||
[testenv:pypy]
|
||||
[testenv:pypy2]
|
||||
deps=
|
||||
unittest2
|
||||
extras=
|
||||
|
|
Loading…
Reference in New Issue
Block a user