mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-11-01 16:37:56 +03:00 
			
		
		
		
	Merge branch 'release/4.37.0' into master
This commit is contained in:
		
						commit
						541131e338
					
				
							
								
								
									
										33
									
								
								.github/workflows/publishing.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.github/workflows/publishing.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -9,20 +9,20 @@ jobs: | ||||||
| 
 | 
 | ||||||
|   tests: |   tests: | ||||||
|     name: Run tests |     name: Run tests | ||||||
|     runs-on: ubuntu-18.04 |     runs-on: ubuntu-20.04 | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.9 |           python-version: "3.10" | ||||||
|       - run: pip install tox |       - run: pip install tox | ||||||
|       - run: tox |       - run: tox | ||||||
|         env: |         env: | ||||||
|           TOXENV: 3.9 |           TOXENV: "3.10" | ||||||
| 
 | 
 | ||||||
|   linters: |   linters: | ||||||
|     name: Run linters |     name: Run linters | ||||||
|     runs-on: ubuntu-18.04 |     runs-on: ubuntu-20.04 | ||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         toxenv: [flake8, pydocstyle, mypy, pylint] |         toxenv: [flake8, pydocstyle, mypy, pylint] | ||||||
|  | @ -30,7 +30,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.9 |           python-version: "3.10" | ||||||
|       - run: pip install tox |       - run: pip install tox | ||||||
|       - run: tox |       - run: tox | ||||||
|         env: |         env: | ||||||
|  | @ -39,12 +39,12 @@ jobs: | ||||||
|   build-sdist: |   build-sdist: | ||||||
|     name: Build source tarball |     name: Build source tarball | ||||||
|     needs: [tests, linters] |     needs: [tests, linters] | ||||||
|     runs-on: ubuntu-18.04 |     runs-on: ubuntu-20.04 | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.9 |           python-version: "3.10" | ||||||
|       - run: python setup.py sdist |       - run: python setup.py sdist | ||||||
|       - uses: actions/upload-artifact@v2 |       - uses: actions/upload-artifact@v2 | ||||||
|         with: |         with: | ||||||
|  | @ -56,15 +56,15 @@ jobs: | ||||||
|     runs-on: ${{ matrix.os }} |     runs-on: ${{ matrix.os }} | ||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         os: [ubuntu-18.04, windows-latest, macos-latest] |         os: [ubuntu-20.04, windows-2019, macOS-10.15] | ||||||
|     env: |     env: | ||||||
|       CIBW_SKIP: cp27-win* |       CIBW_SKIP: cp27-win* | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.9 |           python-version: "3.10" | ||||||
|       - run: pip install cibuildwheel==1.8.0 |       - run: pip install cibuildwheel==2.1.3 | ||||||
|       - run: cibuildwheel --output-dir wheelhouse |       - run: cibuildwheel --output-dir wheelhouse | ||||||
|       - uses: actions/upload-artifact@v2 |       - uses: actions/upload-artifact@v2 | ||||||
|         with: |         with: | ||||||
|  | @ -73,15 +73,15 @@ jobs: | ||||||
|   build-wheels-linux-aarch64: |   build-wheels-linux-aarch64: | ||||||
|     name: Build wheels (ubuntu-latest-aarch64) |     name: Build wheels (ubuntu-latest-aarch64) | ||||||
|     needs: [tests, linters] |     needs: [tests, linters] | ||||||
|     runs-on: ubuntu-18.04 |     runs-on: ubuntu-20.04 | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - name: Set up QEMU |       - name: Set up QEMU | ||||||
|         uses: docker/setup-qemu-action@v1 |         uses: docker/setup-qemu-action@v1 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.9 |           python-version: "3.10" | ||||||
|       - run: pip install cibuildwheel==1.8.0 |       - run: pip install cibuildwheel==2.1.3 | ||||||
|       - run: cibuildwheel --archs aarch64 --output-dir wheelhouse |       - run: cibuildwheel --archs aarch64 --output-dir wheelhouse | ||||||
|       - uses: actions/upload-artifact@v2 |       - uses: actions/upload-artifact@v2 | ||||||
|         with: |         with: | ||||||
|  | @ -90,7 +90,7 @@ jobs: | ||||||
|   publish: |   publish: | ||||||
|     name: Publish on PyPI |     name: Publish on PyPI | ||||||
|     needs: [build-sdist, build-wheels, build-wheels-linux-aarch64] |     needs: [build-sdist, build-wheels, build-wheels-linux-aarch64] | ||||||
|     runs-on: ubuntu-18.04 |     runs-on: ubuntu-20.04 | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/download-artifact@v2 |       - uses: actions/download-artifact@v2 | ||||||
|         with: |         with: | ||||||
|  | @ -100,6 +100,9 @@ jobs: | ||||||
|         with: |         with: | ||||||
|           user: __token__ |           user: __token__ | ||||||
|           password: ${{ secrets.PYPI_API_TOKEN }} |           password: ${{ secrets.PYPI_API_TOKEN }} | ||||||
|  |           # For publishing to Test PyPI, uncomment next two lines: | ||||||
|  |           # password: ${{ secrets.TEST_PYPI_API_TOKEN }} | ||||||
|  |           # repository_url: https://test.pypi.org/legacy/ | ||||||
| 
 | 
 | ||||||
|   publish-docs: |   publish-docs: | ||||||
|     name: Publish docs |     name: Publish docs | ||||||
|  | @ -109,7 +112,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.9 |           python-version: "3.10" | ||||||
|       - run: pip install -r requirements-doc.txt |       - run: pip install -r requirements-doc.txt | ||||||
|       - run: pip install awscli |       - run: pip install awscli | ||||||
|       - run: pip install -e . |       - run: pip install -e . | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								.github/workflows/tests-and-linters.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/tests-and-linters.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -6,10 +6,10 @@ jobs: | ||||||
| 
 | 
 | ||||||
|   test-on-different-versions: |   test-on-different-versions: | ||||||
|     name: Run tests |     name: Run tests | ||||||
|     runs-on: ubuntu-18.04 |     runs-on: ubuntu-latest | ||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3] |         python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10", pypy2, pypy3] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|  | @ -31,7 +31,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.9 |           python-version: "3.10" | ||||||
|       - run: pip install tox cython |       - run: pip install tox cython | ||||||
|       - run: make cythonize |       - run: make cythonize | ||||||
|       - run: tox |       - run: tox | ||||||
|  | @ -48,7 +48,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/setup-python@v2 |       - uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.9 |           python-version: "3.10" | ||||||
|       - run: pip install tox |       - run: pip install tox | ||||||
|       - run: tox |       - run: tox | ||||||
|         env: |         env: | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -36,6 +36,7 @@ reports/ | ||||||
| .cache | .cache | ||||||
| nosetests.xml | nosetests.xml | ||||||
| coverage.xml | coverage.xml | ||||||
|  | .hypothesis/ | ||||||
| 
 | 
 | ||||||
| # Translations | # Translations | ||||||
| *.mo | *.mo | ||||||
|  | @ -54,7 +55,7 @@ target/ | ||||||
| .idea/ | .idea/ | ||||||
| 
 | 
 | ||||||
| # Virtualenv | # Virtualenv | ||||||
| venv/ | venv*/ | ||||||
| 
 | 
 | ||||||
| # SQLite | # SQLite | ||||||
| *.db | *.db | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Makefile
									
									
									
									
									
								
							|  | @ -45,17 +45,10 @@ install: uninstall clean cythonize | ||||||
| uninstall: | uninstall: | ||||||
| 	- pip uninstall -y -q dependency-injector 2> /dev/null | 	- pip uninstall -y -q dependency-injector 2> /dev/null | ||||||
| 
 | 
 | ||||||
| test-py2: build | test: | ||||||
| 	# Unit tests with coverage report | 	# Unit tests with coverage report | ||||||
| 	coverage erase | 	coverage erase | ||||||
| 	coverage run --rcfile=./.coveragerc -m unittest discover -s tests/unit/ -p test_*_py2_py3.py | 	coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini | ||||||
| 	coverage report --rcfile=./.coveragerc |  | ||||||
| 	coverage html --rcfile=./.coveragerc |  | ||||||
| 
 |  | ||||||
| test: build |  | ||||||
| 	# Unit tests with coverage report |  | ||||||
| 	coverage erase |  | ||||||
| 	coverage run --rcfile=./.coveragerc -m unittest discover -s tests/unit/ -p test_*py3*.py |  | ||||||
| 	coverage report --rcfile=./.coveragerc | 	coverage report --rcfile=./.coveragerc | ||||||
| 	coverage html --rcfile=./.coveragerc | 	coverage html --rcfile=./.coveragerc | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.rst
									
									
									
									
									
								
							|  | @ -80,7 +80,7 @@ Key features of the ``Dependency Injector``: | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    from dependency_injector import containers, providers |    from dependency_injector import containers, providers | ||||||
|    from dependency_injector.wiring import inject, Provide |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
|  | @ -104,11 +104,11 @@ Key features of the ``Dependency Injector``: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.api_key.from_env('API_KEY') |        container.config.api_key.from_env("API_KEY") | ||||||
|        container.config.timeout.from_env('TIMEOUT') |        container.config.timeout.from_env("TIMEOUT") | ||||||
|        container.wire(modules=[sys.modules[__name__]]) |        container.wire(modules=[__name__]) | ||||||
| 
 | 
 | ||||||
|        main()  # <-- dependency is injected automatically |        main()  # <-- dependency is injected automatically | ||||||
| 
 | 
 | ||||||
|  | @ -195,7 +195,7 @@ What is the dependency injection? | ||||||
|  - dependency injection is a principle that decreases coupling and increases cohesion |  - dependency injection is a principle that decreases coupling and increases cohesion | ||||||
| 
 | 
 | ||||||
| Why should I do the dependency injection? | Why should I do the dependency injection? | ||||||
|  - your code becomes more flexible, testable and clear 😎 |  - your code becomes more flexible, testable, and clear 😎 | ||||||
| 
 | 
 | ||||||
| How do I start doing the dependency injection? | How do I start doing the dependency injection? | ||||||
|  - you start writing the code following the dependency injection principle |  - you start writing the code following the dependency injection principle | ||||||
|  | @ -204,7 +204,7 @@ How do I start doing the dependency injection? | ||||||
| 
 | 
 | ||||||
| What price do I pay and what do I get? | What price do I pay and what do I get? | ||||||
|  - you need to explicitly specify the dependencies |  - you need to explicitly specify the dependencies | ||||||
|  - it will be extra work in the beginning |  - it will be an extra work in the beginning | ||||||
|  - it will payoff as the project grows |  - it will payoff as the project grows | ||||||
| 
 | 
 | ||||||
| Have a question? | Have a question? | ||||||
|  |  | ||||||
							
								
								
									
										124
									
								
								docs/conf.py
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								docs/conf.py
									
									
									
									
									
								
							|  | @ -20,49 +20,49 @@ import alabaster | ||||||
| # If extensions (or modules to document with autodoc) are in another directory, | # If extensions (or modules to document with autodoc) are in another directory, | ||||||
| # add these directories to sys.path here. If the directory is relative to the | # add these directories to sys.path here. If the directory is relative to the | ||||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. | # documentation root, use os.path.abspath to make it absolute, like shown here. | ||||||
| sys.path.insert(0, os.path.abspath('..')) | sys.path.insert(0, os.path.abspath("..")) | ||||||
| 
 | 
 | ||||||
| # -- General configuration ------------------------------------------------ | # -- General configuration ------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| # If your documentation needs a minimal Sphinx version, state it here. | # If your documentation needs a minimal Sphinx version, state it here. | ||||||
| #needs_sphinx = '1.0' | #needs_sphinx = "1.0" | ||||||
| 
 | 
 | ||||||
