From e01e4dbfa664afbf07284624248e02ad85d0a07b Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Sun, 1 Dec 2019 22:52:13 +0100 Subject: [PATCH 1/5] Fix pylint not executed in service folder --- Pipfile.lock | 30 +++++++++++---------- pylintrc | 4 +-- pyms/flask/services/__init__.py | 0 pyms/flask/services/metrics.py | 8 +++--- pyms/flask/services/requests.py | 47 +++++++++++++-------------------- pyms/flask/services/tracer.py | 26 +++++++++++++++--- pyms/logger/logger.py | 19 ++----------- 7 files changed, 66 insertions(+), 68 deletions(-) create mode 100644 pyms/flask/services/__init__.py diff --git a/Pipfile.lock b/Pipfile.lock index 1c5ee6b..8a1fae8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,10 +1,12 @@ { "_meta": { "hash": { - "sha256": "46717e53a0a7b838196e1d835cd574369f0cccc9a4ecb42c2e96577663ff8ee1" + "sha256": "d634f827a2aa71d150166251feb6d72cec56d7bb08884628ca1316be98c36b55" }, "pipfile-spec": 6, - "requires": {}, + "requires": { + "python_version": "3.6" + }, "sources": [ { "name": "pypi", @@ -104,11 +106,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:b044f07694ef14a6683b097ba56bd081dbc7cdc7c7fe46011e499dfecc082f21", + "sha256:e6ac600a142cf2db707b1998382cc7fc3b02befb7273876e01b8ad10b9652742" ], "markers": "python_version < '3.8'", - "version": "==0.23" + "version": "==1.1.0" }, "inflection": { "hashes": [ @@ -179,10 +181,10 @@ }, "more-itertools": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2", + "sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45" ], - "version": "==7.2.0" + "version": "==8.0.0" }, "openapi-spec-validator": { "hashes": [ @@ -451,11 +453,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:b044f07694ef14a6683b097ba56bd081dbc7cdc7c7fe46011e499dfecc082f21", + "sha256:e6ac600a142cf2db707b1998382cc7fc3b02befb7273876e01b8ad10b9652742" ], "markers": "python_version < '3.8'", - "version": "==0.23" + "version": "==1.1.0" }, "isort": { "hashes": [ @@ -576,10 +578,10 @@ }, "more-itertools": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2", + "sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45" ], - "version": "==7.2.0" + "version": "==8.0.0" }, "opentracing": { "hashes": [ diff --git a/pylintrc b/pylintrc index 93b35c4..405d234 100644 --- a/pylintrc +++ b/pylintrc @@ -55,7 +55,7 @@ confidence= # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=logging-format-interpolation,broad-except,unnecessary-pass,no-member,line-too-long,invalid-name, - missing-module-docstring,missing-class-docstring,missing-function-docstring + missing-module-docstring,missing-class-docstring,missing-function-docstring,too-few-public-methods # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -398,7 +398,7 @@ valid-metaclass-classmethod-first-arg=mcs [DESIGN] # Maximum number of arguments for function / method -max-args=5 +max-args=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 diff --git a/pyms/flask/services/__init__.py b/pyms/flask/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyms/flask/services/metrics.py b/pyms/flask/services/metrics.py index 81575dd..ec75268 100644 --- a/pyms/flask/services/metrics.py +++ b/pyms/flask/services/metrics.py @@ -42,20 +42,22 @@ def __init__(self, service, *args, **kwargs): self.metrics_blueprint = Blueprint("metrics", __name__) self.serve_metrics() - def monitor(self, app): + @staticmethod + def monitor(app): app.before_request(before_request) app.after_request(after_request) def serve_metrics(self): @self.metrics_blueprint.route("/metrics", methods=["GET"]) - def metrics(): + def metrics(): # pylint: disable=unused-variable return Response( generate_latest(), mimetype="text/print()lain", content_type="text/plain; charset=utf-8", ) - def add_logger_handler(self, logger, service_name): + @staticmethod + def add_logger_handler(logger, service_name): logger.addHandler(MetricsLogHandler(service_name)) return logger diff --git a/pyms/flask/services/requests.py b/pyms/flask/services/requests.py index 11eaf34..fba5e4f 100644 --- a/pyms/flask/services/requests.py +++ b/pyms/flask/services/requests.py @@ -2,21 +2,20 @@ """ import logging -import opentracing import requests -from flask import current_app, request -from opentracing_instrumentation import get_current_span +from flask import request from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry +from urllib3.util.retry import Retry from pyms.constants import LOGGER_NAME from pyms.flask.services.driver import DriverService +from pyms.flask.services.tracer import inject_span_in_headers logger = logging.getLogger(LOGGER_NAME) DEFAULT_RETRIES = 3 -DEFAULT_STATUS_RETRIES = (500, 502, 504) +DEFAULTstatus_retries = (500, 502, 504) def retry(f): @@ -24,8 +23,8 @@ def wrapper(*args, **kwargs): response = False i = 0 response_ok = False - retries = args[0]._retries - status_retries = args[0]._status_retries + retries = args[0].retries + status_retries = args[0].status_retries while i < retries and response_ok is False: response = f(*args, **kwargs) i += 1 @@ -45,16 +44,16 @@ class Service(DriverService): "data": "" } tracer = None - _retries = DEFAULT_RETRIES - _status_retries = DEFAULT_STATUS_RETRIES + retries = DEFAULT_RETRIES + status_retries = DEFAULTstatus_retries _propagate_headers = False def __init__(self, service, *args, **kwargs): """Initialization for trace headers propagation""" super().__init__(service, *args, **kwargs) if self.exists_config(): - self._retries = self.config.retries or DEFAULT_RETRIES - self._status_retries = self.config.status_retries or DEFAULT_STATUS_RETRIES + self.retries = self.config.retries or DEFAULT_RETRIES + self.status_retries = self.config.status_retries or DEFAULTstatus_retries self._propagate_headers = self.config.propagate_headers def requests(self, session: requests.Session): @@ -67,18 +66,19 @@ def requests(self, session: requests.Session): :return: """ session_r = session or requests.Session() - retry = Retry( - total=self._retries, - read=self._retries, - connect=self._retries, + max_retries = Retry( + total=self.retries, + read=self.retries, + connect=self.retries, backoff_factor=0.3, - status_forcelist=self._status_retries, + status_forcelist=self.status_retries, ) - adapter = HTTPAdapter(max_retries=retry) + adapter = HTTPAdapter(max_retries=max_retries) session_r.mount('http://', adapter) session_r.mount('https://', adapter) return session_r + @staticmethod def insert_trace_headers(self, headers: dict) -> dict: """Inject trace headers if enabled. @@ -88,14 +88,7 @@ def insert_trace_headers(self, headers: dict) -> dict: """ try: - # FLASK https://github.com/opentracing-contrib/python-flask - span = self.tracer.get_span(request=request) - if not span: # pragma: no cover - span = get_current_span() - if not span: - span = self.tracer.tracer.start_span() - context = span.context if span else None - self._tracer.tracer.inject(context, opentracing.Format.HTTP_HEADERS, headers) + headers = inject_span_in_headers(headers) except Exception as ex: logger.debug("Tracer error {}".format(ex)) return headers @@ -118,9 +111,7 @@ def _get_headers(self, headers, propagate_headers=False): if not headers: headers = {} - self.tracer = current_app.tracer - if self.tracer: - headers = self.insert_trace_headers(headers) + headers = self.insert_trace_headers(headers) if self._propagate_headers or propagate_headers: headers = self.propagate_headers(headers) return headers diff --git a/pyms/flask/services/tracer.py b/pyms/flask/services/tracer.py index dcc6978..eae6047 100644 --- a/pyms/flask/services/tracer.py +++ b/pyms/flask/services/tracer.py @@ -1,6 +1,9 @@ import logging +import opentracing +from flask import current_app, request from jaeger_client.metrics.prometheus import PrometheusMetricsFactory +from opentracing_instrumentation import get_current_span from pyms.config.conf import get_conf from pyms.constants import LOGGER_NAME @@ -14,6 +17,20 @@ DEFAULT_CLIENT = JAEGER_CLIENT +def inject_span_in_headers(headers): + # FLASK https://github.com/opentracing-contrib/python-flask + tracer = current_app.tracer + # Add traces + span = None + if tracer: + span = tracer.get_span(request=request) + if not span: # pragma: no cover + span = get_current_span() + if not span: + span = tracer.tracer.start_span() + context = span.context if span else None + tracer.tracer.inject(context, opentracing.Format.HTTP_HEADERS, headers) + return headers class Service(DriverService): service = "tracer" @@ -52,10 +69,11 @@ def init_jaeger_tracer(self): metrics = PrometheusMetricsFactory() config = Config( config={ - **{'sampler': { - 'type': 'const', - 'param': 1, - }, + **{ + 'sampler': { + 'type': 'const', + 'param': 1, + }, 'propagation': 'b3', 'logging': True }, diff --git a/pyms/logger/logger.py b/pyms/logger/logger.py index fbf9732..922b47a 100644 --- a/pyms/logger/logger.py +++ b/pyms/logger/logger.py @@ -3,12 +3,10 @@ import datetime import logging -import opentracing -from flask import request, current_app -from opentracing_instrumentation import get_current_span from pythonjsonlogger import jsonlogger from pyms.constants import LOGGER_NAME +from pyms.flask.services.tracer import inject_span_in_headers logger = logging.getLogger(LOGGER_NAME + "-tracer") @@ -30,20 +28,7 @@ def add_fields(self, log_record, record, message_dict): log_record["service"] = self.service_name try: - # FLASK https://github.com/opentracing-contrib/python-flask - self.tracer = current_app.tracer - # Add traces - span = None - if self.tracer: - span = self.tracer.get_span(request=request) - if not span: # pragma: no cover - span = get_current_span() - if not span: - span = self.tracer.tracer.start_span() - - headers = {} - context = span.context if span else None - self.tracer.tracer.inject(context, opentracing.Format.HTTP_HEADERS, headers) + headers = inject_span_in_headers({}) log_record["trace"] = headers.get('X-B3-TraceId', "") log_record["span"] = headers.get('X-B3-SpanId', "") log_record["parent"] = headers.get('X-B3-ParentSpanId', "") From 81c26be64a52daf88cf1417e3cdd813396d672d3 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Sun, 1 Dec 2019 23:09:24 +0100 Subject: [PATCH 2/5] Fix SOLID in requests method --- pyms/flask/services/requests.py | 8 ++++++-- pyms/flask/services/tracer.py | 2 ++ tests/config-tests-requests-no-data.yml | 4 ---- tests/config-tests-requests.yml | 16 ++++++++++++++++ tests/test_requests.py | 2 +- 5 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 tests/config-tests-requests.yml diff --git a/pyms/flask/services/requests.py b/pyms/flask/services/requests.py index fba5e4f..cf98547 100644 --- a/pyms/flask/services/requests.py +++ b/pyms/flask/services/requests.py @@ -79,7 +79,7 @@ def requests(self, session: requests.Session): return session_r @staticmethod - def insert_trace_headers(self, headers: dict) -> dict: + def insert_trace_headers(headers: dict) -> dict: """Inject trace headers if enabled. :param headers: dictionary of HTTP Headers to send. @@ -111,7 +111,6 @@ def _get_headers(self, headers, propagate_headers=False): if not headers: headers = {} - headers = self.insert_trace_headers(headers) if self._propagate_headers or propagate_headers: headers = self.propagate_headers(headers) return headers @@ -154,6 +153,7 @@ def get(self, url, path_params=None, params=None, headers=None, propagate_header :param params: (optional) Dictionary, list of tuples or bytes to send in the body of the :class:`Request` (as query string parameters) :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param propagate_headers: Optional arguments that ``request`` takes. :param kwargs: Optional arguments that ``request`` takes. :return: :class:`Response ` object :rtype: requests.Response @@ -161,6 +161,7 @@ def get(self, url, path_params=None, params=None, headers=None, propagate_header full_url = self._build_url(url, path_params) headers = self._get_headers(headers=headers, propagate_headers=propagate_headers) + headers = self.insert_trace_headers(headers) logger.debug("Get with url {}, params {}, headers {}, kwargs {}". format(full_url, params, headers, kwargs)) @@ -202,6 +203,7 @@ def post(self, url, path_params=None, data=None, json=None, headers=None, **kwar full_url = self._build_url(url, path_params) headers = self._get_headers(headers) + headers = self.insert_trace_headers(headers) logger.debug("Post with url {}, data {}, json {}, headers {}, kwargs {}".format(full_url, data, json, headers, kwargs)) @@ -245,6 +247,7 @@ def put(self, url, path_params=None, data=None, headers=None, **kwargs): full_url = self._build_url(url, path_params) headers = self._get_headers(headers) + headers = self.insert_trace_headers(headers) logger.debug("Put with url {}, data {}, headers {}, kwargs {}".format(full_url, data, headers, kwargs)) @@ -285,6 +288,7 @@ def delete(self, url, path_params=None, headers=None, **kwargs): full_url = self._build_url(url, path_params) headers = self._get_headers(headers) + headers = self.insert_trace_headers(headers) logger.debug("Delete with url {}, headers {}, kwargs {}".format(full_url, headers, kwargs)) session = requests.Session() diff --git a/pyms/flask/services/tracer.py b/pyms/flask/services/tracer.py index eae6047..eda96e1 100644 --- a/pyms/flask/services/tracer.py +++ b/pyms/flask/services/tracer.py @@ -17,6 +17,7 @@ DEFAULT_CLIENT = JAEGER_CLIENT + def inject_span_in_headers(headers): # FLASK https://github.com/opentracing-contrib/python-flask tracer = current_app.tracer @@ -32,6 +33,7 @@ def inject_span_in_headers(headers): tracer.tracer.inject(context, opentracing.Format.HTTP_HEADERS, headers) return headers + class Service(DriverService): service = "tracer" default_values = { diff --git a/tests/config-tests-requests-no-data.yml b/tests/config-tests-requests-no-data.yml index 0fbe499..488f4a9 100644 --- a/tests/config-tests-requests-no-data.yml +++ b/tests/config-tests-requests-no-data.yml @@ -3,10 +3,6 @@ pyms: swagger: path: "" file: "swagger.yaml" - tracer: - client: "jaeger" - host: "localhost" - component_name: "Python Microservice" my-ms: DEBUG: true TESTING: true diff --git a/tests/config-tests-requests.yml b/tests/config-tests-requests.yml new file mode 100644 index 0000000..b7b046e --- /dev/null +++ b/tests/config-tests-requests.yml @@ -0,0 +1,16 @@ +pyms: + requests: + data: data + swagger: + path: "" + file: "swagger.yaml" +my-ms: + DEBUG: true + TESTING: true + APP_NAME: "Python Microservice" + APPLICATION_ROOT: / + test_var: general + subservice1: + test: input + subservice2: + test: output diff --git a/tests/test_requests.py b/tests/test_requests.py index 6ef89f2..a615089 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -46,7 +46,7 @@ class RequestServiceTests(unittest.TestCase): BASE_DIR = os.path.dirname(os.path.abspath(__file__)) def setUp(self): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(self.BASE_DIR, "config-tests.yml") + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(self.BASE_DIR, "config-tests-requests.yml") ms = Microservice(service="my-ms", path=__file__) self.app = ms.create_app() self.request = ms.requests From 8befbbc4dd564bfd81bda7c911e378a1c90f71cf Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Sun, 1 Dec 2019 23:13:07 +0100 Subject: [PATCH 3/5] Added Flake8 to CI --- Pipfile | 1 + pyms/flask/healthcheck/__init__.py | 3 +++ tox.ini | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index d07442a..28b4429 100644 --- a/Pipfile +++ b/Pipfile @@ -21,6 +21,7 @@ coverage = "==4.4.0" pytest = "*" pytest-cov = "*" pylint = "*" +flake8 = "*" tox = "*" safety = "*" bandit = "*" diff --git a/pyms/flask/healthcheck/__init__.py b/pyms/flask/healthcheck/__init__.py index 7083aa1..09693a8 100644 --- a/pyms/flask/healthcheck/__init__.py +++ b/pyms/flask/healthcheck/__init__.py @@ -1 +1,4 @@ from pyms.flask.healthcheck.healthcheck import healthcheck_blueprint + + +__all__ = ['healthcheck_blueprint'] diff --git a/tox.ini b/tox.ini index 00c5788..8a38ae0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py36,pylint,safety,bandit,docs +envlist = py36,pylint,flake8,safety,bandit,docs [testenv] deps = -rrequirements-tests.txt @@ -23,6 +23,9 @@ commands = safety check -r requirements-tests.txt [testenv:pylint] basepython = python3.6 commands = pylint --rcfile={toxinidir}/pylintrc {toxinidir}/pyms +[testenv:flake8] +basepython = python3.6 +commands = flake8 --ignore=E501 {toxinidir}/pyms [testenv:docs] basepython = python3.6 skipsdist = True From 44012f7f9a5279829cf47b6a32f4a8fa2250612e Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Sun, 1 Dec 2019 23:16:27 +0100 Subject: [PATCH 4/5] Updated pipfile.lock --- Pipfile.lock | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Pipfile.lock b/Pipfile.lock index 8a1fae8..b889faa 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d634f827a2aa71d150166251feb6d72cec56d7bb08884628ca1316be98c36b55" + "sha256": "350a6f5c53072dd5b3bdd337c3245e51f274ab6e96a9cbaac66f4f85145de094" }, "pipfile-spec": 6, "requires": { @@ -417,6 +417,13 @@ ], "version": "==0.4.1" }, + "entrypoints": { + "hashes": [ + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + ], + "version": "==0.3" + }, "filelock": { "hashes": [ "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", @@ -424,6 +431,14 @@ ], "version": "==3.0.12" }, + "flake8": { + "hashes": [ + "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", + "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" + ], + "index": "pypi", + "version": "==3.7.9" + }, "gitdb2": { "hashes": [ "sha256:1b6df1433567a51a4a9c1a5a0de977aa351a405cc56d7d35f3388bad1f630350", @@ -639,6 +654,20 @@ ], "version": "==1.8.0" }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "version": "==2.5.0" + }, + "pyflakes": { + "hashes": [ + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + ], + "version": "==2.1.1" + }, "pylint": { "hashes": [ "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", From 1dbab481a2b6b1c97e3835501877e0ccbb1461fa Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 2 Dec 2019 19:51:18 +0100 Subject: [PATCH 5/5] Updated requirements tests --- requirements-tests.txt | 55 +++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/requirements-tests.txt b/requirements-tests.txt index a61f2d2..760e841 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,88 +1,93 @@ anyconfig==0.9.10 -astroid==2.3.2 +astroid==2.3.3 atomicwrites==1.3.0 attrs==19.3.0 bandit==1.6.2 -basictracer==3.1.0 +basictracer==3.0.0 bleach==3.1.0 -certifi==2019.9.11 +certifi==2019.11.28 chardet==3.0.4 Click==7.0 clickclick==1.2.2 -connexion==2018.0.dev1 +connexion==2.4.0 contextlib2==0.6.0.post1 coverage==4.4 coveralls==1.8.2 docopt==0.6.2 docutils==0.14 dparse==0.4.1 +entrypoints==0.3 filelock==3.0.12 +flake8==3.7.9 Flask==1.1.1 Flask-OpenTracing==1.1.0 future==0.18.2 gitdb2==2.0.6 -GitPython==3.0.4 +GitPython==3.0.5 googleapis-common-protos==1.6.0 idna==2.8 -importlib-metadata==0.23 +importlib-metadata==1.1.0 inflection==0.3.1 isort==4.3.21 itsdangerous==1.1.0 jaeger-client==4.1.0 Jinja2==2.10.3 jsonpickle==1.2 -jsonschema==2.6.0 +jsonschema==3.2.0 lazy-object-proxy==1.4.3 -lightstep==4.2.0 +lightstep==4.1.0 livereload==2.6.1 Markdown==3.1.1 MarkupSafe==1.1.1 mccabe==0.6.1 mkdocs==1.0.4 mock==2.0.0 -more-itertools==7.2.0 +more-itertools==8.0.0 nose==1.3.7 -numpy==1.16.1 +numpy==1.13.3 openapi-spec-validator==0.2.8 opentracing==2.2.0 opentracing-instrumentation==3.2.1 packaging==19.2 -pbr==5.4.3 +pbr==5.4.4 pkginfo==1.5.0.1 -pluggy==0.13.0 -protobuf==3.9.0rc1 +pluggy==0.13.1 +prometheus-client==0.7.1 +protobuf==3.11.0 py==1.8.0 -py-ms==1.0.1 +py-ms==1.0.0 +pycodestyle==2.5.0 +pyflakes==2.1.1 Pygments==2.3.1 -pylint==2.4.3 -pyparsing==2.4.2 -pytest==5.2.2 +pylint==2.4.4 +pyparsing==2.4.5 +pyrsistent==0.15.6 +pytest==5.3.1 pytest-cov==2.8.1 python-coveralls==2.9.1 python-json-logger==0.1.11 -PyYAML==5.1.2 +PyYAML==5.2b1 readme-renderer==24.0 requests==2.22.0 requests-mock==1.7.0 requests-toolbelt==0.8.0 safety==1.8.5 -six==1.12.0 +six==1.13.0 smmap2==2.0.5 stevedore==1.31.0 -swagger-ui-bundle==0.0.5 +swagger-ui-bundle==0.0.6 threadloop==1.0.2 -thrift==0.11.0 +thrift==0.13.0 toml==0.10.0 tornado==5.1.1 -tox==3.14.0 +tox==3.14.1 tqdm==4.30.0 twine==1.12.1 typed-ast==1.4.0 -urllib3==1.25.6 -virtualenv==16.7.7 +urllib3==1.25.7 +virtualenv==16.7.8 wcwidth==0.1.7 webencodings==0.5.1 Werkzeug==0.16.0 wrapt==1.11.2 zipp==0.6.0 -prometheus_client==0.7.1