mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-03-03 19:25:48 +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/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Wheel
|
:alt: Wheel
|
||||||
|
|
||||||
.. image:: https://api.travis-ci.com/ets-labs/python-dependency-injector.svg?branch=master
|
.. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
|
||||||
:target: https://travis-ci.com/github/ets-labs/python-dependency-injector
|
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||||
:alt: Build Status
|
:alt: Build Status
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
.. 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/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Wheel
|
:alt: Wheel
|
||||||
|
|
||||||
.. image:: https://api.travis-ci.com/ets-labs/python-dependency-injector.svg?branch=master
|
.. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
|
||||||
:target: https://travis-ci.com/github/ets-labs/python-dependency-injector
|
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||||
:alt: Build Status
|
:alt: Build Status
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
.. 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
|
From version 0.7.6 *Dependency Injector* framework strictly
|
||||||
follows `Semantic versioning`_
|
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
|
4.10.3
|
||||||
------
|
------
|
||||||
- Fix a bug in the ``Configuration`` provider: strict mode didn't work when provider
|
- 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".
|
It implements the principle "use first, define later".
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
:backlinks: none
|
||||||
|
|
||||||
Loading from an INI file
|
Loading from an INI file
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
@ -57,9 +61,19 @@ where ``examples/providers/configuration/config.yml`` is:
|
||||||
.. literalinclude:: ../../examples/providers/configuration/config.yml
|
.. literalinclude:: ../../examples/providers/configuration/config.yml
|
||||||
:language: ini
|
:language: ini
|
||||||
|
|
||||||
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation. Use
|
:py:meth:`Configuration.from_yaml` method uses custom version of ``yaml.SafeLoader``.
|
||||||
``${ENV_NAME}`` format in the configuration file to substitute value of the environment
|
|
||||||
variable ``ENV_NAME``.
|
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::
|
.. note::
|
||||||
|
|
||||||
|
@ -113,6 +127,43 @@ where ``examples/providers/configuration/config.local.yml`` is:
|
||||||
.. literalinclude:: ../../examples/providers/configuration/config.local.yml
|
.. literalinclude:: ../../examples/providers/configuration/config.local.yml
|
||||||
:language: ini
|
: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
|
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
|
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.
|
value will be injected. Parameters ``*args`` and ``**kwargs`` are handled as any other injections.
|
||||||
|
|
||||||
|
.. _configuration-strict-mode:
|
||||||
|
|
||||||
Strict mode and required options
|
Strict mode and required options
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
|
@ -154,7 +207,57 @@ on access to any undefined option.
|
||||||
:lines: 3-
|
:lines: 3-
|
||||||
:emphasize-lines: 12
|
: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
|
.. literalinclude:: ../../examples/providers/configuration/configuration_required.py
|
||||||
:language: python
|
:language: python
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Top-level package."""
|
"""Top-level package."""
|
||||||
|
|
||||||
__version__ = '4.10.3'
|
__version__ = '4.11.0'
|
||||||
"""Version number.
|
"""Version number.
|
||||||
|
|
||||||
:type: str
|
:type: str
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
asyncio = None
|
asyncio = None
|
||||||
|
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
cimport cython
|
cimport cython
|
||||||
|
@ -419,18 +420,27 @@ cdef inline object __provide_keyword_args(
|
||||||
cdef inline object __awaitable_args_kwargs_future(object args, list awaitables):
|
cdef inline object __awaitable_args_kwargs_future(object args, list awaitables):
|
||||||
future_result = asyncio.Future()
|
future_result = asyncio.Future()
|
||||||
|
|
||||||
args_future = asyncio.Future()
|
args_ready = asyncio.gather(*[value for _, value in awaitables])
|
||||||
args_future.set_result((future_result, args, awaitables))
|
args_ready.add_done_callback(
|
||||||
|
functools.partial(
|
||||||
args_ready = asyncio.gather(args_future, *[value for _, value in awaitables])
|
__async_prepare_args_kwargs_callback,
|
||||||
args_ready.add_done_callback(__async_prepare_args_kwargs_callback)
|
future_result,
|
||||||
|
args,
|
||||||
|
awaitables,
|
||||||
|
),
|
||||||
|
)
|
||||||
asyncio.ensure_future(args_ready)
|
asyncio.ensure_future(args_ready)
|
||||||
|
|
||||||
return future_result
|
return future_result
|
||||||
|
|
||||||
|
|
||||||
cdef inline void __async_prepare_args_kwargs_callback(object future):
|
cdef inline void __async_prepare_args_kwargs_callback(
|
||||||
(future_result, args, awaitables), *awaited = future.result()
|
object future_result,
|
||||||
|
object args,
|
||||||
|
object awaitables,
|
||||||
|
object future,
|
||||||
|
):
|
||||||
|
awaited = future.result()
|
||||||
for value, (key, _) in zip(awaited, awaitables):
|
for value, (key, _) in zip(awaited, awaitables):
|
||||||
args[key] = value
|
args[key] = value
|
||||||
future_result.set_result(args)
|
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):
|
cdef inline object __async_inject_attributes(future_instance, future_attributes):
|
||||||
future_result = asyncio.Future()
|
future_result = asyncio.Future()
|
||||||
|
|
||||||
future = asyncio.Future()
|
attributes_ready = asyncio.gather(future_instance, future_attributes)
|
||||||
future.set_result(future_result)
|
attributes_ready.add_done_callback(
|
||||||
|
functools.partial(
|
||||||
attributes_ready = asyncio.gather(future, future_instance, future_attributes)
|
__async_inject_attributes_callback,
|
||||||
attributes_ready.add_done_callback(__async_inject_attributes_callback)
|
future_result,
|
||||||
|
),
|
||||||
|
)
|
||||||
asyncio.ensure_future(attributes_ready)
|
asyncio.ensure_future(attributes_ready)
|
||||||
|
|
||||||
return future_result
|
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)
|
__inject_attributes(instance, attributes)
|
||||||
future_result.set_result(instance)
|
future_result.set_result(instance)
|
||||||
|
|
||||||
|
@ -516,11 +529,14 @@ cdef inline object __call(
|
||||||
|
|
||||||
future_result = asyncio.Future()
|
future_result = asyncio.Future()
|
||||||
|
|
||||||
future = asyncio.Future()
|
args_kwargs_ready = asyncio.gather(args, kwargs)
|
||||||
future.set_result((future_result, call))
|
args_kwargs_ready.add_done_callback(
|
||||||
|
functools.partial(
|
||||||
args_kwargs_ready = asyncio.gather(future, args, kwargs)
|
__async_call_callback,
|
||||||
args_kwargs_ready.add_done_callback(__async_call_callback)
|
future_result,
|
||||||
|
call,
|
||||||
|
),
|
||||||
|
)
|
||||||
asyncio.ensure_future(args_kwargs_ready)
|
asyncio.ensure_future(args_kwargs_ready)
|
||||||
|
|
||||||
return future_result
|
return future_result
|
||||||
|
@ -528,12 +544,22 @@ cdef inline object __call(
|
||||||
return call(*args, **kwargs)
|
return call(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
cdef inline void __async_call_callback(object future):
|
cdef inline void __async_call_callback(object future_result, object call, object future):
|
||||||
(future_result, call), args, kwargs = future.result()
|
args, kwargs = future.result()
|
||||||
result = call(*args, **kwargs)
|
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)
|
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):
|
cdef inline object __callable_call(Callable self, tuple args, dict kwargs):
|
||||||
return __call(
|
return __call(
|
||||||
self.__provides,
|
self.__provides,
|
||||||
|
|
|
@ -20,6 +20,11 @@ from typing import (
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
yaml = None
|
||||||
|
|
||||||
from . import resources
|
from . import resources
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,10 +154,10 @@ class ConfigurationOption(Provider[Any]):
|
||||||
def required(self) -> ConfigurationOption: ...
|
def required(self) -> ConfigurationOption: ...
|
||||||
def is_required(self) -> bool: ...
|
def is_required(self) -> bool: ...
|
||||||
def update(self, value: Any) -> None: ...
|
def update(self, value: Any) -> None: ...
|
||||||
def from_ini(self, filepath: Union[Path, str]) -> None: ...
|
def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
|
||||||
def from_yaml(self, filepath: Union[Path, str]) -> 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]) -> None: ...
|
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
|
||||||
def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
|
def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class TypedConfigurationOption(Callable[T]):
|
class TypedConfigurationOption(Callable[T]):
|
||||||
|
@ -170,10 +175,10 @@ class Configuration(Object[Any]):
|
||||||
def set(self, selector: str, value: Any) -> OverridingContext: ...
|
def set(self, selector: str, value: Any) -> OverridingContext: ...
|
||||||
def reset_cache(self) -> None: ...
|
def reset_cache(self) -> None: ...
|
||||||
def update(self, value: Any) -> None: ...
|
def update(self, value: Any) -> None: ...
|
||||||
def from_ini(self, filepath: Union[Path, str]) -> None: ...
|
def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
|
||||||
def from_yaml(self, filepath: Union[Path, str]) -> 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]) -> None: ...
|
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
|
||||||
def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
|
def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class Factory(Provider[T]):
|
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]: ...
|
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
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import errno
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
|
@ -53,14 +54,6 @@ else: # pragma: no cover
|
||||||
copy.deepcopy(obj.im_self, memo),
|
copy.deepcopy(obj.im_self, memo),
|
||||||
obj.im_class)
|
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:
|
if sys.version_info[0] == 3:
|
||||||
class EnvInterpolation(iniconfigparser.BasicInterpolation):
|
class EnvInterpolation(iniconfigparser.BasicInterpolation):
|
||||||
|
@ -72,23 +65,48 @@ if sys.version_info[0] == 3:
|
||||||
|
|
||||||
def _parse_ini_file(filepath):
|
def _parse_ini_file(filepath):
|
||||||
parser = iniconfigparser.ConfigParser(interpolation=EnvInterpolation())
|
parser = iniconfigparser.ConfigParser(interpolation=EnvInterpolation())
|
||||||
parser.read(filepath)
|
with open(filepath) as config_file:
|
||||||
|
parser.read_file(config_file)
|
||||||
return parser
|
return parser
|
||||||
else:
|
else:
|
||||||
import StringIO
|
import StringIO
|
||||||
|
|
||||||
def _parse_ini_file(filepath):
|
def _parse_ini_file(filepath):
|
||||||
parser = iniconfigparser.ConfigParser()
|
parser = iniconfigparser.ConfigParser()
|
||||||
try:
|
|
||||||
with open(filepath) as config_file:
|
with open(filepath) as config_file:
|
||||||
config_string = os.path.expandvars(config_file.read())
|
config_string = os.path.expandvars(config_file.read())
|
||||||
except IOError:
|
|
||||||
return parser
|
|
||||||
else:
|
|
||||||
parser.readfp(StringIO.StringIO(config_string))
|
parser.readfp(StringIO.StringIO(config_string))
|
||||||
return parser
|
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_UNDEFINED = 0
|
||||||
cdef int ASYNC_MODE_ENABLED = 1
|
cdef int ASYNC_MODE_ENABLED = 1
|
||||||
cdef int ASYNC_MODE_DISABLED = 2
|
cdef int ASYNC_MODE_DISABLED = 2
|
||||||
|
@ -1170,14 +1188,12 @@ cdef class ConfigurationOption(Provider):
|
||||||
:py:class:`Configuration` provider.
|
:py:class:`Configuration` provider.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
UNDEFINED = object()
|
|
||||||
|
|
||||||
def __init__(self, name, root, required=False):
|
def __init__(self, name, root, required=False):
|
||||||
self.__name = name
|
self.__name = name
|
||||||
self.__root_ref = weakref.ref(root)
|
self.__root_ref = weakref.ref(root)
|
||||||
self.__children = {}
|
self.__children = {}
|
||||||
self.__required = required
|
self.__required = required
|
||||||
self.__cache = self.UNDEFINED
|
self.__cache = UNDEFINED
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
|
@ -1226,7 +1242,7 @@ cdef class ConfigurationOption(Provider):
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
"""Return new instance."""
|
"""Return new instance."""
|
||||||
if self.__cache is not self.UNDEFINED:
|
if self.__cache is not UNDEFINED:
|
||||||
return self.__cache
|
return self.__cache
|
||||||
|
|
||||||
root = self.__root_ref()
|
root = self.__root_ref()
|
||||||
|
@ -1278,7 +1294,7 @@ cdef class ConfigurationOption(Provider):
|
||||||
raise Error('Configuration option does not support this method')
|
raise Error('Configuration option does not support this method')
|
||||||
|
|
||||||
def reset_cache(self):
|
def reset_cache(self):
|
||||||
self.__cache = self.UNDEFINED
|
self.__cache = UNDEFINED
|
||||||
for child in self.__children.values():
|
for child in self.__children.values():
|
||||||
child.reset_cache()
|
child.reset_cache()
|
||||||
|
|
||||||
|
@ -1296,7 +1312,7 @@ cdef class ConfigurationOption(Provider):
|
||||||
"""
|
"""
|
||||||
self.override(value)
|
self.override(value)
|
||||||
|
|
||||||
def from_ini(self, filepath):
|
def from_ini(self, filepath, required=UNDEFINED):
|
||||||
"""Load configuration from the ini file.
|
"""Load configuration from the ini file.
|
||||||
|
|
||||||
Loaded configuration is merged recursively over existing configuration.
|
Loaded configuration is merged recursively over existing configuration.
|
||||||
|
@ -1304,9 +1320,20 @@ cdef class ConfigurationOption(Provider):
|
||||||
:param filepath: Path to the configuration file.
|
:param filepath: Path to the configuration file.
|
||||||
:type filepath: str
|
:type filepath: str
|
||||||
|
|
||||||
|
:param required: When required is True, raise an exception if file does not exist.
|
||||||
|
:type required: bool
|
||||||
|
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
parser = _parse_ini_file(filepath)
|
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 = {}
|
config = {}
|
||||||
for section in parser.sections():
|
for section in parser.sections():
|
||||||
|
@ -1317,7 +1344,7 @@ cdef class ConfigurationOption(Provider):
|
||||||
current_config = {}
|
current_config = {}
|
||||||
self.override(merge_dicts(current_config, 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.
|
"""Load configuration from the yaml file.
|
||||||
|
|
||||||
Loaded configuration is merged recursively over existing configuration.
|
Loaded configuration is merged recursively over existing configuration.
|
||||||
|
@ -1325,6 +1352,12 @@ cdef class ConfigurationOption(Provider):
|
||||||
:param filepath: Path to the configuration file.
|
:param filepath: Path to the configuration file.
|
||||||
:type filepath: str
|
: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
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if yaml is None:
|
if yaml is None:
|
||||||
|
@ -1334,10 +1367,19 @@ cdef class ConfigurationOption(Provider):
|
||||||
'"pip install dependency-injector[yaml]"'
|
'"pip install dependency-injector[yaml]"'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if loader is None:
|
||||||
|
loader = YamlLoader
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(filepath) as opened_file:
|
with open(filepath) as opened_file:
|
||||||
config = yaml.load(opened_file, yaml.Loader)
|
config = yaml.load(opened_file, loader)
|
||||||
except IOError:
|
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
|
return
|
||||||
|
|
||||||
current_config = self.__call__()
|
current_config = self.__call__()
|
||||||
|
@ -1345,7 +1387,7 @@ cdef class ConfigurationOption(Provider):
|
||||||
current_config = {}
|
current_config = {}
|
||||||
self.override(merge_dicts(current_config, 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.
|
"""Load configuration from the dictionary.
|
||||||
|
|
||||||
Loaded configuration is merged recursively over existing configuration.
|
Loaded configuration is merged recursively over existing configuration.
|
||||||
|
@ -1353,27 +1395,59 @@ cdef class ConfigurationOption(Provider):
|
||||||
:param options: Configuration options.
|
:param options: Configuration options.
|
||||||
:type options: dict
|
:type options: dict
|
||||||
|
|
||||||
|
:param required: When required is True, raise an exception if dictionary is empty.
|
||||||
|
:type required: bool
|
||||||
|
|
||||||
:rtype: None
|
: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')
|
||||||
|
|
||||||
|
try:
|
||||||
current_config = self.__call__()
|
current_config = self.__call__()
|
||||||
|
except Error:
|
||||||
|
current_config = {}
|
||||||
|
else:
|
||||||
if not current_config:
|
if not current_config:
|
||||||
current_config = {}
|
current_config = {}
|
||||||
|
|
||||||
self.override(merge_dicts(current_config, options))
|
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.
|
"""Load configuration value from the environment variable.
|
||||||
|
|
||||||
:param name: Name of the environment variable.
|
:param name: Name of the environment variable.
|
||||||
:type name: str
|
:type name: str
|
||||||
|
|
||||||
:param default: Default value that is used if environment variable does not exist.
|
: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
|
: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)
|
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):
|
cdef class TypedConfigurationOption(Callable):
|
||||||
|
|
||||||
|
@ -1403,7 +1477,6 @@ cdef class Configuration(Object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_NAME = 'config'
|
DEFAULT_NAME = 'config'
|
||||||
UNDEFINED = object()
|
|
||||||
|
|
||||||
def __init__(self, name=DEFAULT_NAME, default=None, strict=False):
|
def __init__(self, name=DEFAULT_NAME, default=None, strict=False):
|
||||||
self.__name = name
|
self.__name = name
|
||||||
|
@ -1474,17 +1547,17 @@ cdef class Configuration(Object):
|
||||||
value = self.__call__()
|
value = self.__call__()
|
||||||
|
|
||||||
if value is None:
|
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))
|
raise Error('Undefined configuration option "{0}.{1}"'.format(self.__name, selector))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
keys = selector.split('.')
|
keys = selector.split('.')
|
||||||
while len(keys) > 0:
|
while len(keys) > 0:
|
||||||
key = keys.pop(0)
|
key = keys.pop(0)
|
||||||
value = value.get(key, self.UNDEFINED)
|
value = value.get(key, UNDEFINED)
|
||||||
|
|
||||||
if value is self.UNDEFINED:
|
if value is UNDEFINED:
|
||||||
if self.__strict or required:
|
if self._is_strict_mode_enabled() or required:
|
||||||
raise Error('Undefined configuration option "{0}.{1}"'.format(self.__name, selector))
|
raise Error('Undefined configuration option "{0}.{1}"'.format(self.__name, selector))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -1572,7 +1645,7 @@ cdef class Configuration(Object):
|
||||||
"""
|
"""
|
||||||
self.override(value)
|
self.override(value)
|
||||||
|
|
||||||
def from_ini(self, filepath):
|
def from_ini(self, filepath, required=UNDEFINED):
|
||||||
"""Load configuration from the ini file.
|
"""Load configuration from the ini file.
|
||||||
|
|
||||||
Loaded configuration is merged recursively over existing configuration.
|
Loaded configuration is merged recursively over existing configuration.
|
||||||
|
@ -1580,9 +1653,20 @@ cdef class Configuration(Object):
|
||||||
:param filepath: Path to the configuration file.
|
:param filepath: Path to the configuration file.
|
||||||
:type filepath: str
|
:type filepath: str
|
||||||
|
|
||||||
|
:param required: When required is True, raise an exception if file does not exist.
|
||||||
|
:type required: bool
|
||||||
|
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
parser = _parse_ini_file(filepath)
|
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 = {}
|
config = {}
|
||||||
for section in parser.sections():
|
for section in parser.sections():
|
||||||
|
@ -1593,7 +1677,7 @@ cdef class Configuration(Object):
|
||||||
current_config = {}
|
current_config = {}
|
||||||
self.override(merge_dicts(current_config, 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.
|
"""Load configuration from the yaml file.
|
||||||
|
|
||||||
Loaded configuration is merged recursively over existing configuration.
|
Loaded configuration is merged recursively over existing configuration.
|
||||||
|
@ -1601,6 +1685,12 @@ cdef class Configuration(Object):
|
||||||
:param filepath: Path to the configuration file.
|
:param filepath: Path to the configuration file.
|
||||||
:type filepath: str
|
: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
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if yaml is None:
|
if yaml is None:
|
||||||
|
@ -1610,10 +1700,18 @@ cdef class Configuration(Object):
|
||||||
'"pip install dependency-injector[yaml]"'
|
'"pip install dependency-injector[yaml]"'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if loader is None:
|
||||||
|
loader = YamlLoader
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(filepath) as opened_file:
|
with open(filepath) as opened_file:
|
||||||
config = yaml.load(opened_file, yaml.Loader)
|
config = yaml.load(opened_file, loader)
|
||||||
except IOError:
|
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
|
return
|
||||||
|
|
||||||
current_config = self.__call__()
|
current_config = self.__call__()
|
||||||
|
@ -1621,7 +1719,7 @@ cdef class Configuration(Object):
|
||||||
current_config = {}
|
current_config = {}
|
||||||
self.override(merge_dicts(current_config, 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.
|
"""Load configuration from the dictionary.
|
||||||
|
|
||||||
Loaded configuration is merged recursively over existing configuration.
|
Loaded configuration is merged recursively over existing configuration.
|
||||||
|
@ -1629,27 +1727,48 @@ cdef class Configuration(Object):
|
||||||
:param options: Configuration options.
|
:param options: Configuration options.
|
||||||
:type options: dict
|
:type options: dict
|
||||||
|
|
||||||
|
:param required: When required is True, raise an exception if dictionary is empty.
|
||||||
|
:type required: bool
|
||||||
|
|
||||||
:rtype: None
|
: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__()
|
current_config = self.__call__()
|
||||||
if not current_config:
|
if not current_config:
|
||||||
current_config = {}
|
current_config = {}
|
||||||
self.override(merge_dicts(current_config, options))
|
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.
|
"""Load configuration value from the environment variable.
|
||||||
|
|
||||||
:param name: Name of the environment variable.
|
:param name: Name of the environment variable.
|
||||||
:type name: str
|
:type name: str
|
||||||
|
|
||||||
:param default: Default value that is used if environment variable does not exist.
|
: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
|
: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)
|
self.override(value)
|
||||||
|
|
||||||
|
def _is_strict_mode_enabled(self):
|
||||||
|
return self.__strict
|
||||||
|
|
||||||
|
|
||||||
cdef class Factory(Provider):
|
cdef class Factory(Provider):
|
||||||
r"""Factory provider creates new instance on every call.
|
r"""Factory provider creates new instance on every call.
|
||||||
|
|
|
@ -236,6 +236,51 @@ class FactoryTests(AsyncTestCase):
|
||||||
|
|
||||||
self.assertIsNot(service1.client, service2.client)
|
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):
|
class FactoryAggregateTests(AsyncTestCase):
|
||||||
|
|
||||||
|
@ -816,3 +861,24 @@ class AsyncTypingStubTests(AsyncTestCase):
|
||||||
self.assertIs(service2.client.resource2, RESOURCE2)
|
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
self.assertIsNot(service1.client, service2.client)
|
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
|
import unittest2 as unittest
|
||||||
|
|
||||||
from dependency_injector import containers, providers, errors
|
from dependency_injector import containers, providers, errors
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
yaml = None
|
||||||
|
|
||||||
|
|
||||||
class ConfigTests(unittest.TestCase):
|
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'})
|
||||||
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):
|
def test_merge(self):
|
||||||
self.config.from_ini(self.config_file_1)
|
self.config.from_ini(self.config_file_1)
|
||||||
self.config.from_ini(self.config_file_2)
|
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'})
|
||||||
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):
|
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})
|
||||||
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):
|
def test_no_yaml_installed(self):
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def no_yaml_module():
|
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'})
|
||||||
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):
|
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'})
|
||||||
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):
|
class ConfigFromEnvTests(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -656,13 +834,77 @@ class ConfigFromEnvTests(unittest.TestCase):
|
||||||
self.config.from_env('CONFIG_TEST_ENV')
|
self.config.from_env('CONFIG_TEST_ENV')
|
||||||
self.assertEqual(self.config(), 'test-value')
|
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):
|
def test_with_children(self):
|
||||||
self.config.section1.value1.from_env('CONFIG_TEST_ENV')
|
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'})
|
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]
|
[tox]
|
||||||
envlist=
|
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]
|
[testenv]
|
||||||
deps=
|
deps=
|
||||||
|
@ -17,7 +17,7 @@ commands=
|
||||||
unit2 discover -s tests/unit -p test_*_py3*.py
|
unit2 discover -s tests/unit -p test_*_py3*.py
|
||||||
|
|
||||||
[testenv:coveralls]
|
[testenv:coveralls]
|
||||||
passenv=TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH DEPENDENCY_INJECTOR_DEBUG_MODE
|
passenv = GITHUB_* COVERALLS_*
|
||||||
basepython=python3.9
|
basepython=python3.9
|
||||||
usedevelop=True
|
usedevelop=True
|
||||||
deps=
|
deps=
|
||||||
|
@ -31,7 +31,7 @@ commands=
|
||||||
coverage report --rcfile=./.coveragerc
|
coverage report --rcfile=./.coveragerc
|
||||||
coveralls
|
coveralls
|
||||||
|
|
||||||
[testenv:py27]
|
[testenv:2.7]
|
||||||
deps=
|
deps=
|
||||||
unittest2
|
unittest2
|
||||||
extras=
|
extras=
|
||||||
|
@ -40,7 +40,7 @@ extras=
|
||||||
commands=
|
commands=
|
||||||
unit2 discover -s tests/unit -p test_*_py2_py3.py
|
unit2 discover -s tests/unit -p test_*_py2_py3.py
|
||||||
|
|
||||||
[testenv:py34]
|
[testenv:3.4]
|
||||||
deps=
|
deps=
|
||||||
unittest2
|
unittest2
|
||||||
extras=
|
extras=
|
||||||
|
@ -48,7 +48,7 @@ extras=
|
||||||
commands=
|
commands=
|
||||||
unit2 discover -s tests/unit -p test_*_py3.py
|
unit2 discover -s tests/unit -p test_*_py3.py
|
||||||
|
|
||||||
[testenv:py35]
|
[testenv:3.5]
|
||||||
deps=
|
deps=
|
||||||
unittest2
|
unittest2
|
||||||
extras=
|
extras=
|
||||||
|
@ -57,7 +57,7 @@ extras=
|
||||||
commands=
|
commands=
|
||||||
unit2 discover -s tests/unit -p test_*_py3.py
|
unit2 discover -s tests/unit -p test_*_py3.py
|
||||||
|
|
||||||
[testenv:pypy]
|
[testenv:pypy2]
|
||||||
deps=
|
deps=
|
||||||
unittest2
|
unittest2
|
||||||
extras=
|
extras=
|
||||||
|
|
Loading…
Reference in New Issue
Block a user