| # Add any Sphinx extension module names here, as strings. They can be | # Add any Sphinx extension module names here, as strings. They can be | ||||||
| # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom | # extensions coming with Sphinx (named "sphinx.ext.*") or your custom | ||||||
| # ones. | # ones. | ||||||
| extensions = [ | extensions = [ | ||||||
|     'alabaster', |     "alabaster", | ||||||
|     'sphinx.ext.autodoc', |     "sphinx.ext.autodoc", | ||||||
|     'sphinxcontrib.disqus', |     "sphinxcontrib.disqus", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| # Add any paths that contain templates here, relative to this directory. | # Add any paths that contain templates here, relative to this directory. | ||||||
| templates_path = ['_templates'] | templates_path = ["_templates"] | ||||||
| 
 | 
 | ||||||
| # The suffix(es) of source filenames. | # The suffix(es) of source filenames. | ||||||
| # You can specify multiple suffix as a list of string: | # You can specify multiple suffix as a list of string: | ||||||
| # source_suffix = ['.rst', '.md'] | # source_suffix = [".rst", ".md"] | ||||||
| source_suffix = '.rst' | source_suffix = ".rst" | ||||||
| 
 | 
 | ||||||
| # The encoding of source files. | # The encoding of source files. | ||||||
| #source_encoding = 'utf-8-sig' | #source_encoding = "utf-8-sig" | ||||||
| 
 | 
 | ||||||
| # The master toctree document. | # The master toctree document. | ||||||
| master_doc = 'index' | master_doc = "index" | ||||||
| 
 | 
 | ||||||
| # General information about the project. | # General information about the project. | ||||||
| project = u'Dependency Injector' | project = "Dependency Injector" | ||||||
| copyright = u'2021, Roman Mogylatov' | copyright = "2021, Roman Mogylatov" | ||||||
| author = u'Roman Mogylatov' | author = "Roman Mogylatov" | ||||||
| 
 | 
 | ||||||
| # The version info for the project you're documenting, acts as replacement for | # The version info for the project you"re documenting, acts as replacement for | ||||||
| # |version| and |release|, also used in various other places throughout the | # |version| and |release|, also used in various other places throughout the | ||||||
| # built documents. | # built documents. | ||||||
| # | # | ||||||
| # The short X.Y version. | # The short X.Y version. | ||||||
| # Getting version: | # Getting version: | ||||||
| with open('../src/dependency_injector/__init__.py') as init_file: | with open("../src/dependency_injector/__init__.py") as init_file: | ||||||
|     version = re.search('__version__ = \'(.*?)\'', init_file.read()).group(1) |     version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1) | ||||||
| 
 | 
 | ||||||
| # The full version, including alpha/beta/rc tags. | # The full version, including alpha/beta/rc tags. | ||||||
| release = version | release = version | ||||||
|  | @ -76,19 +76,19 @@ language = None | ||||||
| 
 | 
 | ||||||
| # There are two options for replacing |today|: either, you set today to some | # There are two options for replacing |today|: either, you set today to some | ||||||
| # non-false value, then it is used: | # non-false value, then it is used: | ||||||
| #today = '' | #today = "" | ||||||
| # Else, today_fmt is used as the format for a strftime call. | # Else, today_fmt is used as the format for a strftime call. | ||||||
| #today_fmt = '%B %d, %Y' | #today_fmt = "%B %d, %Y" | ||||||
| 
 | 
 | ||||||
| # List of patterns, relative to source directory, that match files and | # List of patterns, relative to source directory, that match files and | ||||||
| # directories to ignore when looking for source files. | # directories to ignore when looking for source files. | ||||||
| exclude_patterns = ['_build'] | exclude_patterns = ["_build"] | ||||||
| 
 | 
 | ||||||
| # The reST default role (used for this markup: `text`) to use for all | # The reST default role (used for this markup: `text`) to use for all | ||||||
| # documents. | # documents. | ||||||
| #default_role = None | #default_role = None | ||||||
| 
 | 
 | ||||||
| # If true, '()' will be appended to :func: etc. cross-reference text. | # If true, "()" will be appended to :func: etc. cross-reference text. | ||||||
| #add_function_parentheses = True | #add_function_parentheses = True | ||||||
| 
 | 
 | ||||||
| # If true, the current module name will be prepended to all description | # If true, the current module name will be prepended to all description | ||||||
|  | @ -100,7 +100,7 @@ exclude_patterns = ['_build'] | ||||||
| #show_authors = False | #show_authors = False | ||||||
| 
 | 
 | ||||||
| # The name of the Pygments (syntax highlighting) style to use. | # The name of the Pygments (syntax highlighting) style to use. | ||||||
| pygments_style = 'sphinx' | pygments_style = "sphinx" | ||||||
| 
 | 
 | ||||||
| # A list of ignored prefixes for module index sorting. | # A list of ignored prefixes for module index sorting. | ||||||
| #modindex_common_prefix = [] | #modindex_common_prefix = [] | ||||||
|  | @ -116,8 +116,8 @@ todo_include_todos = False | ||||||
| 
 | 
 | ||||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for | # The theme to use for HTML and HTML Help pages.  See the documentation for | ||||||
| # a list of builtin themes. | # a list of builtin themes. | ||||||
| # html_theme = 'sphinx_rtd_theme' | # html_theme = "sphinx_rtd_theme" | ||||||
| html_theme = 'alabaster' | html_theme = "alabaster" | ||||||
| 
 | 
 | ||||||
| # Theme options are theme-specific and customize the look and feel of a theme | # Theme options are theme-specific and customize the look and feel of a theme | ||||||
| # further.  For a list of options available for each theme, see the | # further.  For a list of options available for each theme, see the | ||||||
|  | @ -141,21 +141,21 @@ html_theme_path = [alabaster.get_path()] | ||||||
| # The name of an image file (within the static path) to use as favicon of the | # The name of an image file (within the static path) to use as favicon of the | ||||||
| # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 | # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 | ||||||
| # pixels large. | # pixels large. | ||||||
| html_favicon = 'favicon.ico' | html_favicon = "favicon.ico" | ||||||
| 
 | 
 | ||||||
| # Add any paths that contain custom static files (such as style sheets) here, | # Add any paths that contain custom static files (such as style sheets) here, | ||||||
| # relative to this directory. They are copied after the builtin static files, | # relative to this directory. They are copied after the builtin static files, | ||||||
| # so a file named "default.css" will overwrite the builtin "default.css". | # so a file named "default.css" will overwrite the builtin "default.css". | ||||||
| html_static_path = ['_static'] | html_static_path = ["_static"] | ||||||
| 
 | 
 | ||||||
| # Add any extra paths that contain custom files (such as robots.txt or | # Add any extra paths that contain custom files (such as robots.txt or | ||||||
| # .htaccess) here, relative to this directory. These files are copied | # .htaccess) here, relative to this directory. These files are copied | ||||||
| # directly to the root of the documentation. | # directly to the root of the documentation. | ||||||
| #html_extra_path = [] | #html_extra_path = [] | ||||||
| 
 | 
 | ||||||
| # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | # If not "", a "Last updated on:" timestamp is inserted at every page bottom, | ||||||
| # using the given strftime format. | # using the given strftime format. | ||||||
| #html_last_updated_fmt = '%b %d, %Y' | #html_last_updated_fmt = "%b %d, %Y" | ||||||
| 
 | 
 | ||||||
| # If true, SmartyPants will be used to convert quotes and dashes to | # If true, SmartyPants will be used to convert quotes and dashes to | ||||||
| # typographically correct entities. | # typographically correct entities. | ||||||
|  | @ -189,50 +189,50 @@ html_static_path = ['_static'] | ||||||
| # If true, an OpenSearch description file will be output, and all pages will | # If true, an OpenSearch description file will be output, and all pages will | ||||||
| # contain a <link> tag referring to it.  The value of this option must be the | # contain a <link> tag referring to it.  The value of this option must be the | ||||||
| # base URL from which the finished HTML is served. | # base URL from which the finished HTML is served. | ||||||
| #html_use_opensearch = '' | #html_use_opensearch = "" | ||||||
| 
 | 
 | ||||||
| # This is the file name suffix for HTML files (e.g. ".xhtml"). | # This is the file name suffix for HTML files (e.g. ".xhtml"). | ||||||
| #html_file_suffix = None | #html_file_suffix = None | ||||||
| 
 | 
 | ||||||
| # Language to be used for generating the HTML full-text search index. | # Language to be used for generating the HTML full-text search index. | ||||||
| # Sphinx supports the following languages: | # Sphinx supports the following languages: | ||||||
| #   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' | #   "da", "de", "en", "es", "fi", "fr", "hu", "it", "ja" | ||||||
| #   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' | #   "nl", "no", "pt", "ro", "ru", "sv", "tr" | ||||||
| #html_search_language = 'en' | #html_search_language = "en" | ||||||
| 
 | 
 | ||||||
| # A dictionary with options for the search language support, empty by default. | # A dictionary with options for the search language support, empty by default. | ||||||
| # Now only 'ja' uses this config value | # Now only "ja" uses this config value | ||||||
| #html_search_options = {'type': 'default'} | #html_search_options = {"type": "default"} | ||||||
| 
 | 
 | ||||||
| # The name of a javascript file (relative to the configuration directory) that | # The name of a javascript file (relative to the configuration directory) that | ||||||
| # implements a search results scorer. If empty, the default will be used. | # implements a search results scorer. If empty, the default will be used. | ||||||
| #html_search_scorer = 'scorer.js' | #html_search_scorer = "scorer.js" | ||||||
| 
 | 
 | ||||||
| # Output file base name for HTML help builder. | # Output file base name for HTML help builder. | ||||||
| htmlhelp_basename = 'dependency_injectordoc' | htmlhelp_basename = "dependency_injectordoc" | ||||||
| 
 | 
 | ||||||
| # -- Options for LaTeX output --------------------------------------------- | # -- Options for LaTeX output --------------------------------------------- | ||||||
| 
 | 
 | ||||||
| latex_elements = { | latex_elements = { | ||||||
| # The paper size ('letterpaper' or 'a4paper'). | # The paper size ("letterpaper" or "a4paper"). | ||||||
| #'papersize': 'letterpaper', | #"papersize": "letterpaper", | ||||||
| 
 | 
 | ||||||
| # The font size ('10pt', '11pt' or '12pt'). | # The font size ("10pt", "11pt" or "12pt"). | ||||||
| #'pointsize': '10pt', | #"pointsize": "10pt", | ||||||
| 
 | 
 | ||||||
| # Additional stuff for the LaTeX preamble. | # Additional stuff for the LaTeX preamble. | ||||||
| #'preamble': '', | #"preamble": "", | ||||||
| 
 | 
 | ||||||
| # Latex figure (float) alignment | # Latex figure (float) alignment | ||||||
| #'figure_align': 'htbp', | #"figure_align": "htbp", | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| # Grouping the document tree into LaTeX files. List of tuples | # Grouping the document tree into LaTeX files. List of tuples | ||||||
| # (source start file, target name, title, | # (source start file, target name, title, | ||||||
| #  author, documentclass [howto, manual, or own class]). | #  author, documentclass [howto, manual, or own class]). | ||||||
| latex_documents = [ | latex_documents = [ | ||||||
|   (master_doc, 'dependency_injector.tex', u'Dependency Injector Documentation', |   (master_doc, "dependency_injector.tex", u"Dependency Injector Documentation", | ||||||
|    u'Roman Mogylatov', 'manual'), |    u"Roman Mogylatov", "manual"), | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| # The name of an image file (relative to this directory) to place at the top of | # The name of an image file (relative to this directory) to place at the top of | ||||||
|  | @ -261,7 +261,7 @@ latex_documents = [ | ||||||
| # One entry per manual page. List of tuples | # One entry per manual page. List of tuples | ||||||
| # (source start file, name, description, authors, manual section). | # (source start file, name, description, authors, manual section). | ||||||
| man_pages = [ | man_pages = [ | ||||||
|     (master_doc, 'Dependency Injector', u'Dependency Injector Documentation', |     (master_doc, "Dependency Injector", u"Dependency Injector Documentation", | ||||||
|      [author], 1) |      [author], 1) | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | @ -275,9 +275,9 @@ man_pages = [ | ||||||
| # (source start file, target name, title, author, | # (source start file, target name, title, author, | ||||||
| #  dir menu entry, description, category) | #  dir menu entry, description, category) | ||||||
| texinfo_documents = [ | texinfo_documents = [ | ||||||
|   (master_doc, 'Dependency Injector', u'Dependency Injector Documentation', |   (master_doc, "Dependency Injector", u"Dependency Injector Documentation", | ||||||
|    author, 'Dependency Injector', 'Dependency injection microframework for Python', |    author, "Dependency Injector", "Dependency injection microframework for Python", | ||||||
|    'Miscellaneous'), |    "Miscellaneous"), | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| # Documents to append as an appendix to all manuals. | # Documents to append as an appendix to all manuals. | ||||||
|  | @ -286,24 +286,24 @@ texinfo_documents = [ | ||||||
| # If false, no module index is generated. | # If false, no module index is generated. | ||||||
| #texinfo_domain_indices = True | #texinfo_domain_indices = True | ||||||
| 
 | 
 | ||||||
| # How to display URL addresses: 'footnote', 'no', or 'inline'. | # How to display URL addresses: "footnote", "no", or "inline". | ||||||
| #texinfo_show_urls = 'footnote' | #texinfo_show_urls = "footnote" | ||||||
| 
 | 
 | ||||||
| # If true, do not generate a @detailmenu in the "Top" node's menu. | # If true, do not generate a @detailmenu in the "Top" node"s menu. | ||||||
| #texinfo_no_detailmenu = False | #texinfo_no_detailmenu = False | ||||||
| 
 | 
 | ||||||
| autodoc_member_order = 'bysource' | autodoc_member_order = "bysource" | ||||||
| 
 | 
 | ||||||
| disqus_shortname = 'python-dependency-injector' | disqus_shortname = "python-dependency-injector" | ||||||
| 
 | 
 | ||||||
| html_theme_options = { | html_theme_options = { | ||||||
|     'github_user': 'ets-labs', |     "github_user": "ets-labs", | ||||||
|     'github_repo': 'python-dependency-injector', |     "github_repo": "python-dependency-injector", | ||||||
|     'github_type': 'star', |     "github_type": "star", | ||||||
|     'github_button': True, |     "github_button": True, | ||||||
|     'github_banner': True, |     "github_banner": True, | ||||||
|     'logo': 'logo.svg', |     "logo": "logo.svg", | ||||||
|     'description': 'Dependency injection framework for Python by Roman Mogylatov', |     "description": "Dependency injection framework for Python by Roman Mogylatov", | ||||||
|     'code_font_size': '10pt', |     "code_font_size": "10pt", | ||||||
|     'analytics_id': 'UA-67012059-1', |     "analytics_id": "UA-67012059-1", | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -34,10 +34,39 @@ Injections in the declarative container are done the usual way: | ||||||
|    :language: python |    :language: python | ||||||
|    :lines: 3- |    :lines: 3- | ||||||
| 
 | 
 | ||||||
| You can override the container providers when you create the container instance: | You can override container providers while creating a container instance: | ||||||
| 
 | 
 | ||||||
| .. literalinclude:: ../../examples/containers/declarative_override_providers.py | .. literalinclude:: ../../examples/containers/declarative_override_providers.py | ||||||
|    :language: python |    :language: python | ||||||
|    :lines: 3- |    :lines: 3- | ||||||
|  |    :emphasize-lines: 13 | ||||||
|  | 
 | ||||||
|  | Alternatively, you can call ``container.override_providers()`` method when the container instance | ||||||
|  | already exists: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 3 | ||||||
|  | 
 | ||||||
|  |    container = Container() | ||||||
|  | 
 | ||||||
|  |    container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar)) | ||||||
|  | 
 | ||||||
|  |    assert isinstance(container.foo(), mock.Mock) | ||||||
|  |    assert isinstance(container.bar(), mock.Mock) | ||||||
|  | 
 | ||||||
|  | You can also use ``container.override_providers()`` with a context manager to reset | ||||||
|  | provided overriding after the context is closed: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 3 | ||||||
|  | 
 | ||||||
|  |    container = Container() | ||||||
|  | 
 | ||||||
|  |    with container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar)): | ||||||
|  |        assert isinstance(container.foo(), mock.Mock) | ||||||
|  |        assert isinstance(container.bar(), mock.Mock) | ||||||
|  | 
 | ||||||
|  |    assert isinstance(container.foo(), Foo) | ||||||
|  |    assert isinstance(container.bar(), Bar) | ||||||
| 
 | 
 | ||||||
| .. disqus:: | .. disqus:: | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ additional arguments. | ||||||
|    ) |    ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        instance = concrete_factory() |        instance = concrete_factory() | ||||||
|        # Same as: # instance = SomeClass(base_argument=1, extra_argument=2) |        # Same as: # instance = SomeClass(base_argument=1, extra_argument=2) | ||||||
| 
 | 
 | ||||||
|  | @ -43,21 +43,21 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro | ||||||
|        providers.Factory(dict, arg1=1), |        providers.Factory(dict, arg1=1), | ||||||
|        arg2=2, |        arg2=2, | ||||||
|    ) |    ) | ||||||
|    print(chained_dict_factory())  # prints: {'arg1': 1, 'arg2': 2} |    print(chained_dict_factory())  # prints: {"arg1": 1, "arg2": 2} | ||||||
| 
 | 
 | ||||||
|    # 2. Keyword arguments of upper level factory have priority |    # 2. Keyword arguments of upper level factory have priority | ||||||
|    chained_dict_factory = providers.Factory( |    chained_dict_factory = providers.Factory( | ||||||
|        providers.Factory(dict, arg1=1), |        providers.Factory(dict, arg1=1), | ||||||
|        arg1=2, |        arg1=2, | ||||||
|    ) |    ) | ||||||
|    print(chained_dict_factory())  # prints: {'arg1': 2} |    print(chained_dict_factory())  # prints: {"arg1": 2} | ||||||
| 
 | 
 | ||||||
|    # 3. Keyword arguments provided from context have the most priority |    # 3. Keyword arguments provided from context have the most priority | ||||||
|    chained_dict_factory = providers.Factory( |    chained_dict_factory = providers.Factory( | ||||||
|        providers.Factory(dict, arg1=1), |        providers.Factory(dict, arg1=1), | ||||||
|        arg1=2, |        arg1=2, | ||||||
|    ) |    ) | ||||||
|    print(chained_dict_factory(arg1=3))  # prints: {'arg1': 3} |    print(chained_dict_factory(arg1=3))  # prints: {"arg1": 3} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Credits | Credits | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ additional arguments. | ||||||
|    ) |    ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        instance = concrete_factory() |        instance = concrete_factory() | ||||||
|        # Same as: # instance = SomeClass(base_argument=1, extra_argument=2) |        # Same as: # instance = SomeClass(base_argument=1, extra_argument=2) | ||||||
| 
 | 
 | ||||||
|  | @ -46,7 +46,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro | ||||||
|        arg1=1, |        arg1=1, | ||||||
|    ) |    ) | ||||||
|    dict_factory = factory_of_dict_factories(arg2=2) |    dict_factory = factory_of_dict_factories(arg2=2) | ||||||
|    print(dict_factory())  # prints: {'arg1': 1, 'arg2': 2} |    print(dict_factory())  # prints: {"arg1": 1, "arg2": 2} | ||||||
| 
 | 
 | ||||||
|    # 2. Keyword arguments of upper level factory have priority |    # 2. Keyword arguments of upper level factory have priority | ||||||
|    factory_of_dict_factories = providers.Factory( |    factory_of_dict_factories = providers.Factory( | ||||||
|  | @ -55,7 +55,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro | ||||||
|        arg1=1, |        arg1=1, | ||||||
|    ) |    ) | ||||||
|    dict_factory = factory_of_dict_factories(arg1=2) |    dict_factory = factory_of_dict_factories(arg1=2) | ||||||
|    print(dict_factory())  # prints: {'arg1': 2} |    print(dict_factory())  # prints: {"arg1": 2} | ||||||
| 
 | 
 | ||||||
|    # 3. Keyword arguments provided from context have the most priority |    # 3. Keyword arguments provided from context have the most priority | ||||||
|    factory_of_dict_factories = providers.Factory( |    factory_of_dict_factories = providers.Factory( | ||||||
|  | @ -64,7 +64,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro | ||||||
|        arg1=1, |        arg1=1, | ||||||
|    ) |    ) | ||||||
|    dict_factory = factory_of_dict_factories(arg1=2) |    dict_factory = factory_of_dict_factories(arg1=2) | ||||||
|    print(dict_factory(arg1=3))  # prints: {'arg1': 3} |    print(dict_factory(arg1=3))  # prints: {"arg1": 3} | ||||||
| 
 | 
 | ||||||
| Credits | Credits | ||||||
| ------- | ------- | ||||||
|  |  | ||||||
|  | @ -70,7 +70,7 @@ Tests use :ref:`provider-overriding` feature to replace giphy client with a mock | ||||||
| 
 | 
 | ||||||
| .. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/tests.py | .. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/tests.py | ||||||
|    :language: python |    :language: python | ||||||
|    :emphasize-lines: 27,54,68 |    :emphasize-lines: 34,61,75 | ||||||
| 
 | 
 | ||||||
| Sources | Sources | ||||||
| ------- | ------- | ||||||
|  |  | ||||||
|  | @ -86,7 +86,7 @@ Key features of the ``Dependency Injector``: | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    from dependency_injector import containers, providers |    from dependency_injector import containers, providers | ||||||
|    from dependency_injector.wiring import inject, Provide |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
|  | @ -110,11 +110,11 @@ Key features of the ``Dependency Injector``: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.api_key.from_env('API_KEY') |        container.config.api_key.from_env("API_KEY") | ||||||
|        container.config.timeout.from_env('TIMEOUT') |        container.config.timeout.from_env("TIMEOUT") | ||||||
|        container.wire(modules=[sys.modules[__name__]]) |        container.wire(modules=[__name__]) | ||||||
| 
 | 
 | ||||||
|        main()  # <-- dependency is injected automatically |        main()  # <-- dependency is injected automatically | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -67,8 +67,8 @@ Before: | ||||||
|    class ApiClient: |    class ApiClient: | ||||||
| 
 | 
 | ||||||
|        def __init__(self): |        def __init__(self): | ||||||
|            self.api_key = os.getenv('API_KEY')  # <-- dependency |            self.api_key = os.getenv("API_KEY")  # <-- dependency | ||||||
|            self.timeout = os.getenv('TIMEOUT')  # <-- dependency |            self.timeout = os.getenv("TIMEOUT")  # <-- dependency | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    class Service: |    class Service: | ||||||
|  | @ -82,7 +82,7 @@ Before: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        main() |        main() | ||||||
| 
 | 
 | ||||||
| After: | After: | ||||||
|  | @ -109,12 +109,12 @@ After: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        main( |        main( | ||||||
|            service=Service( |            service=Service( | ||||||
|                api_client=ApiClient( |                api_client=ApiClient( | ||||||
|                    api_key=os.getenv('API_KEY'), |                    api_key=os.getenv("API_KEY"), | ||||||
|                    timeout=os.getenv('TIMEOUT'), |                    timeout=os.getenv("TIMEOUT"), | ||||||
|                ), |                ), | ||||||
|            ), |            ), | ||||||
|        ) |        ) | ||||||
|  | @ -136,8 +136,8 @@ Now you need to assemble and inject the objects like this: | ||||||
|    main( |    main( | ||||||
|        service=Service( |        service=Service( | ||||||
|            api_client=ApiClient( |            api_client=ApiClient( | ||||||
|                api_key=os.getenv('API_KEY'), |                api_key=os.getenv("API_KEY"), | ||||||
|                timeout=os.getenv('TIMEOUT'), |                timeout=os.getenv("TIMEOUT"), | ||||||
|            ), |            ), | ||||||
|        ), |        ), | ||||||
|    ) |    ) | ||||||
|  | @ -162,7 +162,7 @@ the dependency. | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    from dependency_injector import containers, providers |    from dependency_injector import containers, providers | ||||||
|    from dependency_injector.wiring import inject, Provide |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
|  | @ -186,11 +186,11 @@ the dependency. | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.api_key.from_env('API_KEY') |        container.config.api_key.from_env("API_KEY") | ||||||
|        container.config.timeout.from_env('TIMEOUT') |        container.config.timeout.from_env("TIMEOUT") | ||||||
|        container.wire(modules=[sys.modules[__name__]]) |        container.wire(modules=[__name__]) | ||||||
| 
 | 
 | ||||||
|        main()  # <-- dependency is injected automatically |        main()  # <-- dependency is injected automatically | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ To verify the installed version: | ||||||
| 
 | 
 | ||||||
|     >>> import dependency_injector |     >>> import dependency_injector | ||||||
|     >>> dependency_injector.__version__ |     >>> dependency_injector.__version__ | ||||||
|     '4.0.0' |     '4.37.0' | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
|     When add ``Dependency Injector`` to the ``requirements.txt`` don't forget to pin version |     When add ``Dependency Injector`` to the ``requirements.txt`` don't forget to pin version | ||||||
|  |  | ||||||
|  | @ -7,6 +7,25 @@ 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.37.0 | ||||||
|  | ------ | ||||||
|  | - Add support of Python 3.10. | ||||||
|  | - Improve wiring with adding importing modules and packages from a string | ||||||
|  |   ``container.wire(modules=["yourapp.module1"])``. | ||||||
|  | - Add container wiring configuration ``wiring_config = containers.WiringConfiguration()``. | ||||||
|  | - Add support of ``with`` statement for ``container.override_providers()`` method. | ||||||
|  | - Add ``Configuration(yaml_files=[...])`` argument. | ||||||
|  | - Add ``Configuration(ini_files=[...])`` argument. | ||||||
|  | - Add ``Configuration(pydantic_settings=[...])`` argument. | ||||||
|  | - Drop support of Python 3.4. There are no immediate breaking changes, but Dependency Injector | ||||||
|  |   will no longer be tested on Python 3.4 and any bugs will not be fixed. | ||||||
|  | - Announce the date of dropping Python 3.5 support (Jan 1st 2022). | ||||||
|  | - Fix ``Dependency.is_defined`` attribute to always return boolean value. | ||||||
|  | - Fix ``envs_required=False`` behavior in ``Configuration.from_*()`` methods | ||||||
|  |   to give a priority to the explicitly provided value. | ||||||
|  | - Update documentation and fix typos. | ||||||
|  | - Regenerate C sources using Cython 0.29.24. | ||||||
|  | - Migrate tests to ``pytest``. | ||||||
| 
 | 
 | ||||||
| 4.36.2 | 4.36.2 | ||||||
| ------ | ------ | ||||||
|  |  | ||||||
|  | @ -45,6 +45,21 @@ where ``examples/providers/configuration/config.ini`` is: | ||||||
| .. literalinclude:: ../../examples/providers/configuration/config.ini | .. literalinclude:: ../../examples/providers/configuration/config.ini | ||||||
|    :language: ini |    :language: ini | ||||||
| 
 | 
 | ||||||
|  | Alternatively, you can provide a path to the INI file over the configuration provider argument. In that case, | ||||||
|  | the container will call ``config.from_ini()`` automatically: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 3 | ||||||
|  | 
 | ||||||
|  |    class Container(containers.DeclarativeContainer): | ||||||
|  | 
 | ||||||
|  |        config = providers.Configuration(ini_files=["./config.ini"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    if __name__ == "__main__": | ||||||
|  |        container = Container()  # Config is loaded from ./config.ini | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| :py:meth:`Configuration.from_ini` method supports environment variables interpolation. | :py:meth:`Configuration.from_ini` method supports environment variables interpolation. | ||||||
| 
 | 
 | ||||||
| .. code-block:: ini | .. code-block:: ini | ||||||
|  | @ -72,6 +87,20 @@ where ``examples/providers/configuration/config.yml`` is: | ||||||
| .. literalinclude:: ../../examples/providers/configuration/config.yml | .. literalinclude:: ../../examples/providers/configuration/config.yml | ||||||
|    :language: ini |    :language: ini | ||||||
| 
 | 
 | ||||||
|  | Alternatively, you can provide a path to the YAML file over the configuration provider argument. In that case, | ||||||
|  | the container will call ``config.from_yaml()`` automatically: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 3 | ||||||
|  | 
 | ||||||
|  |    class Container(containers.DeclarativeContainer): | ||||||
|  | 
 | ||||||
|  |        config = providers.Configuration(yaml_files=["./config.yml"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    if __name__ == "__main__": | ||||||
|  |        container = Container()  # Config is loaded from ./config.yml | ||||||
|  | 
 | ||||||
| :py:meth:`Configuration.from_yaml` method supports environment variables interpolation. | :py:meth:`Configuration.from_yaml` method supports environment variables interpolation. | ||||||
| 
 | 
 | ||||||
| .. code-block:: ini | .. code-block:: ini | ||||||
|  | @ -91,7 +120,7 @@ To use another loader use ``loader`` argument: | ||||||
|    import yaml |    import yaml | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    container.config.from_yaml('config.yml', loader=yaml.UnsafeLoader) |    container.config.from_yaml("config.yml", loader=yaml.UnsafeLoader) | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|  | @ -123,7 +152,22 @@ If you need to pass an argument to this call, use ``.from_pydantic()`` keyword a | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    container.config.from_pydantic(Settings(), exclude={'optional'}) |    container.config.from_pydantic(Settings(), exclude={"optional"}) | ||||||
|  | 
 | ||||||
|  | Alternatively, you can provide a ``pydantic`` settings object over the configuration provider argument. In that case, | ||||||
|  | the container will call ``config.from_pydantic()`` automatically: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 3 | ||||||
|  | 
 | ||||||
|  |    class Container(containers.DeclarativeContainer): | ||||||
|  | 
 | ||||||
|  |        config = providers.Configuration(pydantic_settings=[Settings()]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    if __name__ == "__main__": | ||||||
|  |        container = Container()  # Config is loaded from Settings() | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|  | @ -225,7 +269,7 @@ undefined environment variable that doesn't have a default value, pass argument | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    container.config.from_yaml('config.yml', envs_required=True) |    container.config.from_yaml("config.yml", envs_required=True) | ||||||
| 
 | 
 | ||||||
| See also: :ref:`configuration-strict-mode`. | See also: :ref:`configuration-strict-mode`. | ||||||
| 
 | 
 | ||||||
|  | @ -270,13 +314,13 @@ Mandatory YAML file: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    container.config.from_yaml('config.yaml', required=True) |    container.config.from_yaml("config.yaml", required=True) | ||||||
| 
 | 
 | ||||||
| Mandatory INI file: | Mandatory INI file: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    container.config.from_ini('config.ini', required=True) |    container.config.from_ini("config.ini", required=True) | ||||||
| 
 | 
 | ||||||
| Mandatory dictionary: | Mandatory dictionary: | ||||||
| 
 | 
 | ||||||
|  | @ -288,7 +332,7 @@ Mandatory environment variable: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    container.config.api_key.from_env('API_KEY', required=True) |    container.config.api_key.from_env("API_KEY", required=True) | ||||||
| 
 | 
 | ||||||
| See also: :ref:`configuration-strict-mode`. | See also: :ref:`configuration-strict-mode`. | ||||||
| 
 | 
 | ||||||
|  | @ -346,16 +390,16 @@ configuration data is undefined: | ||||||
|        config = providers.Configuration(strict=True) |        config = providers.Configuration(strict=True) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
| 
 | 
 | ||||||
|        try: |        try: | ||||||
|            container.config.from_yaml('does-not_exist.yml')  # raise exception |            container.config.from_yaml("does-not_exist.yml")  # raise exception | ||||||
|        except FileNotFoundError: |        except FileNotFoundError: | ||||||
|            ... |            ... | ||||||
| 
 | 
 | ||||||
|        try: |        try: | ||||||
|            container.config.from_ini('does-not_exist.ini')  # raise exception |            container.config.from_ini("does-not_exist.ini")  # raise exception | ||||||
|        except FileNotFoundError: |        except FileNotFoundError: | ||||||
|            ... |            ... | ||||||
| 
 | 
 | ||||||
|  | @ -365,7 +409,7 @@ configuration data is undefined: | ||||||
|            ... |            ... | ||||||
| 
 | 
 | ||||||
|        try: |        try: | ||||||
|            container.config.from_env('UNDEFINED_ENV_VAR')  # raise exception |            container.config.from_env("UNDEFINED_ENV_VAR")  # raise exception | ||||||
|        except ValueError: |        except ValueError: | ||||||
|            ... |            ... | ||||||
| 
 | 
 | ||||||
|  | @ -385,7 +429,7 @@ an undefined environment variable without a default value. | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|        try: |        try: | ||||||
|            container.config.from_yaml('undefined_env.yml')  # raise exception |            container.config.from_yaml("undefined_env.yml")  # raise exception | ||||||
|        except ValueError: |        except ValueError: | ||||||
|            ... |            ... | ||||||
| 
 | 
 | ||||||
|  | @ -398,11 +442,11 @@ You can override ``.from_*()`` methods behaviour in strict mode using ``required | ||||||
|        config = providers.Configuration(strict=True) |        config = providers.Configuration(strict=True) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
| 
 | 
 | ||||||
|        container.config.from_yaml('config.yml') |        container.config.from_yaml("config.yml") | ||||||
|        container.config.from_yaml('config.local.yml', required=False) |        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 | You can also use ``.required()`` option modifier when making an injection. It does not require to switch | ||||||
| configuration provider to strict mode. | configuration provider to strict mode. | ||||||
|  |  | ||||||
|  | @ -23,8 +23,8 @@ To use non-string keys or keys with ``.`` and ``-`` provide a dictionary as a po | ||||||
| 
 | 
 | ||||||
|    providers.Dict({ |    providers.Dict({ | ||||||
|        SomeClass: providers.Factory(...), |        SomeClass: providers.Factory(...), | ||||||
|        'key.with.periods': providers.Factory(...), |        "key.with.periods": providers.Factory(...), | ||||||
|        'key-with-dashes': providers.Factory(...), |        "key-with-dashes": providers.Factory(...), | ||||||
|    }) |    }) | ||||||
| 
 | 
 | ||||||
| Example: | Example: | ||||||
|  |  | ||||||
|  | @ -98,7 +98,7 @@ you configure global resource: | ||||||
| 
 | 
 | ||||||
|        configure_logging = providers.Resource( |        configure_logging = providers.Resource( | ||||||
|            logging.config.fileConfig, |            logging.config.fileConfig, | ||||||
|            fname='logging.ini', |            fname="logging.ini", | ||||||
|        ) |        ) | ||||||
| 
 | 
 | ||||||
| Function initializer does not provide a way to specify custom resource shutdown. | Function initializer does not provide a way to specify custom resource shutdown. | ||||||
|  | @ -210,8 +210,8 @@ first argument. | ||||||
| 
 | 
 | ||||||
| .. _resource-provider-wiring-closing: | .. _resource-provider-wiring-closing: | ||||||
| 
 | 
 | ||||||
| Resources, wiring and per-function execution scope | Resources, wiring, and per-function execution scope | ||||||
| -------------------------------------------------- | --------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| You can compound ``Resource`` provider with :ref:`wiring` to implement per-function | You can compound ``Resource`` provider with :ref:`wiring` to implement per-function | ||||||
| execution scope. For doing this you need to use additional ``Closing`` marker from | execution scope. For doing this you need to use additional ``Closing`` marker from | ||||||
|  | @ -220,7 +220,7 @@ execution scope. For doing this you need to use additional ``Closing`` marker fr | ||||||
| .. literalinclude:: ../../examples/wiring/flask_resource_closing.py | .. literalinclude:: ../../examples/wiring/flask_resource_closing.py | ||||||
|    :language: python |    :language: python | ||||||
|    :lines: 3- |    :lines: 3- | ||||||
|    :emphasize-lines: 24 |    :emphasize-lines: 22 | ||||||
| 
 | 
 | ||||||
| Framework initializes and injects the resource into the function. With the ``Closing`` marker | Framework initializes and injects the resource into the function. With the ``Closing`` marker | ||||||
| framework calls resource ``shutdown()`` method when function execution is over. | framework calls resource ``shutdown()`` method when function execution is over. | ||||||
|  | @ -325,7 +325,7 @@ When you use resource provider with asynchronous initializer you need to call it | ||||||
|        connection = await container.connection.shutdown() |        connection = await container.connection.shutdown() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        asyncio.run(main()) |        asyncio.run(main()) | ||||||
| 
 | 
 | ||||||
| Container ``init_resources()`` and ``shutdown_resources()`` methods should be used asynchronously if there is | Container ``init_resources()`` and ``shutdown_resources()`` methods should be used asynchronously if there is | ||||||
|  | @ -349,7 +349,7 @@ at least one asynchronous resource provider: | ||||||
|        await container.shutdown_resources() |        await container.shutdown_resources() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        asyncio.run(main()) |        asyncio.run(main()) | ||||||
| 
 | 
 | ||||||
| See also: | See also: | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ IDE. | ||||||
|    provider = providers.Factory(Cat) |    provider = providers.Factory(Cat) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        animal = provider()  # mypy knows that animal is of type "Cat" |        animal = provider()  # mypy knows that animal is of type "Cat" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -54,5 +54,7 @@ function or method. | ||||||
|    provider: providers.Provider[Animal] = providers.Factory(Cat) |    provider: providers.Provider[Animal] = providers.Factory(Cat) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        animal = provider()  # mypy knows that animal is of type "Animal" |        animal = provider()  # mypy knows that animal is of type "Animal" | ||||||
|  | 
 | ||||||
|  | .. disqus:: | ||||||
|  |  | ||||||
|  | @ -127,8 +127,6 @@ Now it's time to install the project requirements. We will use next packages: | ||||||
| 
 | 
 | ||||||
| - ``dependency-injector`` - the dependency injection framework | - ``dependency-injector`` - the dependency injection framework | ||||||
| - ``aiohttp`` - the web framework | - ``aiohttp`` - the web framework | ||||||
| - ``aiohttp-devtools`` - the helper library that will provide a development server with live |  | ||||||
|   reloading |  | ||||||
| - ``pyyaml`` - the YAML files parsing library, used for the reading of the configuration files | - ``pyyaml`` - the YAML files parsing library, used for the reading of the configuration files | ||||||
| - ``pytest-aiohttp`` - the helper library for the testing of the ``aiohttp`` application | - ``pytest-aiohttp`` - the helper library for the testing of the ``aiohttp`` application | ||||||
| - ``pytest-cov`` - the helper library for measuring the test coverage | - ``pytest-cov`` - the helper library for measuring the test coverage | ||||||
|  | @ -139,7 +137,6 @@ Put next lines into the ``requirements.txt`` file: | ||||||
| 
 | 
 | ||||||
|    dependency-injector |    dependency-injector | ||||||
|    aiohttp |    aiohttp | ||||||
|    aiohttp-devtools |  | ||||||
|    pyyaml |    pyyaml | ||||||
|    pytest-aiohttp |    pytest-aiohttp | ||||||
|    pytest-cov |    pytest-cov | ||||||
|  | @ -177,16 +174,16 @@ Edit ``handlers.py``: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    async def index(request: web.Request) -> web.Response: |    async def index(request: web.Request) -> web.Response: | ||||||
|        query = request.query.get('query', 'Dependency Injector') |        query = request.query.get("query", "Dependency Injector") | ||||||
|        limit = int(request.query.get('limit', 10)) |        limit = int(request.query.get("limit", 10)) | ||||||
| 
 | 
 | ||||||
|        gifs = [] |        gifs = [] | ||||||
| 
 | 
 | ||||||
|        return web.json_response( |        return web.json_response( | ||||||
|            { |            { | ||||||
|                'query': query, |                "query": query, | ||||||
|                'limit': limit, |                "limit": limit, | ||||||
|                'gifs': gifs, |                "gifs": gifs, | ||||||
|            }, |            }, | ||||||
|        ) |        ) | ||||||
| 
 | 
 | ||||||
|  | @ -228,30 +225,35 @@ Put next into the ``application.py``: | ||||||
|        app = web.Application() |        app = web.Application() | ||||||
|        app.container = container |        app.container = container | ||||||
|        app.add_routes([ |        app.add_routes([ | ||||||
|            web.get('/', handlers.index), |            web.get("/", handlers.index), | ||||||
|        ]) |        ]) | ||||||
|        return app |        return app | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |    if __name__ == "__main__": | ||||||
|  |        app = create_app() | ||||||
|  |        web.run_app(app) | ||||||
|  | 
 | ||||||
| Now we're ready to run our application | Now we're ready to run our application | ||||||
| 
 | 
 | ||||||
| Do next in the terminal: | Do next in the terminal: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    adev runserver giphynavigator/application.py --livereload |    python -m giphynavigator.application | ||||||
| 
 | 
 | ||||||
| The output should be something like: | The output should be something like: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    [18:52:59] Starting aux server at http://localhost:8001 ◆ |    ======== Running on http://0.0.0.0:8080 ======== | ||||||
|    [18:52:59] Starting dev server at http://localhost:8000 ● |    (Press CTRL+C to quit) | ||||||
| 
 | 
 | ||||||
| Let's check that it works. Open another terminal session and use ``httpie``: | Let's check that it works. Open another terminal session and use ``httpie``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    http http://127.0.0.1:8000/ |    http http://0.0.0.0:8080/ | ||||||
| 
 | 
 | ||||||
| You should see: | You should see: | ||||||
| 
 | 
 | ||||||
|  | @ -261,7 +263,7 @@ You should see: | ||||||
|    Content-Length: 844 |    Content-Length: 844 | ||||||
|    Content-Type: application/json; charset=utf-8 |    Content-Type: application/json; charset=utf-8 | ||||||
|    Date: Wed, 29 Jul 2020 21:01:50 GMT |    Date: Wed, 29 Jul 2020 21:01:50 GMT | ||||||
|    Server: Python/3.8 aiohttp/3.6.2 |    Server: Python/3.10 aiohttp/3.6.2 | ||||||
| 
 | 
 | ||||||
|    { |    { | ||||||
|        "gifs": [], |        "gifs": [], | ||||||
|  | @ -304,7 +306,7 @@ and put next into it: | ||||||
| 
 | 
 | ||||||
|    class GiphyClient: |    class GiphyClient: | ||||||
| 
 | 
 | ||||||
|        API_URL = 'https://api.giphy.com/v1' |        API_URL = "https://api.giphy.com/v1" | ||||||
| 
 | 
 | ||||||
|        def __init__(self, api_key, timeout): |        def __init__(self, api_key, timeout): | ||||||
|            self._api_key = api_key |            self._api_key = api_key | ||||||
|  | @ -312,11 +314,11 @@ and put next into it: | ||||||
| 
 | 
 | ||||||
|        async def search(self, query, limit): |        async def search(self, query, limit): | ||||||
|            """Make search API call and return result.""" |            """Make search API call and return result.""" | ||||||
|            url = f'{self.API_URL}/gifs/search' |            url = f"{self.API_URL}/gifs/search" | ||||||
|            params = { |            params = { | ||||||
|                'q': query, |                "q": query, | ||||||
|                'api_key': self._api_key, |                "api_key": self._api_key, | ||||||
|                'limit': limit, |                "limit": limit, | ||||||
|            } |            } | ||||||
|            async with ClientSession(timeout=self._timeout) as session: |            async with ClientSession(timeout=self._timeout) as session: | ||||||
|                async with session.get(url, params=params) as response: |                async with session.get(url, params=params) as response: | ||||||
|  | @ -328,8 +330,10 @@ Now we need to add ``GiphyClient`` into the container. The ``GiphyClient`` has t | ||||||
| that have to be injected: the API key and the request timeout. We will need to use two more | that have to be injected: the API key and the request timeout. We will need to use two more | ||||||
| providers from the ``dependency_injector.providers`` module: | providers from the ``dependency_injector.providers`` module: | ||||||
| 
 | 
 | ||||||
| - ``Factory`` provider that will create the ``GiphyClient`` client. | - ``Factory`` provider. It will create a ``GiphyClient`` client. | ||||||
| - ``Configuration`` provider that will provide the API key and the request timeout. | - ``Configuration`` provider. It will provide an API key and a request timeout for the ``GiphyClient`` | ||||||
|  |   client. We will specify the location of the configuration file. The configuration provider will parse | ||||||
|  |   the configuration file when we create a container instance. | ||||||
| 
 | 
 | ||||||
| Edit ``containers.py``: | Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|  | @ -345,7 +349,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        giphy_client = providers.Factory( |        giphy_client = providers.Factory( | ||||||
|            giphy.GiphyClient, |            giphy.GiphyClient, | ||||||
|  | @ -353,18 +357,8 @@ Edit ``containers.py``: | ||||||
|            timeout=config.giphy.request_timeout, |            timeout=config.giphy.request_timeout, | ||||||
|        ) |        ) | ||||||
| 
 | 
 | ||||||
| .. note:: | Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml`` in | ||||||
| 
 | the root root of the project: | ||||||
|    We have used the configuration value before it was defined. That's the principle how the |  | ||||||
|    ``Configuration`` provider works. |  | ||||||
| 
 |  | ||||||
|    Use first, define later. |  | ||||||
| 
 |  | ||||||
| Now let's add the configuration file. |  | ||||||
| 
 |  | ||||||
| We will use YAML. |  | ||||||
| 
 |  | ||||||
| Create an empty file ``config.yml`` in the root root of the project: |  | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
|    :emphasize-lines: 9 |    :emphasize-lines: 9 | ||||||
|  | @ -387,17 +381,14 @@ and put next into it: | ||||||
|    giphy: |    giphy: | ||||||
|      request_timeout: 10 |      request_timeout: 10 | ||||||
| 
 | 
 | ||||||
| We will use an environment variable ``GIPHY_API_KEY`` to provide the API key. |  | ||||||
| 
 | 
 | ||||||
| Now we need to edit ``create_app()`` to make two things when application starts: | We will use the ``GIPHY_API_KEY`` environment variable to provide the API key. Let’s edit | ||||||
| 
 | ``create_app()`` to fetch the key value from it. | ||||||
| - Load the configuration file the ``config.yml``. |  | ||||||
| - Load the API key from the ``GIPHY_API_KEY`` environment variable. |  | ||||||
| 
 | 
 | ||||||
| Edit ``application.py``: | Edit ``application.py``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 11-12 |    :emphasize-lines: 11 | ||||||
| 
 | 
 | ||||||
|    """Application module.""" |    """Application module.""" | ||||||
| 
 | 
 | ||||||
|  | @ -409,17 +400,20 @@ Edit ``application.py``: | ||||||
| 
 | 
 | ||||||
|    def create_app() -> web.Application: |    def create_app() -> web.Application: | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.from_yaml('config.yml') |        container.config.giphy.api_key.from_env("GIPHY_API_KEY") | ||||||
|        container.config.giphy.api_key.from_env('GIPHY_API_KEY') |  | ||||||
| 
 | 
 | ||||||
|        app = web.Application() |        app = web.Application() | ||||||
|        app.container = container |        app.container = container | ||||||
|        app.add_routes([ |        app.add_routes([ | ||||||
|            web.get('/', handlers.index), |            web.get("/", handlers.index), | ||||||
|        ]) |        ]) | ||||||
|        return app |        return app | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |    if __name__ == "__main__": | ||||||
|  |        app = create_app() | ||||||
|  |        web.run_app(app) | ||||||
|  | 
 | ||||||
| Now we need to create an API key and set it to the environment variable. | Now we need to create an API key and set it to the environment variable. | ||||||
| 
 | 
 | ||||||
| As for now, don’t worry, just take this one: | As for now, don’t worry, just take this one: | ||||||
|  | @ -483,7 +477,7 @@ and put next into it: | ||||||
| 
 | 
 | ||||||
|            result = await self._giphy_client.search(query, limit) |            result = await self._giphy_client.search(query, limit) | ||||||
| 
 | 
 | ||||||
|            return [{'url': gif['url']} for gif in result['data']] |            return [{"url": gif["url"]} for gif in result["data"]] | ||||||
| 
 | 
 | ||||||
| The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be | The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be | ||||||
| injected when we add ``SearchService`` to the container. | injected when we add ``SearchService`` to the container. | ||||||
|  | @ -502,7 +496,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        giphy_client = providers.Factory( |        giphy_client = providers.Factory( | ||||||
|            giphy.GiphyClient, |            giphy.GiphyClient, | ||||||
|  | @ -531,7 +525,7 @@ Edit ``handlers.py``: | ||||||
|    """Handlers module.""" |    """Handlers module.""" | ||||||
| 
 | 
 | ||||||
|    from aiohttp import web |    from aiohttp import web | ||||||
|    from dependency_injector.wiring import inject, Provide |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
|    from .services import SearchService |    from .services import SearchService | ||||||
|    from .containers import Container |    from .containers import Container | ||||||
|  | @ -542,60 +536,63 @@ Edit ``handlers.py``: | ||||||
|            request: web.Request, |            request: web.Request, | ||||||
|            search_service: SearchService = Provide[Container.search_service], |            search_service: SearchService = Provide[Container.search_service], | ||||||
|    ) -> web.Response: |    ) -> web.Response: | ||||||
|        query = request.query.get('query', 'Dependency Injector') |        query = request.query.get("query", "Dependency Injector") | ||||||
|        limit = int(request.query.get('limit', 10)) |        limit = int(request.query.get("limit", 10)) | ||||||
| 
 | 
 | ||||||
|        gifs = await search_service.search(query, limit) |        gifs = await search_service.search(query, limit) | ||||||
| 
 | 
 | ||||||
|        return web.json_response( |        return web.json_response( | ||||||
|            { |            { | ||||||
|                'query': query, |                "query": query, | ||||||
|                'limit': limit, |                "limit": limit, | ||||||
|                'gifs': gifs, |                "gifs": gifs, | ||||||
|            }, |            }, | ||||||
|        ) |        ) | ||||||
| 
 | 
 | ||||||
| To make the injection work we need to wire the container instance with the ``handlers`` module. | To make the injection work we need to wire the container with the ``handlers`` module. | ||||||
| This needs to be done once. After it's done we can use ``Provide`` markers to specify as many | Let's configure the container to automatically make wiring with the ``handlers`` module when we | ||||||
| injections as needed for any handler. | create a container instance. | ||||||
| 
 | 
 | ||||||
| Edit ``application.py``: | Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 13 |    :emphasize-lines: 10 | ||||||
| 
 | 
 | ||||||
|    """Application module.""" |    """Containers module.""" | ||||||
| 
 | 
 | ||||||
|    from aiohttp import web |    from dependency_injector import containers, providers | ||||||
| 
 | 
 | ||||||
|    from .containers import Container |    from . import giphy, services | ||||||
|    from . import handlers |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def create_app() -> web.Application: |    class Container(containers.DeclarativeContainer): | ||||||
|        container = Container() |  | ||||||
|        container.config.from_yaml('config.yml') |  | ||||||
|        container.config.giphy.api_key.from_env('GIPHY_API_KEY') |  | ||||||
|        container.wire(modules=[handlers]) |  | ||||||
| 
 | 
 | ||||||
|        app = web.Application() |        wiring_config = containers.WiringConfiguration(modules=[".handlers"]) | ||||||
|        app.container = container |  | ||||||
|        app.add_routes([ |  | ||||||
|            web.get('/', handlers.index), |  | ||||||
|        ]) |  | ||||||
|        return app |  | ||||||
| 
 | 
 | ||||||
| Make sure the app is running or use: |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
|  | 
 | ||||||
|  |        giphy_client = providers.Factory( | ||||||
|  |            giphy.GiphyClient, | ||||||
|  |            api_key=config.giphy.api_key, | ||||||
|  |            timeout=config.giphy.request_timeout, | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  |        search_service = providers.Factory( | ||||||
|  |            services.SearchService, | ||||||
|  |            giphy_client=giphy_client, | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  | Make sure the app is running: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    adev runserver giphynavigator/application.py --livereload |    python -m giphynavigator.application | ||||||
| 
 | 
 | ||||||
| and make a request to the API in the terminal: | and make a request to the API in the terminal: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    http http://localhost:8000/ query=="wow,it works" limit==5 |    http http://0.0.0.0:8080/ query=="wow,it works" limit==5 | ||||||
| 
 | 
 | ||||||
| You should see: | You should see: | ||||||
| 
 | 
 | ||||||
|  | @ -605,7 +602,7 @@ You should see: | ||||||
|    Content-Length: 492 |    Content-Length: 492 | ||||||
|    Content-Type: application/json; charset=utf-8 |    Content-Type: application/json; charset=utf-8 | ||||||
|    Date: Fri, 09 Oct 2020 01:35:48 GMT |    Date: Fri, 09 Oct 2020 01:35:48 GMT | ||||||
|    Server: Python/3.8 aiohttp/3.6.2 |    Server: Python/3.10 aiohttp/3.6.2 | ||||||
| 
 | 
 | ||||||
|    { |    { | ||||||
|        "gifs": [ |        "gifs": [ | ||||||
|  | @ -651,7 +648,7 @@ Edit ``handlers.py``: | ||||||
|    """Handlers module.""" |    """Handlers module.""" | ||||||
| 
 | 
 | ||||||
|    from aiohttp import web |    from aiohttp import web | ||||||
|    from dependency_injector.wiring import inject, Provide |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
|    from .services import SearchService |    from .services import SearchService | ||||||
|    from .containers import Container |    from .containers import Container | ||||||
|  | @ -664,16 +661,16 @@ Edit ``handlers.py``: | ||||||
|            default_query: str = Provide[Container.config.default.query], |            default_query: str = Provide[Container.config.default.query], | ||||||
|            default_limit: int = Provide[Container.config.default.limit.as_int()], |            default_limit: int = Provide[Container.config.default.limit.as_int()], | ||||||
|    ) -> web.Response: |    ) -> web.Response: | ||||||
|        query = request.query.get('query', default_query) |        query = request.query.get("query", default_query) | ||||||
|        limit = int(request.query.get('limit', default_limit)) |        limit = int(request.query.get("limit", default_limit)) | ||||||
| 
 | 
 | ||||||
|        gifs = await search_service.search(query, limit) |        gifs = await search_service.search(query, limit) | ||||||
| 
 | 
 | ||||||
|        return web.json_response( |        return web.json_response( | ||||||
|            { |            { | ||||||
|                'query': query, |                "query": query, | ||||||
|                'limit': limit, |                "limit": limit, | ||||||
|                'gifs': gifs, |                "gifs": gifs, | ||||||
|            }, |            }, | ||||||
|        ) |        ) | ||||||
| 
 | 
 | ||||||
|  | @ -745,29 +742,29 @@ and put next into it: | ||||||
|    async def test_index(client, app): |    async def test_index(client, app): | ||||||
|        giphy_client_mock = mock.AsyncMock(spec=GiphyClient) |        giphy_client_mock = mock.AsyncMock(spec=GiphyClient) | ||||||
|        giphy_client_mock.search.return_value = { |        giphy_client_mock.search.return_value = { | ||||||
|            'data': [ |            "data": [ | ||||||
|                {'url': 'https://giphy.com/gif1.gif'}, |                {"url": "https://giphy.com/gif1.gif"}, | ||||||
|                {'url': 'https://giphy.com/gif2.gif'}, |                {"url": "https://giphy.com/gif2.gif"}, | ||||||
|            ], |            ], | ||||||
|        } |        } | ||||||
| 
 | 
 | ||||||
|        with app.container.giphy_client.override(giphy_client_mock): |        with app.container.giphy_client.override(giphy_client_mock): | ||||||
|            response = await client.get( |            response = await client.get( | ||||||
|                '/', |                "/", | ||||||
|                params={ |                params={ | ||||||
|                    'query': 'test', |                    "query": "test", | ||||||
|                    'limit': 10, |                    "limit": 10, | ||||||
|                }, |                }, | ||||||
|            ) |            ) | ||||||
| 
 | 
 | ||||||
|        assert response.status == 200 |        assert response.status == 200 | ||||||
|        data = await response.json() |        data = await response.json() | ||||||
|        assert data == { |        assert data == { | ||||||
|            'query': 'test', |            "query": "test", | ||||||
|            'limit': 10, |            "limit": 10, | ||||||
|            'gifs': [ |            "gifs": [ | ||||||
|                {'url': 'https://giphy.com/gif1.gif'}, |                {"url": "https://giphy.com/gif1.gif"}, | ||||||
|                {'url': 'https://giphy.com/gif2.gif'}, |                {"url": "https://giphy.com/gif2.gif"}, | ||||||
|            ], |            ], | ||||||
|        } |        } | ||||||
| 
 | 
 | ||||||
|  | @ -775,30 +772,30 @@ and put next into it: | ||||||
|    async def test_index_no_data(client, app): |    async def test_index_no_data(client, app): | ||||||
|        giphy_client_mock = mock.AsyncMock(spec=GiphyClient) |        giphy_client_mock = mock.AsyncMock(spec=GiphyClient) | ||||||
|        giphy_client_mock.search.return_value = { |        giphy_client_mock.search.return_value = { | ||||||
|            'data': [], |            "data": [], | ||||||
|        } |        } | ||||||
| 
 | 
 | ||||||
|        with app.container.giphy_client.override(giphy_client_mock): |        with app.container.giphy_client.override(giphy_client_mock): | ||||||
|            response = await client.get('/') |            response = await client.get("/") | ||||||
| 
 | 
 | ||||||
|        assert response.status == 200 |        assert response.status == 200 | ||||||
|        data = await response.json() |        data = await response.json() | ||||||
|        assert data['gifs'] == [] |        assert data["gifs"] == [] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    async def test_index_default_params(client, app): |    async def test_index_default_params(client, app): | ||||||
|        giphy_client_mock = mock.AsyncMock(spec=GiphyClient) |        giphy_client_mock = mock.AsyncMock(spec=GiphyClient) | ||||||
|        giphy_client_mock.search.return_value = { |        giphy_client_mock.search.return_value = { | ||||||
|            'data': [], |            "data": [], | ||||||
|        } |        } | ||||||
| 
 | 
 | ||||||
|        with app.container.giphy_client.override(giphy_client_mock): |        with app.container.giphy_client.override(giphy_client_mock): | ||||||
|            response = await client.get('/') |            response = await client.get("/") | ||||||
| 
 | 
 | ||||||
|        assert response.status == 200 |        assert response.status == 200 | ||||||
|        data = await response.json() |        data = await response.json() | ||||||
|        assert data['query'] == app.container.config.default.query() |        assert data["query"] == app.container.config.default.query() | ||||||
|        assert data['limit'] == app.container.config.default.limit() |        assert data["limit"] == app.container.config.default.limit() | ||||||
| 
 | 
 | ||||||
| Now let's run it and check the coverage: | Now let's run it and check the coverage: | ||||||
| 
 | 
 | ||||||
|  | @ -810,24 +807,24 @@ You should see: | ||||||
| 
 | 
 | ||||||
| .. code-block:: | .. code-block:: | ||||||
| 
 | 
 | ||||||
|    platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1 |    platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 | ||||||
|    plugins: cov-2.10.0, aiohttp-0.3.0, asyncio-0.14.0 |    plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0 | ||||||
|    collected 3 items |    collected 3 items | ||||||
| 
 | 
 | ||||||
|    giphynavigator/tests.py ...                                     [100%] |    giphynavigator/tests.py ...                                     [100%] | ||||||
| 
 | 
 | ||||||
|    ---------- coverage: platform darwin, python 3.8.3-final-0 ----------- |    ---------- coverage: platform darwin, python 3.10.0-final-0 ---------- | ||||||
|    Name                            Stmts   Miss  Cover |    Name                            Stmts   Miss  Cover | ||||||
|    --------------------------------------------------- |    --------------------------------------------------- | ||||||
|    giphynavigator/__init__.py          0      0   100% |    giphynavigator/__init__.py          0      0   100% | ||||||
|    giphynavigator/application.py      12      0   100% |    giphynavigator/application.py      13      2    85% | ||||||
|    giphynavigator/containers.py        6      0   100% |    giphynavigator/containers.py        7      0   100% | ||||||
|    giphynavigator/giphy.py            14      9    36% |    giphynavigator/giphy.py            14      9    36% | ||||||
|    giphynavigator/handlers.py         10      0   100% |    giphynavigator/handlers.py         10      0   100% | ||||||
|    giphynavigator/services.py          9      1    89% |    giphynavigator/services.py          9      1    89% | ||||||
|    giphynavigator/tests.py            37      0   100% |    giphynavigator/tests.py            37      0   100% | ||||||
|    --------------------------------------------------- |    --------------------------------------------------- | ||||||
|    TOTAL                              88     10    89% |    TOTAL                              90     12    87% | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -59,8 +59,8 @@ The output should look something like: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    Docker version 19.03.12, build 48a66213fe |    Docker version 20.10.5, build 55c4c88 | ||||||
|    docker-compose version 1.26.2, build eefe0d31 |    docker-compose version 1.29.0, build 07737305 | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|  | @ -129,13 +129,13 @@ Put next lines into the ``requirements.txt`` file: | ||||||
|    pytest-cov |    pytest-cov | ||||||
| 
 | 
 | ||||||
| Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and | Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and | ||||||
| specify how to run it. We will use ``python:3.8-buster`` as a base image. | specify how to run it. We will use ``python:3.9-buster`` as a base image. | ||||||
| 
 | 
 | ||||||
| Put next lines into the ``Dockerfile`` file: | Put next lines into the ``Dockerfile`` file: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    FROM python:3.8-buster |    FROM python:3.10-buster | ||||||
| 
 | 
 | ||||||
|    ENV PYTHONUNBUFFERED=1 |    ENV PYTHONUNBUFFERED=1 | ||||||
| 
 | 
 | ||||||
|  | @ -204,11 +204,11 @@ Logging and configuration | ||||||
| 
 | 
 | ||||||
| In this section we will configure the logging and configuration file parsing. | In this section we will configure the logging and configuration file parsing. | ||||||
| 
 | 
 | ||||||
| Let's start with the the main part of our application - the container. Container will keep all of | Let's start with the the main part of our application – the container. Container will keep all of | ||||||
| the application components and their dependencies. | the application components and their dependencies. | ||||||
| 
 | 
 | ||||||
| First two components that we're going to add are the config object and the provider for | First two components that we're going to add are the configuration provider and the resource provider | ||||||
| configuring the logging. | for configuring the logging. | ||||||
| 
 | 
 | ||||||
| Put next lines into the ``containers.py`` file: | Put next lines into the ``containers.py`` file: | ||||||
| 
 | 
 | ||||||
|  | @ -224,7 +224,7 @@ Put next lines into the ``containers.py`` file: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        logging = providers.Resource( |        logging = providers.Resource( | ||||||
|            logging.basicConfig, |            logging.basicConfig, | ||||||
|  | @ -233,16 +233,7 @@ Put next lines into the ``containers.py`` file: | ||||||
|            format=config.log.format, |            format=config.log.format, | ||||||
|        ) |        ) | ||||||
| 
 | 
 | ||||||
| .. note:: | The configuration file will keep the logging settings. Put next lines into the ``config.yml`` file: | ||||||
| 
 |  | ||||||
|    We have used the configuration value before it was defined. That's the principle how the |  | ||||||
|    ``Configuration`` provider works. |  | ||||||
| 
 |  | ||||||
|    Use first, define later. |  | ||||||
| 
 |  | ||||||
| The configuration file will keep the logging settings. |  | ||||||
| 
 |  | ||||||
| Put next lines into the ``config.yml`` file: |  | ||||||
| 
 | 
 | ||||||
| .. code-block:: yaml | .. code-block:: yaml | ||||||
| 
 | 
 | ||||||
|  | @ -250,9 +241,10 @@ Put next lines into the ``config.yml`` file: | ||||||
|      level: "INFO" |      level: "INFO" | ||||||
|      format: "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s" |      format: "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s" | ||||||
| 
 | 
 | ||||||
| Now let's create the function that will run our daemon. It's traditionally called | Now let's create the function that will run our daemon. It's traditionally called ``main()``. | ||||||
| ``main()``. The ``main()`` function will create the container. Then it will use the container | The ``main()`` function will start the dispatcher, but we will keep it empty for now. | ||||||
| to parse the ``config.yml`` file and call the logging configuration provider. | We will create the container instance before calling ``main()`` in ``if __name__ == "__main__"``. | ||||||
|  | Container instance will parse ``config.yml`` and then we will call the logging configuration provider. | ||||||
| 
 | 
 | ||||||
| Put next lines into the ``__main__.py`` file: | Put next lines into the ``__main__.py`` file: | ||||||
| 
 | 
 | ||||||
|  | @ -267,9 +259,8 @@ Put next lines into the ``__main__.py`` file: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.from_yaml('config.yml') |  | ||||||
|        container.init_resources() |        container.init_resources() | ||||||
| 
 | 
 | ||||||
|        main() |        main() | ||||||
|  | @ -356,7 +347,7 @@ and next into the ``dispatcher.py``: | ||||||
|            asyncio.run(self.start()) |            asyncio.run(self.start()) | ||||||
| 
 | 
 | ||||||
|        async def start(self) -> None: |        async def start(self) -> None: | ||||||
|            self._logger.info('Starting up') |            self._logger.info("Starting up") | ||||||
| 
 | 
 | ||||||
|            for monitor in self._monitors: |            for monitor in self._monitors: | ||||||
|                self._monitor_tasks.append( |                self._monitor_tasks.append( | ||||||
|  | @ -376,11 +367,11 @@ and next into the ``dispatcher.py``: | ||||||
| 
 | 
 | ||||||
|            self._stopping = True |            self._stopping = True | ||||||
| 
 | 
 | ||||||
|            self._logger.info('Shutting down') |            self._logger.info("Shutting down") | ||||||
|            for task, monitor in zip(self._monitor_tasks, self._monitors): |            for task, monitor in zip(self._monitor_tasks, self._monitors): | ||||||
|                task.cancel() |                task.cancel() | ||||||
|            self._monitor_tasks.clear() |            self._monitor_tasks.clear() | ||||||
|            self._logger.info('Shutdown finished successfully') |            self._logger.info("Shutdown finished successfully") | ||||||
| 
 | 
 | ||||||
|        @staticmethod |        @staticmethod | ||||||
|        async def _run_monitor(monitor: Monitor) -> None: |        async def _run_monitor(monitor: Monitor) -> None: | ||||||
|  | @ -396,7 +387,7 @@ and next into the ``dispatcher.py``: | ||||||
|                except asyncio.CancelledError: |                except asyncio.CancelledError: | ||||||
|                    break |                    break | ||||||
|                except Exception: |                except Exception: | ||||||
|                    monitor.logger.exception('Error executing monitor check') |                    monitor.logger.exception("Error executing monitor check") | ||||||
| 
 | 
 | ||||||
|                await asyncio.sleep(_until_next(last=time_start)) |                await asyncio.sleep(_until_next(last=time_start)) | ||||||
| 
 | 
 | ||||||
|  | @ -419,7 +410,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        logging = providers.Resource( |        logging = providers.Resource( | ||||||
|            logging.basicConfig, |            logging.basicConfig, | ||||||
|  | @ -442,13 +433,11 @@ and call the ``run()`` method. We will use :ref:`wiring` feature. | ||||||
| Edit ``__main__.py``: | Edit ``__main__.py``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 3-7,11-13,20 |    :emphasize-lines: 3-5,9-11,17 | ||||||
| 
 | 
 | ||||||
|    """Main module.""" |    """Main module.""" | ||||||
| 
 | 
 | ||||||
|    import sys |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 |  | ||||||
|    from dependency_injector.wiring import inject, Provide |  | ||||||
| 
 | 
 | ||||||
|    from .dispatcher import Dispatcher |    from .dispatcher import Dispatcher | ||||||
|    from .containers import Container |    from .containers import Container | ||||||
|  | @ -459,11 +448,10 @@ Edit ``__main__.py``: | ||||||
|        dispatcher.run() |        dispatcher.run() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.from_yaml('config.yml') |  | ||||||
|        container.init_resources() |        container.init_resources() | ||||||
|        container.wire(modules=[sys.modules[__name__]]) |        container.wire(modules=[__name__]) | ||||||
| 
 | 
 | ||||||
|        main() |        main() | ||||||
| 
 | 
 | ||||||
|  | @ -561,7 +549,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        logging = providers.Resource( |        logging = providers.Resource( | ||||||
|            logging.basicConfig, |            logging.basicConfig, | ||||||
|  | @ -613,10 +601,10 @@ Edit ``monitors.py``: | ||||||
|                options: Dict[str, Any], |                options: Dict[str, Any], | ||||||
|        ) -> None: |        ) -> None: | ||||||
|            self._client = http_client |            self._client = http_client | ||||||
|            self._method = options.pop('method') |            self._method = options.pop("method") | ||||||
|            self._url = options.pop('url') |            self._url = options.pop("url") | ||||||
|            self._timeout = options.pop('timeout') |            self._timeout = options.pop("timeout") | ||||||
|            super().__init__(check_every=options.pop('check_every')) |            super().__init__(check_every=options.pop("check_every")) | ||||||
| 
 | 
 | ||||||
|        async def check(self) -> None: |        async def check(self) -> None: | ||||||
|            time_start = time.time() |            time_start = time.time() | ||||||
|  | @ -631,11 +619,11 @@ Edit ``monitors.py``: | ||||||
|            time_took = time_end - time_start |            time_took = time_end - time_start | ||||||
| 
 | 
 | ||||||
|            self.logger.info( |            self.logger.info( | ||||||
|                'Check\n' |                "Check\n" | ||||||
|                '    %s %s\n' |                "    %s %s\n" | ||||||
|                '    response code: %s\n' |                "    response code: %s\n" | ||||||
|                '    content length: %s\n' |                "    content length: %s\n" | ||||||
|                '    request took: %s seconds', |                "    request took: %s seconds", | ||||||
|                self._method, |                self._method, | ||||||
|                self._url, |                self._url, | ||||||
|                response.status, |                response.status, | ||||||
|  | @ -666,7 +654,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        logging = providers.Resource( |        logging = providers.Resource( | ||||||
|            logging.basicConfig, |            logging.basicConfig, | ||||||
|  | @ -765,7 +753,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        logging = providers.Resource( |        logging = providers.Resource( | ||||||
|            logging.basicConfig, |            logging.basicConfig, | ||||||
|  | @ -890,7 +878,7 @@ Create ``tests.py`` in the ``monitoringdaemon`` package: | ||||||
| and put next into it: | and put next into it: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 54,70-71 |    :emphasize-lines: 54,70-73 | ||||||
| 
 | 
 | ||||||
|    """Tests module.""" |    """Tests module.""" | ||||||
| 
 | 
 | ||||||
|  | @ -911,33 +899,33 @@ and put next into it: | ||||||
| 
 | 
 | ||||||
|    @pytest.fixture |    @pytest.fixture | ||||||
|    def container(): |    def container(): | ||||||
|        container = Container() |        return Container( | ||||||
|        container.config.from_dict({ |            config={ | ||||||
|            'log': { |                "log": { | ||||||
|                'level': 'INFO', |                    "level": "INFO", | ||||||
|                'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s', |                    "formant": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s", | ||||||
|                }, |                }, | ||||||
|            'monitors': { |                "monitors": { | ||||||
|                'example': { |                    "example": { | ||||||
|                    'method': 'GET', |                        "method": "GET", | ||||||
|                    'url': 'http://fake-example.com', |                        "url": "http://fake-example.com", | ||||||
|                    'timeout': 1, |                        "timeout": 1, | ||||||
|                    'check_every': 1, |                        "check_every": 1, | ||||||
|                    }, |                    }, | ||||||
|                'httpbin': { |                    "httpbin": { | ||||||
|                    'method': 'GET', |                        "method": "GET", | ||||||
|                    'url': 'https://fake-httpbin.org/get', |                        "url": "https://fake-httpbin.org/get", | ||||||
|                    'timeout': 1, |                        "timeout": 1, | ||||||
|                    'check_every': 1, |                        "check_every": 1, | ||||||
|                    }, |                    }, | ||||||
|                }, |                }, | ||||||
|        }) |            } | ||||||
|        return container |        ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    @pytest.mark.asyncio |    @pytest.mark.asyncio | ||||||
|    async def test_example_monitor(container, caplog): |    async def test_example_monitor(container, caplog): | ||||||
|        caplog.set_level('INFO') |        caplog.set_level("INFO") | ||||||
| 
 | 
 | ||||||
|        http_client_mock = mock.AsyncMock() |        http_client_mock = mock.AsyncMock() | ||||||
|        http_client_mock.request.return_value = RequestStub( |        http_client_mock.request.return_value = RequestStub( | ||||||
|  | @ -949,21 +937,22 @@ and put next into it: | ||||||
|            example_monitor = container.example_monitor() |            example_monitor = container.example_monitor() | ||||||
|            await example_monitor.check() |            await example_monitor.check() | ||||||
| 
 | 
 | ||||||
|        assert 'http://fake-example.com' in caplog.text |        assert "http://fake-example.com" in caplog.text | ||||||
|        assert 'response code: 200' in caplog.text |        assert "response code: 200" in caplog.text | ||||||
|        assert 'content length: 635' in caplog.text |        assert "content length: 635" in caplog.text | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    @pytest.mark.asyncio |    @pytest.mark.asyncio | ||||||
|    async def test_dispatcher(container, caplog, event_loop): |    async def test_dispatcher(container, caplog, event_loop): | ||||||
|        caplog.set_level('INFO') |        caplog.set_level("INFO") | ||||||
| 
 | 
 | ||||||
|        example_monitor_mock = mock.AsyncMock() |        example_monitor_mock = mock.AsyncMock() | ||||||
|        httpbin_monitor_mock = mock.AsyncMock() |        httpbin_monitor_mock = mock.AsyncMock() | ||||||
| 
 | 
 | ||||||
|        with container.example_monitor.override(example_monitor_mock), \ |        with container.override_providers( | ||||||
|                container.httpbin_monitor.override(httpbin_monitor_mock): |                example_monitor=example_monitor_mock, | ||||||
| 
 |                httpbin_monitor=httpbin_monitor_mock, | ||||||
|  |        ): | ||||||
|            dispatcher = container.dispatcher() |            dispatcher = container.dispatcher() | ||||||
|            event_loop.create_task(dispatcher.start()) |            event_loop.create_task(dispatcher.start()) | ||||||
|            await asyncio.sleep(0.1) |            await asyncio.sleep(0.1) | ||||||
|  | @ -982,25 +971,25 @@ You should see: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    platform linux -- Python 3.8.3, pytest-6.0.1, py-1.9.0, pluggy-0.13.1 |    platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 | ||||||
|    rootdir: /code |    rootdir: /code | ||||||
|    plugins: asyncio-0.14.0, cov-2.10.0 |    plugins: asyncio-0.16.0, cov-3.0.0 | ||||||
|    collected 2 items |    collected 2 items | ||||||
| 
 | 
 | ||||||
|    monitoringdaemon/tests.py ..                                    [100%] |    monitoringdaemon/tests.py ..                                    [100%] | ||||||
| 
 | 
 | ||||||
|    ----------- coverage: platform linux, python 3.8.3-final-0 ----------- |    ---------- coverage: platform linux, python 3.10.0-final-0 ----------- | ||||||
|    Name                             Stmts   Miss  Cover |    Name                             Stmts   Miss  Cover | ||||||
|    ---------------------------------------------------- |    ---------------------------------------------------- | ||||||
|    monitoringdaemon/__init__.py         0      0   100% |    monitoringdaemon/__init__.py         0      0   100% | ||||||
|    monitoringdaemon/__main__.py        13     13     0% |    monitoringdaemon/__main__.py        11     11     0% | ||||||
|    monitoringdaemon/containers.py      11      0   100% |    monitoringdaemon/containers.py      11      0   100% | ||||||
|    monitoringdaemon/dispatcher.py      44      5    89% |    monitoringdaemon/dispatcher.py      45      5    89% | ||||||
|    monitoringdaemon/http.py             6      3    50% |    monitoringdaemon/http.py             6      3    50% | ||||||
|    monitoringdaemon/monitors.py        23      1    96% |    monitoringdaemon/monitors.py        23      1    96% | ||||||
|    monitoringdaemon/tests.py           37      0   100% |    monitoringdaemon/tests.py           35      0   100% | ||||||
|    ---------------------------------------------------- |    ---------------------------------------------------- | ||||||
|    TOTAL                              134     22    84% |    TOTAL                              131     20    85% | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -160,19 +160,19 @@ Second put next in the ``fixtures.py``: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    SAMPLE_DATA = [ |    SAMPLE_DATA = [ | ||||||
|        ('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'), |        ("The Hunger Games: Mockingjay - Part 2", 2015, "Francis Lawrence"), | ||||||
|        ('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'), |        ("Rogue One: A Star Wars Story", 2016, "Gareth Edwards"), | ||||||
|        ('The Jungle Book', 2016, 'Jon Favreau'), |        ("The Jungle Book", 2016, "Jon Favreau"), | ||||||
|    ] |    ] | ||||||
| 
 | 
 | ||||||
|    FILE = pathlib.Path(__file__) |    FILE = pathlib.Path(__file__) | ||||||
|    DIR = FILE.parent |    DIR = FILE.parent | ||||||
|    CSV_FILE = DIR / 'movies.csv' |    CSV_FILE = DIR / "movies.csv" | ||||||
|    SQLITE_FILE = DIR / 'movies.db' |    SQLITE_FILE = DIR / "movies.db" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def create_csv(movies_data, path): |    def create_csv(movies_data, path): | ||||||
|        with open(path, 'w') as opened_file: |        with open(path, "w") as opened_file: | ||||||
|            writer = csv.writer(opened_file) |            writer = csv.writer(opened_file) | ||||||
|            for row in movies_data: |            for row in movies_data: | ||||||
|                writer.writerow(row) |                writer.writerow(row) | ||||||
|  | @ -181,20 +181,20 @@ Second put next in the ``fixtures.py``: | ||||||
|    def create_sqlite(movies_data, path): |    def create_sqlite(movies_data, path): | ||||||
|        with sqlite3.connect(path) as db: |        with sqlite3.connect(path) as db: | ||||||
|            db.execute( |            db.execute( | ||||||
|                'CREATE TABLE IF NOT EXISTS movies ' |                "CREATE TABLE IF NOT EXISTS movies " | ||||||
|                '(title text, year int, director text)' |                "(title text, year int, director text)" | ||||||
|            ) |            ) | ||||||
|            db.execute('DELETE FROM movies') |            db.execute("DELETE FROM movies") | ||||||
|            db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data) |            db.executemany("INSERT INTO movies VALUES (?,?,?)", movies_data) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def main(): |    def main(): | ||||||
|        create_csv(SAMPLE_DATA, CSV_FILE) |        create_csv(SAMPLE_DATA, CSV_FILE) | ||||||
|        create_sqlite(SAMPLE_DATA, SQLITE_FILE) |        create_sqlite(SAMPLE_DATA, SQLITE_FILE) | ||||||
|        print('OK') |        print("OK") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        main() |        main() | ||||||
| 
 | 
 | ||||||
| Now run in the terminal: | Now run in the terminal: | ||||||
|  | @ -266,7 +266,7 @@ Edit ``__main__.py``: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
| 
 | 
 | ||||||
|        main() |        main() | ||||||
|  | @ -321,7 +321,7 @@ and put next into it: | ||||||
|            self.director = str(director) |            self.director = str(director) | ||||||
| 
 | 
 | ||||||
|        def __repr__(self): |        def __repr__(self): | ||||||
|            return '{0}(title={1}, year={2}, director={3})'.format( |            return "{0}(title={1}, year={2}, director={3})".format( | ||||||
|                self.__class__.__name__, |                self.__class__.__name__, | ||||||
|                repr(self.title), |                repr(self.title), | ||||||
|                repr(self.year), |                repr(self.year), | ||||||
|  | @ -428,7 +428,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        movie = providers.Factory(entities.Movie) |        movie = providers.Factory(entities.Movie) | ||||||
| 
 | 
 | ||||||
|  | @ -445,15 +445,9 @@ This is also called the delegation of the provider. If we just pass the movie fa | ||||||
| as the dependency, it will be called when csv finder is created and the ``Movie`` instance will | as the dependency, it will be called when csv finder is created and the ``Movie`` instance will | ||||||
| be injected. With the ``.provider`` attribute the provider itself will be injected. | be injected. With the ``.provider`` attribute the provider itself will be injected. | ||||||
| 
 | 
 | ||||||
| The csv finder also has a few dependencies on the configuration options. We added configuration | The csv finder also has a few dependencies on the configuration options. We added a configuration | ||||||
| provider to provide these dependencies. | provider to provide these dependencies and specified the location of the configuration file. | ||||||
| 
 | The configuration provider will parse the configuration file when we create a container instance. | ||||||
| .. note:: |  | ||||||
| 
 |  | ||||||
|    We have used the configuration value before it was defined. That's the principle how the |  | ||||||
|    Configuration provider works. |  | ||||||
| 
 |  | ||||||
|    Use first, define later. |  | ||||||
| 
 | 
 | ||||||
| Not let's define the configuration values. | Not let's define the configuration values. | ||||||
| 
 | 
 | ||||||
|  | @ -467,29 +461,7 @@ Edit ``config.yml``: | ||||||
|        path: "data/movies.csv" |        path: "data/movies.csv" | ||||||
|        delimiter: "," |        delimiter: "," | ||||||
| 
 | 
 | ||||||
| The configuration file is ready. Now let's update the  ``main()`` function to specify its location. | The configuration file is ready. Move on to the lister. | ||||||
| 
 |  | ||||||
| Edit ``__main__.py``: |  | ||||||
| 
 |  | ||||||
| .. code-block:: python |  | ||||||
|    :emphasize-lines: 12 |  | ||||||
| 
 |  | ||||||
|    """Main module.""" |  | ||||||
| 
 |  | ||||||
|    from .containers import Container |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|    def main() -> None: |  | ||||||
|        ... |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|    if __name__ == '__main__': |  | ||||||
|        container = Container() |  | ||||||
|        container.config.from_yaml('config.yml') |  | ||||||
| 
 |  | ||||||
|        main() |  | ||||||
| 
 |  | ||||||
| Move on to the lister. |  | ||||||
| 
 | 
 | ||||||
| Create the ``listers.py`` in the ``movies`` package: | Create the ``listers.py`` in the ``movies`` package: | ||||||
| 
 | 
 | ||||||
|  | @ -552,7 +524,7 @@ and edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        movie = providers.Factory(entities.Movie) |        movie = providers.Factory(entities.Movie) | ||||||
| 
 | 
 | ||||||
|  | @ -575,13 +547,11 @@ Let's inject the ``lister`` into the  ``main()`` function. | ||||||
| Edit ``__main__.py``: | Edit ``__main__.py``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 3-7,11-12,19 |    :emphasize-lines: 3-5,9-10,16 | ||||||
| 
 | 
 | ||||||
|    """Main module.""" |    """Main module.""" | ||||||
| 
 | 
 | ||||||
|    import sys |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 |  | ||||||
|    from dependency_injector.wiring import inject, Provide |  | ||||||
| 
 | 
 | ||||||
|    from .listers import MovieLister |    from .listers import MovieLister | ||||||
|    from .containers import Container |    from .containers import Container | ||||||
|  | @ -592,10 +562,9 @@ Edit ``__main__.py``: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.from_yaml('config.yml') |        container.wire(modules=[__name__]) | ||||||
|        container.wire(modules=[sys.modules[__name__]]) |  | ||||||
| 
 | 
 | ||||||
|        main() |        main() | ||||||
| 
 | 
 | ||||||
|  | @ -607,13 +576,11 @@ Francis Lawrence and movies released in 2016. | ||||||
| Edit ``__main__.py``: | Edit ``__main__.py``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 13-19 |    :emphasize-lines: 11-17 | ||||||
| 
 | 
 | ||||||
|    """Main module.""" |    """Main module.""" | ||||||
| 
 | 
 | ||||||
|    import sys |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 |  | ||||||
|    from dependency_injector.wiring import inject, Provide |  | ||||||
| 
 | 
 | ||||||
|    from .listers import MovieLister |    from .listers import MovieLister | ||||||
|    from .containers import Container |    from .containers import Container | ||||||
|  | @ -621,19 +588,18 @@ Edit ``__main__.py``: | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def main(lister: MovieLister = Provide[Container.lister]) -> None: |    def main(lister: MovieLister = Provide[Container.lister]) -> None: | ||||||
|        print('Francis Lawrence movies:') |        print("Francis Lawrence movies:") | ||||||
|        for movie in lister.movies_directed_by('Francis Lawrence'): |        for movie in lister.movies_directed_by("Francis Lawrence"): | ||||||
|            print('\t-', movie) |            print("\t-", movie) | ||||||
| 
 | 
 | ||||||
|        print('2016 movies:') |        print("2016 movies:") | ||||||
|        for movie in lister.movies_released_in(2016): |        for movie in lister.movies_released_in(2016): | ||||||
|            print('\t-', movie) |            print("\t-", movie) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.from_yaml('config.yml') |        container.wire(modules=[__name__]) | ||||||
|        container.wire(modules=[sys.modules[__name__]]) |  | ||||||
| 
 | 
 | ||||||
|        main() |        main() | ||||||
| 
 | 
 | ||||||
|  | @ -718,7 +684,7 @@ Edit ``finders.py``: | ||||||
| 
 | 
 | ||||||
|        def find_all(self) -> List[Movie]: |        def find_all(self) -> List[Movie]: | ||||||
|            with self._database as db: |            with self._database as db: | ||||||
|                rows = db.execute('SELECT title, year, director FROM movies') |                rows = db.execute("SELECT title, year, director FROM movies") | ||||||
|                return [self._movie_factory(*row) for row in rows] |                return [self._movie_factory(*row) for row in rows] | ||||||
| 
 | 
 | ||||||
| Now we need to add the sqlite finder to the container and update lister's dependency to use it. | Now we need to add the sqlite finder to the container and update lister's dependency to use it. | ||||||
|  | @ -737,7 +703,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        movie = providers.Factory(entities.Movie) |        movie = providers.Factory(entities.Movie) | ||||||
| 
 | 
 | ||||||
|  | @ -826,7 +792,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        movie = providers.Factory(entities.Movie) |        movie = providers.Factory(entities.Movie) | ||||||
| 
 | 
 | ||||||
|  | @ -863,13 +829,11 @@ Now we need to read the value of the ``config.finder.type`` option from the envi | ||||||
| Edit ``__main__.py``: | Edit ``__main__.py``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 25 |    :emphasize-lines: 22 | ||||||
| 
 | 
 | ||||||
|    """Main module.""" |    """Main module.""" | ||||||
| 
 | 
 | ||||||
|    import sys |    from dependency_injector.wiring import Provide, inject | ||||||
| 
 |  | ||||||
|    from dependency_injector.wiring import inject, Provide |  | ||||||
| 
 | 
 | ||||||
|    from .listers import MovieLister |    from .listers import MovieLister | ||||||
|    from .containers import Container |    from .containers import Container | ||||||
|  | @ -877,19 +841,18 @@ Edit ``__main__.py``: | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def main(lister: MovieLister = Provide[Container.lister]) -> None: |    def main(lister: MovieLister = Provide[Container.lister]) -> None: | ||||||
|        print('Francis Lawrence movies:') |        print("Francis Lawrence movies:") | ||||||
|        for movie in lister.movies_directed_by('Francis Lawrence'): |        for movie in lister.movies_directed_by("Francis Lawrence"): | ||||||
|            print('\t-', movie) |            print("\t-", movie) | ||||||
| 
 | 
 | ||||||
|        print('2016 movies:') |        print("2016 movies:") | ||||||
|        for movie in lister.movies_released_in(2016): |        for movie in lister.movies_released_in(2016): | ||||||
|            print('\t-', movie) |            print("\t-", movie) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.from_yaml('config.yml') |        container.config.finder.type.from_env("MOVIE_FINDER_TYPE") | ||||||
|        container.config.finder.type.from_env('MOVIE_FINDER_TYPE') |  | ||||||
|        container.wire(modules=[sys.modules[__name__]]) |        container.wire(modules=[sys.modules[__name__]]) | ||||||
| 
 | 
 | ||||||
|        main() |        main() | ||||||
|  | @ -948,7 +911,7 @@ Create ``tests.py`` in the ``movies`` package: | ||||||
| and put next into it: | and put next into it: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 35,50 |    :emphasize-lines: 36,51 | ||||||
| 
 | 
 | ||||||
|    """Tests module.""" |    """Tests module.""" | ||||||
| 
 | 
 | ||||||
|  | @ -961,42 +924,43 @@ and put next into it: | ||||||
| 
 | 
 | ||||||
|    @pytest.fixture |    @pytest.fixture | ||||||
|    def container(): |    def container(): | ||||||
|        container = Container() |        container = Container( | ||||||
|        container.config.from_dict({ |            config={ | ||||||
|            'finder': { |                "finder": { | ||||||
|                'type': 'csv', |                    "type": "csv", | ||||||
|                'csv': { |                    "csv": { | ||||||
|                    'path': '/fake-movies.csv', |                        "path": "/fake-movies.csv", | ||||||
|                    'delimiter': ',', |                        "delimiter": ",", | ||||||
|                    }, |                    }, | ||||||
|                'sqlite': { |                    "sqlite": { | ||||||
|                    'path': '/fake-movies.db', |                        "path": "/fake-movies.db", | ||||||
|                    }, |                    }, | ||||||
|                }, |                }, | ||||||
|        }) |            }, | ||||||
|  |        ) | ||||||
|        return container |        return container | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def test_movies_directed_by(container): |    def test_movies_directed_by(container): | ||||||
|        finder_mock = mock.Mock() |        finder_mock = mock.Mock() | ||||||
|        finder_mock.find_all.return_value = [ |        finder_mock.find_all.return_value = [ | ||||||
|            container.movie('The 33', 2015, 'Patricia Riggen'), |            container.movie("The 33", 2015, "Patricia Riggen"), | ||||||
|            container.movie('The Jungle Book', 2016, 'Jon Favreau'), |            container.movie("The Jungle Book", 2016, "Jon Favreau"), | ||||||
|        ] |        ] | ||||||
| 
 | 
 | ||||||
|        with container.finder.override(finder_mock): |        with container.finder.override(finder_mock): | ||||||
|            lister = container.lister() |            lister = container.lister() | ||||||
|            movies = lister.movies_directed_by('Jon Favreau') |            movies = lister.movies_directed_by("Jon Favreau") | ||||||
| 
 | 
 | ||||||
|        assert len(movies) == 1 |        assert len(movies) == 1 | ||||||
|        assert movies[0].title == 'The Jungle Book' |        assert movies[0].title == "The Jungle Book" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def test_movies_released_in(container): |    def test_movies_released_in(container): | ||||||
|        finder_mock = mock.Mock() |        finder_mock = mock.Mock() | ||||||
|        finder_mock.find_all.return_value = [ |        finder_mock.find_all.return_value = [ | ||||||
|            container.movie('The 33', 2015, 'Patricia Riggen'), |            container.movie("The 33", 2015, "Patricia Riggen"), | ||||||
|            container.movie('The Jungle Book', 2016, 'Jon Favreau'), |            container.movie("The Jungle Book", 2016, "Jon Favreau"), | ||||||
|        ] |        ] | ||||||
| 
 | 
 | ||||||
|        with container.finder.override(finder_mock): |        with container.finder.override(finder_mock): | ||||||
|  | @ -1004,7 +968,7 @@ and put next into it: | ||||||
|            movies = lister.movies_released_in(2015) |            movies = lister.movies_released_in(2015) | ||||||
| 
 | 
 | ||||||
|        assert len(movies) == 1 |        assert len(movies) == 1 | ||||||
|        assert movies[0].title == 'The 33' |        assert movies[0].title == "The 33" | ||||||
| 
 | 
 | ||||||
| Run in the terminal: | Run in the terminal: | ||||||
| 
 | 
 | ||||||
|  | @ -1016,24 +980,24 @@ You should see: | ||||||
| 
 | 
 | ||||||
| .. code-block:: | .. code-block:: | ||||||
| 
 | 
 | ||||||
|    platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1 |    platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 | ||||||
|    plugins: cov-2.10.0 |    plugins: cov-3.0.0 | ||||||
|    collected 2 items |    collected 2 items | ||||||
| 
 | 
 | ||||||
|    movies/tests.py ..                                              [100%] |    movies/tests.py ..                                              [100%] | ||||||
| 
 | 
 | ||||||
|    ---------- coverage: platform darwin, python 3.8.5-final-0 ----------- |    ---------- coverage: platform darwin, python 3.10 ----------- | ||||||
|    Name                   Stmts   Miss  Cover |    Name                   Stmts   Miss  Cover | ||||||
|    ------------------------------------------ |    ------------------------------------------ | ||||||
|    movies/__init__.py         0      0   100% |    movies/__init__.py         0      0   100% | ||||||
|    movies/__main__.py        18     18     0% |    movies/__main__.py        16     16     0% | ||||||
|    movies/containers.py       9      0   100% |    movies/containers.py       9      0   100% | ||||||
|    movies/entities.py         7      1    86% |    movies/entities.py         7      1    86% | ||||||
|    movies/finders.py         26     13    50% |    movies/finders.py         26     13    50% | ||||||
|    movies/listers.py          8      0   100% |    movies/listers.py          8      0   100% | ||||||
|    movies/tests.py           24      0   100% |    movies/tests.py           23      0   100% | ||||||
|    ------------------------------------------ |    ------------------------------------------ | ||||||
|    TOTAL                     92     32    65% |    TOTAL                     89     30    66% | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|  | @ -1053,7 +1017,7 @@ We've used the ``Dependency Injector`` as a dependency injection framework. | ||||||
| With a help of :ref:`containers` and :ref:`providers` we have defined how to assemble application components. | With a help of :ref:`containers` and :ref:`providers` we have defined how to assemble application components. | ||||||
| 
 | 
 | ||||||
| ``Selector`` provider served as a switch for selecting the database format based on a configuration. | ``Selector`` provider served as a switch for selecting the database format based on a configuration. | ||||||
| :ref:`configuration-provider` helped to deal with reading YAML file and environment variable. | :ref:`configuration-provider` helped to deal with reading a YAML file and environment variables. | ||||||
| 
 | 
 | ||||||
| We used :ref:`wiring` feature to inject the dependencies into the ``main()`` function. | We used :ref:`wiring` feature to inject the dependencies into the ``main()`` function. | ||||||
| :ref:`provider-overriding` feature helped in testing. | :ref:`provider-overriding` feature helped in testing. | ||||||
|  |  | ||||||
|  | @ -110,9 +110,9 @@ You should see something like: | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    (venv) $ python -c "import dependency_injector; print(dependency_injector.__version__)" |    (venv) $ python -c "import dependency_injector; print(dependency_injector.__version__)" | ||||||
|    4.0.0 |    4.37.0 | ||||||
|    (venv) $ python -c "import flask; print(flask.__version__)" |    (venv) $ python -c "import flask; print(flask.__version__)" | ||||||
|    1.1.2 |    2.0.2 | ||||||
| 
 | 
 | ||||||
| *Versions can be different. That's fine.* | *Versions can be different. That's fine.* | ||||||
| 
 | 
 | ||||||
|  | @ -129,7 +129,7 @@ Put next into the ``views.py``: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def index(): |    def index(): | ||||||
|        return 'Hello, World!' |        return "Hello, World!" | ||||||
| 
 | 
 | ||||||
| Ok, we have the view. | Ok, we have the view. | ||||||
| 
 | 
 | ||||||
|  | @ -170,7 +170,7 @@ Put next into the ``application.py``: | ||||||
| 
 | 
 | ||||||
|        app = Flask(__name__) |        app = Flask(__name__) | ||||||
|        app.container = container |        app.container = container | ||||||
|        app.add_url_rule('/', 'index', views.index) |        app.add_url_rule("/", "index", views.index) | ||||||
| 
 | 
 | ||||||
|        return app |        return app | ||||||
| 
 | 
 | ||||||
|  | @ -246,7 +246,7 @@ Edit ``application.py``: | ||||||
| 
 | 
 | ||||||
|        app = Flask(__name__) |        app = Flask(__name__) | ||||||
|        app.container = container |        app.container = container | ||||||
|        app.add_url_rule('/', 'index', views.index) |        app.add_url_rule("/", "index", views.index) | ||||||
| 
 | 
 | ||||||
|        bootstrap = Bootstrap() |        bootstrap = Bootstrap() | ||||||
|        bootstrap.init_app(app) |        bootstrap.init_app(app) | ||||||
|  | @ -398,13 +398,13 @@ Edit ``views.py``: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def index(): |    def index(): | ||||||
|        query = request.args.get('query', 'Dependency Injector') |        query = request.args.get("query", "Dependency Injector") | ||||||
|        limit = request.args.get('limit', 10, int) |        limit = request.args.get("limit", 10, int) | ||||||
| 
 | 
 | ||||||
|        repositories = [] |        repositories = [] | ||||||
| 
 | 
 | ||||||
|        return render_template( |        return render_template( | ||||||
|            'index.html', |            "index.html", | ||||||
|            query=query, |            query=query, | ||||||
|            limit=limit, |            limit=limit, | ||||||
|            repositories=repositories, |            repositories=repositories, | ||||||
|  | @ -444,9 +444,10 @@ and run in the terminal: | ||||||
| Now we need to add Github API client the container. We will need to add two more providers from | Now we need to add Github API client the container. We will need to add two more providers from | ||||||
| the ``dependency_injector.providers`` module: | the ``dependency_injector.providers`` module: | ||||||
| 
 | 
 | ||||||
| - ``Factory`` provider that will create ``Github`` client. | - ``Factory`` provider. It will create a ``Github`` client. | ||||||
| - ``Configuration`` provider that will be used for providing the API token and the request timeout | - ``Configuration`` provider. It will provide an API token and a request timeout for the ``Github`` client. | ||||||
|   for the ``Github`` client. |   We will specify the location of the configuration file. The configuration provider will parse | ||||||
|  |   the configuration file when we create a container instance. | ||||||
| 
 | 
 | ||||||
| Edit ``containers.py``: | Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|  | @ -461,7 +462,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        github_client = providers.Factory( |        github_client = providers.Factory( | ||||||
|            Github, |            Github, | ||||||
|  | @ -469,23 +470,14 @@ Edit ``containers.py``: | ||||||
|            timeout=config.github.request_timeout, |            timeout=config.github.request_timeout, | ||||||
|        ) |        ) | ||||||
| 
 | 
 | ||||||
| .. note:: |  | ||||||
| 
 |  | ||||||
|    We have used the configuration value before it was defined. That's the principle how |  | ||||||
|    ``Configuration`` provider works. |  | ||||||
| 
 |  | ||||||
|    Use first, define later. |  | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|    Don't forget to remove the Ellipsis ``...`` from the container. We don't need it anymore |    Don't forget to remove the Ellipsis ``...`` from the container. We don't need it anymore | ||||||
|    since we container is not empty. |    since we container is not empty. | ||||||
| 
 | 
 | ||||||
| Now let's add the configuration file. | Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml`` | ||||||
| 
 | in the root of the project: | ||||||
| We will use YAML. |  | ||||||
| 
 |  | ||||||
| Create an empty file ``config.yml`` in the root of the project: |  | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
|    :emphasize-lines: 11 |    :emphasize-lines: 11 | ||||||
|  | @ -530,17 +522,13 @@ and install it: | ||||||
| 
 | 
 | ||||||
|    pip install -r requirements.txt |    pip install -r requirements.txt | ||||||
| 
 | 
 | ||||||
| We will use environment variable ``GITHUB_TOKEN`` to provide the API token. | We will use the ``GITHUB_TOKEN`` environment variable to provide the API token. Let's edit | ||||||
| 
 | ``create_app()`` to fetch the token value from it. | ||||||
| Now we need to edit ``create_app()`` to make two things when application starts: |  | ||||||
| 
 |  | ||||||
| - Load the configuration file the ``config.yml``. |  | ||||||
| - Load the API token from the ``GITHUB_TOKEN`` environment variable. |  | ||||||
| 
 | 
 | ||||||
| Edit ``application.py``: | Edit ``application.py``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 12-13 |    :emphasize-lines: 12 | ||||||
| 
 | 
 | ||||||
|    """Application module.""" |    """Application module.""" | ||||||
| 
 | 
 | ||||||
|  | @ -553,12 +541,11 @@ Edit ``application.py``: | ||||||
| 
 | 
 | ||||||
|    def create_app() -> Flask: |    def create_app() -> Flask: | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.config.from_yaml('config.yml') |        container.config.github.auth_token.from_env("GITHUB_TOKEN") | ||||||
|        container.config.github.auth_token.from_env('GITHUB_TOKEN') |  | ||||||
| 
 | 
 | ||||||
|        app = Flask(__name__) |        app = Flask(__name__) | ||||||
|        app.container = container |        app.container = container | ||||||
|        app.add_url_rule('/', 'index', views.index) |        app.add_url_rule("/", "index", views.index) | ||||||
| 
 | 
 | ||||||
|        bootstrap = Bootstrap() |        bootstrap = Bootstrap() | ||||||
|        bootstrap.init_app(app) |        bootstrap.init_app(app) | ||||||
|  | @ -639,7 +626,7 @@ and put next into it: | ||||||
|            """Search for repositories and return formatted data.""" |            """Search for repositories and return formatted data.""" | ||||||
|            repositories = self._github_client.search_repositories( |            repositories = self._github_client.search_repositories( | ||||||
|                query=query, |                query=query, | ||||||
|                **{'in': 'name'}, |                **{"in": "name"}, | ||||||
|            ) |            ) | ||||||
|            return [ |            return [ | ||||||
|                self._format_repo(repository) |                self._format_repo(repository) | ||||||
|  | @ -649,22 +636,22 @@ and put next into it: | ||||||
|        def _format_repo(self, repository: Repository): |        def _format_repo(self, repository: Repository): | ||||||
|            commits = repository.get_commits() |            commits = repository.get_commits() | ||||||
|            return { |            return { | ||||||
|                'url': repository.html_url, |                "url": repository.html_url, | ||||||
|                'name': repository.name, |                "name": repository.name, | ||||||
|                'owner': { |                "owner": { | ||||||
|                    'login': repository.owner.login, |                    "login": repository.owner.login, | ||||||
|                    'url': repository.owner.html_url, |                    "url": repository.owner.html_url, | ||||||
|                    'avatar_url': repository.owner.avatar_url, |                    "avatar_url": repository.owner.avatar_url, | ||||||
|                }, |                }, | ||||||
|                'latest_commit': self._format_commit(commits[0]) if commits else {}, |                "latest_commit": self._format_commit(commits[0]) if commits else {}, | ||||||
|            } |            } | ||||||
| 
 | 
 | ||||||
|        def _format_commit(self, commit: Commit): |        def _format_commit(self, commit: Commit): | ||||||
|            return { |            return { | ||||||
|                'sha': commit.sha, |                "sha": commit.sha, | ||||||
|                'url': commit.html_url, |                "url": commit.html_url, | ||||||
|                'message': commit.commit.message, |                "message": commit.commit.message, | ||||||
|                'author_name': commit.commit.author.name, |                "author_name": commit.commit.author.name, | ||||||
|            } |            } | ||||||
| 
 | 
 | ||||||
| Now let's add ``SearchService`` to the container. | Now let's add ``SearchService`` to the container. | ||||||
|  | @ -684,7 +671,7 @@ Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
|    class Container(containers.DeclarativeContainer): |    class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|        config = providers.Configuration() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|        github_client = providers.Factory( |        github_client = providers.Factory( | ||||||
|            Github, |            Github, | ||||||
|  | @ -720,50 +707,51 @@ Edit ``views.py``: | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def index(search_service: SearchService = Provide[Container.search_service]): |    def index(search_service: SearchService = Provide[Container.search_service]): | ||||||
|        query = request.args.get('query', 'Dependency Injector') |        query = request.args.get("query", "Dependency Injector") | ||||||
|        limit = request.args.get('limit', 10, int) |        limit = request.args.get("limit", 10, int) | ||||||
| 
 | 
 | ||||||
|        repositories = search_service.search_repositories(query, limit) |        repositories = search_service.search_repositories(query, limit) | ||||||
| 
 | 
 | ||||||
|        return render_template( |        return render_template( | ||||||
|            'index.html', |            "index.html", | ||||||
|            query=query, |            query=query, | ||||||
|            limit=limit, |            limit=limit, | ||||||
|            repositories=repositories, |            repositories=repositories, | ||||||
|        ) |        ) | ||||||
| 
 | 
 | ||||||
| To make the injection work we need to wire the container instance with the ``views`` module. | To make the injection work we need to wire the container with the ``views`` module. | ||||||
| This needs to be done once. After it's done we can use ``Provide`` markers to specify as many | Let's configure the container to automatically make wiring with the ``views`` module when we | ||||||
| injections as needed for any view. | create a container instance. | ||||||
| 
 | 
 | ||||||
| Edit ``application.py``: | Edit ``containers.py``: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 14 |    :emphasize-lines: 11 | ||||||
| 
 | 
 | ||||||
|    """Application module.""" |    """Containers module.""" | ||||||
| 
 | 
 | ||||||
|    from flask import Flask |    from dependency_injector import containers, providers | ||||||
|    from flask_bootstrap import Bootstrap |    from github import Github | ||||||
| 
 | 
 | ||||||
|    from .containers import Container |    from . import services | ||||||
|    from . import views |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def create_app() -> Flask: |    class Container(containers.DeclarativeContainer): | ||||||
|        container = Container() |  | ||||||
|        container.config.from_yaml('config.yml') |  | ||||||
|        container.config.github.auth_token.from_env('GITHUB_TOKEN') |  | ||||||
|        container.wire(modules=[views]) |  | ||||||
| 
 | 
 | ||||||
|        app = Flask(__name__) |        wiring_config = containers.WiringConfiguration(modules=[".views"]) | ||||||
|        app.container = container |  | ||||||
|        app.add_url_rule('/', 'index', views.index) |  | ||||||
| 
 | 
 | ||||||
|        bootstrap = Bootstrap() |        config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
|        bootstrap.init_app(app) |  | ||||||
| 
 | 
 | ||||||
|        return app |        github_client = providers.Factory( | ||||||
|  |            Github, | ||||||
|  |            login_or_token=config.github.auth_token, | ||||||
|  |            timeout=config.github.request_timeout, | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  |        search_service = providers.Factory( | ||||||
|  |            services.SearchService, | ||||||
|  |            github_client=github_client, | ||||||
|  |        ) | ||||||
| 
 | 
 | ||||||
| Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:5000/``. | Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:5000/``. | ||||||
| 
 | 
 | ||||||
|  | @ -801,13 +789,13 @@ Edit ``views.py``: | ||||||
|            default_query: str = Provide[Container.config.default.query], |            default_query: str = Provide[Container.config.default.query], | ||||||
|            default_limit: int = Provide[Container.config.default.limit.as_int()], |            default_limit: int = Provide[Container.config.default.limit.as_int()], | ||||||
|    ): |    ): | ||||||
|        query = request.args.get('query', default_query) |        query = request.args.get("query", default_query) | ||||||
|        limit = request.args.get('limit', default_limit, int) |        limit = request.args.get("limit", default_limit, int) | ||||||
| 
 | 
 | ||||||
|        repositories = search_service.search_repositories(query, limit) |        repositories = search_service.search_repositories(query, limit) | ||||||
| 
 | 
 | ||||||
|        return render_template( |        return render_template( | ||||||
|            'index.html', |            "index.html", | ||||||
|            query=query, |            query=query, | ||||||
|            limit=limit, |            limit=limit, | ||||||
|            repositories=repositories, |            repositories=repositories, | ||||||
|  | @ -900,44 +888,44 @@ and put next into it: | ||||||
|        github_client_mock = mock.Mock(spec=Github) |        github_client_mock = mock.Mock(spec=Github) | ||||||
|        github_client_mock.search_repositories.return_value = [ |        github_client_mock.search_repositories.return_value = [ | ||||||
|            mock.Mock( |            mock.Mock( | ||||||
|                html_url='repo1-url', |                html_url="repo1-url", | ||||||
|                name='repo1-name', |                name="repo1-name", | ||||||
|                owner=mock.Mock( |                owner=mock.Mock( | ||||||
|                    login='owner1-login', |                    login="owner1-login", | ||||||
|                    html_url='owner1-url', |                    html_url="owner1-url", | ||||||
|                    avatar_url='owner1-avatar-url', |                    avatar_url="owner1-avatar-url", | ||||||
|                ), |                ), | ||||||
|                get_commits=mock.Mock(return_value=[mock.Mock()]), |                get_commits=mock.Mock(return_value=[mock.Mock()]), | ||||||
|            ), |            ), | ||||||
|            mock.Mock( |            mock.Mock( | ||||||
|                html_url='repo2-url', |                html_url="repo2-url", | ||||||
|                name='repo2-name', |                name="repo2-name", | ||||||
|                owner=mock.Mock( |                owner=mock.Mock( | ||||||
|                    login='owner2-login', |                    login="owner2-login", | ||||||
|                    html_url='owner2-url', |                    html_url="owner2-url", | ||||||
|                    avatar_url='owner2-avatar-url', |                    avatar_url="owner2-avatar-url", | ||||||
|                ), |                ), | ||||||
|                get_commits=mock.Mock(return_value=[mock.Mock()]), |                get_commits=mock.Mock(return_value=[mock.Mock()]), | ||||||
|            ), |            ), | ||||||
|        ] |        ] | ||||||
| 
 | 
 | ||||||
|        with app.container.github_client.override(github_client_mock): |        with app.container.github_client.override(github_client_mock): | ||||||
|            response = client.get(url_for('index')) |            response = client.get(url_for("index")) | ||||||
| 
 | 
 | ||||||
|        assert response.status_code == 200 |        assert response.status_code == 200 | ||||||
|        assert b'Results found: 2' in response.data |        assert b"Results found: 2" in response.data | ||||||
| 
 | 
 | ||||||
|        assert b'repo1-url' in response.data |        assert b"repo1-url" in response.data | ||||||
|        assert b'repo1-name' in response.data |        assert b"repo1-name" in response.data | ||||||
|        assert b'owner1-login' in response.data |        assert b"owner1-login" in response.data | ||||||
|        assert b'owner1-url' in response.data |        assert b"owner1-url" in response.data | ||||||
|        assert b'owner1-avatar-url' in response.data |        assert b"owner1-avatar-url" in response.data | ||||||
| 
 | 
 | ||||||
|        assert b'repo2-url' in response.data |        assert b"repo2-url" in response.data | ||||||
|        assert b'repo2-name' in response.data |        assert b"repo2-name" in response.data | ||||||
|        assert b'owner2-login' in response.data |        assert b"owner2-login" in response.data | ||||||
|        assert b'owner2-url' in response.data |        assert b"owner2-url" in response.data | ||||||
|        assert b'owner2-avatar-url' in response.data |        assert b"owner2-avatar-url" in response.data | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    def test_index_no_results(client, app): |    def test_index_no_results(client, app): | ||||||
|  | @ -945,10 +933,10 @@ and put next into it: | ||||||
|        github_client_mock.search_repositories.return_value = [] |        github_client_mock.search_repositories.return_value = [] | ||||||
| 
 | 
 | ||||||
|        with app.container.github_client.override(github_client_mock): |        with app.container.github_client.override(github_client_mock): | ||||||
|            response = client.get(url_for('index')) |            response = client.get(url_for("index")) | ||||||
| 
 | 
 | ||||||
|        assert response.status_code == 200 |        assert response.status_code == 200 | ||||||
|        assert b'Results found: 0' in response.data |        assert b"Results found: 0" in response.data | ||||||
| 
 | 
 | ||||||
| Now let's run it and check the coverage: | Now let's run it and check the coverage: | ||||||
| 
 | 
 | ||||||
|  | @ -960,23 +948,23 @@ You should see: | ||||||
| 
 | 
 | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|    platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1 |    platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 | ||||||
|    plugins: flask-1.0.0, cov-2.10.0 |    plugins: cov-3.0.0, flask-1.2.0 | ||||||
|    collected 2 items |    collected 2 items | ||||||
| 
 | 
 | ||||||
|    githubnavigator/tests.py ..                                     [100%] |    githubnavigator/tests.py ..                                     [100%] | ||||||
| 
 | 
 | ||||||
|    ---------- coverage: platform darwin, python 3.8.3-final-0 ----------- |    ---------- coverage: platform darwin, python 3.10.0-final-0 ---------- | ||||||
|    Name                             Stmts   Miss  Cover |    Name                             Stmts   Miss  Cover | ||||||
|    ---------------------------------------------------- |    ---------------------------------------------------- | ||||||
|    githubnavigator/__init__.py          0      0   100% |    githubnavigator/__init__.py          0      0   100% | ||||||
|    githubnavigator/application.py      15      0   100% |    githubnavigator/application.py      13      0   100% | ||||||
|    githubnavigator/containers.py        7      0   100% |    githubnavigator/containers.py        8      0   100% | ||||||
|    githubnavigator/services.py         14      0   100% |    githubnavigator/services.py         14      0   100% | ||||||
|    githubnavigator/tests.py            34      0   100% |    githubnavigator/tests.py            34      0   100% | ||||||
|    githubnavigator/views.py            10      0   100% |    githubnavigator/views.py            10      0   100% | ||||||
|    ---------------------------------------------------- |    ---------------------------------------------------- | ||||||
|    TOTAL                               80      0   100% |    TOTAL                               79      0   100% | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										167
									
								
								docs/wiring.rst
									
									
									
									
									
								
							
							
						
						
									
										167
									
								
								docs/wiring.rst
									
									
									
									
									
								
							|  | @ -95,17 +95,17 @@ Also you can use ``Provide`` marker to inject a container. | ||||||
| 
 | 
 | ||||||
| .. literalinclude:: ../examples/wiring/example_container.py | .. literalinclude:: ../examples/wiring/example_container.py | ||||||
|    :language: python |    :language: python | ||||||
|    :emphasize-lines: 16-19 |    :emphasize-lines: 14-17 | ||||||
|    :lines: 3- |    :lines: 3- | ||||||
| 
 | 
 | ||||||
| Strings identifiers | String identifiers | ||||||
| ------------------- | ------------------ | ||||||
| 
 | 
 | ||||||
| You can use wiring with string identifiers. String identifier should match provider name in the container: | You can use wiring with string identifiers. String identifier should match provider name in the container: | ||||||
| 
 | 
 | ||||||
| .. literalinclude:: ../examples/wiring/example_string_id.py | .. literalinclude:: ../examples/wiring/example_string_id.py | ||||||
|    :language: python |    :language: python | ||||||
|    :emphasize-lines: 17 |    :emphasize-lines: 15 | ||||||
|    :lines: 3- |    :lines: 3- | ||||||
| 
 | 
 | ||||||
| With string identifiers you don't need to use a container to specify an injection. | With string identifiers you don't need to use a container to specify an injection. | ||||||
|  | @ -115,7 +115,7 @@ To specify an injection from a nested container use point ``.`` as a separator: | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(service: UserService = Provide['services.user']) -> None: |    def foo(service: UserService = Provide["services.user"]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| You can also use injection modifiers: | You can also use injection modifiers: | ||||||
|  | @ -135,34 +135,34 @@ You can also use injection modifiers: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(value: int = Provide['config.option', as_int()]) -> None: |    def foo(value: int = Provide["config.option", as_int()]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(value: float = Provide['config.option', as_float()]) -> None: |    def foo(value: float = Provide["config.option", as_float()]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(value: Decimal = Provide['config.option', as_(Decimal)]) -> None: |    def foo(value: Decimal = Provide["config.option", as_(Decimal)]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(value: str = Provide['config.option', required()]) -> None: |    def foo(value: str = Provide["config.option", required()]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(value: int = Provide['config.option', required().as_int()]) -> None: |    def foo(value: int = Provide["config.option", required().as_int()]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(value: int = Provide['config.option', invariant('config.switch')]) -> None: |    def foo(value: int = Provide["config.option", invariant("config.switch")]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(value: int = Provide['service', provided().foo['bar'].call()]) -> None: |    def foo(value: int = Provide["service", provided().foo["bar"].call()]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -171,7 +171,7 @@ To inject a container use special identifier ``<container>``: | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    @inject |    @inject | ||||||
|    def foo(container: Container = Provide['<container>']) -> None: |    def foo(container: Container = Provide["<container>"]) -> None: | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -183,25 +183,63 @@ You can use wiring to make injections into modules and class attributes. | ||||||
| .. literalinclude:: ../examples/wiring/example_attribute.py | .. literalinclude:: ../examples/wiring/example_attribute.py | ||||||
|    :language: python |    :language: python | ||||||
|    :lines: 3- |    :lines: 3- | ||||||
|    :emphasize-lines: 16,21 |    :emphasize-lines: 14,19 | ||||||
| 
 | 
 | ||||||
| You could also use string identifiers to avoid a dependency on a container: | You could also use string identifiers to avoid a dependency on a container: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|    :emphasize-lines: 1,6 |    :emphasize-lines: 1,6 | ||||||
| 
 | 
 | ||||||
|    service: Service = Provide['service'] |    service: Service = Provide["service"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    class Main: |    class Main: | ||||||
| 
 | 
 | ||||||
|        service: Service = Provide['service'] |        service: Service = Provide["service"] | ||||||
| 
 | 
 | ||||||
| Wiring with modules and packages | Wiring with modules and packages | ||||||
| -------------------------------- | -------------------------------- | ||||||
| 
 | 
 | ||||||
| To wire a container with a module you need to call ``container.wire(modules=[...])`` method. Argument | To wire a container with the modules you need to call ``container.wire()`` method: | ||||||
| ``modules`` is an iterable of the module objects. | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    container.wire( | ||||||
|  |        modules=[ | ||||||
|  |            "yourapp.module1", | ||||||
|  |            "yourapp.module2", | ||||||
|  |        ], | ||||||
|  |    ) | ||||||
|  | 
 | ||||||
|  | Method ``container.wire()`` can resolve relative imports: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    # In module "yourapp.main": | ||||||
|  | 
 | ||||||
|  |    container.wire( | ||||||
|  |        modules=[ | ||||||
|  |            ".module1",  # Resolved to: "yourapp.module1" | ||||||
|  |            ".module2",  # Resolved to: "yourapp.module2" | ||||||
|  |        ], | ||||||
|  |    ) | ||||||
|  | 
 | ||||||
|  | You can also manually specify a base package for resolving relative imports with | ||||||
|  | the ``from_package`` argument: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    # In module "yourapp.main": | ||||||
|  | 
 | ||||||
|  |    container.wire( | ||||||
|  |        modules=[ | ||||||
|  |            ".module1",  # Resolved to: "anotherapp.module1" | ||||||
|  |            ".module2",  # Resolved to: "anotherapp.module2" | ||||||
|  |        ], | ||||||
|  |        from_package="anotherapp", | ||||||
|  |    ) | ||||||
|  | 
 | ||||||
|  | Argument ``modules`` can also take already imported modules: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|  | @ -211,15 +249,16 @@ To wire a container with a module you need to call ``container.wire(modules=[... | ||||||
|    container = Container() |    container = Container() | ||||||
|    container.wire(modules=[module1, module2]) |    container.wire(modules=[module1, module2]) | ||||||
| 
 | 
 | ||||||
| You can wire container with a package. Container walks recursively over package modules. | You can wire container with a package. Container walks recursively over the package modules: | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|    from yourapp import package1, package2 |    container.wire( | ||||||
| 
 |        packages=[ | ||||||
| 
 |            "yourapp.package1", | ||||||
|    container = Container() |            "yourapp.package2", | ||||||
|    container.wire(packages=[package1, package2]) |        ], | ||||||
|  |    ) | ||||||
| 
 | 
 | ||||||
| Arguments ``modules`` and ``packages`` can be used together. | Arguments ``modules`` and ``packages`` can be used together. | ||||||
| 
 | 
 | ||||||
|  | @ -233,7 +272,7 @@ When wiring is done functions and methods with the markers are patched to provid | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    container = Container() |    container = Container() | ||||||
|    container.wire(modules=[sys.modules[__name__]]) |    container.wire(modules=[__name__]) | ||||||
| 
 | 
 | ||||||
|    foo()  # <--- Argument "bar" is injected |    foo()  # <--- Argument "bar" is injected | ||||||
| 
 | 
 | ||||||
|  | @ -267,7 +306,7 @@ You can use that in testing to re-create and re-wire a container before each tes | ||||||
| 
 | 
 | ||||||
|        def setUp(self): |        def setUp(self): | ||||||
|            self.container = Container() |            self.container = Container() | ||||||
|            self.container.wire(modules=[module1, module2]) |            self.container.wire(modules=["yourapp.module1", "yourapp.module2"]) | ||||||
|            self.addCleanup(self.container.unwire) |            self.addCleanup(self.container.unwire) | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|  | @ -278,7 +317,7 @@ You can use that in testing to re-create and re-wire a container before each tes | ||||||
|    @pytest.fixture |    @pytest.fixture | ||||||
|    def container(): |    def container(): | ||||||
|        container = Container() |        container = Container() | ||||||
|        container.wire(modules=[module1, module2]) |        container.wire(modules=["yourapp.module1", "yourapp.module2"]) | ||||||
|        yield container |        yield container | ||||||
|        container.unwire() |        container.unwire() | ||||||
| 
 | 
 | ||||||
|  | @ -309,6 +348,76 @@ You can use that in testing to re-create and re-wire a container before each tes | ||||||
| 
 | 
 | ||||||
|       module.fn() |       module.fn() | ||||||
| 
 | 
 | ||||||
|  | Wiring configuration | ||||||
|  | -------------------- | ||||||
|  | 
 | ||||||
|  | You can specify wiring configuration in the container. When wiring configuration is defined, | ||||||
|  | container will call method ``.wire()`` automatically when you create an instance: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    class Container(containers.DeclarativeContainer): | ||||||
|  | 
 | ||||||
|  |        wiring_config = containers.WiringConfiguration( | ||||||
|  |            modules=[ | ||||||
|  |                "yourapp.module1", | ||||||
|  |                "yourapp.module2", | ||||||
|  |            ], | ||||||
|  |            packages=[ | ||||||
|  |                "yourapp.package1", | ||||||
|  |                "yourapp.package2", | ||||||
|  |            ], | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  |        ... | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    if __name__ == "__main__": | ||||||
|  |        container = Container()  # container.wire() is called automatically | ||||||
|  |        ... | ||||||
|  | 
 | ||||||
|  | You can also use relative imports. Container will resolve them corresponding | ||||||
|  | to the module of the container class: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    # In module "yourapp.container": | ||||||
|  | 
 | ||||||
|  |    class Container(containers.DeclarativeContainer): | ||||||
|  | 
 | ||||||
|  |        wiring_config = containers.WiringConfiguration( | ||||||
|  |            modules=[ | ||||||
|  |               ".module1",  # Resolved to: "yourapp.module1" | ||||||
|  |               ".module2",  # Resolved to: "yourapp.module2" | ||||||
|  |            ], | ||||||
|  |        ) | ||||||
|  |    ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    # In module "yourapp.foo.bar.main": | ||||||
|  | 
 | ||||||
|  |    if __name__ == "__main__": | ||||||
|  |        container = Container()  # wire to "yourapp.module1" and "yourapp.module2" | ||||||
|  |        ... | ||||||
|  | 
 | ||||||
|  | To use wiring configuration and call method ``.wire()`` manually, set flag ``auto_wire=False``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 5 | ||||||
|  | 
 | ||||||
|  |    class Container(containers.DeclarativeContainer): | ||||||
|  | 
 | ||||||
|  |        wiring_config = containers.WiringConfiguration( | ||||||
|  |            modules=["yourapp.module1"], | ||||||
|  |            auto_wire=False, | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    if __name__ == "__main__": | ||||||
|  |        container = Container()  # container.wire() is NOT called automatically | ||||||
|  |        container.wire()         # wire to "yourapp.module1" | ||||||
|  |        ... | ||||||
|  | 
 | ||||||
| .. _async-injections-wiring: | .. _async-injections-wiring: | ||||||
| 
 | 
 | ||||||
| Asynchronous injections | Asynchronous injections | ||||||
|  | @ -402,11 +511,11 @@ This is useful when you import modules dynamically. | ||||||
|    from .containers import Container |    from .containers import Container | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|    if __name__ == '__main__': |    if __name__ == "__main__": | ||||||
|        container = Container() |        container = Container() | ||||||
|        register_loader_containers(container)  # <--- installs import hook |        register_loader_containers(container)  # <--- installs import hook | ||||||
| 
 | 
 | ||||||
|        module = importlib.import_module('package.module') |        module = importlib.import_module("package.module") | ||||||
|        module.foo() |        module.foo() | ||||||
| 
 | 
 | ||||||
| You can register multiple containers in the import hook. For doing this call register function | You can register multiple containers in the import hook. For doing this call register function | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ class Container(containers.DeclarativeContainer): | ||||||
|     service2 = providers.Dependency() |     service2 = providers.Dependency() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
|     container.check_dependencies()  # <-- raises error: |     container.check_dependencies()  # <-- raises error: | ||||||
|     # Container has undefined dependencies: "Container.service1", "Container.service2" |     # Container has undefined dependencies: "Container.service1", "Container.service2" | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ class Container(containers.DeclarativeContainer): | ||||||
|     factory2 = providers.Factory(object) |     factory2 = providers.Factory(object) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     object1 = container.factory1() |     object1 = container.factory1() | ||||||
|  | @ -18,6 +18,6 @@ if __name__ == '__main__': | ||||||
| 
 | 
 | ||||||
|     print(container.providers) |     print(container.providers) | ||||||
|     # { |     # { | ||||||
|     #     'factory1': <dependency_injector.providers.Factory(...), |     #     "factory1": <dependency_injector.providers.Factory(...), | ||||||
|     #     'factory2': <dependency_injector.providers.Factory(...), |     #     "factory2": <dependency_injector.providers.Factory(...), | ||||||
|     # } |     # } | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ class Service: | ||||||
| 
 | 
 | ||||||
| class SourceContainer(containers.DeclarativeContainer): | class SourceContainer(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     database = providers.Singleton(sqlite3.connect, ':memory:') |     database = providers.Singleton(sqlite3.connect, ":memory:") | ||||||
|     service = providers.Factory(Service, db=database) |     service = providers.Factory(Service, db=database) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -24,7 +24,7 @@ class DestinationContainer(SourceContainer): | ||||||
|     database = providers.Singleton(mock.Mock) |     database = providers.Singleton(mock.Mock) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = DestinationContainer() |     container = DestinationContainer() | ||||||
| 
 | 
 | ||||||
|     service = container.service() |     service = container.service() | ||||||
|  |  | ||||||
|  | @ -9,21 +9,21 @@ class Service: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Base(containers.DeclarativeContainer): | class Base(containers.DeclarativeContainer): | ||||||
|     dependency = providers.Dependency(instance_of=str, default='Default value') |     dependency = providers.Dependency(instance_of=str, default="Default value") | ||||||
|     service = providers.Factory(Service, dependency=dependency) |     service = providers.Factory(Service, dependency=dependency) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @containers.copy(Base) | @containers.copy(Base) | ||||||
| class Derived1(Base): | class Derived1(Base): | ||||||
|     dependency = providers.Dependency(instance_of=str, default='Derived 1') |     dependency = providers.Dependency(instance_of=str, default="Derived 1") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # @containers.copy(Base)  # <-- No @copy decorator | # @containers.copy(Base)  # <-- No @copy decorator | ||||||
| class Derived2(Base): | class Derived2(Base): | ||||||
|     dependency = providers.Dependency(instance_of=str, default='Derived 2') |     dependency = providers.Dependency(instance_of=str, default="Derived 2") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container1 = Derived1() |     container1 = Derived1() | ||||||
|     service1 = container1.service() |     service1 = container1.service() | ||||||
|     print(service1.dependency)  # Derived 1 |     print(service1.dependency)  # Derived 1 | ||||||
|  |  | ||||||
|  | @ -14,21 +14,21 @@ class ContainerB(ContainerA): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| assert ContainerA.providers == { | assert ContainerA.providers == { | ||||||
|     'provider1': ContainerA.provider1, |     "provider1": ContainerA.provider1, | ||||||
| } | } | ||||||
| assert ContainerB.providers == { | assert ContainerB.providers == { | ||||||
|     'provider1': ContainerA.provider1, |     "provider1": ContainerA.provider1, | ||||||
|     'provider2': ContainerB.provider2, |     "provider2": ContainerB.provider2, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| assert ContainerA.cls_providers == { | assert ContainerA.cls_providers == { | ||||||
|     'provider1': ContainerA.provider1, |     "provider1": ContainerA.provider1, | ||||||
| } | } | ||||||
| assert ContainerB.cls_providers == { | assert ContainerB.cls_providers == { | ||||||
|     'provider2': ContainerB.provider2, |     "provider2": ContainerB.provider2, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| assert ContainerA.inherited_providers == {} | assert ContainerA.inherited_providers == {} | ||||||
| assert ContainerB.inherited_providers == { | assert ContainerB.inherited_providers == { | ||||||
|     'provider1': ContainerA.provider1, |     "provider1": ContainerA.provider1, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ class AuthService: | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     database = providers.Singleton(sqlite3.connect, ':memory:') |     database = providers.Singleton(sqlite3.connect, ":memory:") | ||||||
| 
 | 
 | ||||||
|     user_service = providers.Factory( |     user_service = providers.Factory( | ||||||
|         UserService, |         UserService, | ||||||
|  | @ -32,7 +32,7 @@ class Container(containers.DeclarativeContainer): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     user_service = container.user_service() |     user_service = container.user_service() | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ from dependency_injector import containers, providers | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     database = providers.Singleton(sqlite3.connect, ':memory:') |     database = providers.Singleton(sqlite3.connect, ":memory:") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Overriding ``Container`` with ``OverridingContainer``: | # Overriding ``Container`` with ``OverridingContainer``: | ||||||
|  | @ -18,7 +18,7 @@ class OverridingContainer(containers.DeclarativeContainer): | ||||||
|     database = providers.Singleton(mock.Mock) |     database = providers.Singleton(mock.Mock) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     database = container.database() |     database = container.database() | ||||||
|  |  | ||||||
|  | @ -8,10 +8,10 @@ from dependency_injector import containers, providers | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     database = providers.Singleton(sqlite3.connect, ':memory:') |     database = providers.Singleton(sqlite3.connect, ":memory:") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container(database=mock.Mock(sqlite3.Connection)) |     container = Container(database=mock.Mock(sqlite3.Connection)) | ||||||
| 
 | 
 | ||||||
|     database = container.database() |     database = container.database() | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| from dependency_injector import containers, providers | from dependency_injector import containers, providers | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = containers.DynamicContainer() |     container = containers.DynamicContainer() | ||||||
|     container.factory1 = providers.Factory(object) |     container.factory1 = providers.Factory(object) | ||||||
|     container.factory2 = providers.Factory(object) |     container.factory2 = providers.Factory(object) | ||||||
|  | @ -13,6 +13,6 @@ if __name__ == '__main__': | ||||||
| 
 | 
 | ||||||
|     print(container.providers) |     print(container.providers) | ||||||
|     # { |     # { | ||||||
|     #     'factory1': <dependency_injector.providers.Factory(...), |     #     "factory1": <dependency_injector.providers.Factory(...), | ||||||
|     #     'factory2': <dependency_injector.providers.Factory(...), |     #     "factory2": <dependency_injector.providers.Factory(...), | ||||||
|     # } |     # } | ||||||
|  |  | ||||||
|  | @ -13,20 +13,20 @@ class AuthService: | ||||||
| 
 | 
 | ||||||
| def populate_container(container, providers_config): | def populate_container(container, providers_config): | ||||||
|     for provider_name, provider_info in providers_config.items(): |     for provider_name, provider_info in providers_config.items(): | ||||||
|         provided_cls = globals().get(provider_info['class']) |         provided_cls = globals().get(provider_info["class"]) | ||||||
|         provider_cls = getattr(providers, provider_info['provider_class']) |         provider_cls = getattr(providers, provider_info["provider_class"]) | ||||||
|         setattr(container, provider_name, provider_cls(provided_cls)) |         setattr(container, provider_name, provider_cls(provided_cls)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     services_config = { |     services_config = { | ||||||
|         'user': { |         "user": { | ||||||
|             'class': 'UserService', |             "class": "UserService", | ||||||
|             'provider_class': 'Factory', |             "provider_class": "Factory", | ||||||
|         }, |         }, | ||||||
|         'auth': { |         "auth": { | ||||||
|             'class': 'AuthService', |             "class": "AuthService", | ||||||
|             'provider_class': 'Factory', |             "provider_class": "Factory", | ||||||
|         }, |         }, | ||||||
|     } |     } | ||||||
|     services = containers.DynamicContainer() |     services = containers.DynamicContainer() | ||||||
|  |  | ||||||
|  | @ -21,14 +21,14 @@ class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     __self__ = providers.Self() |     __self__ = providers.Self() | ||||||
| 
 | 
 | ||||||
|     service1 = providers.Factory(Service, name='Service 1') |     service1 = providers.Factory(Service, name="Service 1") | ||||||
|     service2 = providers.Factory(Service, name='Service 2') |     service2 = providers.Factory(Service, name="Service 2") | ||||||
|     service3 = providers.Factory(Service, name='Service 3') |     service3 = providers.Factory(Service, name="Service 3") | ||||||
| 
 | 
 | ||||||
|     dispatcher = providers.Singleton(ServiceDispatcher, __self__) |     dispatcher = providers.Singleton(ServiceDispatcher, __self__) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     dispatcher = container.dispatcher() |     dispatcher = container.dispatcher() | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ class OverridingContainer(containers.DeclarativeContainer): | ||||||
|     service = providers.Factory(ServiceStub) |     service = providers.Factory(ServiceStub) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
|     overriding_container = OverridingContainer() |     overriding_container = OverridingContainer() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ class Container(containers.DeclarativeContainer): | ||||||
|     service2 = providers.Singleton(object) |     service2 = providers.Singleton(object) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     service1 = container.service1() |     service1 = container.service1() | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ class Container(containers.DeclarativeContainer): | ||||||
|     sub = providers.Container(SubContainer) |     sub = providers.Container(SubContainer) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     service1 = container.service() |     service1 = container.service() | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ class Container(containers.DeclarativeContainer): | ||||||
|     service = providers.Singleton(object) |     service = providers.Singleton(object) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     service1 = container.service() |     service1 = container.service() | ||||||
|  |  | ||||||
|  | @ -34,15 +34,15 @@ class Container(containers.DeclarativeContainer): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     for provider in container.traverse(): |     for provider in container.traverse(): | ||||||
|         print(provider) |         print(provider) | ||||||
| 
 | 
 | ||||||
|     # <dependency_injector.providers.Configuration('config') at 0x10d37d200> |     # <dependency_injector.providers.Configuration("config") at 0x10d37d200> | ||||||
|     # <dependency_injector.providers.Factory(<class '__main__.Service'>) at 0x10d3a2820> |     # <dependency_injector.providers.Factory(<class "__main__.Service">) at 0x10d3a2820> | ||||||
|     # <dependency_injector.providers.Resource(<function init_database at 0x10bd2cb80>) at 0x10d346b40> |     # <dependency_injector.providers.Resource(<function init_database at 0x10bd2cb80>) at 0x10d346b40> | ||||||
|     # <dependency_injector.providers.ConfigurationOption('config.cache_hosts') at 0x10d37d350> |     # <dependency_injector.providers.ConfigurationOption("config.cache_hosts") at 0x10d37d350> | ||||||
|     # <dependency_injector.providers.Resource(<function init_cache at 0x10be373a0>) at 0x10d346bc0> |     # <dependency_injector.providers.Resource(<function init_cache at 0x10be373a0>) at 0x10d346bc0> | ||||||
|     # <dependency_injector.providers.ConfigurationOption('config.database_url') at 0x10d37d2e0> |     # <dependency_injector.providers.ConfigurationOption("config.database_url") at 0x10d37d2e0> | ||||||
|  |  | ||||||
|  | @ -18,12 +18,12 @@ def main(service: Service):  # <-- dependency is injected | ||||||
|     ... |     ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     main( |     main( | ||||||
|         service=Service( |         service=Service( | ||||||
|             api_client=ApiClient( |             api_client=ApiClient( | ||||||
|                 api_key=os.getenv('API_KEY'), |                 api_key=os.getenv("API_KEY"), | ||||||
|                 timeout=os.getenv('TIMEOUT'), |                 timeout=os.getenv("TIMEOUT"), | ||||||
|             ), |             ), | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -4,8 +4,8 @@ import os | ||||||
| class ApiClient: | class ApiClient: | ||||||
| 
 | 
 | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.api_key = os.getenv('API_KEY')  # <-- dependency |         self.api_key = os.getenv("API_KEY")  # <-- dependency | ||||||
|         self.timeout = os.getenv('TIMEOUT')  # <-- dependency |         self.timeout = os.getenv("TIMEOUT")  # <-- dependency | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Service: | class Service: | ||||||
|  | @ -19,5 +19,5 @@ def main() -> None: | ||||||
|     ... |     ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     main() |     main() | ||||||
|  |  | ||||||
|  | @ -1,8 +1,7 @@ | ||||||
| import sys |  | ||||||
| from unittest import mock | from unittest import mock | ||||||
| 
 | 
 | ||||||
| from dependency_injector import containers, providers | from dependency_injector import containers, providers | ||||||
| from dependency_injector.wiring import inject, Provide | from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
| from after import ApiClient, Service | from after import ApiClient, Service | ||||||
| 
 | 
 | ||||||
|  | @ -28,11 +27,11 @@ def main(service: Service = Provide[Container.service]): | ||||||
|     ... |     ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
|     container.config.api_key.from_env('API_KEY') |     container.config.api_key.from_env("API_KEY") | ||||||
|     container.config.timeout.from_env('TIMEOUT') |     container.config.timeout.from_env("TIMEOUT") | ||||||
|     container.wire(modules=[sys.modules[__name__]]) |     container.wire(modules=[__name__]) | ||||||
| 
 | 
 | ||||||
|     main()  # <-- dependency is injected automatically |     main()  # <-- dependency is injected automatically | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,16 +27,16 @@ To run the application do: | ||||||
| .. code-block:: bash | .. code-block:: bash | ||||||
| 
 | 
 | ||||||
|     export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0 |     export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0 | ||||||
|     adev runserver giphynavigator/application.py --livereload |     python -m giphynavigator.application | ||||||
| 
 | 
 | ||||||
| The output should be something like: | The output should be something like: | ||||||
| 
 | 
 | ||||||
| .. code-block:: | .. code-block:: | ||||||
| 
 | 
 | ||||||
|    [18:52:59] Starting aux server at http://localhost:8001 ◆ |    ======== Running on http://0.0.0.0:8080 ======== | ||||||
|    [18:52:59] Starting dev server at http://localhost:8000 ● |    (Press CTRL+C to quit) | ||||||
| 
 | 
 | ||||||
| After that visit http://127.0.0.1:8000/ in your browser or use CLI command (``curl``, ``httpie``, | After that visit http://0.0.0.0:8080/ in your browser or use CLI command (``curl``, ``httpie``, | ||||||
| etc). You should see something like: | etc). You should see something like: | ||||||
| 
 | 
 | ||||||
| .. code-block:: json | .. code-block:: json | ||||||
|  | @ -98,21 +98,21 @@ The output should be something like: | ||||||
| 
 | 
 | ||||||
| .. code-block:: | .. code-block:: | ||||||
| 
 | 
 | ||||||
|    platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1 |    platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 | ||||||
|    plugins: cov-2.10.0, aiohttp-0.3.0, asyncio-0.14.0 |    plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0 | ||||||
|    collected 3 items |    collected 3 items | ||||||
| 
 | 
 | ||||||
|    giphynavigator/tests.py ...                                     [100%] |    giphynavigator/tests.py ...                                     [100%] | ||||||
| 
 | 
 | ||||||
|    ---------- coverage: platform darwin, python 3.8.3-final-0 ----------- |    ---------- coverage: platform darwin, python 3.10.0-final-0 ---------- | ||||||
|    Name                            Stmts   Miss  Cover |    Name                            Stmts   Miss  Cover | ||||||
|    --------------------------------------------------- |    --------------------------------------------------- | ||||||
|    giphynavigator/__init__.py          0      0   100% |    giphynavigator/__init__.py          0      0   100% | ||||||
|    giphynavigator/application.py      12      0   100% |    giphynavigator/application.py      13      2    85% | ||||||
|    giphynavigator/containers.py        6      0   100% |    giphynavigator/containers.py        7      0   100% | ||||||
|    giphynavigator/giphy.py            14      9    36% |    giphynavigator/giphy.py            14      9    36% | ||||||
|    giphynavigator/handlers.py         10      0   100% |    giphynavigator/handlers.py         10      0   100% | ||||||
|    giphynavigator/services.py          9      1    89% |    giphynavigator/services.py          9      1    89% | ||||||
|    giphynavigator/tests.py            37      0   100% |    giphynavigator/tests.py            37      0   100% | ||||||
|    --------------------------------------------------- |    --------------------------------------------------- | ||||||
|    TOTAL                              88     10    89% |    TOTAL                              90     12    87% | ||||||
|  |  | ||||||
|  | @ -8,13 +8,16 @@ from . import handlers | ||||||
| 
 | 
 | ||||||
| def create_app() -> web.Application: | def create_app() -> web.Application: | ||||||
|     container = Container() |     container = Container() | ||||||
|     container.config.from_yaml('config.yml') |     container.config.giphy.api_key.from_env("GIPHY_API_KEY") | ||||||
|     container.config.giphy.api_key.from_env('GIPHY_API_KEY') |  | ||||||
|     container.wire(modules=[handlers]) |  | ||||||
| 
 | 
 | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.container = container |     app.container = container | ||||||
|     app.add_routes([ |     app.add_routes([ | ||||||
|         web.get('/', handlers.index), |         web.get("/", handlers.index), | ||||||
|     ]) |     ]) | ||||||
|     return app |     return app | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     app = create_app() | ||||||
|  |     web.run_app(app) | ||||||
|  |  | ||||||
|  | @ -7,7 +7,9 @@ from . import giphy, services | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     config = providers.Configuration() |     wiring_config = containers.WiringConfiguration(modules=[".handlers"]) | ||||||
|  | 
 | ||||||
|  |     config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|     giphy_client = providers.Factory( |     giphy_client = providers.Factory( | ||||||
|         giphy.GiphyClient, |         giphy.GiphyClient, | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ from aiohttp import ClientSession, ClientTimeout | ||||||
| 
 | 
 | ||||||
| class GiphyClient: | class GiphyClient: | ||||||
| 
 | 
 | ||||||
|     API_URL = 'https://api.giphy.com/v1' |     API_URL = "https://api.giphy.com/v1" | ||||||
| 
 | 
 | ||||||
|     def __init__(self, api_key, timeout): |     def __init__(self, api_key, timeout): | ||||||
|         self._api_key = api_key |         self._api_key = api_key | ||||||
|  | @ -13,11 +13,11 @@ class GiphyClient: | ||||||
| 
 | 
 | ||||||
|     async def search(self, query, limit): |     async def search(self, query, limit): | ||||||
|         """Make search API call and return result.""" |         """Make search API call and return result.""" | ||||||
|         url = f'{self.API_URL}/gifs/search' |         url = f"{self.API_URL}/gifs/search" | ||||||
|         params = { |         params = { | ||||||
|             'q': query, |             "q": query, | ||||||
|             'api_key': self._api_key, |             "api_key": self._api_key, | ||||||
|             'limit': limit, |             "limit": limit, | ||||||
|         } |         } | ||||||
|         async with ClientSession(timeout=self._timeout) as session: |         async with ClientSession(timeout=self._timeout) as session: | ||||||
|             async with session.get(url, params=params) as response: |             async with session.get(url, params=params) as response: | ||||||
|  |  | ||||||
|  | @ -14,15 +14,15 @@ async def index( | ||||||
|         default_query: str = Provide[Container.config.default.query], |         default_query: str = Provide[Container.config.default.query], | ||||||
|         default_limit: int = Provide[Container.config.default.limit.as_int()], |         default_limit: int = Provide[Container.config.default.limit.as_int()], | ||||||
| ) -> web.Response: | ) -> web.Response: | ||||||
|     query = request.query.get('query', default_query) |     query = request.query.get("query", default_query) | ||||||
|     limit = int(request.query.get('limit', default_limit)) |     limit = int(request.query.get("limit", default_limit)) | ||||||
| 
 | 
 | ||||||
|     gifs = await search_service.search(query, limit) |     gifs = await search_service.search(query, limit) | ||||||
| 
 | 
 | ||||||
|     return web.json_response( |     return web.json_response( | ||||||
|         { |         { | ||||||
|             'query': query, |             "query": query, | ||||||
|             'limit': limit, |             "limit": limit, | ||||||
|             'gifs': gifs, |             "gifs": gifs, | ||||||
|         }, |         }, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -15,4 +15,4 @@ class SearchService: | ||||||
| 
 | 
 | ||||||
|         result = await self._giphy_client.search(query, limit) |         result = await self._giphy_client.search(query, limit) | ||||||
| 
 | 
 | ||||||
|         return [{'url': gif['url']} for gif in result['data']] |         return [{"url": gif["url"]} for gif in result["data"]] | ||||||
|  |  | ||||||
|  | @ -23,29 +23,29 @@ def client(app, aiohttp_client, loop): | ||||||
| async def test_index(client, app): | async def test_index(client, app): | ||||||
|     giphy_client_mock = mock.AsyncMock(spec=GiphyClient) |     giphy_client_mock = mock.AsyncMock(spec=GiphyClient) | ||||||
|     giphy_client_mock.search.return_value = { |     giphy_client_mock.search.return_value = { | ||||||
|         'data': [ |         "data": [ | ||||||
|             {'url': 'https://giphy.com/gif1.gif'}, |             {"url": "https://giphy.com/gif1.gif"}, | ||||||
|             {'url': 'https://giphy.com/gif2.gif'}, |             {"url": "https://giphy.com/gif2.gif"}, | ||||||
|         ], |         ], | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     with app.container.giphy_client.override(giphy_client_mock): |     with app.container.giphy_client.override(giphy_client_mock): | ||||||
|         response = await client.get( |         response = await client.get( | ||||||
|             '/', |             "/", | ||||||
|             params={ |             params={ | ||||||
|                 'query': 'test', |                 "query": "test", | ||||||
|                 'limit': 10, |                 "limit": 10, | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|     data = await response.json() |     data = await response.json() | ||||||
|     assert data == { |     assert data == { | ||||||
|         'query': 'test', |         "query": "test", | ||||||
|         'limit': 10, |         "limit": 10, | ||||||
|         'gifs': [ |         "gifs": [ | ||||||
|             {'url': 'https://giphy.com/gif1.gif'}, |             {"url": "https://giphy.com/gif1.gif"}, | ||||||
|             {'url': 'https://giphy.com/gif2.gif'}, |             {"url": "https://giphy.com/gif2.gif"}, | ||||||
|         ], |         ], | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -53,27 +53,27 @@ async def test_index(client, app): | ||||||
| async def test_index_no_data(client, app): | async def test_index_no_data(client, app): | ||||||
|     giphy_client_mock = mock.AsyncMock(spec=GiphyClient) |     giphy_client_mock = mock.AsyncMock(spec=GiphyClient) | ||||||
|     giphy_client_mock.search.return_value = { |     giphy_client_mock.search.return_value = { | ||||||
|         'data': [], |         "data": [], | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     with app.container.giphy_client.override(giphy_client_mock): |     with app.container.giphy_client.override(giphy_client_mock): | ||||||
|         response = await client.get('/') |         response = await client.get("/") | ||||||
| 
 | 
 | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|     data = await response.json() |     data = await response.json() | ||||||
|     assert data['gifs'] == [] |     assert data["gifs"] == [] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| async def test_index_default_params(client, app): | async def test_index_default_params(client, app): | ||||||
|     giphy_client_mock = mock.AsyncMock(spec=GiphyClient) |     giphy_client_mock = mock.AsyncMock(spec=GiphyClient) | ||||||
|     giphy_client_mock.search.return_value = { |     giphy_client_mock.search.return_value = { | ||||||
|         'data': [], |         "data": [], | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     with app.container.giphy_client.override(giphy_client_mock): |     with app.container.giphy_client.override(giphy_client_mock): | ||||||
|         response = await client.get('/') |         response = await client.get("/") | ||||||
| 
 | 
 | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|     data = await response.json() |     data = await response.json() | ||||||
|     assert data['query'] == app.container.config.default.query() |     assert data["query"] == app.container.config.default.query() | ||||||
|     assert data['limit'] == app.container.config.default.limit() |     assert data["limit"] == app.container.config.default.limit() | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| dependency-injector | dependency-injector | ||||||
| aiohttp | aiohttp | ||||||
| aiohttp-devtools |  | ||||||
| pyyaml | pyyaml | ||||||
| pytest-aiohttp | pytest-aiohttp | ||||||
| pytest-cov | pytest-cov | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| from .containers import Application | from .containers import Application | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     application = Application() |     application = Application() | ||||||
|     config = application.service.config() |     config = application.service.config() | ||||||
|     config.build() |     config.build() | ||||||
|  |  | ||||||
|  | @ -6,17 +6,17 @@ from .services import ConfigService | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Core(containers.DeclarativeContainer): | class Core(containers.DeclarativeContainer): | ||||||
|     config = providers.Configuration('config') |     config = providers.Configuration("config") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Storage(containers.DeclarativeContainer): | class Storage(containers.DeclarativeContainer): | ||||||
|     queue = providers.Singleton(lambda: 'Some storage') |     queue = providers.Singleton(lambda: "Some storage") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Adapter(containers.DeclarativeContainer): | class Adapter(containers.DeclarativeContainer): | ||||||
|     core = providers.DependenciesContainer(config=providers.Configuration()) |     core = providers.DependenciesContainer(config=providers.Configuration()) | ||||||
|     tinydb = providers.Singleton( |     tinydb = providers.Singleton( | ||||||
|         lambda db_path: f'DB Path=[{db_path}]', |         lambda db_path: f"DB Path=[{db_path}]", | ||||||
|         db_path=core.config.default.db_path, |         db_path=core.config.default.db_path, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  | @ -25,7 +25,7 @@ class Repository(containers.DeclarativeContainer): | ||||||
|     adapter = providers.DependenciesContainer() |     adapter = providers.DependenciesContainer() | ||||||
|     storage = providers.DependenciesContainer() |     storage = providers.DependenciesContainer() | ||||||
|     site = providers.Singleton( |     site = providers.Singleton( | ||||||
|         lambda adapter, queue: f'Adapter=[{adapter}], queue=[{queue}]', |         lambda adapter, queue: f"Adapter=[{adapter}], queue=[{queue}]", | ||||||
|         adapter=adapter.tinydb, |         adapter=adapter.tinydb, | ||||||
|         queue=storage.queue, |         queue=storage.queue, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -6,4 +6,4 @@ class ConfigService: | ||||||
|         self._config = config |         self._config = config | ||||||
| 
 | 
 | ||||||
|     def build(self): |     def build(self): | ||||||
|         self._config.from_dict({'default': {'db_path': '~/test'}}) |         self._config.from_dict({"default": {"db_path": "~/test"}}) | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| from dependency_injector.wiring import inject, Provide | from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
| from .services import UserService, AuthService, PhotoService | from .services import UserService, AuthService, PhotoService | ||||||
| from .containers import Application | from .containers import Application | ||||||
|  | @ -22,10 +22,9 @@ def main( | ||||||
|     photo_service.upload_photo(user, photo) |     photo_service.upload_photo(user, photo) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     application = Application() |     application = Application() | ||||||
|     application.config.from_yaml('config.yml') |  | ||||||
|     application.core.init_resources() |     application.core.init_resources() | ||||||
|     application.wire(modules=[sys.modules[__name__]]) |     application.wire(modules=[__name__]) | ||||||
| 
 | 
 | ||||||
|     main(*sys.argv[1:]) |     main(*sys.argv[1:]) | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ class Gateways(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     s3_client = providers.Singleton( |     s3_client = providers.Singleton( | ||||||
|         boto3.client, |         boto3.client, | ||||||
|         service_name='s3', |         service_name="s3", | ||||||
|         aws_access_key_id=config.aws.access_key_id, |         aws_access_key_id=config.aws.access_key_id, | ||||||
|         aws_secret_access_key=config.aws.secret_access_key, |         aws_secret_access_key=config.aws.secret_access_key, | ||||||
|     ) |     ) | ||||||
|  | @ -61,7 +61,7 @@ class Services(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
| class Application(containers.DeclarativeContainer): | class Application(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     config = providers.Configuration() |     config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|     core = providers.Container( |     core = providers.Container( | ||||||
|         Core, |         Core, | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ class BaseService: | ||||||
| 
 | 
 | ||||||
|     def __init__(self) -> None: |     def __init__(self) -> None: | ||||||
|         self.logger = logging.getLogger( |         self.logger = logging.getLogger( | ||||||
|             f'{__name__}.{self.__class__.__name__}', |             f"{__name__}.{self.__class__.__name__}", | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -22,8 +22,8 @@ class UserService(BaseService): | ||||||
|         super().__init__() |         super().__init__() | ||||||
| 
 | 
 | ||||||
|     def get_user(self, email: str) -> Dict[str, str]: |     def get_user(self, email: str) -> Dict[str, str]: | ||||||
|         self.logger.debug('User %s has been found in database', email) |         self.logger.debug("User %s has been found in database", email) | ||||||
|         return {'email': email, 'password_hash': '...'} |         return {"email": email, "password_hash": "..."} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AuthService(BaseService): | class AuthService(BaseService): | ||||||
|  | @ -36,8 +36,8 @@ class AuthService(BaseService): | ||||||
|     def authenticate(self, user: Dict[str, str], password: str) -> None: |     def authenticate(self, user: Dict[str, str], password: str) -> None: | ||||||
|         assert password is not None |         assert password is not None | ||||||
|         self.logger.debug( |         self.logger.debug( | ||||||
|             'User %s has been successfully authenticated', |             "User %s has been successfully authenticated", | ||||||
|             user['email'], |             user["email"], | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -50,7 +50,7 @@ class PhotoService(BaseService): | ||||||
| 
 | 
 | ||||||
|     def upload_photo(self, user: Dict[str, str], photo_path: str) -> None: |     def upload_photo(self, user: Dict[str, str], photo_path: str) -> None: | ||||||
|         self.logger.debug( |         self.logger.debug( | ||||||
|             'Photo %s has been successfully uploaded by user %s', |             "Photo %s has been successfully uploaded by user %s", | ||||||
|             photo_path, |             photo_path, | ||||||
|             user['email'], |             user["email"], | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| from dependency_injector.wiring import inject, Provide | from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
| from .services import UserService, AuthService, PhotoService | from .services import UserService, AuthService, PhotoService | ||||||
| from .containers import Container | from .containers import Container | ||||||
|  | @ -22,10 +22,9 @@ def main( | ||||||
|     photo_service.upload_photo(user, photo) |     photo_service.upload_photo(user, photo) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
|     container.init_resources() |     container.init_resources() | ||||||
|     container.config.from_ini('config.ini') |     container.wire(modules=[__name__]) | ||||||
|     container.wire(modules=[sys.modules[__name__]]) |  | ||||||
| 
 | 
 | ||||||
|     main(*sys.argv[1:]) |     main(*sys.argv[1:]) | ||||||
|  |  | ||||||
|  | @ -11,11 +11,11 @@ from . import services | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     config = providers.Configuration() |     config = providers.Configuration(ini_files=["config.ini"]) | ||||||
| 
 | 
 | ||||||
|     logging = providers.Resource( |     logging = providers.Resource( | ||||||
|         logging.config.fileConfig, |         logging.config.fileConfig, | ||||||
|         fname='logging.ini', |         fname="logging.ini", | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     # Gateways |     # Gateways | ||||||
|  | @ -27,7 +27,7 @@ class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     s3_client = providers.Singleton( |     s3_client = providers.Singleton( | ||||||
|         boto3.client, |         boto3.client, | ||||||
|         service_name='s3', |         service_name="s3", | ||||||
|         aws_access_key_id=config.aws.access_key_id, |         aws_access_key_id=config.aws.access_key_id, | ||||||
|         aws_secret_access_key=config.aws.secret_access_key, |         aws_secret_access_key=config.aws.secret_access_key, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ class BaseService: | ||||||
| 
 | 
 | ||||||
|     def __init__(self) -> None: |     def __init__(self) -> None: | ||||||
|         self.logger = logging.getLogger( |         self.logger = logging.getLogger( | ||||||
|             f'{__name__}.{self.__class__.__name__}', |             f"{__name__}.{self.__class__.__name__}", | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -22,8 +22,8 @@ class UserService(BaseService): | ||||||
|         super().__init__() |         super().__init__() | ||||||
| 
 | 
 | ||||||
|     def get_user(self, email: str) -> Dict[str, str]: |     def get_user(self, email: str) -> Dict[str, str]: | ||||||
|         self.logger.debug('User %s has been found in database', email) |         self.logger.debug("User %s has been found in database", email) | ||||||
|         return {'email': email, 'password_hash': '...'} |         return {"email": email, "password_hash": "..."} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AuthService(BaseService): | class AuthService(BaseService): | ||||||
|  | @ -36,8 +36,8 @@ class AuthService(BaseService): | ||||||
|     def authenticate(self, user: Dict[str, str], password: str) -> None: |     def authenticate(self, user: Dict[str, str], password: str) -> None: | ||||||
|         assert password is not None |         assert password is not None | ||||||
|         self.logger.debug( |         self.logger.debug( | ||||||
|             'User %s has been successfully authenticated', |             "User %s has been successfully authenticated", | ||||||
|             user['email'], |             user["email"], | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -50,7 +50,7 @@ class PhotoService(BaseService): | ||||||
| 
 | 
 | ||||||
|     def upload_photo(self, user: Dict[str, str], photo_path: str) -> None: |     def upload_photo(self, user: Dict[str, str], photo_path: str) -> None: | ||||||
|         self.logger.debug( |         self.logger.debug( | ||||||
|             'Photo %s has been successfully uploaded by user %s', |             "Photo %s has been successfully uploaded by user %s", | ||||||
|             photo_path, |             photo_path, | ||||||
|             user['email'], |             user["email"], | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| FROM python:3.8-buster | FROM python:3.10-buster | ||||||
| 
 | 
 | ||||||
| ENV PYTHONUNBUFFERED=1 | ENV PYTHONUNBUFFERED=1 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -65,22 +65,22 @@ The output should be something like: | ||||||
| 
 | 
 | ||||||
| .. code-block:: | .. code-block:: | ||||||
| 
 | 
 | ||||||
|    platform linux -- Python 3.8.3, pytest-6.0.1, py-1.9.0, pluggy-0.13.1 |    platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 | ||||||
|    rootdir: /code |    rootdir: /code | ||||||
|    plugins: asyncio-0.14.0, cov-2.10.0 |    plugins: asyncio-0.16.0, cov-3.0.0 | ||||||
|    collected 2 items |    collected 2 items | ||||||
| 
 | 
 | ||||||
|    monitoringdaemon/tests.py ..                                    [100%] |    monitoringdaemon/tests.py ..                                    [100%] | ||||||
| 
 | 
 | ||||||
|    ----------- coverage: platform linux, python 3.8.3-final-0 ----------- |    ---------- coverage: platform linux, python 3.10.0-final-0 ----------- | ||||||
|    Name                             Stmts   Miss  Cover |    Name                             Stmts   Miss  Cover | ||||||
|    ---------------------------------------------------- |    ---------------------------------------------------- | ||||||
|    monitoringdaemon/__init__.py         0      0   100% |    monitoringdaemon/__init__.py         0      0   100% | ||||||
|    monitoringdaemon/__main__.py        13     13     0% |    monitoringdaemon/__main__.py        11     11     0% | ||||||
|    monitoringdaemon/containers.py      11      0   100% |    monitoringdaemon/containers.py      11      0   100% | ||||||
|    monitoringdaemon/dispatcher.py      44      5    89% |    monitoringdaemon/dispatcher.py      45      5    89% | ||||||
|    monitoringdaemon/http.py             6      3    50% |    monitoringdaemon/http.py             6      3    50% | ||||||
|    monitoringdaemon/monitors.py        23      1    96% |    monitoringdaemon/monitors.py        23      1    96% | ||||||
|    monitoringdaemon/tests.py           37      0   100% |    monitoringdaemon/tests.py           35      0   100% | ||||||
|    ---------------------------------------------------- |    ---------------------------------------------------- | ||||||
|    TOTAL                              134     22    84% |    TOTAL                              131     20    85% | ||||||
|  |  | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| """Main module.""" | """Main module.""" | ||||||
| 
 | 
 | ||||||
| import sys |  | ||||||
| 
 |  | ||||||
| from dependency_injector.wiring import inject, Provide | from dependency_injector.wiring import inject, Provide | ||||||
| 
 | 
 | ||||||
| from .dispatcher import Dispatcher | from .dispatcher import Dispatcher | ||||||
|  | @ -13,10 +11,9 @@ def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None: | ||||||
|     dispatcher.run() |     dispatcher.run() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
|     container.config.from_yaml('config.yml') |  | ||||||
|     container.init_resources() |     container.init_resources() | ||||||
|     container.wire(modules=[sys.modules[__name__]]) |     container.wire(modules=[__name__]) | ||||||
| 
 | 
 | ||||||
|     main() |     main() | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ from . import http, monitors, dispatcher | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     config = providers.Configuration() |     config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|     logging = providers.Resource( |     logging = providers.Resource( | ||||||
|         logging.basicConfig, |         logging.basicConfig, | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ class Dispatcher: | ||||||
|         asyncio.run(self.start()) |         asyncio.run(self.start()) | ||||||
| 
 | 
 | ||||||
|     async def start(self) -> None: |     async def start(self) -> None: | ||||||
|         self._logger.info('Starting up') |         self._logger.info("Starting up") | ||||||
| 
 | 
 | ||||||
|         for monitor in self._monitors: |         for monitor in self._monitors: | ||||||
|             self._monitor_tasks.append( |             self._monitor_tasks.append( | ||||||
|  | @ -41,11 +41,11 @@ class Dispatcher: | ||||||
| 
 | 
 | ||||||
|         self._stopping = True |         self._stopping = True | ||||||
| 
 | 
 | ||||||
|         self._logger.info('Shutting down') |         self._logger.info("Shutting down") | ||||||
|         for task, monitor in zip(self._monitor_tasks, self._monitors): |         for task, monitor in zip(self._monitor_tasks, self._monitors): | ||||||
|             task.cancel() |             task.cancel() | ||||||
|         self._monitor_tasks.clear() |         self._monitor_tasks.clear() | ||||||
|         self._logger.info('Shutdown finished successfully') |         self._logger.info("Shutdown finished successfully") | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     async def _run_monitor(monitor: Monitor) -> None: |     async def _run_monitor(monitor: Monitor) -> None: | ||||||
|  | @ -61,6 +61,6 @@ class Dispatcher: | ||||||
|             except asyncio.CancelledError: |             except asyncio.CancelledError: | ||||||
|                 break |                 break | ||||||
|             except Exception: |             except Exception: | ||||||
|                 monitor.logger.exception('Error executing monitor check') |                 monitor.logger.exception("Error executing monitor check") | ||||||
| 
 | 
 | ||||||
|             await asyncio.sleep(_until_next(last=time_start)) |             await asyncio.sleep(_until_next(last=time_start)) | ||||||
|  |  | ||||||
|  | @ -25,10 +25,10 @@ class HttpMonitor(Monitor): | ||||||
|             options: Dict[str, Any], |             options: Dict[str, Any], | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         self._client = http_client |         self._client = http_client | ||||||
|         self._method = options.pop('method') |         self._method = options.pop("method") | ||||||
|         self._url = options.pop('url') |         self._url = options.pop("url") | ||||||
|         self._timeout = options.pop('timeout') |         self._timeout = options.pop("timeout") | ||||||
|         super().__init__(check_every=options.pop('check_every')) |         super().__init__(check_every=options.pop("check_every")) | ||||||
| 
 | 
 | ||||||
|     async def check(self) -> None: |     async def check(self) -> None: | ||||||
|         time_start = time.time() |         time_start = time.time() | ||||||
|  | @ -43,11 +43,11 @@ class HttpMonitor(Monitor): | ||||||
|         time_took = time_end - time_start |         time_took = time_end - time_start | ||||||
| 
 | 
 | ||||||
|         self.logger.info( |         self.logger.info( | ||||||
|             'Check\n' |             "Check\n" | ||||||
|             '    %s %s\n' |             "    %s %s\n" | ||||||
|             '    response code: %s\n' |             "    response code: %s\n" | ||||||
|             '    content length: %s\n' |             "    content length: %s\n" | ||||||
|             '    request took: %s seconds', |             "    request took: %s seconds", | ||||||
|             self._method, |             self._method, | ||||||
|             self._url, |             self._url, | ||||||
|             response.status, |             response.status, | ||||||
|  |  | ||||||
|  | @ -17,33 +17,33 @@ class RequestStub: | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def container(): | def container(): | ||||||
|     container = Container() |     return Container( | ||||||
|     container.config.from_dict({ |         config={ | ||||||
|         'log': { |             "log": { | ||||||
|             'level': 'INFO', |                 "level": "INFO", | ||||||
|             'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s', |                 "formant": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s", | ||||||
|             }, |             }, | ||||||
|         'monitors': { |             "monitors": { | ||||||
|             'example': { |                 "example": { | ||||||
|                 'method': 'GET', |                     "method": "GET", | ||||||
|                 'url': 'http://fake-example.com', |                     "url": "http://fake-example.com", | ||||||
|                 'timeout': 1, |                     "timeout": 1, | ||||||
|                 'check_every': 1, |                     "check_every": 1, | ||||||
|                 }, |                 }, | ||||||
|             'httpbin': { |                 "httpbin": { | ||||||
|                 'method': 'GET', |                     "method": "GET", | ||||||
|                 'url': 'https://fake-httpbin.org/get', |                     "url": "https://fake-httpbin.org/get", | ||||||
|                 'timeout': 1, |                     "timeout": 1, | ||||||
|                 'check_every': 1, |                     "check_every": 1, | ||||||
|                 }, |                 }, | ||||||
|             }, |             }, | ||||||
|     }) |         } | ||||||
|     return container |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| async def test_example_monitor(container, caplog): | async def test_example_monitor(container, caplog): | ||||||
|     caplog.set_level('INFO') |     caplog.set_level("INFO") | ||||||
| 
 | 
 | ||||||
|     http_client_mock = mock.AsyncMock() |     http_client_mock = mock.AsyncMock() | ||||||
|     http_client_mock.request.return_value = RequestStub( |     http_client_mock.request.return_value = RequestStub( | ||||||
|  | @ -55,21 +55,22 @@ async def test_example_monitor(container, caplog): | ||||||
|         example_monitor = container.example_monitor() |         example_monitor = container.example_monitor() | ||||||
|         await example_monitor.check() |         await example_monitor.check() | ||||||
| 
 | 
 | ||||||
|     assert 'http://fake-example.com' in caplog.text |     assert "http://fake-example.com" in caplog.text | ||||||
|     assert 'response code: 200' in caplog.text |     assert "response code: 200" in caplog.text | ||||||
|     assert 'content length: 635' in caplog.text |     assert "content length: 635" in caplog.text | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| async def test_dispatcher(container, caplog, event_loop): | async def test_dispatcher(container, caplog, event_loop): | ||||||
|     caplog.set_level('INFO') |     caplog.set_level("INFO") | ||||||
| 
 | 
 | ||||||
|     example_monitor_mock = mock.AsyncMock() |     example_monitor_mock = mock.AsyncMock() | ||||||
|     httpbin_monitor_mock = mock.AsyncMock() |     httpbin_monitor_mock = mock.AsyncMock() | ||||||
| 
 | 
 | ||||||
|     with container.example_monitor.override(example_monitor_mock), \ |     with container.override_providers( | ||||||
|             container.httpbin_monitor.override(httpbin_monitor_mock): |             example_monitor=example_monitor_mock, | ||||||
| 
 |             httpbin_monitor=httpbin_monitor_mock, | ||||||
|  |     ): | ||||||
|         dispatcher = container.dispatcher() |         dispatcher = container.dispatcher() | ||||||
|         event_loop.create_task(dispatcher.start()) |         event_loop.create_task(dispatcher.start()) | ||||||
|         await asyncio.sleep(0.1) |         await asyncio.sleep(0.1) | ||||||
|  |  | ||||||
|  | @ -23,12 +23,12 @@ class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     s3_client = providers.Resource( |     s3_client = providers.Resource( | ||||||
|         session.provided.client.call(), |         session.provided.client.call(), | ||||||
|         service_name='s3', |         service_name="s3", | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     sqs_client = providers.Resource( |     sqs_client = providers.Resource( | ||||||
|         providers.MethodCaller(session.provided.client),  # Alternative syntax |         providers.MethodCaller(session.provided.client),  # Alternative syntax | ||||||
|         service_name='sqs', |         service_name="sqs", | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     service1 = providers.Factory( |     service1 = providers.Factory( | ||||||
|  | @ -39,16 +39,16 @@ class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     service2 = providers.Factory( |     service2 = providers.Factory( | ||||||
|         Service, |         Service, | ||||||
|         s3_client=session.provided.client.call(service_name='s3'),    # Alternative inline syntax |         s3_client=session.provided.client.call(service_name="s3"),    # Alternative inline syntax | ||||||
|         sqs_client=session.provided.client.call(service_name='sqs'),  # Alternative inline syntax |         sqs_client=session.provided.client.call(service_name="sqs"),  # Alternative inline syntax | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def main(): | def main(): | ||||||
|     container = Container() |     container = Container() | ||||||
|     container.config.aws_access_key_id.from_env('AWS_ACCESS_KEY_ID') |     container.config.aws_access_key_id.from_env("AWS_ACCESS_KEY_ID") | ||||||
|     container.config.aws_secret_access_key.from_env('AWS_SECRET_ACCESS_KEY') |     container.config.aws_secret_access_key.from_env("AWS_SECRET_ACCESS_KEY") | ||||||
|     container.config.aws_session_token.from_env('AWS_SESSION_TOKEN') |     container.config.aws_session_token.from_env("AWS_SESSION_TOKEN") | ||||||
|     container.init_resources() |     container.init_resources() | ||||||
| 
 | 
 | ||||||
|     s3_client = container.s3_client() |     s3_client = container.s3_client() | ||||||
|  | @ -62,11 +62,11 @@ def main(): | ||||||
|     assert service1.s3_client is s3_client |     assert service1.s3_client is s3_client | ||||||
|     assert service1.sqs_client is sqs_client |     assert service1.sqs_client is sqs_client | ||||||
| 
 | 
 | ||||||
|     service2 = container.service1() |     service2 = container.service2() | ||||||
|     print(service2, service2.s3_client, service2.sqs_client) |     print(service2, service2.s3_client, service2.sqs_client) | ||||||
|     assert service2.s3_client is s3_client |     assert service2.s3_client.__class__.__name__ == "S3" | ||||||
|     assert service2.sqs_client is sqs_client |     assert service2.sqs_client.__class__.__name__ == "SQS" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     main() |     main() | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ from .containers import Container | ||||||
| from .commands import SaveRating, DoSomethingElse | from .commands import SaveRating, DoSomethingElse | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
|     message_bus = container.message_bus() |     message_bus = container.message_bus() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ class CommandHandler: | ||||||
|         self.rating_repo = rating_repo |         self.rating_repo = rating_repo | ||||||
| 
 | 
 | ||||||
|     def save_rating(self): |     def save_rating(self): | ||||||
|         print('Saving rating') |         print("Saving rating") | ||||||
| 
 | 
 | ||||||
|     def something_else(self): |     def something_else(self): | ||||||
|         print('Doing something else') |         print("Doing something else") | ||||||
|  |  | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| """Main module.""" | """Main module.""" | ||||||
| 
 | 
 | ||||||
| import sys | from dependency_injector.wiring import Provide, inject | ||||||
| 
 |  | ||||||
| from dependency_injector.wiring import inject, Provide |  | ||||||
| 
 | 
 | ||||||
| from .user.repositories import UserRepository | from .user.repositories import UserRepository | ||||||
| from .photo.repositories import PhotoRepository | from .photo.repositories import PhotoRepository | ||||||
|  | @ -24,20 +22,19 @@ def main( | ||||||
| ) -> None: | ) -> None: | ||||||
|     user1 = user_repository.get(id=1) |     user1 = user_repository.get(id=1) | ||||||
|     user1_photos = photo_repository.get_photos(user1.id) |     user1_photos = photo_repository.get_photos(user1.id) | ||||||
|     print(f'Retrieve user id={user1.id}, photos count={len(user1_photos)}') |     print(f"Retrieve user id={user1.id}, photos count={len(user1_photos)}") | ||||||
| 
 | 
 | ||||||
|     user2 = user_repository.get(id=2) |     user2 = user_repository.get(id=2) | ||||||
|     user2_photos = photo_repository.get_photos(user2.id) |     user2_photos = photo_repository.get_photos(user2.id) | ||||||
|     print(f'Retrieve user id={user2.id}, photos count={len(user2_photos)}') |     print(f"Retrieve user id={user2.id}, photos count={len(user2_photos)}") | ||||||
| 
 | 
 | ||||||
|     assert aggregation_service.user_repository is user_repository |     assert aggregation_service.user_repository is user_repository | ||||||
|     assert aggregation_service.photo_repository is photo_repository |     assert aggregation_service.photo_repository is photo_repository | ||||||
|     print('Aggregate analytics from user and photo packages') |     print("Aggregate analytics from user and photo packages") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     application = ApplicationContainer() |     application = ApplicationContainer() | ||||||
|     application.config.from_ini('config.ini') |     application.wire(modules=[__name__]) | ||||||
|     application.wire(modules=[sys.modules[__name__]]) |  | ||||||
| 
 | 
 | ||||||
|     main() |     main() | ||||||
|  |  | ||||||
|  | @ -12,13 +12,13 @@ from .analytics.containers import AnalyticsContainer | ||||||
| 
 | 
 | ||||||
| class ApplicationContainer(containers.DeclarativeContainer): | class ApplicationContainer(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     config = providers.Configuration() |     config = providers.Configuration(ini_files=["config.ini"]) | ||||||
| 
 | 
 | ||||||
|     sqlite = providers.Singleton(sqlite3.connect, config.database.dsn) |     sqlite = providers.Singleton(sqlite3.connect, config.database.dsn) | ||||||
| 
 | 
 | ||||||
|     s3 = providers.Singleton( |     s3 = providers.Singleton( | ||||||
|         boto3.client, |         boto3.client, | ||||||
|         service_name='s3', |         service_name="s3", | ||||||
|         aws_access_key_id=config.aws.access_key_id, |         aws_access_key_id=config.aws.access_key_id, | ||||||
|         aws_secret_access_key=config.aws.secret_access_key, |         aws_secret_access_key=config.aws.secret_access_key, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -105,9 +105,9 @@ The output should be something like: | ||||||
|    githubnavigator/wsgi.py             4      4     0% |    githubnavigator/wsgi.py             4      4     0% | ||||||
|    manage.py                          12      2    83% |    manage.py                          12      2    83% | ||||||
|    web/__init__.py                     0      0   100% |    web/__init__.py                     0      0   100% | ||||||
|    web/apps.py                         7      0   100% |    web/apps.py                         6      0   100% | ||||||
|    web/tests.py                       28      0   100% |    web/tests.py                       28      0   100% | ||||||
|    web/urls.py                         3      0   100% |    web/urls.py                         3      0   100% | ||||||
|    web/views.py                       12      0   100% |    web/views.py                       12      0   100% | ||||||
|    --------------------------------------------------- |    --------------------------------------------------- | ||||||
|    TOTAL                             121     10    92% |    TOTAL                             120     10    92% | ||||||
|  |  | ||||||
|  | @ -10,6 +10,6 @@ import os | ||||||
| 
 | 
 | ||||||
| from django.core.asgi import get_asgi_application | from django.core.asgi import get_asgi_application | ||||||
| 
 | 
 | ||||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'githubnavigator.settings') | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "githubnavigator.settings") | ||||||
| 
 | 
 | ||||||
| application = get_asgi_application() | application = get_asgi_application() | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ class SearchService: | ||||||
|         """Search for repositories and return formatted data.""" |         """Search for repositories and return formatted data.""" | ||||||
|         repositories = self._github_client.search_repositories( |         repositories = self._github_client.search_repositories( | ||||||
|             query=query, |             query=query, | ||||||
|             **{'in': 'name'}, |             **{"in": "name"}, | ||||||
|         ) |         ) | ||||||
|         return [ |         return [ | ||||||
|             self._format_repo(repository) |             self._format_repo(repository) | ||||||
|  | @ -25,20 +25,20 @@ class SearchService: | ||||||
|     def _format_repo(self, repository: Repository): |     def _format_repo(self, repository: Repository): | ||||||
|         commits = repository.get_commits() |         commits = repository.get_commits() | ||||||
|         return { |         return { | ||||||
|             'url': repository.html_url, |             "url": repository.html_url, | ||||||
|             'name': repository.name, |             "name": repository.name, | ||||||
|             'owner': { |             "owner": { | ||||||
|                 'login': repository.owner.login, |                 "login": repository.owner.login, | ||||||
|                 'url': repository.owner.html_url, |                 "url": repository.owner.html_url, | ||||||
|                 'avatar_url': repository.owner.avatar_url, |                 "avatar_url": repository.owner.avatar_url, | ||||||
|             }, |             }, | ||||||
|             'latest_commit': self._format_commit(commits[0]) if commits else {}, |             "latest_commit": self._format_commit(commits[0]) if commits else {}, | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     def _format_commit(self, commit: Commit): |     def _format_commit(self, commit: Commit): | ||||||
|         return { |         return { | ||||||
|             'sha': commit.sha, |             "sha": commit.sha, | ||||||
|             'url': commit.html_url, |             "url": commit.html_url, | ||||||
|             'message': commit.commit.message, |             "message": commit.commit.message, | ||||||
|             'author_name': commit.commit.author.name, |             "author_name": commit.commit.author.name, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||||||
| # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ | ||||||
| 
 | 
 | ||||||
| # SECURITY WARNING: keep the secret key used in production secret! | # SECURITY WARNING: keep the secret key used in production secret! | ||||||
| SECRET_KEY = ')6*iyg26c9l!fvyvwd&3+vyf-dcw)e=5x2t(j)(*c29z@ykhi0' | SECRET_KEY = ")6*iyg26c9l!fvyvwd&3+vyf-dcw)e=5x2t(j)(*c29z@ykhi0" | ||||||
| 
 | 
 | ||||||
| # SECURITY WARNING: don't run with debug turned on in production! | # SECURITY WARNING: don't run with debug turned on in production! | ||||||
| DEBUG = True | DEBUG = True | ||||||
|  | @ -31,54 +31,54 @@ ALLOWED_HOSTS = [] | ||||||
| # Application definition | # Application definition | ||||||
| 
 | 
 | ||||||
| INSTALLED_APPS = [ | INSTALLED_APPS = [ | ||||||
|     'web.apps.WebConfig', |     "web.apps.WebConfig", | ||||||
|     'bootstrap4', |     "bootstrap4", | ||||||
|     'django.contrib.admin', |     "django.contrib.admin", | ||||||
|     'django.contrib.auth', |     "django.contrib.auth", | ||||||
|     'django.contrib.contenttypes', |     "django.contrib.contenttypes", | ||||||
|     'django.contrib.sessions', |     "django.contrib.sessions", | ||||||
|     'django.contrib.messages', |     "django.contrib.messages", | ||||||
|     'django.contrib.staticfiles', |     "django.contrib.staticfiles", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| MIDDLEWARE = [ | MIDDLEWARE = [ | ||||||
|     'django.middleware.security.SecurityMiddleware', |     "django.middleware.security.SecurityMiddleware", | ||||||
|     'django.contrib.sessions.middleware.SessionMiddleware', |     "django.contrib.sessions.middleware.SessionMiddleware", | ||||||
|     'django.middleware.common.CommonMiddleware', |     "django.middleware.common.CommonMiddleware", | ||||||
|     'django.middleware.csrf.CsrfViewMiddleware', |     "django.middleware.csrf.CsrfViewMiddleware", | ||||||
|     'django.contrib.auth.middleware.AuthenticationMiddleware', |     "django.contrib.auth.middleware.AuthenticationMiddleware", | ||||||
|     'django.contrib.messages.middleware.MessageMiddleware', |     "django.contrib.messages.middleware.MessageMiddleware", | ||||||
|     'django.middleware.clickjacking.XFrameOptionsMiddleware', |     "django.middleware.clickjacking.XFrameOptionsMiddleware", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| ROOT_URLCONF = 'githubnavigator.urls' | ROOT_URLCONF = "githubnavigator.urls" | ||||||
| 
 | 
 | ||||||
| TEMPLATES = [ | TEMPLATES = [ | ||||||
|     { |     { | ||||||
|         'BACKEND': 'django.template.backends.django.DjangoTemplates', |         "BACKEND": "django.template.backends.django.DjangoTemplates", | ||||||
|         'DIRS': [], |         "DIRS": [], | ||||||
|         'APP_DIRS': True, |         "APP_DIRS": True, | ||||||
|         'OPTIONS': { |         "OPTIONS": { | ||||||
|             'context_processors': [ |             "context_processors": [ | ||||||
|                 'django.template.context_processors.debug', |                 "django.template.context_processors.debug", | ||||||
|                 'django.template.context_processors.request', |                 "django.template.context_processors.request", | ||||||
|                 'django.contrib.auth.context_processors.auth', |                 "django.contrib.auth.context_processors.auth", | ||||||
|                 'django.contrib.messages.context_processors.messages', |                 "django.contrib.messages.context_processors.messages", | ||||||
|             ], |             ], | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| WSGI_APPLICATION = 'githubnavigator.wsgi.application' | WSGI_APPLICATION = "githubnavigator.wsgi.application" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Database | # Database | ||||||
| # https://docs.djangoproject.com/en/3.0/ref/settings/#databases | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases | ||||||
| 
 | 
 | ||||||
| DATABASES = { | DATABASES = { | ||||||
|     'default': { |     "default": { | ||||||
|         'ENGINE': 'django.db.backends.sqlite3', |         "ENGINE": "django.db.backends.sqlite3", | ||||||
|         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), |         "NAME": os.path.join(BASE_DIR, "db.sqlite3"), | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -88,16 +88,16 @@ DATABASES = { | ||||||
| 
 | 
 | ||||||
| AUTH_PASSWORD_VALIDATORS = [ | AUTH_PASSWORD_VALIDATORS = [ | ||||||
|     { |     { | ||||||
|         'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', |         "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', |         "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', |         "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', |         "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", | ||||||
|     }, |     }, | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | @ -105,9 +105,9 @@ AUTH_PASSWORD_VALIDATORS = [ | ||||||
| # Internationalization | # Internationalization | ||||||
| # https://docs.djangoproject.com/en/3.0/topics/i18n/ | # https://docs.djangoproject.com/en/3.0/topics/i18n/ | ||||||
| 
 | 
 | ||||||
| LANGUAGE_CODE = 'en-us' | LANGUAGE_CODE = "en-us" | ||||||
| 
 | 
 | ||||||
| TIME_ZONE = 'UTC' | TIME_ZONE = "UTC" | ||||||
| 
 | 
 | ||||||
| USE_I18N = True | USE_I18N = True | ||||||
| 
 | 
 | ||||||
|  | @ -119,13 +119,13 @@ USE_TZ = True | ||||||
| # Static files (CSS, JavaScript, Images) | # Static files (CSS, JavaScript, Images) | ||||||
| # https://docs.djangoproject.com/en/3.0/howto/static-files/ | # https://docs.djangoproject.com/en/3.0/howto/static-files/ | ||||||
| 
 | 
 | ||||||
| STATIC_URL = '/static/' | STATIC_URL = "/static/" | ||||||
| 
 | 
 | ||||||
| # Github client settings | # Github client settings | ||||||
| GITHUB_TOKEN = os.getenv('GITHUB_TOKEN') | GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") | ||||||
| GITHUB_REQUEST_TIMEOUT = 10 | GITHUB_REQUEST_TIMEOUT = 10 | ||||||
| 
 | 
 | ||||||
| # Search settings | # Search settings | ||||||
| DEFAULT_LIMIT = 5 | DEFAULT_LIMIT = 5 | ||||||
| DEFAULT_QUERY = 'Dependency Injector' | DEFAULT_QUERY = "Dependency Injector" | ||||||
| LIMIT_OPTIONS = [5, 10, 20] | LIMIT_OPTIONS = [5, 10, 20] | ||||||
|  |  | ||||||
|  | @ -17,6 +17,6 @@ from django.contrib import admin | ||||||
| from django.urls import path, include | from django.urls import path, include | ||||||
| 
 | 
 | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
|     path('', include('web.urls')), |     path("", include("web.urls")), | ||||||
|     path('admin/', admin.site.urls), |     path("admin/", admin.site.urls), | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -11,6 +11,6 @@ import os | ||||||
| 
 | 
 | ||||||
| from django.core.wsgi import get_wsgi_application | from django.core.wsgi import get_wsgi_application | ||||||
| 
 | 
 | ||||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'githubnavigator.settings') | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "githubnavigator.settings") | ||||||
| 
 | 
 | ||||||
| application = get_wsgi_application() | application = get_wsgi_application() | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import sys | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def main(): | def main(): | ||||||
|     os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'githubnavigator.settings') |     os.environ.setdefault("DJANGO_SETTINGS_MODULE", "githubnavigator.settings") | ||||||
|     try: |     try: | ||||||
|         from django.core.management import execute_from_command_line |         from django.core.management import execute_from_command_line | ||||||
|     except ImportError as exc: |     except ImportError as exc: | ||||||
|  | @ -17,5 +17,5 @@ def main(): | ||||||
|     execute_from_command_line(sys.argv) |     execute_from_command_line(sys.argv) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     main() |     main() | ||||||
|  |  | ||||||
|  | @ -3,11 +3,10 @@ | ||||||
| from django.apps import AppConfig | from django.apps import AppConfig | ||||||
| 
 | 
 | ||||||
| from githubnavigator import container | from githubnavigator import container | ||||||
| from . import views |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class WebConfig(AppConfig): | class WebConfig(AppConfig): | ||||||
|     name = 'web' |     name = "web" | ||||||
| 
 | 
 | ||||||
|     def ready(self): |     def ready(self): | ||||||
|         container.wire(modules=[views]) |         container.wire(modules=[".views"]) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| {% extends 'bootstrap4/bootstrap4.html' %} | {% extends "bootstrap4/bootstrap4.html" %} | ||||||
| 
 | 
 | ||||||
| {% load bootstrap4 %} | {% load bootstrap4 %} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,49 +15,49 @@ class IndexTests(TestCase): | ||||||
|         github_client_mock = mock.Mock(spec=Github) |         github_client_mock = mock.Mock(spec=Github) | ||||||
|         github_client_mock.search_repositories.return_value = [ |         github_client_mock.search_repositories.return_value = [ | ||||||
|             mock.Mock( |             mock.Mock( | ||||||
|                 html_url='repo1-url', |                 html_url="repo1-url", | ||||||
|                 name='repo1-name', |                 name="repo1-name", | ||||||
|                 owner=mock.Mock( |                 owner=mock.Mock( | ||||||
|                     login='owner1-login', |                     login="owner1-login", | ||||||
|                     html_url='owner1-url', |                     html_url="owner1-url", | ||||||
|                     avatar_url='owner1-avatar-url', |                     avatar_url="owner1-avatar-url", | ||||||
|                 ), |                 ), | ||||||
|                 get_commits=mock.Mock(return_value=[mock.Mock()]), |                 get_commits=mock.Mock(return_value=[mock.Mock()]), | ||||||
|             ), |             ), | ||||||
|             mock.Mock( |             mock.Mock( | ||||||
|                 html_url='repo2-url', |                 html_url="repo2-url", | ||||||
|                 name='repo2-name', |                 name="repo2-name", | ||||||
|                 owner=mock.Mock( |                 owner=mock.Mock( | ||||||
|                     login='owner2-login', |                     login="owner2-login", | ||||||
|                     html_url='owner2-url', |                     html_url="owner2-url", | ||||||
|                     avatar_url='owner2-avatar-url', |                     avatar_url="owner2-avatar-url", | ||||||
|                 ), |                 ), | ||||||
|                 get_commits=mock.Mock(return_value=[mock.Mock()]), |                 get_commits=mock.Mock(return_value=[mock.Mock()]), | ||||||
|             ), |             ), | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
|         with container.github_client.override(github_client_mock): |         with container.github_client.override(github_client_mock): | ||||||
|             response = self.client.get(reverse('index')) |             response = self.client.get(reverse("index")) | ||||||
| 
 | 
 | ||||||
|         self.assertContains(response, 'Results found: 2') |         self.assertContains(response, "Results found: 2") | ||||||
| 
 | 
 | ||||||
|         self.assertContains(response, 'repo1-url') |         self.assertContains(response, "repo1-url") | ||||||
|         self.assertContains(response, 'repo1-name') |         self.assertContains(response, "repo1-name") | ||||||
|         self.assertContains(response, 'owner1-login') |         self.assertContains(response, "owner1-login") | ||||||
|         self.assertContains(response, 'owner1-url') |         self.assertContains(response, "owner1-url") | ||||||
|         self.assertContains(response, 'owner1-avatar-url') |         self.assertContains(response, "owner1-avatar-url") | ||||||
| 
 | 
 | ||||||
|         self.assertContains(response, 'repo2-url') |         self.assertContains(response, "repo2-url") | ||||||
|         self.assertContains(response, 'repo2-name') |         self.assertContains(response, "repo2-name") | ||||||
|         self.assertContains(response, 'owner2-login') |         self.assertContains(response, "owner2-login") | ||||||
|         self.assertContains(response, 'owner2-url') |         self.assertContains(response, "owner2-url") | ||||||
|         self.assertContains(response, 'owner2-avatar-url') |         self.assertContains(response, "owner2-avatar-url") | ||||||
| 
 | 
 | ||||||
|     def test_index_no_results(self): |     def test_index_no_results(self): | ||||||
|         github_client_mock = mock.Mock(spec=Github) |         github_client_mock = mock.Mock(spec=Github) | ||||||
|         github_client_mock.search_repositories.return_value = [] |         github_client_mock.search_repositories.return_value = [] | ||||||
| 
 | 
 | ||||||
|         with container.github_client.override(github_client_mock): |         with container.github_client.override(github_client_mock): | ||||||
|             response = self.client.get(reverse('index')) |             response = self.client.get(reverse("index")) | ||||||
| 
 | 
 | ||||||
|         self.assertContains(response, 'Results found: 0') |         self.assertContains(response, "Results found: 0") | ||||||
|  |  | ||||||
|  | @ -5,5 +5,5 @@ from django.urls import path | ||||||
| from . import views | from . import views | ||||||
| 
 | 
 | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
|     path('', views.index, name='index'), |     path("", views.index, name="index"), | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -18,18 +18,18 @@ def index( | ||||||
|         default_limit: int = Provide[Container.config.DEFAULT_LIMIT.as_int()], |         default_limit: int = Provide[Container.config.DEFAULT_LIMIT.as_int()], | ||||||
|         limit_options: List[int] = Provide[Container.config.LIMIT_OPTIONS], |         limit_options: List[int] = Provide[Container.config.LIMIT_OPTIONS], | ||||||
| ) -> HttpResponse: | ) -> HttpResponse: | ||||||
|     query = request.GET.get('query', default_query) |     query = request.GET.get("query", default_query) | ||||||
|     limit = int(request.GET.get('limit', default_limit)) |     limit = int(request.GET.get("limit", default_limit)) | ||||||
| 
 | 
 | ||||||
|     repositories = search_service.search_repositories(query, limit) |     repositories = search_service.search_repositories(query, limit) | ||||||
| 
 | 
 | ||||||
|     return render( |     return render( | ||||||
|         request, |         request, | ||||||
|         template_name='index.html', |         template_name="index.html", | ||||||
|         context={ |         context={ | ||||||
|             'query': query, |             "query": query, | ||||||
|             'limit': limit, |             "limit": limit, | ||||||
|             'limit_options': limit_options, |             "limit_options": limit_options, | ||||||
|             'repositories': repositories, |             "repositories": repositories, | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ class Container(containers.DeclarativeContainer): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     token_service = container.token_service() |     token_service = container.token_service() | ||||||
|  |  | ||||||
|  | @ -58,7 +58,7 @@ class Container(containers.DeclarativeContainer): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == "__main__": | ||||||
|     container = Container() |     container = Container() | ||||||
| 
 | 
 | ||||||
|     token_service = container.token_service() |     token_service = container.token_service() | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| FROM python:3.8-buster | FROM python:3.9-buster | ||||||
| 
 | 
 | ||||||
| ENV PYTHONUNBUFFERED=1 | ENV PYTHONUNBUFFERED=1 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -69,14 +69,14 @@ The output should be something like: | ||||||
| 
 | 
 | ||||||
| .. code-block:: | .. code-block:: | ||||||
| 
 | 
 | ||||||
|    platform linux -- Python 3.8.6, pytest-6.2.1, py-1.10.0, pluggy-0.13.1 |    platform linux -- Python 3.9, pytest-6.2.1, py-1.10.0, pluggy-0.13.1 | ||||||
|    rootdir: /code |    rootdir: /code | ||||||
|    plugins: cov-2.10.1, asyncio-0.14.0 |    plugins: cov-2.10.1, asyncio-0.14.0 | ||||||
|    collected 1 item |    collected 1 item | ||||||
| 
 | 
 | ||||||
|    fastapiredis/tests.py .                                         [100%] |    fastapiredis/tests.py .                                         [100%] | ||||||
| 
 | 
 | ||||||
|    ----------- coverage: platform linux, python 3.8.6-final-0 ----------- |    ----------- coverage: platform linux, python 3.9 ----------- | ||||||
|    Name                          Stmts   Miss  Cover |    Name                          Stmts   Miss  Cover | ||||||
|    ------------------------------------------------- |    ------------------------------------------------- | ||||||
|    fastapiredis/__init__.py          0      0   100% |    fastapiredis/__init__.py          0      0   100% | ||||||
|  |  | ||||||
|  | @ -1,9 +1,7 @@ | ||||||
| """Application module.""" | """Application module.""" | ||||||
| 
 | 
 | ||||||
| import sys |  | ||||||
| 
 |  | ||||||
| from fastapi import FastAPI, Depends |  | ||||||
| from dependency_injector.wiring import inject, Provide | from dependency_injector.wiring import inject, Provide | ||||||
|  | from fastapi import FastAPI, Depends | ||||||
| 
 | 
 | ||||||
| from .containers import Container | from .containers import Container | ||||||
| from .services import Service | from .services import Service | ||||||
|  | @ -12,14 +10,14 @@ from .services import Service | ||||||
| app = FastAPI() | app = FastAPI() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.api_route('/') | @app.api_route("/") | ||||||
| @inject | @inject | ||||||
| async def index(service: Service = Depends(Provide[Container.service])): | async def index(service: Service = Depends(Provide[Container.service])): | ||||||
|     value = await service.process() |     value = await service.process() | ||||||
|     return {'result': value} |     return {"result": value} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| container = Container() | container = Container() | ||||||
| container.config.redis_host.from_env('REDIS_HOST', 'localhost') | container.config.redis_host.from_env("REDIS_HOST", "localhost") | ||||||
| container.config.redis_password.from_env('REDIS_PASSWORD', 'password') | container.config.redis_password.from_env("REDIS_PASSWORD", "password") | ||||||
| container.wire(modules=[sys.modules[__name__]]) | container.wire(modules=[__name__]) | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ from aioredis import create_redis_pool, Redis | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]: | async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]: | ||||||
|     pool = await create_redis_pool(f'redis://{host}', password=password) |     pool = await create_redis_pool(f"redis://{host}", password=password) | ||||||
|     yield pool |     yield pool | ||||||
|     pool.close() |     pool.close() | ||||||
|     await pool.wait_closed() |     await pool.wait_closed() | ||||||
|  |  | ||||||
|  | @ -8,5 +8,5 @@ class Service: | ||||||
|         self._redis = redis |         self._redis = redis | ||||||
| 
 | 
 | ||||||
|     async def process(self) -> str: |     async def process(self) -> str: | ||||||
|         await self._redis.set('my-key', 'value') |         await self._redis.set("my-key", "value") | ||||||
|         return await self._redis.get('my-key', encoding='utf-8') |         return await self._redis.get("my-key", encoding="utf-8") | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ from .services import Service | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def client(event_loop): | def client(event_loop): | ||||||
|     client = AsyncClient(app=app, base_url='http://test') |     client = AsyncClient(app=app, base_url="http://test") | ||||||
|     yield client |     yield client | ||||||
|     event_loop.run_until_complete(client.aclose()) |     event_loop.run_until_complete(client.aclose()) | ||||||
| 
 | 
 | ||||||
|  | @ -19,10 +19,10 @@ def client(event_loop): | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| async def test_index(client): | async def test_index(client): | ||||||
|     service_mock = mock.AsyncMock(spec=Service) |     service_mock = mock.AsyncMock(spec=Service) | ||||||
|     service_mock.process.return_value = 'Foo' |     service_mock.process.return_value = "Foo" | ||||||
| 
 | 
 | ||||||
|     with container.service.override(service_mock): |     with container.service.override(service_mock): | ||||||
|         response = await client.get('/') |         response = await client.get("/") | ||||||
| 
 | 
 | ||||||
|     assert response.status_code == 200 |     assert response.status_code == 200 | ||||||
|     assert response.json() == {'result': 'Foo'} |     assert response.json() == {"result": "Foo"} | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| dependency-injector | dependency-injector | ||||||
| fastapi | fastapi | ||||||
| uvicorn | uvicorn | ||||||
| aioredis | aioredis<2  # TODO: Update example to work with aioredis >= 2.0 | ||||||
| 
 | 
 | ||||||
| # For testing: | # For testing: | ||||||
| pytest | pytest | ||||||
|  |  | ||||||
|  | @ -1,13 +1,11 @@ | ||||||
| import sys |  | ||||||
| 
 |  | ||||||
| from fastapi import FastAPI, Depends | from fastapi import FastAPI, Depends | ||||||
| from dependency_injector import containers, providers | from dependency_injector import containers, providers | ||||||
| from dependency_injector.wiring import inject, Provide | from dependency_injector.wiring import Provide, inject | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Service: | class Service: | ||||||
|     async def process(self) -> str: |     async def process(self) -> str: | ||||||
|         return 'Ok' |         return "OK" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
|  | @ -18,12 +16,12 @@ class Container(containers.DeclarativeContainer): | ||||||
| app = FastAPI() | app = FastAPI() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.api_route('/') | @app.api_route("/") | ||||||
| @inject | @inject | ||||||
| async def index(service: Service = Depends(Provide[Container.service])): | async def index(service: Service = Depends(Provide[Container.service])): | ||||||
|     result = await service.process() |     result = await service.process() | ||||||
|     return {'result': result} |     return {"result": result} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| container = Container() | container = Container() | ||||||
| container.wire(modules=[sys.modules[__name__]]) | container.wire(modules=[__name__]) | ||||||
|  |  | ||||||
|  | @ -7,19 +7,18 @@ from fastapi_di_example import app, container, Service | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def client(event_loop): | async def client(event_loop): | ||||||
|     client = AsyncClient(app=app, base_url='http://test') |     async with AsyncClient(app=app, base_url="http://test") as client: | ||||||
|         yield client |         yield client | ||||||
|     event_loop.run_until_complete(client.aclose()) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| async def test_index(client): | async def test_index(client): | ||||||
|     service_mock = mock.AsyncMock(spec=Service) |     service_mock = mock.AsyncMock(spec=Service) | ||||||
|     service_mock.process.return_value = 'Foo' |     service_mock.process.return_value = "Foo" | ||||||
| 
 | 
 | ||||||
|     with container.service.override(service_mock): |     with container.service.override(service_mock): | ||||||
|         response = await client.get('/') |         response = await client.get("/") | ||||||
| 
 | 
 | ||||||
|     assert response.status_code == 200 |     assert response.status_code == 200 | ||||||
|     assert response.json() == {'result': 'Foo'} |     assert response.json() == {"result": "Foo"} | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| FROM python:3.9-buster | FROM python:3.10-buster | ||||||
| 
 | 
 | ||||||
| ENV PYTHONUNBUFFERED=1 | ENV PYTHONUNBUFFERED=1 | ||||||
| ENV HOST=0.0.0.0 | ENV HOST=0.0.0.0 | ||||||
|  |  | ||||||
|  | @ -73,19 +73,19 @@ The output should be something like: | ||||||
| 
 | 
 | ||||||
| .. code-block:: | .. code-block:: | ||||||
| 
 | 
 | ||||||
|    platform linux -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 |    platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 | ||||||
|    rootdir: /code |    rootdir: /code | ||||||
|    plugins: cov-2.11.1 |    plugins: cov-3.0.0 | ||||||
|    collected 7 items |    collected 7 items | ||||||
| 
 | 
 | ||||||
|    webapp/tests.py .......                                         [100%] |    webapp/tests.py .......                                         [100%] | ||||||
| 
 | 
 | ||||||
|    ----------- coverage: platform linux, python 3.9.1-final-0 ----------- |    ---------- coverage: platform linux, python 3.10.0-final-0 ---------- | ||||||
|    Name                     Stmts   Miss  Cover |    Name                     Stmts   Miss  Cover | ||||||
|    -------------------------------------------- |    -------------------------------------------- | ||||||
|    webapp/__init__.py           0      0   100% |    webapp/__init__.py           0      0   100% | ||||||
|    webapp/application.py       14      0   100% |    webapp/application.py       12      0   100% | ||||||
|    webapp/containers.py         9      0   100% |    webapp/containers.py        10      0   100% | ||||||
|    webapp/database.py          24      8    67% |    webapp/database.py          24      8    67% | ||||||
|    webapp/endpoints.py         32      0   100% |    webapp/endpoints.py         32      0   100% | ||||||
|    webapp/models.py            10      1    90% |    webapp/models.py            10      1    90% | ||||||
|  | @ -93,4 +93,4 @@ The output should be something like: | ||||||
|    webapp/services.py          16      0   100% |    webapp/services.py          16      0   100% | ||||||
|    webapp/tests.py             59      0   100% |    webapp/tests.py             59      0   100% | ||||||
|    -------------------------------------------- |    -------------------------------------------- | ||||||
|    TOTAL                      200     29    86% |    TOTAL                      199     29    85% | ||||||
|  |  | ||||||
|  | @ -8,8 +8,6 @@ from . import endpoints | ||||||
| 
 | 
 | ||||||
| def create_app() -> FastAPI: | def create_app() -> FastAPI: | ||||||
|     container = Container() |     container = Container() | ||||||
|     container.config.from_yaml('config.yml') |  | ||||||
|     container.wire(modules=[endpoints]) |  | ||||||
| 
 | 
 | ||||||
|     db = container.db() |     db = container.db() | ||||||
|     db.create_database() |     db.create_database() | ||||||
|  |  | ||||||
|  | @ -9,7 +9,9 @@ from .services import UserService | ||||||
| 
 | 
 | ||||||
| class Container(containers.DeclarativeContainer): | class Container(containers.DeclarativeContainer): | ||||||
| 
 | 
 | ||||||
|     config = providers.Configuration() |     wiring_config = containers.WiringConfiguration(modules=[".endpoints"]) | ||||||
|  | 
 | ||||||
|  |     config = providers.Configuration(yaml_files=["config.yml"]) | ||||||
| 
 | 
 | ||||||
|     db = providers.Singleton(Database, db_url=config.db.url) |     db = providers.Singleton(Database, db_url=config.db.url) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ class Database: | ||||||
|         try: |         try: | ||||||
|             yield session |             yield session | ||||||
|         except Exception: |         except Exception: | ||||||
|             logger.exception('Session rollback because of exception') |             logger.exception("Session rollback because of exception") | ||||||
|             session.rollback() |             session.rollback() | ||||||
|             raise |             raise | ||||||
|         finally: |         finally: | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ from .repositories import NotFoundError | ||||||
| router = APIRouter() | router = APIRouter() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @router.get('/users') | @router.get("/users") | ||||||
| @inject | @inject | ||||||
| def get_list( | def get_list( | ||||||
|         user_service: UserService = Depends(Provide[Container.user_service]), |         user_service: UserService = Depends(Provide[Container.user_service]), | ||||||
|  | @ -18,7 +18,7 @@ def get_list( | ||||||
|     return user_service.get_users() |     return user_service.get_users() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @router.get('/users/{user_id}') | @router.get("/users/{user_id}") | ||||||
| @inject | @inject | ||||||
| def get_by_id( | def get_by_id( | ||||||
|         user_id: int, |         user_id: int, | ||||||
|  | @ -30,7 +30,7 @@ def get_by_id( | ||||||
|         return Response(status_code=status.HTTP_404_NOT_FOUND) |         return Response(status_code=status.HTTP_404_NOT_FOUND) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @router.post('/users', status_code=status.HTTP_201_CREATED) | @router.post("/users", status_code=status.HTTP_201_CREATED) | ||||||
| @inject | @inject | ||||||
| def add( | def add( | ||||||
|         user_service: UserService = Depends(Provide[Container.user_service]), |         user_service: UserService = Depends(Provide[Container.user_service]), | ||||||
|  | @ -38,7 +38,7 @@ def add( | ||||||
|     return user_service.create_user() |     return user_service.create_user() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @router.delete('/users/{user_id}', status_code=status.HTTP_204_NO_CONTENT) | @router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) | ||||||
| @inject | @inject | ||||||
| def remove( | def remove( | ||||||
|         user_id: int, |         user_id: int, | ||||||
|  | @ -52,6 +52,6 @@ def remove( | ||||||
|         return Response(status_code=status.HTTP_204_NO_CONTENT) |         return Response(status_code=status.HTTP_204_NO_CONTENT) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @router.get('/status') | @router.get("/status") | ||||||
| def get_status(): | def get_status(): | ||||||
|     return {'status': 'OK'} |     return {"status": "OK"} | ||||||
|  |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user