Skip to content

Commit 98f2f95

Browse files
authored
Reload config and read once and services (#83)
* @miguelgrubin Refactor config and memoize file * @miguelgrubin Reload config * @miguelgrubin Fix error: removed memoize of service. This feature create an error on tests when create a instance of Microservice out of tests * Reload services and app when the configuration was reloaded
1 parent c470a40 commit 98f2f95

13 files changed

+138
-68
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ flask-opentracing = "*"
1515
opentracing = ">=2.1"
1616
opentracing-instrumentation = "==3.2.1"
1717
prometheus_client = ">=0.7.1"
18+
1819
[dev-packages]
1920
requests-mock = "*"
2021
coverage = "==4.5.4"

pyms/config/conf.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
from pyms.exceptions import ServiceDoesNotExistException
33

44

5-
__service_configs = {}
6-
7-
85
def get_conf(*args, **kwargs):
96
"""
107
Returns an object with a set of attributes retrieved from the configuration file. Each subblock is a append of the
@@ -33,12 +30,12 @@ def get_conf(*args, **kwargs):
3330
block config
3431
:param args:
3532
:param kwargs:
33+
3634
:return:
3735
"""
3836
service = kwargs.pop('service', None)
39-
memoize = kwargs.pop('memoize', True)
4037
if not service:
4138
raise ServiceDoesNotExistException("Service not defined")
42-
if not memoize or service not in __service_configs:
43-
__service_configs[service] = ConfFile(*args, **kwargs)
44-
return getattr(__service_configs[service], service)
39+
40+
config = ConfFile(*args, **kwargs)
41+
return getattr(config, service)

pyms/config/confile.py

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Module to read yaml or json conf"""
22
import logging
33
import os
4-
from typing import Text
54

65
import anyconfig
76

@@ -10,6 +9,8 @@
109

1110
logger = logging.getLogger(LOGGER_NAME)
1211

12+
config_cache = {}
13+
1314

1415
class ConfFile(dict):
1516
"""Recursive get configuration from dictionary, a config file in JSON or YAML format from a path or
@@ -18,8 +19,9 @@ class ConfFile(dict):
1819
* empty_init: Allow blank variables
1920
* default_file: search for config.yml file
2021
"""
21-
empty_init = False
22-
default_file = "config.yml"
22+
_empty_init = False
23+
_default_file = "config.yml"
24+
__path = None
2325

2426
def __init__(self, *args, **kwargs):
2527
"""
@@ -32,31 +34,38 @@ def __init__(self, *args, **kwargs):
3234
self[key] = getattr(obj, key)
3335
```
3436
"""
35-
self.empty_init = kwargs.get("empty_init", False)
37+
self._empty_init = kwargs.get("empty_init", False)
3638
config = kwargs.get("config")
37-
uppercase = kwargs.get("uppercase", True)
3839
if config is None:
39-
config = self._get_conf_from_file(kwargs.get("path")) or self._get_conf_from_env()
40+
self.set_path(kwargs.get("path"))
41+
config = self._get_conf_from_file() or self._get_conf_from_env()
4042

4143
if not config:
42-
if self.empty_init:
44+
if self._empty_init:
4345
config = {}
4446
else:
4547
raise ConfigDoesNotFoundException("Configuration file not found")
4648

49+
config = self.set_config(config)
50+
51+
super(ConfFile, self).__init__(config)
52+
53+
def set_path(self, path):
54+
self.__path = path
55+
56+
def to_flask(self):
57+
return ConfFile(config={k.upper(): v for k, v in self.items()})
58+
59+
def set_config(self, config):
4760
config = dict(self.normalize_config(config))
4861
for k, v in config.items():
4962
setattr(self, k, v)
50-
# Flask search for uppercase keys
51-
if uppercase:
52-
setattr(self, k.upper(), v)
53-
54-
super(ConfFile, self).__init__(config)
63+
return config
5564

5665
def normalize_config(self, config):
5766
for key, item in config.items():
5867
if isinstance(item, dict):
59-
item = ConfFile(config=item, empty_init=self.empty_init)
68+
item = ConfFile(config=item, empty_init=self._empty_init)
6069
yield self.normalize_keys(key), item
6170

6271
@staticmethod
@@ -78,22 +87,32 @@ def __getattr__(self, name, *args, **kwargs):
7887
aux_dict = aux_dict[k]
7988
return aux_dict
8089
except KeyError:
81-
if self.empty_init:
82-
return ConfFile(config={}, empty_init=self.empty_init)
90+
if self._empty_init:
91+
return ConfFile(config={}, empty_init=self._empty_init)
8392
raise AttrDoesNotExistException("Variable {} not exist in the config file".format(name))
8493

8594
def _get_conf_from_env(self):
86-
config_file = os.environ.get(CONFIGMAP_FILE_ENVIRONMENT, self.default_file)
95+
config_file = os.environ.get(CONFIGMAP_FILE_ENVIRONMENT, self._default_file)
8796
logger.debug("[CONF] Searching file in ENV[{}]: {}...".format(CONFIGMAP_FILE_ENVIRONMENT, config_file))
88-
return self._get_conf_from_file(config_file)
97+
self.set_path(config_file)
98+
return self._get_conf_from_file()
8999

90-
@staticmethod
91-
def _get_conf_from_file(path: Text) -> dict:
92-
if not path or not os.path.isfile(path):
100+
def _get_conf_from_file(self) -> dict:
101+
if not self.__path or not os.path.isfile(self.__path):
102+
logger.debug("[CONF] Configmap {} NOT FOUND".format(self.__path))
93103
return {}
94-
logger.debug("[CONF] Configmap {} found".format(path))
95-
conf = anyconfig.load(path)
96-
return conf
104+
if self.__path not in config_cache:
105+
logger.debug("[CONF] Configmap {} found".format(self.__path))
106+
config_cache[self.__path] = anyconfig.load(self.__path)
107+
return config_cache[self.__path]
108+
109+
def load(self):
110+
config_src = self._get_conf_from_file() or self._get_conf_from_env()
111+
self.set_config(config_src)
112+
113+
def reload(self):
114+
config_cache.pop(self.__path, None)
115+
self.load()
97116

98117
def __setattr__(self, name, value, *args, **kwargs):
99118
super(ConfFile, self).__setattr__(name, value)

pyms/flask/app/create_app.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def example():
9090
Current services are swagger, request, tracer, metrics
9191
"""
9292
service = None
93+
services = []
9394
application = None
9495
swagger = False
9596
request = False
@@ -98,7 +99,7 @@ def example():
9899

99100
def __init__(self, *args, **kwargs):
100101
self.path = os.path.dirname(kwargs.get("path", __file__))
101-
self.config = get_conf(service=CONFIG_BASE, memoize=self._singleton)
102+
self.config = get_conf(service=CONFIG_BASE)
102103
self.init_services()
103104

104105
def init_services(self) -> None:
@@ -107,9 +108,21 @@ def init_services(self) -> None:
107108
:return: None
108109
"""
109110
service_manager = ServicesManager()
110-
for service_name, service in service_manager.get_services(memoize=self._singleton):
111+
for service_name, service in service_manager.get_services():
112+
self.services.append(service_name)
111113
setattr(self, service_name, service)
112114

115+
def delete_services(self) -> None:
116+
"""
117+
Set the Attributes of all service defined in config.yml and exists in `pyms.flask.service`
118+
:return: None
119+
"""
120+
for service_name in self.services:
121+
try:
122+
delattr(self, service_name)
123+
except AttributeError:
124+
pass
125+
113126
def init_libs(self) -> Flask:
114127
"""This function exists to override if you need to set more libs such as SQLAlchemy, CORs, and any else
115128
library needs to be init over flask, like the usual pattern [MYLIB].init_app(app)
@@ -153,7 +166,7 @@ def init_app(self) -> Flask:
153166
:return: None
154167
"""
155168
if self._exists_service("swagger"):
156-
application = self.swagger.init_app(config=self.config, path=self.path)
169+
application = self.swagger.init_app(config=self.config.to_flask(), path=self.path)
157170
else:
158171
check_package_exists("flask")
159172
application = Flask(__name__, static_folder=os.path.join(self.path, 'static'),
@@ -175,6 +188,12 @@ def init_metrics(self):
175188
)
176189
self.metrics.monitor(self.application)
177190

191+
def reload_conf(self):
192+
self.delete_services()
193+
self.config.reload()
194+
self.init_services()
195+
self.create_app()
196+
178197
def create_app(self):
179198
"""Initialize the Flask app, register blueprints and initialize
180199
all libraries like Swagger, database,
@@ -183,7 +202,7 @@ def create_app(self):
183202
:return:
184203
"""
185204
self.application = self.init_app()
186-
self.application.config.from_object(self.config)
205+
self.application.config.from_object(self.config.to_flask())
187206
self.application.tracer = None
188207
self.application.ms = self
189208

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

200219
self.init_metrics()
201220

221+
logger.debug("Started app with PyMS and this services: {}".format(self.services))
222+
202223
return self.application
203224

204225
def _exists_service(self, service_name: Text) -> bool:

pyms/flask/services/driver.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class DriverService:
3838

3939
def __init__(self, *args, **kwargs):
4040
self.service = get_service_name(service=self.service)
41-
self.config = get_conf(service=self.service, empty_init=True, memoize=kwargs.get("memoize", True))
41+
self.config = get_conf(service=self.service, empty_init=True)
4242

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

6161
def __init__(self):
62-
self.config = get_conf(service=self.service, empty_init=True, memoize=False, uppercase=False)
62+
self.config = get_conf(service=self.service, empty_init=True, uppercase=False)
6363

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

7171
@staticmethod
7272
def get_service(service: Text, *args, **kwargs) -> DriverService:

pyms/flask/services/tracer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def init_jaeger_tracer(self):
6868
'reporting_host': self.host
6969
}
7070
}
71-
metrics_config = get_conf(service=get_service_name(service="metrics"), empty_init=True, memoize=False)
71+
metrics_config = get_conf(service=get_service_name(service="metrics"), empty_init=True)
7272
metrics = ""
7373
if metrics_config:
7474
metrics = PrometheusMetricsFactory()

tests/config-tests-cache.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pyms:
2+
config:
3+
debug: true
4+
testing: true
5+
my_cache: 1234
6+
app_name: "Python Microservice"
7+
application_root: /
8+
test_var: general
9+
subservice1:
10+
test: input
11+
subservice2:
12+
test: output

tests/config-tests-cache2.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pyms:
2+
config:
3+
debug: true
4+
testing: true
5+
my_cache: 12345678
6+
app_name: "Python Microservice"
7+
application_root: /
8+
test_var: general
9+
subservice1:
10+
test: input
11+
subservice2:
12+
test: output

tests/config-tests-flask.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ pyms:
88
subservice1:
99
test: input
1010
subservice2:
11-
test: output
11+
test: output
12+
subservice_flask:
13+
test: flask

tests/config-tests-requests-no-data.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pyms:
22
services:
3-
requests: true
3+
requests:
4+
enabled: true
45
swagger:
56
path: ""
67
file: "swagger.yaml"

0 commit comments

Comments
 (0)