Skip to content

Commit c001b28

Browse files
miguelgrubinavara1986
authored andcommitted
Memoize config per service (#63)
* Memoizes config file per service * Updates dependencies
1 parent 2fb51be commit c001b28

File tree

8 files changed

+291
-146
lines changed

8 files changed

+291
-146
lines changed

Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ python-json-logger = ">=0.1.10"
99
pyyaml = ">=5.1.2"
1010
anyconfig = ">=0.9.8"
1111
swagger-ui-bundle = ">=0.0.2"
12-
connexion = {extras = ["swagger-ui"],version = ">=2.2.0"}
12+
connexion = {extras = ["swagger-ui"],version = "==2.4.0"}
1313
jaeger-client = "==4.1.0"
1414
flask-opentracing = "*"
1515
opentracing = ">=2.1"

Pipfile.lock

Lines changed: 182 additions & 92 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyms/config/conf.py

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

44

5-
class Config:
6-
service = None
7-
_config = False
5+
__service_configs = {}
86

9-
def __init__(self):
10-
pass
117

12-
def config(self, *args, **kwargs):
13-
"""Set the configuration, if our yaml file is like:
14-
myservice:
15-
myservice1:
16-
myvar1
17-
and we want to get the configuration of service1, our self.service will be "myservice.myservice1"
18-
"""
19-
if not self._config:
20-
self._config = ConfFile(*args, **kwargs)
21-
if not self.service:
22-
raise ServiceDoesNotExistException("Service not defined")
23-
return getattr(self._config, self.service)
24-
25-
26-
def get_conf(service=None, *args, **kwargs):
27-
config = Config()
28-
config.service = service
29-
return config.config(*args, **kwargs)
8+
def get_conf(*args, **kwargs):
9+
service = kwargs.pop('service', None)
10+
memoize = kwargs.pop('memoize', True)
11+
if not service:
12+
raise ServiceDoesNotExistException("Service not defined")
13+
if not memoize or service not in __service_configs:
14+
__service_configs[service] = ConfFile(*args, **kwargs)
15+
return getattr(__service_configs[service], service)

pyms/flask/app/create_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Microservice(metaclass=SingletonMeta):
4444
def __init__(self, *args, **kwargs):
4545
self.service = kwargs.get("service", os.environ.get(SERVICE_ENVIRONMENT, "ms"))
4646
self.path = os.path.dirname(kwargs.get("path", __file__))
47-
self.config = get_conf(service=self.service)
47+
self.config = get_conf(service=self.service, memoize=self._singleton)
4848
self.init_services()
4949

5050
def init_services(self):

pyms/flask/services/driver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class ServicesManager:
2525

2626
def __init__(self, service=None):
2727
self.service = (service if service else SERVICE_BASE)
28-
self.config = get_conf(service=self.service, empty_init=True)
28+
self.config = get_conf(service=self.service, empty_init=True, memoize=False)
2929

3030
def get_services(self):
3131
return ((k, self.get_service(k)) for k in self.config.__dict__.keys() if k not in ['empty_init', ])

tests/test_config.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import logging
22
import os
33
import unittest
4+
from unittest import mock
45

5-
from pyms.config.conf import Config
6+
from pyms.config.conf import get_conf
67
from pyms.config.confile import ConfFile
78
from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, LOGGER_NAME
89
from pyms.exceptions import AttrDoesNotExistException, ConfigDoesNotFoundException, ServiceDoesNotExistException
@@ -67,6 +68,14 @@ def test_equal_instances_ok2(self):
6768
config2 = {"test_1": {"test_1_1": "a", "test_1_2": "b"}}
6869
self.assertEqual(config1, config2)
6970

71+
def test_equal_instances_ko(self):
72+
config = ConfFile(config={"test-1": {"test-1-1": "a"}})
73+
no_valid_type = ConfigDoesNotFoundException
74+
75+
result = config == no_valid_type
76+
77+
self.assertEqual(result, False)
78+
7079
def test_dictionary_attribute_not_exists(self):
7180
config = ConfFile(config={"test-1": "a"})
7281
with self.assertRaises(AttrDoesNotExistException):
@@ -89,22 +98,6 @@ def test_example_test_json_file(self):
8998
self.assertEqual(config.my_ms.test_var, "general")
9099

91100

92-
class ConfServiceTests(unittest.TestCase):
93-
94-
def test_config_with_service(self):
95-
class MyService(Config):
96-
service = "service"
97-
98-
config = MyService()
99-
configuration = config.config(config={"service": {"service1": "a", "service2": "b"}})
100-
self.assertEqual(configuration.service1, "a")
101-
102-
def test_config_with_service_not_exist(self):
103-
config = Config()
104-
with self.assertRaises(ServiceDoesNotExistException):
105-
configuration = config.config(config={"service": {"service1": "a", "service2": "b"}})
106-
107-
108101
class ConfNotExistTests(unittest.TestCase):
109102
def test_empty_conf(self):
110103
config = ConfFile(empty_init=True)
@@ -119,5 +112,39 @@ def test_empty_conf_three_levels(self):
119112
self.assertEqual(config.my_ms.level_two.level_three, {})
120113

121114

122-
if __name__ == '__main__':
123-
unittest.main()
115+
116+
class GetConfig(unittest.TestCase):
117+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
118+
119+
def setUp(self):
120+
os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(self.BASE_DIR, "config-tests.yml")
121+
122+
def tearDown(self):
123+
del os.environ[CONFIGMAP_FILE_ENVIRONMENT]
124+
125+
def test_default(self):
126+
config = get_conf(service="my-ms")
127+
128+
assert config.APP_NAME == "Python Microservice"
129+
assert config.subservice1.test == "input"
130+
131+
@mock.patch('pyms.config.conf.ConfFile')
132+
def test_memoized(self, mock_confile):
133+
mock_confile.pyms = {}
134+
get_conf(service="pyms")
135+
get_conf(service="pyms")
136+
137+
mock_confile.assert_called_once()
138+
139+
@mock.patch('pyms.config.conf.ConfFile')
140+
def test_without_memoize(self, mock_confile):
141+
mock_confile.pyms = {}
142+
get_conf(service="pyms", memoize=False)
143+
get_conf(service="pyms", memoize=False)
144+
145+
assert mock_confile.call_count == 2
146+
147+
@mock.patch('pyms.config.conf.ConfFile')
148+
def test_without_params(self, mock_confile):
149+
with self.assertRaises(ServiceDoesNotExistException):
150+
get_conf()

tests/test_flask.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import unittest
3+
from unittest import mock
34

45
import pytest
56
from flask import current_app
@@ -38,6 +39,12 @@ def test_home(self):
3839
response = self.client.get('/')
3940
self.assertEqual(404, response.status_code)
4041

42+
def test_healthcheck(self):
43+
response = self.client.get('/healthcheck')
44+
self.assertEqual(b"OK", response.data)
45+
self.assertEqual(200, response.status_code)
46+
47+
4148

4249
class MicroserviceTest(unittest.TestCase):
4350
"""
@@ -68,22 +75,28 @@ def test_import_config_without_create_app(self):
6875
ms1 = MyMicroservice(service="my-ms", path=__file__, override_instance=True)
6976
self.assertEqual(ms1.config.subservice1, config().subservice1)
7077

78+
def test_config_singleton(self):
79+
conf_one = config().subservice1
80+
conf_two = config().subservice1
81+
82+
assert conf_one is conf_two
83+
7184

7285
@pytest.mark.parametrize("payload, configfile, status_code", [
7386
(
74-
"Python Microservice",
75-
"config-tests.yml",
76-
200
87+
"Python Microservice",
88+
"config-tests.yml",
89+
200
7790
),
7891
(
79-
"Python Microservice With Flask",
80-
"config-tests-flask.yml",
81-
404
92+
"Python Microservice With Flask",
93+
"config-tests-flask.yml",
94+
404
8295
),
8396
(
84-
"Python Microservice With Flask and Lightstep",
85-
"config-tests-flask-trace-lightstep.yml",
86-
200
97+
"Python Microservice With Flask and Lightstep",
98+
"config-tests-flask-trace-lightstep.yml",
99+
200
87100
)
88101
])
89102
def test_configfiles(payload, configfile, status_code):

tests/test_requests.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,35 @@ def test_propagate_headers_propagate_no_override(self):
268268

269269
self.assertEqual(expected_headers, headers)
270270

271+
def test_propagate_headers_on_get(self):
272+
url = "http://www.my-site.com/users"
273+
mock_headers = {
274+
'A': 'b',
275+
}
276+
self.request.propagate_headers = unittest.mock.Mock()
277+
self.request.propagate_headers.return_value = mock_headers
278+
with self.app.test_request_context(
279+
'/tests/', data={'format': 'short'}, headers=mock_headers):
280+
self.request.get(url, propagate_headers=True)
281+
282+
self.request.propagate_headers.assert_called_once_with({})
283+
284+
def test_propagate_headers_on_get_with_headers(self):
285+
url = "http://www.my-site.com/users"
286+
mock_headers = {
287+
'A': 'b',
288+
}
289+
get_headers = {
290+
'C': 'd',
291+
}
292+
self.request.propagate_headers = unittest.mock.Mock()
293+
self.request.propagate_headers.return_value = mock_headers
294+
with self.app.test_request_context(
295+
'/tests/', data={'format': 'short'}, headers=mock_headers):
296+
self.request.get(url, headers=get_headers, propagate_headers=True)
297+
298+
self.request.propagate_headers.assert_called_once_with(get_headers)
299+
271300
@requests_mock.Mocker()
272301
def test_retries_with_500(self, mock_request):
273302
url = 'http://localhost:9999'

0 commit comments

Comments
 (0)