From 7c12e92cd69a71717f621f0c2cc65593c57d98ec Mon Sep 17 00:00:00 2001 From: zzzhang6 Date: Sat, 3 Oct 2020 15:37:42 +0800 Subject: [PATCH 1/5] Support tornado web framework --- examples/images/tornado.png | Bin 0 -> 56637 bytes examples/tornado/README.md | 17 ++++ examples/tornado/pyproject.toml | 18 ++++ examples/tornado/tornado_example_app.py | 37 ++++++++ pyctuator/impl/tornado_pyctuator.py | 113 ++++++++++++++++++++++++ pyctuator/pyctuator.py | 16 ++++ 6 files changed, 201 insertions(+) create mode 100644 examples/images/tornado.png create mode 100644 examples/tornado/README.md create mode 100644 examples/tornado/pyproject.toml create mode 100644 examples/tornado/tornado_example_app.py create mode 100644 pyctuator/impl/tornado_pyctuator.py diff --git a/examples/images/tornado.png b/examples/images/tornado.png new file mode 100644 index 0000000000000000000000000000000000000000..8f6fc056a500601db37eb98c3387fc2085d68189 GIT binary patch literal 56637 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 >MrV20Wh3r+)D*w>&Ggta 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 O!~2T&awQ)>|33gY<}}d& literal 0 HcmV?d00001 diff --git a/examples/tornado/README.md b/examples/tornado/README.md new file mode 100644 index 0000000..e8e799b --- /dev/null +++ b/examples/tornado/README.md @@ -0,0 +1,17 @@ +# Tornado example +This example demonstrates the integration with the [Tornado](https://www.tornadoweb.org/). + +## Running the example +1. Start an instance of SBA (Spring Boot Admin): + ```sh + docker run --rm -p 8080:8080 michayaak/spring-boot-admin:2.2.3-1 + ``` +2. Once Spring Boot Admin is running, you can run the examples as follow: + ```sh + cd examples/tornado + poetry install + poetry run python -m tornado_example_app + ``` + +![tornado Example](../images/tornado.png) + diff --git a/examples/tornado/pyproject.toml b/examples/tornado/pyproject.toml new file mode 100644 index 0000000..9e3acb6 --- /dev/null +++ b/examples/tornado/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "tornado-pyctuator-example" +version = "1.0.0" +description = "Example of using Pyctuator" +authors = [ + "Desmond Stonie ", +] + +[tool.poetry.dependencies] +python = "^3.7" +psutil = { version = "^5.6" } +tornado = { version = "^6.0.4" } +pyctuator = { version = "^0.13" } + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" + diff --git a/examples/tornado/tornado_example_app.py b/examples/tornado/tornado_example_app.py new file mode 100644 index 0000000..5a4c2d8 --- /dev/null +++ b/examples/tornado/tornado_example_app.py @@ -0,0 +1,37 @@ +import datetime +import logging +import random + +from tornado import ioloop +from tornado.web import Application, RequestHandler +from tornado.httpserver import HTTPServer + +from pyctuator.pyctuator import Pyctuator + +my_logger = logging.getLogger("example") + +class HomeHandler(RequestHandler): + def get(self): + my_logger.debug(f"{datetime.datetime.now()} - {str(random.randint(0, 100))}") + print("Printing to STDOUT") + self.write("Hello World!") + +app = Application([ + (r"/", HomeHandler) +], debug=False) + +example_app_address = "host.docker.internal" +example_sba_address = "localhost" + +Pyctuator( + app, + "Tornado Pyctuator", + app_url=f"http://{example_app_address}:5000", + pyctuator_endpoint_url=f"http://{example_app_address}:5000/pyctuator", + registration_url=f"http://{example_sba_address}:8080/instances", + app_description="Demonstrate Spring Boot Admin Integration with Tornado", +) + +http_server = HTTPServer(app, decompress_request=True) +http_server.listen(5000) +ioloop.IOLoop.current().start() diff --git a/pyctuator/impl/tornado_pyctuator.py b/pyctuator/impl/tornado_pyctuator.py new file mode 100644 index 0000000..08625ce --- /dev/null +++ b/pyctuator/impl/tornado_pyctuator.py @@ -0,0 +1,113 @@ +import dataclasses +import json +from collections import defaultdict +from datetime import datetime +from functools import partial +from typing import Any, Callable, List, Mapping + +from tornado.web import Application, RequestHandler + +from pyctuator.httptrace import TraceRecord, TraceRequest, TraceResponse +from pyctuator.impl import SBA_V2_CONTENT_TYPE +from pyctuator.impl.pyctuator_impl import PyctuatorImpl +from pyctuator.impl.pyctuator_router import PyctuatorRouter + +class AbstractPyctuatorHandler(RequestHandler): + pyctuator = None + dumps = None + def initialize(self): + self.pyctuator = self.application.settings.get('pyctuator') + self.dumps = self.application.settings.get('custom_dumps') + +class PyctuatorHandler(AbstractPyctuatorHandler): + def get(self): + resp = self.pyctuator.get_endpoints_data() + self.write(self.dumps(resp)) + +# GET /env +class EnvHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.get_environment() + self.write(self.dumps(resp)) + +# GET /info +class InfoHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.app_info + self.write(self.dumps(resp)) + +# GET /health +class HealthHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.get_health() + self.write(self.dumps(resp)) + +# GET /metrics +class MetricsHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.get_metric_names() + self.write(self.dumps(resp)) + +# GET "/metrics/{metric_name}" +class MetricsNameHandler(AbstractPyctuatorHandler): + def get(self, metric_name): + resp = self.pyctuator.pyctuator_impl.get_metric_measurement(metric_name) + self.write(self.dumps(resp)) + +# GET /loggers +class LoggersHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.logging.get_loggers() + self.write(self.dumps(resp)) + +# GET /loggers/{logger_name} +# POST /loggers/{logger_name} +class LoggersNameHandler(AbstractPyctuatorHandler): + def get(self, logger_name): + resp = self.pyctuator.pyctuator_impl.logging.get_logger(logger_name) + self.write(self.dumps(resp)) + def post(self, logger_name): + body = self.request.body.decode('utf-8') + body = json.loads(body) + self.pyctuator.pyctuator_impl.logging.set_logger_level(logger_name, body.get('configuredLevel', None)) + self.write('') + +# pylint: disable=too-many-locals,unused-argument +class TornadoHttpPyctuator(PyctuatorRouter): + def __init__(self, app: Application, pyctuator_impl: PyctuatorImpl) -> None: + super().__init__(app, pyctuator_impl) + + custom_dumps = partial( + json.dumps, default=self._custom_json_serializer + ) + + app.settings.setdefault("pyctuator", self) + app.settings.setdefault("custom_dumps", custom_dumps) + app.add_handlers(".*$", [ + (r"/pyctuator", PyctuatorHandler), + (r"/pyctuator/env", EnvHandler), + (r"/pyctuator/info", InfoHandler), + (r"/pyctuator/health", HealthHandler), + (r"/pyctuator/metrics", MetricsHandler), + (r"/pyctuator/metrics/(?P.*$)", MetricsNameHandler), + (r"/pyctuator/loggers", LoggersHandler), + (r"/pyctuator/loggers/(?P.*$)", LoggersNameHandler), + ]) + + def _custom_json_serializer(self, value: Any) -> Any: + if dataclasses.is_dataclass(value): + return dataclasses.asdict(value) + + if isinstance(value, datetime): + return str(value) + return None diff --git a/pyctuator/pyctuator.py b/pyctuator/pyctuator.py index c692d2a..47de625 100644 --- a/pyctuator/pyctuator.py +++ b/pyctuator/pyctuator.py @@ -53,6 +53,8 @@ def __init__( * aiohttp - `app` is an instance of `aiohttp.web.Application` + * Tornado - `app` is an instance of `tornado.web.Application` + :param app: an instance of a supported web-framework with which the pyctuator endpoints will be registered :param app_name: the application's name that will be presented in the "Info" section in boot-admin :param app_description: a description that will be presented in the "Info" section in boot-admin @@ -101,6 +103,7 @@ def __init__( "flask": self._integrate_flask, "fastapi": self._integrate_fastapi, "aiohttp": self._integrate_aiohttp, + "tornado": self._integrate_tornado } for framework_name, framework_integration_function in framework_integrations.items(): if self._is_framework_installed(framework_name): @@ -195,3 +198,16 @@ def _integrate_aiohttp(self, app: Any, pyctuator_impl: PyctuatorImpl) -> bool: AioHttpPyctuator(app, pyctuator_impl) return True return False + + def _integrate_tornado(self, app: Any, pyctuator_impl: PyctuatorImpl) -> bool: + """ + This method should only be called if we detected that tornado is installed. + It will then check whether the given app is a tornado app, and if so - it will add the Pyctuator + endpoints to it. + """ + from tornado.web import Application + if isinstance(app, Application): + from pyctuator.impl.tornado_pyctuator import TornadoHttpPyctuator + TornadoHttpPyctuator(app, pyctuator_impl) + return True + return False From 874165d375bd4a22d8301331f2016347942b2eb4 Mon Sep 17 00:00:00 2001 From: zzzhang6 Date: Sat, 3 Oct 2020 15:48:29 +0800 Subject: [PATCH 2/5] Fixed missing dependency --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4dca14b..e7dba9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ PyMySQL = {version = "^0.9.3", optional = true} cryptography = {version = "^2.8", optional = true} redis = {version = "^3.3", optional = true} aiohttp = {version = "^3.6.2", optional = true} +tornado = {version = "^6.0.4", optional = true} [tool.poetry.dev-dependencies] requests = "^2.22" @@ -56,6 +57,7 @@ psutil = ["psutil"] fastapi = ["fastapi", "uvicorn"] flask = ["flask"] aiohttp = ["aiohttp"] +tornado = ["tornado"] db = ["sqlalchemy", "PyMySQL", "cryptography"] redis = ["redis"] From 30f355a0514906d127cf6c8a604e36527db537de Mon Sep 17 00:00:00 2001 From: "michael.yak" Date: Sat, 3 Oct 2020 15:24:31 +0300 Subject: [PATCH 3/5] Tornady support should follow mypy & pylint conventions --- README.md | 2 +- poetry.lock | 315 ++++++++++++++++------------ pyctuator/impl/tornado_pyctuator.py | 111 +++++++--- 3 files changed, 254 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 95e6391..ce6e87e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Monitor Python web apps using [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin). -Pyctuator supports **[Flask](https://palletsprojects.com/p/flask/)**, **[FastAPI](https://fastapi.tiangolo.com/)** and **[aiohttp](docs.aiohttp.org)**. **Django** support is planned as well. +Pyctuator supports **[Flask](https://palletsprojects.com/p/flask/)**, **[FastAPI](https://fastapi.tiangolo.com/)**, **[aiohttp](docs.aiohttp.org)** and **[Tornado](https://www.tornadoweb.org/)**. **Django** support is planned as well. The following video shows a FastAPI web app being monitored and controled using Spring Boot Admin. diff --git a/poetry.lock b/poetry.lock index b42758e..e352b88 100644 --- a/poetry.lock +++ b/poetry.lock @@ -55,13 +55,13 @@ description = "Classes Without Boilerplate" name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" +version = "20.2.0" [package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] category = "dev" @@ -69,7 +69,7 @@ description = "A tool that automatically formats Python code to conform to the P name = "autopep8" optional = false python-versions = "*" -version = "1.5.3" +version = "1.5.4" [package.dependencies] pycodestyle = ">=2.6.0" @@ -89,7 +89,7 @@ description = "Foreign Function Interface for Python calling C code." name = "cffi" optional = true python-versions = "*" -version = "1.14.0" +version = "1.14.3" [package.dependencies] pycparser = "*" @@ -125,7 +125,7 @@ description = "Code coverage measurement for Python" name = "coverage" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.2" +version = "5.3" [package.extras] toml = ["toml"] @@ -218,7 +218,7 @@ marker = "python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.7.0" +version = "2.0.0" [package.dependencies] zipp = ">=0.5" @@ -293,7 +293,7 @@ description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" -version = "8.4.0" +version = "8.5.0" [[package]] category = "main" @@ -492,7 +492,7 @@ description = "Database Abstraction Library" name = "sqlalchemy" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.18" +version = "1.3.19" [package.extras] mssql = ["pyodbc"] @@ -525,6 +525,14 @@ optional = false python-versions = "*" version = "0.10.1" +[[package]] +category = "main" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +name = "tornado" +optional = true +python-versions = ">= 3.5" +version = "6.0.4" + [[package]] category = "dev" description = "a fork of Python 2 and 3 ast modules with type comment support" @@ -534,12 +542,12 @@ python-versions = "*" version = "1.4.1" [[package]] -category = "dev" +category = "main" description = "Backported and Experimental Type Hints for Python 3.5+" name = "typing-extensions" optional = false python-versions = "*" -version = "3.7.4.2" +version = "3.7.4.3" [[package]] category = "dev" @@ -547,7 +555,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and name = "urllib3" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.9" +version = "1.25.10" [package.extras] brotli = ["brotlipy (>=0.6.0)"] @@ -612,12 +620,16 @@ description = "Yet another URL library" name = "yarl" optional = true python-versions = ">=3.5" -version = "1.4.2" +version = "1.6.0" [package.dependencies] idna = ">=2.0" multidict = ">=4.0" +[package.dependencies.typing-extensions] +python = "<3.8" +version = ">=3.7.4" + [[package]] category = "dev" description = "Backport of pathlib-compatible object wrapper for zip files" @@ -625,11 +637,11 @@ marker = "python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=3.6" -version = "3.1.0" +version = "3.2.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["jaraco.itertools", "func-timeout"] +testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] aiohttp = ["aiohttp"] @@ -638,9 +650,11 @@ fastapi = ["fastapi", "uvicorn"] flask = ["flask"] psutil = ["psutil"] redis = ["redis"] +tornado = ["tornado"] [metadata] -content-hash = "d1ec0c09450515b35371b77898676a9fc0fa03795ec1172f9a2a9da3330691ea" +content-hash = "4b58495428cc0d474d40dd52f377676e9647fa958654c029bc0fb3b97f3f1388" +lock-version = "1.0" python-versions = "^3.7" [metadata.files] @@ -671,45 +685,53 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, + {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, + {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, ] autopep8 = [ - {file = "autopep8-1.5.3.tar.gz", hash = "sha256:60fd8c4341bab59963dafd5d2a566e94f547e660b9b396f772afe67d8481dbf0"}, + {file = "autopep8-1.5.4.tar.gz", hash = "sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094"}, ] certifi = [ {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, ] cffi = [ - {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, - {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, - {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, - {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, - {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, - {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, - {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, - {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, - {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, - {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, - {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, - {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, - {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, - {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, - {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, - {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, + {file = "cffi-1.14.3-2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc"}, + {file = "cffi-1.14.3-2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768"}, + {file = "cffi-1.14.3-2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d"}, + {file = "cffi-1.14.3-2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1"}, + {file = "cffi-1.14.3-2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca"}, + {file = "cffi-1.14.3-2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a"}, + {file = "cffi-1.14.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c"}, + {file = "cffi-1.14.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730"}, + {file = "cffi-1.14.3-cp27-cp27m-win32.whl", hash = "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d"}, + {file = "cffi-1.14.3-cp27-cp27m-win_amd64.whl", hash = "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05"}, + {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b"}, + {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171"}, + {file = "cffi-1.14.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f"}, + {file = "cffi-1.14.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4"}, + {file = "cffi-1.14.3-cp35-cp35m-win32.whl", hash = "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d"}, + {file = "cffi-1.14.3-cp35-cp35m-win_amd64.whl", hash = "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537"}, + {file = "cffi-1.14.3-cp36-cp36m-win32.whl", hash = "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0"}, + {file = "cffi-1.14.3-cp36-cp36m-win_amd64.whl", hash = "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394"}, + {file = "cffi-1.14.3-cp37-cp37m-win32.whl", hash = "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc"}, + {file = "cffi-1.14.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9"}, + {file = "cffi-1.14.3-cp38-cp38-win32.whl", hash = "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522"}, + {file = "cffi-1.14.3-cp38-cp38-win_amd64.whl", hash = "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15"}, + {file = "cffi-1.14.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d"}, + {file = "cffi-1.14.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c"}, + {file = "cffi-1.14.3-cp39-cp39-win32.whl", hash = "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b"}, + {file = "cffi-1.14.3-cp39-cp39-win_amd64.whl", hash = "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3"}, + {file = "cffi-1.14.3.tar.gz", hash = "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -724,40 +746,40 @@ colorama = [ {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] coverage = [ - {file = "coverage-5.2-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:d9ad0a988ae20face62520785ec3595a5e64f35a21762a57d115dae0b8fb894a"}, - {file = "coverage-5.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:4bb385a747e6ae8a65290b3df60d6c8a692a5599dc66c9fa3520e667886f2e10"}, - {file = "coverage-5.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9702e2cb1c6dec01fb8e1a64c015817c0800a6eca287552c47a5ee0ebddccf62"}, - {file = "coverage-5.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:42fa45a29f1059eda4d3c7b509589cc0343cd6bbf083d6118216830cd1a51613"}, - {file = "coverage-5.2-cp27-cp27m-win32.whl", hash = "sha256:41d88736c42f4a22c494c32cc48a05828236e37c991bd9760f8923415e3169e4"}, - {file = "coverage-5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:bbb387811f7a18bdc61a2ea3d102be0c7e239b0db9c83be7bfa50f095db5b92a"}, - {file = "coverage-5.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3740b796015b889e46c260ff18b84683fa2e30f0f75a171fb10d2bf9fb91fc70"}, - {file = "coverage-5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ebf2431b2d457ae5217f3a1179533c456f3272ded16f8ed0b32961a6d90e38ee"}, - {file = "coverage-5.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:d54d7ea74cc00482a2410d63bf10aa34ebe1c49ac50779652106c867f9986d6b"}, - {file = "coverage-5.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:87bdc8135b8ee739840eee19b184804e5d57f518578ffc797f5afa2c3c297913"}, - {file = "coverage-5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ed9a21502e9223f563e071759f769c3d6a2e1ba5328c31e86830368e8d78bc9c"}, - {file = "coverage-5.2-cp35-cp35m-win32.whl", hash = "sha256:509294f3e76d3f26b35083973fbc952e01e1727656d979b11182f273f08aa80b"}, - {file = "coverage-5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:ca63dae130a2e788f2b249200f01d7fa240f24da0596501d387a50e57aa7075e"}, - {file = "coverage-5.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:5c74c5b6045969b07c9fb36b665c9cac84d6c174a809fc1b21bdc06c7836d9a0"}, - {file = "coverage-5.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c32aa13cc3fe86b0f744dfe35a7f879ee33ac0a560684fef0f3e1580352b818f"}, - {file = "coverage-5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1e58fca3d9ec1a423f1b7f2aa34af4f733cbfa9020c8fe39ca451b6071237405"}, - {file = "coverage-5.2-cp36-cp36m-win32.whl", hash = "sha256:3b2c34690f613525672697910894b60d15800ac7e779fbd0fccf532486c1ba40"}, - {file = "coverage-5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4d511012beb967a39580ba7d2549edf1e6865a33e5fe51e4dce550522b3ac0e"}, - {file = "coverage-5.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:32ecee61a43be509b91a526819717d5e5650e009a8d5eda8631a59c721d5f3b6"}, - {file = "coverage-5.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6f91b4492c5cde83bfe462f5b2b997cdf96a138f7c58b1140f05de5751623cf1"}, - {file = "coverage-5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bfcc811883699ed49afc58b1ed9f80428a18eb9166422bce3c31a53dba00fd1d"}, - {file = "coverage-5.2-cp37-cp37m-win32.whl", hash = "sha256:60a3d36297b65c7f78329b80120f72947140f45b5c7a017ea730f9112b40f2ec"}, - {file = "coverage-5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:12eaccd86d9a373aea59869bc9cfa0ab6ba8b1477752110cb4c10d165474f703"}, - {file = "coverage-5.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:d82db1b9a92cb5c67661ca6616bdca6ff931deceebb98eecbd328812dab52032"}, - {file = "coverage-5.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:214eb2110217f2636a9329bc766507ab71a3a06a8ea30cdeebb47c24dce5972d"}, - {file = "coverage-5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8a3decd12e7934d0254939e2bf434bf04a5890c5bf91a982685021786a08087e"}, - {file = "coverage-5.2-cp38-cp38-win32.whl", hash = "sha256:1dcebae667b73fd4aa69237e6afb39abc2f27520f2358590c1b13dd90e32abe7"}, - {file = "coverage-5.2-cp38-cp38-win_amd64.whl", hash = "sha256:f50632ef2d749f541ca8e6c07c9928a37f87505ce3a9f20c8446ad310f1aa87b"}, - {file = "coverage-5.2-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:7403675df5e27745571aba1c957c7da2dacb537c21e14007ec3a417bf31f7f3d"}, - {file = "coverage-5.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0fc4e0d91350d6f43ef6a61f64a48e917637e1dcfcba4b4b7d543c628ef82c2d"}, - {file = "coverage-5.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:25fe74b5b2f1b4abb11e103bb7984daca8f8292683957d0738cd692f6a7cc64c"}, - {file = "coverage-5.2-cp39-cp39-win32.whl", hash = "sha256:d67599521dff98ec8c34cd9652cbcfe16ed076a2209625fca9dc7419b6370e5c"}, - {file = "coverage-5.2-cp39-cp39-win_amd64.whl", hash = "sha256:10f2a618a6e75adf64329f828a6a5b40244c1c50f5ef4ce4109e904e69c71bd2"}, - {file = "coverage-5.2.tar.gz", hash = "sha256:1874bdc943654ba46d28f179c1846f5710eda3aeb265ff029e0ac2b52daae404"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] cryptography = [ {file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"}, @@ -800,8 +822,8 @@ idna = [ {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] importlib-metadata = [ - {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, - {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, + {file = "importlib_metadata-2.0.0-py2.py3-none-any.whl", hash = "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3"}, + {file = "importlib_metadata-2.0.0.tar.gz", hash = "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da"}, ] isort = [ {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, @@ -878,8 +900,8 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] more-itertools = [ - {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, - {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, + {file = "more-itertools-8.5.0.tar.gz", hash = "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20"}, + {file = "more_itertools-8.5.0-py3-none-any.whl", hash = "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"}, ] multidict = [ {file = "multidict-4.7.6-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000"}, @@ -983,34 +1005,38 @@ six = [ {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] sqlalchemy = [ - {file = "SQLAlchemy-1.3.18-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:f11c2437fb5f812d020932119ba02d9e2bc29a6eca01a055233a8b449e3e1e7d"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0ec575db1b54909750332c2e335c2bb11257883914a03bc5a3306a4488ecc772"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f57be5673e12763dd400fea568608700a63ce1c6bd5bdbc3cc3a2c5fdb045274"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27m-win32.whl", hash = "sha256:8cac7bb373a5f1423e28de3fd5fc8063b9c8ffe8957dc1b1a59cb90453db6da1"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27m-win_amd64.whl", hash = "sha256:adad60eea2c4c2a1875eb6305a0b6e61a83163f8e233586a4d6a55221ef984fe"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:57aa843b783179ab72e863512e14bdcba186641daf69e4e3a5761d705dcc35b1"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:621f58cd921cd71ba6215c42954ffaa8a918eecd8c535d97befa1a8acad986dd"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:fc728ece3d5c772c196fd338a99798e7efac7a04f9cb6416299a3638ee9a94cd"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:736d41cfebedecc6f159fc4ac0769dc89528a989471dc1d378ba07d29a60ba1c"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:427273b08efc16a85aa2b39892817e78e3ed074fcb89b2a51c4979bae7e7ba98"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-win32.whl", hash = "sha256:cbe1324ef52ff26ccde2cb84b8593c8bf930069dfc06c1e616f1bfd4e47f48a3"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-win_amd64.whl", hash = "sha256:8fd452dc3d49b3cc54483e033de6c006c304432e6f84b74d7b2c68afa2569ae5"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e89e0d9e106f8a9180a4ca92a6adde60c58b1b0299e1b43bd5e0312f535fbf33"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6ac2558631a81b85e7fb7a44e5035347938b0a73f5fdc27a8566777d0792a6a4"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:87fad64529cde4f1914a5b9c383628e1a8f9e3930304c09cf22c2ae118a1280e"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-win32.whl", hash = "sha256:e4624d7edb2576cd72bb83636cd71c8ce544d8e272f308bd80885056972ca299"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-win_amd64.whl", hash = "sha256:89494df7f93b1836cae210c42864b292f9b31eeabca4810193761990dc689cce"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:716754d0b5490bdcf68e1e4925edc02ac07209883314ad01a137642ddb2056f1"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:50c4ee32f0e1581828843267d8de35c3298e86ceecd5e9017dc45788be70a864"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d98bc827a1293ae767c8f2f18be3bb5151fd37ddcd7da2a5f9581baeeb7a3fa1"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-win32.whl", hash = "sha256:0942a3a0df3f6131580eddd26d99071b48cfe5aaf3eab2783076fbc5a1c1882e"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-win_amd64.whl", hash = "sha256:16593fd748944726540cd20f7e83afec816c2ac96b082e26ae226e8f7e9688cf"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c26f95e7609b821b5f08a72dab929baa0d685406b953efd7c89423a511d5c413"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:512a85c3c8c3995cc91af3e90f38f460da5d3cade8dc3a229c8e0879037547c9"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d05c4adae06bd0c7f696ae3ec8d993ed8ffcc4e11a76b1b35a5af8a099bd2284"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-win32.whl", hash = "sha256:109581ccc8915001e8037b73c29590e78ce74be49ca0a3630a23831f9e3ed6c7"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-win_amd64.whl", hash = "sha256:8619b86cb68b185a778635be5b3e6018623c0761dde4df2f112896424aa27bd8"}, - {file = "SQLAlchemy-1.3.18.tar.gz", hash = "sha256:da2fb75f64792c1fc64c82313a00c728a7c301efe6a60b7a9fe35b16b4368ce7"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:f2e8a9c0c8813a468aa659a01af6592f71cd30237ec27c4cc0683f089f90dcfc"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:33d29ae8f1dc7c75b191bb6833f55a19c932514b9b5ce8c3ab9bc3047da5db36"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3292a28344922415f939ee7f4fc0c186f3d5a0bf02192ceabd4f1129d71b08de"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-win32.whl", hash = "sha256:883c9fb62cebd1e7126dd683222b3b919657590c3e2db33bdc50ebbad53e0338"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-win_amd64.whl", hash = "sha256:860d0fe234922fd5552b7f807fbb039e3e7ca58c18c8d38aa0d0a95ddf4f6c23"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73a40d4fcd35fdedce07b5885905753d5d4edf413fbe53544dd871f27d48bd4f"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5a49e8473b1ab1228302ed27365ea0fadd4bf44bc0f9e73fe38e10fdd3d6b4fc"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:6547b27698b5b3bbfc5210233bd9523de849b2bb8a0329cd754c9308fc8a05ce"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:107d4af989831d7b091e382d192955679ec07a9209996bf8090f1f539ffc5804"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:eb1d71643e4154398b02e88a42fc8b29db8c44ce4134cf0f4474bfc5cb5d4dac"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:b6ff91356354b7ff3bd208adcf875056d3d886ed7cef90c571aef2ab8a554b12"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-win32.whl", hash = "sha256:96f51489ac187f4bab588cf51f9ff2d40b6d170ac9a4270ffaed535c8404256b"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-win_amd64.whl", hash = "sha256:618db68745682f64cedc96ca93707805d1f3a031747b5a0d8e150cfd5055ae4d"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6557af9e0d23f46b8cd56f8af08eaac72d2e3c632ac8d5cf4e20215a8dca7cea"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8280f9dae4adb5889ce0bb3ec6a541bf05434db5f9ab7673078c00713d148365"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:b595e71c51657f9ee3235db8b53d0b57c09eee74dfb5b77edff0e46d2218dc02"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:51064ee7938526bab92acd049d41a1dc797422256086b39c08bafeffb9d304c6"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-win32.whl", hash = "sha256:8afcb6f4064d234a43fea108859942d9795c4060ed0fbd9082b0f280181a15c1"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-win_amd64.whl", hash = "sha256:e49947d583fe4d29af528677e4f0aa21f5e535ca2ae69c48270ebebd0d8843c0"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:9e865835e36dfbb1873b65e722ea627c096c11b05f796831e3a9b542926e979e"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:276936d41111a501cf4a1a0543e25449108d87e9f8c94714f7660eaea89ae5fe"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c7adb1f69a80573698c2def5ead584138ca00fff4ad9785a4b0b2bf927ba308d"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:465c999ef30b1c7525f81330184121521418a67189053bcf585824d833c05b66"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-win32.whl", hash = "sha256:aa0554495fe06172b550098909be8db79b5accdf6ffb59611900bea345df5eba"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-win_amd64.whl", hash = "sha256:15c0bcd3c14f4086701c33a9e87e2c7ceb3bcb4a246cd88ec54a49cf2a5bd1a6"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:fe7fe11019fc3e6600819775a7d55abc5446dda07e9795f5954fdbf8a49e1c37"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c898b3ebcc9eae7b36bd0b4bbbafce2d8076680f6868bcbacee2d39a7a9726a7"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:072766c3bd09294d716b2d114d46ffc5ccf8ea0b714a4e1c48253014b771c6bb"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:26c5ca9d09f0e21b8671a32f7d83caad5be1f6ff45eef5ec2f6fd0db85fc5dc0"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-win32.whl", hash = "sha256:b70bad2f1a5bd3460746c3fb3ab69e4e0eb5f59d977a23f9b66e5bdc74d97b86"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-win_amd64.whl", hash = "sha256:83469ad15262402b0e0974e612546bc0b05f379b5aa9072ebf66d0f8fef16bea"}, + {file = "SQLAlchemy-1.3.19.tar.gz", hash = "sha256:3bba2e9fbedb0511769780fe1d63007081008c5c2d7d715e91858c94dbaa260e"}, ] starlette = [ {file = "starlette-0.12.9.tar.gz", hash = "sha256:c2ac9a42e0e0328ad20fe444115ac5e3760c1ee2ac1ff8cdb5ec915c4a453411"}, @@ -1019,6 +1045,17 @@ toml = [ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, ] +tornado = [ + {file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"}, + {file = "tornado-6.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"}, + {file = "tornado-6.0.4-cp36-cp36m-win32.whl", hash = "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673"}, + {file = "tornado-6.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a"}, + {file = "tornado-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6"}, + {file = "tornado-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b"}, + {file = "tornado-6.0.4-cp38-cp38-win32.whl", hash = "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52"}, + {file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"}, + {file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"}, +] typed-ast = [ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, @@ -1043,13 +1080,13 @@ typed-ast = [ {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"}, - {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"}, - {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"}, + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] urllib3 = [ - {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, - {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, + {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, + {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] uvicorn = [ {file = "uvicorn-0.9.1.tar.gz", hash = "sha256:33c7cfcf71450d2170c9a4c4c7559767329040a36b159e3889554132e4b89457"}, @@ -1097,25 +1134,25 @@ wrapt = [ {file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"}, ] yarl = [ - {file = "yarl-1.4.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b"}, - {file = "yarl-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1"}, - {file = "yarl-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080"}, - {file = "yarl-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a"}, - {file = "yarl-1.4.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f"}, - {file = "yarl-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea"}, - {file = "yarl-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb"}, - {file = "yarl-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70"}, - {file = "yarl-1.4.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d"}, - {file = "yarl-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce"}, - {file = "yarl-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"}, - {file = "yarl-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce"}, - {file = "yarl-1.4.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b"}, - {file = "yarl-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae"}, - {file = "yarl-1.4.2-cp38-cp38-win32.whl", hash = "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462"}, - {file = "yarl-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6"}, - {file = "yarl-1.4.2.tar.gz", hash = "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b"}, + {file = "yarl-1.6.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:db9eb8307219d7e09b33bcb43287222ef35cbcf1586ba9472b0a4b833666ada1"}, + {file = "yarl-1.6.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e31fef4e7b68184545c3d68baec7074532e077bd1906b040ecfba659737df188"}, + {file = "yarl-1.6.0-cp35-cp35m-win32.whl", hash = "sha256:5d84cc36981eb5a8533be79d6c43454c8e6a39ee3118ceaadbd3c029ab2ee580"}, + {file = "yarl-1.6.0-cp35-cp35m-win_amd64.whl", hash = "sha256:5e447e7f3780f44f890360ea973418025e8c0cdcd7d6a1b221d952600fd945dc"}, + {file = "yarl-1.6.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6f6898429ec3c4cfbef12907047136fd7b9e81a6ee9f105b45505e633427330a"}, + {file = "yarl-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d088ea9319e49273f25b1c96a3763bf19a882cff774d1792ae6fba34bd40550a"}, + {file = "yarl-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b7c199d2cbaf892ba0f91ed36d12ff41ecd0dde46cbf64ff4bfe997a3ebc925e"}, + {file = "yarl-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:67c5ea0970da882eaf9efcf65b66792557c526f8e55f752194eff8ec722c75c2"}, + {file = "yarl-1.6.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:04a54f126a0732af75e5edc9addeaa2113e2ca7c6fce8974a63549a70a25e50e"}, + {file = "yarl-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcbe419805c9b20db9a51d33b942feddbf6e7fb468cb20686fd7089d4164c12a"}, + {file = "yarl-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:c604998ab8115db802cc55cb1b91619b2831a6128a62ca7eea577fc8ea4d3131"}, + {file = "yarl-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c22607421f49c0cb6ff3ed593a49b6a99c6ffdeaaa6c944cdda83c2393c8864d"}, + {file = "yarl-1.6.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:7ce35944e8e61927a8f4eb78f5bc5d1e6da6d40eadd77e3f79d4e9399e263921"}, + {file = "yarl-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c15d71a640fb1f8e98a1423f9c64d7f1f6a3a168f803042eaf3a5b5022fde0c1"}, + {file = "yarl-1.6.0-cp38-cp38-win32.whl", hash = "sha256:3cc860d72ed989f3b1f3abbd6ecf38e412de722fb38b8f1b1a086315cf0d69c5"}, + {file = "yarl-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:e32f0fb443afcfe7f01f95172b66f279938fbc6bdaebe294b0ff6747fb6db020"}, + {file = "yarl-1.6.0.tar.gz", hash = "sha256:61d3ea3c175fe45f1498af868879c6ffeb989d4143ac542163c45538ba5ec21b"}, ] zipp = [ - {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, - {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, + {file = "zipp-3.2.0-py3-none-any.whl", hash = "sha256:43f4fa8d8bb313e65d8323a3952ef8756bf40f9a5c3ea7334be23ee4ec8278b6"}, + {file = "zipp-3.2.0.tar.gz", hash = "sha256:b52f22895f4cfce194bc8172f3819ee8de7540aa6d873535a8668b730b8b411f"}, ] diff --git a/pyctuator/impl/tornado_pyctuator.py b/pyctuator/impl/tornado_pyctuator.py index 08625ce..848a7cd 100644 --- a/pyctuator/impl/tornado_pyctuator.py +++ b/pyctuator/impl/tornado_pyctuator.py @@ -1,87 +1,130 @@ import dataclasses import json -from collections import defaultdict from datetime import datetime from functools import partial -from typing import Any, Callable, List, Mapping +from typing import Any, Optional from tornado.web import Application, RequestHandler -from pyctuator.httptrace import TraceRecord, TraceRequest, TraceResponse -from pyctuator.impl import SBA_V2_CONTENT_TYPE from pyctuator.impl.pyctuator_impl import PyctuatorImpl from pyctuator.impl.pyctuator_router import PyctuatorRouter + +# pylint: disable=abstract-method class AbstractPyctuatorHandler(RequestHandler): - pyctuator = None - dumps = None - def initialize(self): - self.pyctuator = self.application.settings.get('pyctuator') + pyctuator_router: Optional[PyctuatorRouter] = None + dumps: Optional[partial[str]] = None + + def initialize(self) -> None: + self.pyctuator_router = self.application.settings.get('pyctuator_router') self.dumps = self.application.settings.get('custom_dumps') + class PyctuatorHandler(AbstractPyctuatorHandler): - def get(self): - resp = self.pyctuator.get_endpoints_data() + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + resp = self.pyctuator_router.get_endpoints_data() self.write(self.dumps(resp)) + # GET /env class EnvHandler(AbstractPyctuatorHandler): - def options(self): + def options(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None self.write('') - def get(self): - resp = self.pyctuator.pyctuator_impl.get_environment() + + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + resp = self.pyctuator_router.pyctuator_impl.get_environment() self.write(self.dumps(resp)) + # GET /info class InfoHandler(AbstractPyctuatorHandler): - def options(self): + def options(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None self.write('') - def get(self): - resp = self.pyctuator.pyctuator_impl.app_info + + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + resp = self.pyctuator_router.pyctuator_impl.app_info self.write(self.dumps(resp)) + # GET /health class HealthHandler(AbstractPyctuatorHandler): - def options(self): + def options(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None self.write('') - def get(self): - resp = self.pyctuator.pyctuator_impl.get_health() + + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + resp = self.pyctuator_router.pyctuator_impl.get_health() self.write(self.dumps(resp)) + # GET /metrics class MetricsHandler(AbstractPyctuatorHandler): - def options(self): + def options(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None self.write('') - def get(self): - resp = self.pyctuator.pyctuator_impl.get_metric_names() + + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + resp = self.pyctuator_router.pyctuator_impl.get_metric_names() self.write(self.dumps(resp)) + # GET "/metrics/{metric_name}" class MetricsNameHandler(AbstractPyctuatorHandler): - def get(self, metric_name): - resp = self.pyctuator.pyctuator_impl.get_metric_measurement(metric_name) + def get(self, metric_name: str) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + resp = self.pyctuator_router.pyctuator_impl.get_metric_measurement(metric_name) self.write(self.dumps(resp)) + # GET /loggers class LoggersHandler(AbstractPyctuatorHandler): - def options(self): + def options(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None self.write('') - def get(self): - resp = self.pyctuator.pyctuator_impl.logging.get_loggers() + + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + resp = self.pyctuator_router.pyctuator_impl.logging.get_loggers() self.write(self.dumps(resp)) + # GET /loggers/{logger_name} # POST /loggers/{logger_name} class LoggersNameHandler(AbstractPyctuatorHandler): - def get(self, logger_name): - resp = self.pyctuator.pyctuator_impl.logging.get_logger(logger_name) + def get(self, logger_name: str) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + resp = self.pyctuator_router.pyctuator_impl.logging.get_logger(logger_name) self.write(self.dumps(resp)) - def post(self, logger_name): - body = self.request.body.decode('utf-8') - body = json.loads(body) - self.pyctuator.pyctuator_impl.logging.set_logger_level(logger_name, body.get('configuredLevel', None)) + + def post(self, logger_name: str) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + body_str = self.request.body.decode('utf-8') + body = json.loads(body_str) + self.pyctuator_router.pyctuator_impl.logging.set_logger_level(logger_name, body.get('configuredLevel', None)) self.write('') + # pylint: disable=too-many-locals,unused-argument class TornadoHttpPyctuator(PyctuatorRouter): def __init__(self, app: Application, pyctuator_impl: PyctuatorImpl) -> None: @@ -91,7 +134,7 @@ def __init__(self, app: Application, pyctuator_impl: PyctuatorImpl) -> None: json.dumps, default=self._custom_json_serializer ) - app.settings.setdefault("pyctuator", self) + app.settings.setdefault("pyctuator_router", self) app.settings.setdefault("custom_dumps", custom_dumps) app.add_handlers(".*$", [ (r"/pyctuator", PyctuatorHandler), From e0ecab6e5ab0d09f5796d03daf8125a2f04f190f Mon Sep 17 00:00:00 2001 From: "michael.yak" Date: Sat, 3 Oct 2020 15:48:18 +0300 Subject: [PATCH 4/5] E2E should include Tornado --- .github/workflows/python_package_build.yml | 2 +- examples/tornado/tornado_example_app.py | 14 +- pyctuator/impl/aiohttp_pyctuator.py | 5 +- pyctuator/impl/tornado_pyctuator.py | 164 +++++++++++++-------- tests/test_pyctuator_e2e.py | 7 +- tests/tornado_test_server.py | 84 +++++++++++ 6 files changed, 205 insertions(+), 71 deletions(-) create mode 100644 tests/tornado_test_server.py diff --git a/.github/workflows/python_package_build.yml b/.github/workflows/python_package_build.yml index 486d9a4..c6fe9bb 100644 --- a/.github/workflows/python_package_build.yml +++ b/.github/workflows/python_package_build.yml @@ -27,7 +27,7 @@ jobs: - run: poetry build -vvv # Install all dependencies except for psutil and run the tests with coverage - this tests handling missing psutil - - run: poetry install --extras flask --extras fastapi --extras aiohttp --extras db --extras redis + - run: poetry install --extras flask --extras fastapi --extras aiohttp --extras tornado --extras db --extras redis - run: make coverage # Run pylint+mypy after installing psutil so they don't complain on missing dependencies diff --git a/examples/tornado/tornado_example_app.py b/examples/tornado/tornado_example_app.py index 5a4c2d8..9d3ddfc 100644 --- a/examples/tornado/tornado_example_app.py +++ b/examples/tornado/tornado_example_app.py @@ -3,22 +3,26 @@ import random from tornado import ioloop -from tornado.web import Application, RequestHandler from tornado.httpserver import HTTPServer +from tornado.web import Application, RequestHandler from pyctuator.pyctuator import Pyctuator my_logger = logging.getLogger("example") + class HomeHandler(RequestHandler): def get(self): my_logger.debug(f"{datetime.datetime.now()} - {str(random.randint(0, 100))}") - print("Printing to STDOUT") self.write("Hello World!") -app = Application([ - (r"/", HomeHandler) -], debug=False) + +app = Application( + [ + (r"/", HomeHandler) + ], + debug=False +) example_app_address = "host.docker.internal" example_sba_address = "localhost" diff --git a/pyctuator/impl/aiohttp_pyctuator.py b/pyctuator/impl/aiohttp_pyctuator.py index 7b18390..5414e9a 100644 --- a/pyctuator/impl/aiohttp_pyctuator.py +++ b/pyctuator/impl/aiohttp_pyctuator.py @@ -122,10 +122,7 @@ async def intercept_requests_and_responses(request: web.Request, handler: Callab web.get("/pyctuator/info", get_info), web.get("/pyctuator/health", get_health), web.get("/pyctuator/metrics", get_metric_names), - web.get( - "/pyctuator/metrics/{metric_name}", - get_metric_measurement - ), + web.get("/pyctuator/metrics/{metric_name}", get_metric_measurement), web.get("/pyctuator/loggers", get_loggers), web.get("/pyctuator/loggers/{logger_name}", get_logger), web.post("/pyctuator/loggers/{logger_name}", set_logger_level), diff --git a/pyctuator/impl/tornado_pyctuator.py b/pyctuator/impl/tornado_pyctuator.py index 848a7cd..827b86f 100644 --- a/pyctuator/impl/tornado_pyctuator.py +++ b/pyctuator/impl/tornado_pyctuator.py @@ -1,11 +1,14 @@ import dataclasses import json -from datetime import datetime +from datetime import datetime, timedelta from functools import partial -from typing import Any, Optional +from http import HTTPStatus +from typing import Any, Optional, Callable from tornado.web import Application, RequestHandler +from pyctuator.httptrace import TraceRecord, TraceRequest, TraceResponse +from pyctuator.impl import SBA_V2_CONTENT_TYPE from pyctuator.impl.pyctuator_impl import PyctuatorImpl from pyctuator.impl.pyctuator_router import PyctuatorRouter @@ -13,75 +16,56 @@ # pylint: disable=abstract-method class AbstractPyctuatorHandler(RequestHandler): pyctuator_router: Optional[PyctuatorRouter] = None - dumps: Optional[partial[str]] = None + dumps: Optional[Callable[[Any], str]] = None def initialize(self) -> None: - self.pyctuator_router = self.application.settings.get('pyctuator_router') - self.dumps = self.application.settings.get('custom_dumps') + self.pyctuator_router = self.application.settings.get("pyctuator_router") + self.dumps = self.application.settings.get("custom_dumps") + self.set_header("Content-Type", SBA_V2_CONTENT_TYPE) + + def options(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + self.write("") class PyctuatorHandler(AbstractPyctuatorHandler): def get(self) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - resp = self.pyctuator_router.get_endpoints_data() - self.write(self.dumps(resp)) + self.write(self.dumps(self.pyctuator_router.get_endpoints_data())) # GET /env class EnvHandler(AbstractPyctuatorHandler): - def options(self) -> None: - assert self.pyctuator_router is not None - assert self.dumps is not None - self.write('') - def get(self) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - resp = self.pyctuator_router.pyctuator_impl.get_environment() - self.write(self.dumps(resp)) + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.get_environment())) # GET /info class InfoHandler(AbstractPyctuatorHandler): - def options(self) -> None: - assert self.pyctuator_router is not None - assert self.dumps is not None - self.write('') - def get(self) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - resp = self.pyctuator_router.pyctuator_impl.app_info - self.write(self.dumps(resp)) + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.app_info)) # GET /health class HealthHandler(AbstractPyctuatorHandler): - def options(self) -> None: - assert self.pyctuator_router is not None - assert self.dumps is not None - self.write('') - def get(self) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - resp = self.pyctuator_router.pyctuator_impl.get_health() - self.write(self.dumps(resp)) + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.get_health())) # GET /metrics class MetricsHandler(AbstractPyctuatorHandler): - def options(self) -> None: - assert self.pyctuator_router is not None - assert self.dumps is not None - self.write('') - def get(self) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - resp = self.pyctuator_router.pyctuator_impl.get_metric_names() - self.write(self.dumps(resp)) + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.get_metric_names())) # GET "/metrics/{metric_name}" @@ -89,22 +73,15 @@ class MetricsNameHandler(AbstractPyctuatorHandler): def get(self, metric_name: str) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - resp = self.pyctuator_router.pyctuator_impl.get_metric_measurement(metric_name) - self.write(self.dumps(resp)) + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.get_metric_measurement(metric_name))) # GET /loggers class LoggersHandler(AbstractPyctuatorHandler): - def options(self) -> None: - assert self.pyctuator_router is not None - assert self.dumps is not None - self.write('') - def get(self) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - resp = self.pyctuator_router.pyctuator_impl.logging.get_loggers() - self.write(self.dumps(resp)) + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.logging.get_loggers())) # GET /loggers/{logger_name} @@ -113,16 +90,50 @@ class LoggersNameHandler(AbstractPyctuatorHandler): def get(self, logger_name: str) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - resp = self.pyctuator_router.pyctuator_impl.logging.get_logger(logger_name) - self.write(self.dumps(resp)) + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.logging.get_logger(logger_name))) def post(self, logger_name: str) -> None: assert self.pyctuator_router is not None assert self.dumps is not None - body_str = self.request.body.decode('utf-8') + body_str = self.request.body.decode("utf-8") body = json.loads(body_str) - self.pyctuator_router.pyctuator_impl.logging.set_logger_level(logger_name, body.get('configuredLevel', None)) - self.write('') + self.pyctuator_router.pyctuator_impl.logging.set_logger_level(logger_name, body.get("configuredLevel", None)) + self.write("") + + +# GET /threaddump +class ThreadDumpHandler(AbstractPyctuatorHandler): + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.get_thread_dump())) + + +# GET /logfile +class LogFileHandler(AbstractPyctuatorHandler): + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + + range_header = self.request.headers.get("range") + if not range_header: + self.write(f"{self.pyctuator_router.pyctuator_impl.logfile.log_messages.get_range()}") + + else: + str_res, start, end = self.pyctuator_router.pyctuator_impl.logfile.get_logfile(range_header) + self.set_status(HTTPStatus.PARTIAL_CONTENT.value) + self.add_header("Content-Type", "text/html; charset=UTF-8") + self.add_header("Accept-Ranges", "bytes") + self.add_header("Content-Range", f"bytes {start}-{end}/{end}") + self.write(str_res) + + +# GET /httptrace +class HttpTraceHandler(AbstractPyctuatorHandler): + def get(self) -> None: + assert self.pyctuator_router is not None + assert self.dumps is not None + self.write(self.dumps(self.pyctuator_router.pyctuator_impl.http_tracer.get_httptrace())) # pylint: disable=too-many-locals,unused-argument @@ -136,16 +147,51 @@ def __init__(self, app: Application, pyctuator_impl: PyctuatorImpl) -> None: app.settings.setdefault("pyctuator_router", self) app.settings.setdefault("custom_dumps", custom_dumps) - app.add_handlers(".*$", [ - (r"/pyctuator", PyctuatorHandler), - (r"/pyctuator/env", EnvHandler), - (r"/pyctuator/info", InfoHandler), - (r"/pyctuator/health", HealthHandler), - (r"/pyctuator/metrics", MetricsHandler), - (r"/pyctuator/metrics/(?P.*$)", MetricsNameHandler), - (r"/pyctuator/loggers", LoggersHandler), - (r"/pyctuator/loggers/(?P.*$)", LoggersNameHandler), - ]) + + # Register a log-function that records request and response in traces and than delegates to the original func + self.delegate_log_function = app.settings.get("log_function") + app.settings.setdefault("log_function", self._intercept_request_and_response) + + app.add_handlers( + ".*$", + [ + (r"/pyctuator", PyctuatorHandler), + (r"/pyctuator/env", EnvHandler), + (r"/pyctuator/info", InfoHandler), + (r"/pyctuator/health", HealthHandler), + (r"/pyctuator/metrics", MetricsHandler), + (r"/pyctuator/metrics/(?P.*$)", MetricsNameHandler), + (r"/pyctuator/loggers", LoggersHandler), + (r"/pyctuator/loggers/(?P.*$)", LoggersNameHandler), + (r"/pyctuator/dump", ThreadDumpHandler), + (r"/pyctuator/threaddump", ThreadDumpHandler), + (r"/pyctuator/logfile", LogFileHandler), + (r"/pyctuator/trace", HttpTraceHandler), + (r"/pyctuator/httptrace", HttpTraceHandler), + ] + ) + + def _intercept_request_and_response(self, handler: RequestHandler) -> None: + # Record the request and response + record = TraceRecord( + timestamp=datetime.now() - timedelta(seconds=handler.request.request_time()), + principal=None, + session=None, + request=TraceRequest( + method=handler.request.method or "", + uri=handler.request.full_url(), + headers={k.lower(): v for k, v in handler.request.headers.items()} + ), + response=TraceResponse( + status=handler.get_status(), + headers={k.lower(): [v] for k, v in handler._headers.items()} # pylint: disable=protected-access + ), + timeTaken=int(handler.request.request_time() * 1000), + ) + self.pyctuator_impl.http_tracer.add_record(record) + + if self.delegate_log_function: + self.delegate_log_function(handler) def _custom_json_serializer(self, value: Any) -> Any: if dataclasses.is_dataclass(value): diff --git a/tests/test_pyctuator_e2e.py b/tests/test_pyctuator_e2e.py index 7dd3f02..941a87f 100644 --- a/tests/test_pyctuator_e2e.py +++ b/tests/test_pyctuator_e2e.py @@ -22,9 +22,12 @@ # mypy: ignore_errors +from tests.tornado_test_server import TornadoPyctuatorServer + + @pytest.fixture( - params=[FastApiPyctuatorServer, FlaskPyctuatorServer, AiohttpPyctuatorServer], - ids=["FastAPI", "Flask", "aiohttp"] + params=[FastApiPyctuatorServer, FlaskPyctuatorServer, AiohttpPyctuatorServer, TornadoPyctuatorServer], + ids=["FastAPI", "Flask", "aiohttp", "Tornado"] ) def pyctuator_server(request) -> Generator: # type: ignore # Start a the web-server in which the pyctuator is integrated diff --git a/tests/tornado_test_server.py b/tests/tornado_test_server.py new file mode 100644 index 0000000..99d2e9d --- /dev/null +++ b/tests/tornado_test_server.py @@ -0,0 +1,84 @@ +import logging +import threading +import time +from typing import Optional + +from tornado import ioloop +from tornado.httpserver import HTTPServer +from tornado.web import Application, RequestHandler + +from pyctuator.pyctuator import Pyctuator +from tests.conftest import PyctuatorServer + + +class TornadoPyctuatorServer(PyctuatorServer): + def __init__(self) -> None: + + # pylint: disable=abstract-method + class LogfileTestRepeater(RequestHandler): + def get(self) -> None: + repeated_string = self.get_argument("repeated_string") + logging.error(repeated_string) + self.write(repeated_string) + + # pylint: disable=abstract-method + class GetHttptraceTestUrl(RequestHandler): + def get(self) -> None: + sleep_sec: Optional[str] = self.get_argument("sleep_sec", None) + # Sleep if requested to sleep - used for asserting httptraces timing + if sleep_sec: + logging.info("Sleeping %s seconds before replying", sleep_sec) + time.sleep(int(sleep_sec)) + + # Echo 'User-Data' header as 'resp-data' - used for asserting headers are captured properly + self.add_header("resp-data", str(self.request.headers.get("User-Data"))) + self.write("my content") + + self.app = Application( + [ + ("/logfile_test_repeater", LogfileTestRepeater), + ("/httptrace_test_url", GetHttptraceTestUrl) + ], + debug=True) + + self.pyctuator = Pyctuator( + self.app, + "Tornado Pyctuator", + app_url=f"http://localhost:5000", + pyctuator_endpoint_url=f"http://localhost:5000/pyctuator", + registration_url=f"http://localhost:8001/register", + app_description="Demonstrate Spring Boot Admin Integration with Tornado", + registration_interval_sec=1, + ) + + self.io_loop: Optional[ioloop.IOLoop] = None + self.http_server = HTTPServer(self.app, decompress_request=True) + self.thread = threading.Thread(target=self._start_in_thread) + + def _start_in_thread(self) -> None: + self.io_loop = ioloop.IOLoop() + self.http_server.listen(5000) + self.io_loop.start() + + def start(self) -> None: + logging.info("Starting Tornado server") + self.thread.start() + time.sleep(0.5) + + def stop(self) -> None: + logging.info("Stopping Tornado server") + self.pyctuator.stop() + + # Allow the recurring registration to complete any in-progress request before stopping Tornado + time.sleep(1) + + assert self.io_loop is not None + + self.http_server.stop() + self.io_loop.add_callback(self.io_loop.stop) + self.thread.join() + self.io_loop.close(all_fds=True) + + def atexit(self) -> None: + if self.pyctuator.boot_admin_registration_handler: + self.pyctuator.boot_admin_registration_handler.deregister_from_admin_server() From 808f488f9d7c94cf7c233bf8628733f214a42c0f Mon Sep 17 00:00:00 2001 From: "michael.yak" Date: Sun, 4 Oct 2020 08:42:27 +0300 Subject: [PATCH 5/5] Bump pyctuator version to 0.13.1 --- examples/Advanced/pyproject.toml | 2 +- examples/FastAPI/pyproject.toml | 2 +- examples/Flask/pyproject.toml | 2 +- examples/aiohttp/pyproject.toml | 2 +- examples/tornado/pyproject.toml | 2 +- pyproject.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/Advanced/pyproject.toml b/examples/Advanced/pyproject.toml index a44c927..7e13731 100644 --- a/examples/Advanced/pyproject.toml +++ b/examples/Advanced/pyproject.toml @@ -11,7 +11,7 @@ python = "^3.7" psutil = { version = "^5.6" } fastapi = { version = "^0.41.0" } uvicorn = { version = "^0.9.0" } -pyctuator = { version = "^0.13" } +pyctuator = { version = "^0.13.1" } sqlalchemy = { version = "^1.3" } PyMySQL = { version = "^0.9.3" } cryptography = { version = "^2.8" } diff --git a/examples/FastAPI/pyproject.toml b/examples/FastAPI/pyproject.toml index 1bd5325..07d8ba3 100644 --- a/examples/FastAPI/pyproject.toml +++ b/examples/FastAPI/pyproject.toml @@ -11,7 +11,7 @@ python = "^3.7" psutil = { version = "^5.6" } fastapi = { version = "^0.41.0" } uvicorn = { version = "^0.9.0" } -pyctuator = { version = "^0.13" } +pyctuator = { version = "^0.13.1" } [build-system] requires = ["poetry>=0.12"] diff --git a/examples/Flask/pyproject.toml b/examples/Flask/pyproject.toml index fd37620..0b62727 100644 --- a/examples/Flask/pyproject.toml +++ b/examples/Flask/pyproject.toml @@ -10,7 +10,7 @@ authors = [ python = "^3.7" psutil = { version = "^5.6" } flask = { version = "^1.1" } -pyctuator = { version = "^0.13" } +pyctuator = { version = "^0.13.1" } [build-system] requires = ["poetry>=0.12"] diff --git a/examples/aiohttp/pyproject.toml b/examples/aiohttp/pyproject.toml index 351f704..e8ed9ed 100644 --- a/examples/aiohttp/pyproject.toml +++ b/examples/aiohttp/pyproject.toml @@ -10,7 +10,7 @@ authors = [ python = "^3.7" psutil = { version = "^5.6" } aiohttp = { version = "^3.5.4" } -pyctuator = { version = "^0.13" } +pyctuator = { version = "^0.13.1" } [build-system] requires = ["poetry>=0.12"] diff --git a/examples/tornado/pyproject.toml b/examples/tornado/pyproject.toml index 9e3acb6..667ef34 100644 --- a/examples/tornado/pyproject.toml +++ b/examples/tornado/pyproject.toml @@ -10,7 +10,7 @@ authors = [ python = "^3.7" psutil = { version = "^5.6" } tornado = { version = "^6.0.4" } -pyctuator = { version = "^0.13" } +pyctuator = { version = "^0.13.1" } [build-system] requires = ["poetry>=0.12"] diff --git a/pyproject.toml b/pyproject.toml index e7dba9e..2489324 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyctuator" -version = "0.13" +version = "0.13.1" description = "A Python implementation of the Spring Actuator API for popular web frameworks" authors = [ "Michael Yakobi ",