Asyncio daemon tutorial (#278)

* Fix a typo in the docblock of the Configuration provider

* Update the changelog

* Add tutorial sections

* Switch to use https for httpbin.org requests

* Add what we are going to build section

* Fix ``Makefile`` to run ``aiohttp`` integration tests on Python 3.5+

* Add prerequisities and prepare the env sections

* Add logging, config and the dispacher sections

* Change logging

* Fix multiple typos in the ``flask`` and ``aiohttp`` tutorials

* Add the initial and dirty version

* Fix multiple typos in the ``flask`` and ``aiohttp`` tutorials

* Fix the 3.27.0 changelog

* Finish all the parts before the dispatcher

* Finish dispatcher section

* Update http monitor logging format

* Finish the tutorial

* Fix docblock in the dispatcher module

* Update changelog

* Add meta keywords and description
This commit is contained in:
Roman Mogylatov 2020-08-08 14:48:05 -04:00 committed by GitHub
parent 2ff36b44ab
commit 3a61457be7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 3073 additions and 1954 deletions

View File

@ -55,7 +55,7 @@ test-py2: build
test-py3: build test-py3: build
# Unit tests with coverage report # Unit tests with coverage report
coverage erase coverage erase
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*py3.py coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*py3*.py
coverage report --rcfile=./.coveragerc coverage report --rcfile=./.coveragerc
coverage html --rcfile=./.coveragerc coverage html --rcfile=./.coveragerc

View File

@ -7,7 +7,8 @@ Dependency Injector --- Dependency injection framework for Python
:keywords: Python,Dependency injection,DI,Inversion of Control,IoC, :keywords: Python,Dependency injection,DI,Inversion of Control,IoC,
IoC Container,Factory, Singleton, Design Patterns IoC Container,Factory, Singleton, Design Patterns
:description: Dependency Injector is a dependency injection framework :description: Dependency Injector is a dependency injection framework
for Python. It was designed to be unified, developer-friendly for Python. It helps to maintain you application structure.
It was designed to be unified, developer-friendly
tool that helps to implement dependency injection design tool that helps to implement dependency injection design
pattern in formal, pretty, Pythonic way. Dependency Injector pattern in formal, pretty, Pythonic way. Dependency Injector
provides implementations of such popular design patterns provides implementations of such popular design patterns

View File

@ -131,8 +131,10 @@ What's next?
Choose one of the following as a next step: Choose one of the following as a next step:
+ Pass the dependency injection :ref:`flask-tutorial`. + Pass one of the dependency injection tutorials:
+ Look at the other dependency injection :ref:`tutorials`. + :ref:`flask-tutorial`
+ :ref:`aiohttp-tutorial`
+ :ref:`asyncio-daemon-tutorial`
+ Know more about the :ref:`providers`. + Know more about the :ref:`providers`.
+ Go to the :ref:`contents`. + Go to the :ref:`contents`.

View File

@ -7,9 +7,18 @@ 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`_
Development version
-------------------
- Add ``asyncio`` + ``Dependency Injector`` example ``monitoring-daemon-asyncio``.
- Add ``asyncio`` + ``Dependency Injector`` monitoring daemon tutorial.
- Fix a typo in the docblock of the ``Configuration`` provider.
- Fix multiple typos in the ``flask`` and ``aiohttp`` tutorials.
- Fix ``Makefile`` to run ``aiohttp`` integration tests on Python 3.5+.
3.27.0 3.27.0
------ ------
- Add deep init injections overriding for ``Factory`` provider. - Add deep init injections overriding for ``Factory`` provider.
- Add ``asyncio`` monitoring daemon example.
3.26.0 3.26.0
------ ------

View File

@ -1,9 +1,17 @@
.. _aiohttp-tutorial:
Aiohttp tutorial Aiohttp tutorial
================ ================
.. _aiohttp-tutorial: .. meta::
:keywords: Python,Aiohttp,Tutorial,Education,Web,API,REST API,Example,DI,Dependency injection,
IoC,Inversion of control,Refactoring,Tests,Unit tests,Pytest,py.test,Bootstrap,
HTML,CSS
:description: This tutorial shows how to build an aiohttp application following the dependency
injection principle. You will create the REST API application, connect to the
Giphy API, cover it with the unit test and make some refactoring.
This tutorials shows how to build an ``aiohttp`` REST API application following the dependency This tutorial shows how to build an ``aiohttp`` REST API application following the dependency
injection principle. injection principle.
Start from the scratch or jump to the section: Start from the scratch or jump to the section:
@ -908,9 +916,9 @@ You should see:
Conclusion Conclusion
---------- ----------
In this tutorial we've build an ``aiohttp`` REST API application following the dependency In this tutorial we've built an ``aiohttp`` REST API application following the dependency
injection principle. injection principle.
We've used ``Dependency Injector`` as a dependency injection framework. We've used the ``Dependency Injector`` as a dependency injection framework.
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
when you need to understand or change your application structure. It's easy with the container, when you need to understand or change your application structure. It's easy with the container,

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
Asyncio tutorial
================
Coming soon...
.. disqus::

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -3,7 +3,15 @@
Flask tutorial Flask tutorial
============== ==============
This tutorials shows how to build ``Flask`` application following the dependency injection .. meta::
:keywords: Python,Flask,Tutorial,Education,Web,Example,DI,Dependency injection,IoC,
Inversion of control,Refactoring,Tests,Unit tests,Pytest,py.test,Bootstrap,
HTML,CSS
:description: This tutorial shows how to build a Flask application following the dependency
injection principle. You will create the web application, connect to the Github
API, cover it with unit the test and make some refactoring.
This tutorial shows how to build a ``Flask`` application following the dependency injection
principle. principle.
Start from the scratch or jump to the section: Start from the scratch or jump to the section:
@ -1085,8 +1093,8 @@ Conclusion
We are done. We are done.
In this tutorial we've build ``Flask`` application following the dependency injection principle. In this tutorial we've built a ``Flask`` application following the dependency injection principle.
We've used ``Dependency Injector`` as a dependency injection framework. We've used the ``Dependency Injector`` as a dependency injection framework.
The main part of this application is the container. It keeps all the application components and The main part of this application is the container. It keeps all the application components and
their dependencies in one place: their dependencies in one place:

View File

@ -11,6 +11,6 @@ frameworks.
flask flask
aiohttp aiohttp
asyncio asyncio-daemon
.. disqus:: .. disqus::

View File

@ -25,11 +25,31 @@ The output should be something like:
Starting monitoring-daemon-asyncio_monitor_1 ... done Starting monitoring-daemon-asyncio_monitor_1 ... done
Attaching to monitoring-daemon-asyncio_monitor_1 Attaching to monitoring-daemon-asyncio_monitor_1
monitor_1 | [2020-08-06 01:57:08,249] [INFO] [monitoringdaemon.dispatcher]: Dispatcher is starting up monitor_1 | [2020-08-08 17:04:36,655] [INFO] [Dispatcher]: Starting up
monitor_1 | [2020-08-06 01:57:08,249] [INFO] [monitoringdaemon.dispatcher]: Monitoring task has been started monitoringdaemon.monitors.HttpMonitor(url="http://example.com") monitor_1 | [2020-08-08 17:04:36,732] [INFO] [HttpMonitor]: Check
monitor_1 | [2020-08-06 01:57:08,249] [INFO] [monitoringdaemon.dispatcher]: Monitoring task has been started monitoringdaemon.monitors.HttpMonitor(url="http://httpbin.org/get") monitor_1 | GET http://example.com
monitor_1 | [2020-08-06 01:57:08,318] [INFO] [monitoringdaemon.monitors.HttpMonitor(url="http://example.com")]: Response code: 200, content length: 648, request took: 0.067 seconds monitor_1 | response code: 200
monitor_1 | [2020-08-06 01:57:08,363] [INFO] [monitoringdaemon.monitors.HttpMonitor(url="http://httpbin.org/get")]: Response code: 200, content length: 309, request took: 0.112 seconds monitor_1 | content length: 648
monitor_1 | request took: 0.074 seconds
monitor_1 |
monitor_1 | [2020-08-08 17:04:36,811] [INFO] [HttpMonitor]: Check
monitor_1 | GET https://httpbin.org/get
monitor_1 | response code: 200
monitor_1 | content length: 310
monitor_1 | request took: 0.153 seconds
monitor_1 |
monitor_1 | [2020-08-08 17:04:41,731] [INFO] [HttpMonitor]: Check
monitor_1 | GET http://example.com
monitor_1 | response code: 200
monitor_1 | content length: 648
monitor_1 | request took: 0.067 seconds
monitor_1 |
monitor_1 | [2020-08-08 17:04:41,787] [INFO] [HttpMonitor]: Check
monitor_1 | GET https://httpbin.org/get
monitor_1 | response code: 200
monitor_1 | content length: 310
monitor_1 | request took: 0.122 seconds
monitor_1 |
Test Test
---- ----
@ -59,9 +79,9 @@ The output should be something like:
monitoringdaemon/__init__.py 0 0 100% monitoringdaemon/__init__.py 0 0 100%
monitoringdaemon/__main__.py 9 9 0% monitoringdaemon/__main__.py 9 9 0%
monitoringdaemon/containers.py 11 0 100% monitoringdaemon/containers.py 11 0 100%
monitoringdaemon/dispatcher.py 45 5 89% monitoringdaemon/dispatcher.py 43 5 88%
monitoringdaemon/http.py 6 3 50% monitoringdaemon/http.py 6 3 50%
monitoringdaemon/monitors.py 29 2 93% monitoringdaemon/monitors.py 23 1 96%
monitoringdaemon/tests.py 37 0 100% monitoringdaemon/tests.py 37 0 100%
---------------------------------------------------- ----------------------------------------------------
TOTAL 137 19 86% TOTAL 129 18 86%

View File

@ -12,6 +12,6 @@ monitors:
httpbin: httpbin:
method: "GET" method: "GET"
url: "http://httpbin.org/get" url: "https://httpbin.org/get"
timeout: 5 timeout: 5
check_every: 5 check_every: 5

View File

@ -9,30 +9,24 @@ from typing import List
from .monitors import Monitor from .monitors import Monitor
logger = logging.getLogger(__name__)
class Dispatcher: class Dispatcher:
def __init__(self, monitors: List[Monitor]) -> None: def __init__(self, monitors: List[Monitor]) -> None:
self._monitors = monitors self._monitors = monitors
self._monitor_tasks: List[asyncio.Task] = [] self._monitor_tasks: List[asyncio.Task] = []
self._logger = logging.getLogger(self.__class__.__name__)
self._stopping = False self._stopping = False
def run(self) -> None: def run(self) -> None:
asyncio.run(self.start()) asyncio.run(self.start())
async def start(self) -> None: async def start(self) -> None:
logger.info('Dispatcher is 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(
asyncio.create_task(self._run_monitor(monitor)), asyncio.create_task(self._run_monitor(monitor)),
) )
logger.info(
'Monitoring task has been started %s',
monitor.full_name,
)
asyncio.get_event_loop().add_signal_handler(signal.SIGTERM, self.stop) asyncio.get_event_loop().add_signal_handler(signal.SIGTERM, self.stop)
asyncio.get_event_loop().add_signal_handler(signal.SIGINT, self.stop) asyncio.get_event_loop().add_signal_handler(signal.SIGINT, self.stop)
@ -47,11 +41,10 @@ class Dispatcher:
self._stopping = True self._stopping = True
logger.info('Dispatcher is 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()
logger.info('Monitoring task has been stopped %s', monitor.full_name) self._logger.info('Shutdown finished successfully')
logger.info('Dispatcher shutting down finished successfully')
@staticmethod @staticmethod
async def _run_monitor(monitor: Monitor) -> None: async def _run_monitor(monitor: Monitor) -> None:
@ -67,6 +60,6 @@ class Dispatcher:
except asyncio.CancelledError: except asyncio.CancelledError:
break break
except Exception: except Exception:
monitor.logger.exception('Error running monitoring check') monitor.logger.exception('Error executing monitor check')
await asyncio.sleep(_until_next(last=time_start)) await asyncio.sleep(_until_next(last=time_start))

View File

@ -11,11 +11,7 @@ class Monitor:
def __init__(self, check_every: int) -> None: def __init__(self, check_every: int) -> None:
self.check_every = check_every self.check_every = check_every
self.logger = logging.getLogger(self.full_name) self.logger = logging.getLogger(self.__class__.__name__)
@property
def full_name(self) -> str:
raise NotImplementedError()
async def check(self) -> None: async def check(self) -> None:
raise NotImplementedError() raise NotImplementedError()
@ -34,10 +30,6 @@ class HttpMonitor(Monitor):
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'))
@property
def full_name(self) -> str:
return '{0}.{1}(url="{2}")'.format(__name__, self.__class__.__name__, self._url)
async def check(self) -> None: async def check(self) -> None:
time_start = time.time() time_start = time.time()
@ -51,7 +43,13 @@ class HttpMonitor(Monitor):
time_took = time_end - time_start time_took = time_end - time_start
self.logger.info( self.logger.info(
'Response code: %s, content length: %s, request took: %s seconds', 'Check\n'
' %s %s\n'
' response code: %s\n'
' content length: %s\n'
' request took: %s seconds\n',
self._method,
self._url,
response.status, response.status,
response.content_length, response.content_length,
round(time_took, 3) round(time_took, 3)

View File

@ -32,7 +32,7 @@ def container():
}, },
'httpbin': { 'httpbin': {
'method': 'GET', 'method': 'GET',
'url': 'http://fake-httpbin.org/get', 'url': 'https://fake-httpbin.org/get',
'timeout': 1, 'timeout': 1,
'check_every': 1, 'check_every': 1,
}, },
@ -56,7 +56,7 @@ async def test_example_monitor(container, caplog):
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

File diff suppressed because it is too large Load Diff

View File

@ -1239,6 +1239,7 @@ cdef class Configuration(Object):
"""Configuration provider provides configuration options to the other providers. """Configuration provider provides configuration options to the other providers.
.. code-block:: python .. code-block:: python
config = Configuration('config') config = Configuration('config')
print(config.section1.option1()) # None print(config.section1.option1()) # None
print(config.section1.option2()) # None print(config.section1.option2()) # None