diff --git a/examples/microservice_configuration/config.yml b/examples/microservice_configuration/config.yml index 84d800b..edfc2d1 100644 --- a/examples/microservice_configuration/config.yml +++ b/examples/microservice_configuration/config.yml @@ -4,7 +4,7 @@ pyms: swagger: path: "" file: "swagger.yaml" -my-configure-microservice: +ms: DEBUG: true TESTING: false APP_NAME: "Python Microservice" diff --git a/examples/microservice_swagger/config.yml b/examples/microservice_swagger/config.yml index 482c399..15452b2 100644 --- a/examples/microservice_swagger/config.yml +++ b/examples/microservice_swagger/config.yml @@ -2,6 +2,7 @@ pyms: swagger: path: "" file: "swagger.yaml" + url: "ws-doc/" my-ms: DEBUG: true TESTING: false diff --git a/pyms/config/confile.py b/pyms/config/confile.py index 7ff9470..7f7dc96 100644 --- a/pyms/config/confile.py +++ b/pyms/config/confile.py @@ -31,10 +31,6 @@ def __init__(self, *args, **kwargs): else: raise ConfigDoesNotFoundException("Configuration file not found") - logger.debug("[CONF] INIT: Settings {kwargs}".format( - kwargs=kwargs, - )) - config = dict(self.normalize_config(config)) _ = [setattr(self, k, v) for k, v in config.items()] super(ConfFile, self).__init__(config) diff --git a/pyms/flask/app/create_app.py b/pyms/flask/app/create_app.py index bc68ce8..4c14e7b 100644 --- a/pyms/flask/app/create_app.py +++ b/pyms/flask/app/create_app.py @@ -2,7 +2,6 @@ import os from typing import Text -import connexion from flask import Flask from flask_opentracing import FlaskTracing @@ -50,7 +49,7 @@ def __init__(self, *args, **kwargs): def init_services(self): service_manager = ServicesManager() - for service_name, service in service_manager.get_services(): + for service_name, service in service_manager.get_services(memoize=self._singleton): setattr(self, service_name, service) def init_libs(self): @@ -81,17 +80,7 @@ def init_logger(self): def init_app(self) -> Flask: if self._exists_service("swagger"): - check_package_exists("connexion") - app = connexion.App(__name__, specification_dir=os.path.join(self.path, self.swagger.path)) - app.add_api( - self.swagger.file, - arguments={'title': self.config.APP_NAME}, - base_path=self.config.APPLICATION_ROOT - ) - - # Invert the objects, instead connexion with a Flask object, a Flask object with - application = app.app - application.connexion_app = app + application = self.swagger.init_app(config=self.config, path=self.path) else: check_package_exists("flask") application = Flask(__name__, static_folder=os.path.join(self.path, 'static'), diff --git a/pyms/flask/services/driver.py b/pyms/flask/services/driver.py index 3c8fd60..7cc1c6e 100644 --- a/pyms/flask/services/driver.py +++ b/pyms/flask/services/driver.py @@ -13,7 +13,7 @@ class DriverService: def __init__(self, service, *args, **kwargs): self.service = ".".join([service, self.service]) - self.config = get_conf(service=self.service, empty_init=True) + self.config = get_conf(service=self.service, empty_init=True, memoize=kwargs.get("memoize", True)) def __getattr__(self, attr, *args, **kwargs): config_attribute = getattr(self.config, attr) @@ -31,8 +31,8 @@ def __init__(self, service=None): self.service = (service if service else SERVICE_BASE) self.config = get_conf(service=self.service, empty_init=True, memoize=False) - def get_services(self): - return ((k, self.get_service(k)) for k in self.config.__dict__.keys() if k not in ['empty_init', ]) + def get_services(self, memoize): + return ((k, self.get_service(k, memoize=memoize)) for k in self.config.__dict__.keys() if k not in ['empty_init', ]) def get_service(self, service, *args, **kwargs): service_object = import_from("pyms.flask.services.{}".format(service), "Service") diff --git a/pyms/flask/services/requests.py b/pyms/flask/services/requests.py index cf98547..89b53c4 100644 --- a/pyms/flask/services/requests.py +++ b/pyms/flask/services/requests.py @@ -15,7 +15,7 @@ DEFAULT_RETRIES = 3 -DEFAULTstatus_retries = (500, 502, 504) +DEFAULT_STATUS_RETRIES = (500, 502, 504) def retry(f): @@ -41,20 +41,12 @@ def wrapper(*args, **kwargs): class Service(DriverService): service = "requests" default_values = { - "data": "" + "data": "", + "retries": DEFAULT_RETRIES, + "status_retries": DEFAULT_STATUS_RETRIES, + "propagate_headers": False, } tracer = None - 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 DEFAULTstatus_retries - self._propagate_headers = self.config.propagate_headers def requests(self, session: requests.Session): """ @@ -94,7 +86,7 @@ def insert_trace_headers(headers: dict) -> dict: return headers @staticmethod - def propagate_headers(headers: dict) -> dict: + def set_propagate_headers(headers: dict) -> dict: for k, v in request.headers: if not headers.get(k): headers.update({k: v}) @@ -112,7 +104,7 @@ def _get_headers(self, headers, propagate_headers=False): headers = {} if self._propagate_headers or propagate_headers: - headers = self.propagate_headers(headers) + headers = self.set_propagate_headers(headers) return headers @staticmethod @@ -137,8 +129,8 @@ def parse_response(self, response): try: data = response.json() - if self.config.data: - data = data.get(self.config.data, {}) + if self.data: + data = data.get(self.data, {}) return data except ValueError: logger.warning("Response.content is not a valid json {}".format(response.content)) diff --git a/pyms/flask/services/swagger.py b/pyms/flask/services/swagger.py index 4bdf166..0a68a9b 100644 --- a/pyms/flask/services/swagger.py +++ b/pyms/flask/services/swagger.py @@ -1,12 +1,33 @@ +import os + +import connexion + from pyms.flask.services.driver import DriverService +from pyms.utils import check_package_exists SWAGGER_PATH = "swagger" SWAGGER_FILE = "swagger.yaml" +SWAGGER_URL = "ui/" class Service(DriverService): service = "swagger" default_values = { "path": SWAGGER_PATH, - "file": SWAGGER_FILE + "file": SWAGGER_FILE, + "url": SWAGGER_URL } + + def init_app(self, config, path): + check_package_exists("connexion") + app = connexion.App(__name__, specification_dir=os.path.join(path, self.path)) + app.add_api( + self.file, + arguments={'title': config.APP_NAME}, + base_path=config.APPLICATION_ROOT, + options={"swagger_url": self.url} + ) + # Invert the objects, instead connexion with a Flask object, a Flask object with + application = app.app + application.connexion_app = app + return application diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 0000000..46d913a --- /dev/null +++ b/tests/common.py @@ -0,0 +1,8 @@ +from pyms.flask.app import Microservice + +class MyMicroserviceNoSingleton(Microservice): + _singleton = False + + +class MyMicroservice(Microservice): + pass diff --git a/tests/config-tests-flask-swagger.yml b/tests/config-tests-flask-swagger.yml new file mode 100644 index 0000000..ece9173 --- /dev/null +++ b/tests/config-tests-flask-swagger.yml @@ -0,0 +1,14 @@ +pyms: + swagger: + path: "" + file: "swagger.yaml" +my-ms: + DEBUG: true + TESTING: true + APP_NAME: "Python Microservice With Flask" + APPLICATION_ROOT: / + test_var: general + subservice1: + test: input + subservice2: + test: output \ No newline at end of file diff --git a/tests/config-tests-swagger.yml b/tests/config-tests-swagger.yml new file mode 100644 index 0000000..37aa37e --- /dev/null +++ b/tests/config-tests-swagger.yml @@ -0,0 +1,10 @@ +pyms: + swagger: + path: "" + file: "swagger.yaml" + url: "ws-doc/" +my-ms: + DEBUG: true + TESTING: true + APP_NAME: "Python Microservice Swagger" + APPLICATION_ROOT: / \ No newline at end of file diff --git a/tests/test_flask.py b/tests/test_flask.py index 4c6638f..e00dbd5 100644 --- a/tests/test_flask.py +++ b/tests/test_flask.py @@ -1,12 +1,12 @@ import os import unittest -from unittest import mock import pytest from flask import current_app from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, SERVICE_ENVIRONMENT from pyms.flask.app import Microservice, config +from tests.common import MyMicroserviceNoSingleton, MyMicroservice def home(): @@ -14,14 +14,6 @@ def home(): return "OK" -class MyMicroserviceNoSingleton(Microservice): - _singleton = False - - -class MyMicroservice(Microservice): - pass - - class HomeWithFlaskTests(unittest.TestCase): """ Tests for healthcheack endpoints @@ -30,7 +22,7 @@ class HomeWithFlaskTests(unittest.TestCase): def setUp(self): os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config-tests-flask.yml") - ms = MyMicroserviceNoSingleton(service="my-ms", path=__file__) + ms = MyMicroserviceNoSingleton(service="my-ms", path=__file__, override_instance=True) self.app = ms.create_app() self.client = self.app.test_client() self.assertEqual("Python Microservice With Flask", self.app.config["APP_NAME"]) @@ -44,6 +36,32 @@ def test_healthcheck(self): self.assertEqual(b"OK", response.data) self.assertEqual(200, response.status_code) + def test_swagger(self): + response = self.client.get('/ui/') + self.assertEqual(404, response.status_code) + + +class FlaskWithSwaggerTests(unittest.TestCase): + """ + Tests for healthcheack endpoints + """ + + def setUp(self): + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "config-tests-flask-swagger.yml") + ms = MyMicroserviceNoSingleton(service="my-ms", path=__file__, override_instance=True) + self.app = ms.create_app() + self.client = self.app.test_client() + self.assertEqual("Python Microservice With Flask", self.app.config["APP_NAME"]) + + def test_healthcheck(self): + response = self.client.get('/healthcheck') + self.assertEqual(b"OK", response.data) + self.assertEqual(200, response.status_code) + + def test_swagger(self): + response = self.client.get('/ui/') + self.assertEqual(200, response.status_code) class MicroserviceTest(unittest.TestCase): @@ -84,19 +102,19 @@ def test_config_singleton(self): @pytest.mark.parametrize("payload, configfile, status_code", [ ( - "Python Microservice", - "config-tests.yml", - 200 + "Python Microservice", + "config-tests.yml", + 200 ), ( - "Python Microservice With Flask", - "config-tests-flask.yml", - 404 + "Python Microservice With Flask", + "config-tests-flask.yml", + 404 ), ( - "Python Microservice With Flask and Lightstep", - "config-tests-flask-trace-lightstep.yml", - 200 + "Python Microservice With Flask and Lightstep", + "config-tests-flask-trace-lightstep.yml", + 200 ) ]) def test_configfiles(payload, configfile, status_code): diff --git a/tests/test_requests.py b/tests/test_requests.py index a615089..2a63aed 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -2,7 +2,6 @@ """ import json import os -import time import unittest import requests_mock @@ -29,7 +28,7 @@ def test_get(self, mock_request): url = "http://www.my-site.com/users" full_url = url text = json.dumps([{'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'}, - {'id': 2, 'name': 'Jon', 'email': 'jon@my-site.com.com'}]) + {'id': 2, 'name': 'Jon', 'email': 'jon@my-site.com.com'}]) with self.app.app_context(): mock_request.get(full_url, text=text) @@ -247,7 +246,7 @@ def test_propagate_headers_empty(self, ): } with self.app.test_request_context( '/tests/', data={'format': 'short'}): - headers = self.request.propagate_headers(input_headers) + headers = self.request.set_propagate_headers(input_headers) self.assertEqual(expected_headers, headers) @@ -260,7 +259,7 @@ def test_propagate_headers_no_override(self): } with self.app.test_request_context( '/tests/'): - headers = self.request.propagate_headers(input_headers) + headers = self.request.set_propagate_headers(input_headers) self.assertEqual(expected_headers, headers) @@ -275,7 +274,7 @@ def test_propagate_headers_propagate(self): } with self.app.test_request_context( '/tests/', data={'format': 'short'}, headers={'a': 'b'}): - headers = self.request.propagate_headers(input_headers) + headers = self.request.set_propagate_headers(input_headers) self.assertEqual(expected_headers, headers) @@ -291,7 +290,7 @@ def test_propagate_headers_propagate_no_override(self): } with self.app.test_request_context( '/tests/', headers={'a': 'b', 'span': '5678'}): - headers = self.request.propagate_headers(input_headers) + headers = self.request.set_propagate_headers(input_headers) self.assertEqual(expected_headers, headers) @@ -300,13 +299,13 @@ def test_propagate_headers_on_get(self): mock_headers = { 'A': 'b', } - self.request.propagate_headers = unittest.mock.Mock() - self.request.propagate_headers.return_value = mock_headers + self.request.set_propagate_headers = unittest.mock.Mock() + self.request.set_propagate_headers.return_value = mock_headers with self.app.test_request_context( '/tests/', data={'format': 'short'}, headers=mock_headers): self.request.get(url, propagate_headers=True) - self.request.propagate_headers.assert_called_once_with({}) + self.request.set_propagate_headers.assert_called_once_with({}) def test_propagate_headers_on_get_with_headers(self): url = "http://www.my-site.com/users" @@ -316,13 +315,13 @@ def test_propagate_headers_on_get_with_headers(self): get_headers = { 'C': 'd', } - self.request.propagate_headers = unittest.mock.Mock() - self.request.propagate_headers.return_value = mock_headers + self.request.set_propagate_headers = unittest.mock.Mock() + self.request.set_propagate_headers.return_value = mock_headers with self.app.test_request_context( '/tests/', data={'format': 'short'}, headers=mock_headers): self.request.get(url, headers=get_headers, propagate_headers=True) - self.request.propagate_headers.assert_called_once_with(get_headers) + self.request.set_propagate_headers.assert_called_once_with(get_headers) @requests_mock.Mocker() def test_retries_with_500(self, mock_request): diff --git a/tests/test_swagger.py b/tests/test_swagger.py new file mode 100644 index 0000000..9ffd52d --- /dev/null +++ b/tests/test_swagger.py @@ -0,0 +1,25 @@ +"""Test common rest operations wrapper. +""" +import os +import unittest + +from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT +from tests.common import MyMicroserviceNoSingleton + + +class SwaggerTests(unittest.TestCase): + """Test common rest operations wrapper. + """ + + 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-swagger.yml") + ms = MyMicroserviceNoSingleton(service="my-ms", path=__file__) + self.app = ms.create_app() + self.client = self.app.test_client() + self.assertEqual("Python Microservice Swagger", self.app.config["APP_NAME"]) + + def test_default(self): + response = self.client.get('/ws-doc/') + self.assertEqual(200, response.status_code)