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
# Unit tests with coverage report
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 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,
IoC Container,Factory, Singleton, Design Patterns
: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
pattern in formal, pretty, Pythonic way. Dependency Injector
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:
+ Pass the dependency injection :ref:`flask-tutorial`.
+ Look at the other dependency injection :ref:`tutorials`.
+ Pass one of the dependency injection tutorials:
+ :ref:`flask-tutorial`
+ :ref:`aiohttp-tutorial`
+ :ref:`asyncio-daemon-tutorial`
+ Know more about the :ref:`providers`.
+ 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
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
------
- Add deep init injections overriding for ``Factory`` provider.
- Add ``asyncio`` monitoring daemon example.
3.26.0
------

View File

@ -1,9 +1,17 @@
.. _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.
Start from the scratch or jump to the section:
@ -908,9 +916,9 @@ You should see:
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.
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
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
==============
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.
Start from the scratch or jump to the section:
@ -1085,8 +1093,8 @@ Conclusion
We are done.
In this tutorial we've build ``Flask`` application following the dependency injection principle.
We've used ``Dependency Injector`` as a dependency injection framework.
In this tutorial we've built a ``Flask`` application following the dependency injection principle.
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
their dependencies in one place:

View File

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

View File

@ -25,11 +25,31 @@ The output should be something like:
Starting monitoring-daemon-asyncio_monitor_1 ... done
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-06 01:57:08,249] [INFO] [monitoringdaemon.dispatcher]: Monitoring task has been started monitoringdaemon.monitors.HttpMonitor(url="http://example.com")
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 | [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 | [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 | [2020-08-08 17:04:36,655] [INFO] [Dispatcher]: Starting up
monitor_1 | [2020-08-08 17:04:36,732] [INFO] [HttpMonitor]: Check
monitor_1 | GET http://example.com
monitor_1 | response code: 200
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
----
@ -59,9 +79,9 @@ The output should be something like:
monitoringdaemon/__init__.py 0 0 100%
monitoringdaemon/__main__.py 9 9 0%
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/monitors.py 29 2 93%
monitoringdaemon/monitors.py 23 1 96%
monitoringdaemon/tests.py 37 0 100%
----------------------------------------------------
TOTAL 137 19 86%
TOTAL 129 18 86%

View File

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

View File

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

View File

@ -11,11 +11,7 @@ class Monitor:
def __init__(self, check_every: int) -> None:
self.check_every = check_every
self.logger = logging.getLogger(self.full_name)
@property
def full_name(self) -> str:
raise NotImplementedError()
self.logger = logging.getLogger(self.__class__.__name__)
async def check(self) -> None:
raise NotImplementedError()
@ -34,10 +30,6 @@ class HttpMonitor(Monitor):
self._timeout = options.pop('timeout')
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:
time_start = time.time()
@ -51,7 +43,13 @@ class HttpMonitor(Monitor):
time_took = time_end - time_start
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.content_length,
round(time_took, 3)

View File

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

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.
.. code-block:: python
config = Configuration('config')
print(config.section1.option1()) # None
print(config.section1.option2()) # None