Skip to content

Commit

Permalink
Merge 73745de into c470a40
Browse files Browse the repository at this point in the history
  • Loading branch information
avara1986 committed Jan 22, 2020
2 parents c470a40 + 73745de commit 39699cd
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 68 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ flask-opentracing = "*"
opentracing = ">=2.1"
opentracing-instrumentation = "==3.2.1"
prometheus_client = ">=0.7.1"

[dev-packages]
requests-mock = "*"
coverage = "==4.5.4"
Expand Down
11 changes: 4 additions & 7 deletions pyms/config/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
from pyms.exceptions import ServiceDoesNotExistException


__service_configs = {}


def get_conf(*args, **kwargs):
"""
Returns an object with a set of attributes retrieved from the configuration file. Each subblock is a append of the
Expand Down Expand Up @@ -33,12 +30,12 @@ def get_conf(*args, **kwargs):
block config
:param args:
:param kwargs:
:return:
"""
service = kwargs.pop('service', None)
memoize = kwargs.pop('memoize', True)
if not service:
raise ServiceDoesNotExistException("Service not defined")
if not memoize or service not in __service_configs:
__service_configs[service] = ConfFile(*args, **kwargs)
return getattr(__service_configs[service], service)

config = ConfFile(*args, **kwargs)
return getattr(config, service)
65 changes: 42 additions & 23 deletions pyms/config/confile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Module to read yaml or json conf"""
import logging
import os
from typing import Text

import anyconfig

Expand All @@ -10,6 +9,8 @@

logger = logging.getLogger(LOGGER_NAME)

config_cache = {}


class ConfFile(dict):
"""Recursive get configuration from dictionary, a config file in JSON or YAML format from a path or
Expand All @@ -18,8 +19,9 @@ class ConfFile(dict):
* empty_init: Allow blank variables
* default_file: search for config.yml file
"""
empty_init = False
default_file = "config.yml"
_empty_init = False
_default_file = "config.yml"
__path = None

def __init__(self, *args, **kwargs):
"""
Expand All @@ -32,31 +34,38 @@ def __init__(self, *args, **kwargs):
self[key] = getattr(obj, key)
```
"""
self.empty_init = kwargs.get("empty_init", False)
self._empty_init = kwargs.get("empty_init", False)
config = kwargs.get("config")
uppercase = kwargs.get("uppercase", True)
if config is None:
config = self._get_conf_from_file(kwargs.get("path")) or self._get_conf_from_env()
self.set_path(kwargs.get("path"))
config = self._get_conf_from_file() or self._get_conf_from_env()

if not config:
if self.empty_init:
if self._empty_init:
config = {}
else:
raise ConfigDoesNotFoundException("Configuration file not found")

config = self.set_config(config)

super(ConfFile, self).__init__(config)

def set_path(self, path):
self.__path = path

def to_flask(self):
return ConfFile(config={k.upper(): v for k, v in self.items()})

def set_config(self, config):
config = dict(self.normalize_config(config))
for k, v in config.items():
setattr(self, k, v)
# Flask search for uppercase keys
if uppercase:
setattr(self, k.upper(), v)

super(ConfFile, self).__init__(config)
return config

def normalize_config(self, config):
for key, item in config.items():
if isinstance(item, dict):
item = ConfFile(config=item, empty_init=self.empty_init)
item = ConfFile(config=item, empty_init=self._empty_init)
yield self.normalize_keys(key), item

@staticmethod
Expand All @@ -78,22 +87,32 @@ def __getattr__(self, name, *args, **kwargs):
aux_dict = aux_dict[k]
return aux_dict
except KeyError:
if self.empty_init:
return ConfFile(config={}, empty_init=self.empty_init)
if self._empty_init:
return ConfFile(config={}, empty_init=self._empty_init)
raise AttrDoesNotExistException("Variable {} not exist in the config file".format(name))

def _get_conf_from_env(self):
config_file = os.environ.get(CONFIGMAP_FILE_ENVIRONMENT, self.default_file)
config_file = os.environ.get(CONFIGMAP_FILE_ENVIRONMENT, self._default_file)
logger.debug("[CONF] Searching file in ENV[{}]: {}...".format(CONFIGMAP_FILE_ENVIRONMENT, config_file))
return self._get_conf_from_file(config_file)
self.set_path(config_file)
return self._get_conf_from_file()

@staticmethod
def _get_conf_from_file(path: Text) -> dict:
if not path or not os.path.isfile(path):
def _get_conf_from_file(self) -> dict:
if not self.__path or not os.path.isfile(self.__path):
logger.debug("[CONF] Configmap {} NOT FOUND".format(self.__path))
return {}
logger.debug("[CONF] Configmap {} found".format(path))
conf = anyconfig.load(path)
return conf
if self.__path not in config_cache:
logger.debug("[CONF] Configmap {} found".format(self.__path))
config_cache[self.__path] = anyconfig.load(self.__path)
return config_cache[self.__path]

def load(self):
config_src = self._get_conf_from_file() or self._get_conf_from_env()
self.set_config(config_src)

def reload(self):
config_cache.pop(self.__path, None)
self.load()

def __setattr__(self, name, value, *args, **kwargs):
super(ConfFile, self).__setattr__(name, value)
29 changes: 25 additions & 4 deletions pyms/flask/app/create_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def example():
Current services are swagger, request, tracer, metrics
"""
service = None
services = []
application = None
swagger = False
request = False
Expand All @@ -98,7 +99,7 @@ def example():

def __init__(self, *args, **kwargs):
self.path = os.path.dirname(kwargs.get("path", __file__))
self.config = get_conf(service=CONFIG_BASE, memoize=self._singleton)
self.config = get_conf(service=CONFIG_BASE)
self.init_services()

def init_services(self) -> None:
Expand All @@ -107,9 +108,21 @@ def init_services(self) -> None:
:return: None
"""
service_manager = ServicesManager()
for service_name, service in service_manager.get_services(memoize=self._singleton):
for service_name, service in service_manager.get_services():
self.services.append(service_name)
setattr(self, service_name, service)

def delete_services(self) -> None:
"""
Set the Attributes of all service defined in config.yml and exists in `pyms.flask.service`
:return: None
"""
for service_name in self.services:
try:
delattr(self, service_name)
except AttributeError:
pass

def init_libs(self) -> Flask:
"""This function exists to override if you need to set more libs such as SQLAlchemy, CORs, and any else
library needs to be init over flask, like the usual pattern [MYLIB].init_app(app)
Expand Down Expand Up @@ -153,7 +166,7 @@ def init_app(self) -> Flask:
:return: None
"""
if self._exists_service("swagger"):
application = self.swagger.init_app(config=self.config, path=self.path)
application = self.swagger.init_app(config=self.config.to_flask(), path=self.path)
else:
check_package_exists("flask")
application = Flask(__name__, static_folder=os.path.join(self.path, 'static'),
Expand All @@ -175,6 +188,12 @@ def init_metrics(self):
)
self.metrics.monitor(self.application)

def reload_conf(self):
self.delete_services()
self.config.reload()
self.init_services()
self.create_app()

def create_app(self):
"""Initialize the Flask app, register blueprints and initialize
all libraries like Swagger, database,
Expand All @@ -183,7 +202,7 @@ def create_app(self):
:return:
"""
self.application = self.init_app()
self.application.config.from_object(self.config)
self.application.config.from_object(self.config.to_flask())
self.application.tracer = None
self.application.ms = self

Expand All @@ -199,6 +218,8 @@ def create_app(self):

self.init_metrics()

logger.debug("Started app with PyMS and this services: {}".format(self.services))

return self.application

def _exists_service(self, service_name: Text) -> bool:
Expand Down
12 changes: 6 additions & 6 deletions pyms/flask/services/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class DriverService:

def __init__(self, *args, **kwargs):
self.service = get_service_name(service=self.service)
self.config = get_conf(service=self.service, empty_init=True, memoize=kwargs.get("memoize", True))
self.config = get_conf(service=self.service, empty_init=True)

def __getattr__(self, attr, *args, **kwargs):
config_attribute = getattr(self.config, attr)
Expand All @@ -59,14 +59,14 @@ class ServicesManager:
service = SERVICE_BASE

def __init__(self):
self.config = get_conf(service=self.service, empty_init=True, memoize=False, uppercase=False)
self.config = get_conf(service=self.service, empty_init=True, uppercase=False)

def get_services(self, memoize: bool) -> Tuple[Text, DriverService]:
def get_services(self) -> Tuple[Text, DriverService]:
for k in self.config.__dict__.keys():
if k.islower() and k not in ['empty_init', ]:
service = self.get_service(k, memoize=memoize)
if k.islower() and not k.startswith("_"):
service = self.get_service(k)
if service.is_enabled():
yield (k, service)
yield k, service

@staticmethod
def get_service(service: Text, *args, **kwargs) -> DriverService:
Expand Down
2 changes: 1 addition & 1 deletion pyms/flask/services/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def init_jaeger_tracer(self):
'reporting_host': self.host
}
}
metrics_config = get_conf(service=get_service_name(service="metrics"), empty_init=True, memoize=False)
metrics_config = get_conf(service=get_service_name(service="metrics"), empty_init=True)
metrics = ""
if metrics_config:
metrics = PrometheusMetricsFactory()
Expand Down
12 changes: 12 additions & 0 deletions tests/config-tests-cache.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pyms:
config:
debug: true
testing: true
my_cache: 1234
app_name: "Python Microservice"
application_root: /
test_var: general
subservice1:
test: input
subservice2:
test: output
12 changes: 12 additions & 0 deletions tests/config-tests-cache2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pyms:
config:
debug: true
testing: true
my_cache: 12345678
app_name: "Python Microservice"
application_root: /
test_var: general
subservice1:
test: input
subservice2:
test: output
4 changes: 3 additions & 1 deletion tests/config-tests-flask.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ pyms:
subservice1:
test: input
subservice2:
test: output
test: output
subservice_flask:
test: flask
3 changes: 2 additions & 1 deletion tests/config-tests-requests-no-data.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pyms:
services:
requests: true
requests:
enabled: true
swagger:
path: ""
file: "swagger.yaml"
Expand Down
36 changes: 19 additions & 17 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ def test_example_test_json_file(self):
self.assertEqual(config.pyms.config.test_var, "general")


class ConfCacheTests(unittest.TestCase):
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def test_get_cache(self):
config = ConfFile(path=os.path.join(self.BASE_DIR, "config-tests-cache.yml"))
config.set_path(os.path.join(self.BASE_DIR, "config-tests-cache2.yml"))
self.assertEqual(config.pyms.config.my_cache, 1234)

def test_get_cache_and_reload(self):
config = ConfFile(path=os.path.join(self.BASE_DIR, "config-tests-cache.yml"))
config.set_path(os.path.join(self.BASE_DIR, "config-tests-cache2.yml"))
config.reload()
self.assertEqual(config.pyms.config.my_cache, 12345678)


class ConfNotExistTests(unittest.TestCase):
def test_empty_conf(self):
config = ConfFile(empty_init=True)
Expand All @@ -121,26 +135,14 @@ def tearDown(self):
del os.environ[CONFIGMAP_FILE_ENVIRONMENT]

def test_default(self):
config = get_conf(service=CONFIG_BASE)
assert config.APP_NAME == "Python Microservice"
config = get_conf(service=CONFIG_BASE, uppercase=True)
assert config.app_name == "Python Microservice"
assert config.subservice1.test == "input"

@mock.patch('pyms.config.conf.ConfFile')
def test_memoized(self, mock_confile):
mock_confile.pyms = {}
get_conf(service="pyms")
get_conf(service="pyms")

mock_confile.assert_called_once()

@mock.patch('pyms.config.conf.ConfFile')
def test_without_memoize(self, mock_confile):
mock_confile.pyms = {}
get_conf(service="pyms", memoize=False)
get_conf(service="pyms", memoize=False)

assert mock_confile.call_count == 2
def test_default_flask(self):
config = get_conf(service=CONFIG_BASE, uppercase=True).to_flask()
assert config.APP_NAME == "Python Microservice"
assert config.SUBSERVICE1.test == "input"

@mock.patch('pyms.config.conf.ConfFile')
def test_without_params(self, mock_confile):
Expand Down

0 comments on commit 39699cd

Please sign in to comment.