mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 09:36:48 +03:00
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:
parent
2ff36b44ab
commit
3a61457be7
2
Makefile
2
Makefile
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
------
|
------
|
||||||
|
|
|
@ -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,
|
||||||
|
|
1085
docs/tutorials/asyncio-daemon.rst
Normal file
1085
docs/tutorials/asyncio-daemon.rst
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +0,0 @@
|
||||||
Asyncio tutorial
|
|
||||||
================
|
|
||||||
|
|
||||||
Coming soon...
|
|
||||||
|
|
||||||
.. disqus::
|
|
BIN
docs/tutorials/asyncio_images/class_1.png
Normal file
BIN
docs/tutorials/asyncio_images/class_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
docs/tutorials/asyncio_images/class_2.png
Normal file
BIN
docs/tutorials/asyncio_images/class_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
docs/tutorials/asyncio_images/diagram.png
Normal file
BIN
docs/tutorials/asyncio_images/diagram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
|
@ -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:
|
||||||
|
|
|
@ -11,6 +11,6 @@ frameworks.
|
||||||
|
|
||||||
flask
|
flask
|
||||||
aiohttp
|
aiohttp
|
||||||
asyncio
|
asyncio-daemon
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -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%
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user