Skip to content

Commit 7fdb462

Browse files
Reducing dependencies from global objects: Config instance is now passed on creation of resources.
1 parent a1392bf commit 7fdb462

File tree

7 files changed

+96
-124
lines changed

7 files changed

+96
-124
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ from connect.logger import logger
4343
from connect.models import ActivationTemplateResponse, ActivationTileResponse
4444
from connect.models.exception import FulfillmentFail, FulfillmentInquire, Skip
4545

46-
Config(file='config.json')
47-
4846
# set logger level / default level ERROR
4947
logger.setLevel("DEBUG")
5048

@@ -84,7 +82,7 @@ class ExampleRequestProcessor(FulfillmentAutomation):
8482

8583

8684
if __name__ == '__main__':
87-
request = ExampleRequestProcessor()
85+
request = ExampleRequestProcessor(Config(file='config.json'))
8886
request.process()
8987
```
9088

connect/config.py

Lines changed: 43 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,97 +10,63 @@
1010

1111

1212
class Config(object):
13-
api_url = None
14-
api_key = None
15-
products = []
16-
1713
def __init__(
1814
self,
1915
api_url=None,
2016
api_key=None,
2117
products=None,
22-
file=None,
23-
**kwargs
18+
file=None
2419
):
2520
"""
2621
initialization config for public api
2722
:param api_url: Public api url
2823
:param api_key: Service user ApiKey
2924
:param products (optional): Id products
3025
:param file: Config file path
31-
:param kwargs:
32-
"""
33-
if not all([Config.api_key, Config.api_url]):
34-
self.load(api_url, api_key, products, file, **kwargs)
35-
36-
def load(self, api_url, api_key, products, file, **kwargs):
37-
if not any([api_key, api_url, file]):
38-
return
39-
40-
if file:
41-
return self.load_from_file(file)
42-
43-
self.check_credentials(api_url, api_key, products)
44-
self._set_attr(api_url, api_key, products)
45-
46-
@staticmethod
47-
def _set_attr(api_url, api_key, products=None):
48-
Config.api_key = api_key
49-
50-
# URL must end with a character
51-
Config.api_url = api_url
52-
if products:
53-
Config.products = [products] if isinstance(
54-
products, str) else products
55-
56-
@staticmethod
57-
def check_credentials(api_url, api_key, products):
58-
"""
59-
:param api_url:
60-
:param api_key:
61-
:param products:
62-
:return: True or raise ValueError
6326
"""
64-
if not all([api_key, api_url]):
65-
raise ValueError('Please provide your credentials.'
66-
'Not set value for `api_key` or `api_url`')
67-
27+
# Check arguments
28+
if not file and not any([api_key, api_url]):
29+
raise ValueError('Filename or api_key and api_url are expected in Config initialization')
6830
if products and not isinstance(products, (str, list)):
6931
raise TypeError('Products can be string or string list. Found type '
7032
+ type(products).__name__)
7133

72-
return
73-
74-
def load_from_file(self, file):
75-
"""
76-
Format file (json): {
77-
"api_key":
78-
"api_url":
79-
"products" (optional):
80-
}
81-
:param file (str): Path to the file
82-
:return: Init configuration parameters.
83-
Set Config.api_url/.api_key/.products
84-
"""
85-
if not os.path.exists(file):
86-
raise IOError('Not file `{}` on directory'.format(file))
87-
88-
with open(file) as config_file:
89-
configs = config_file.read()
90-
91-
try:
92-
configs = json.loads(configs)
93-
except Exception as ex:
94-
raise TypeError('Invalid config file `{}`\n'
95-
'ERROR: {}'.format(file, str(ex)))
96-
97-
(api_url, api_key, products) = (configs.get('apiEndpoint', ''),
98-
configs.get('apiKey', ''),
99-
configs.get('products', ''))
100-
101-
products = products.encode('utf-8') if not isinstance(products, (str, list)) else products
102-
api_url = api_url.encode('utf-8') if not isinstance(api_url, str) else api_url
103-
api_key = api_key.encode('utf-8') if not isinstance(api_key, str) else api_key
104-
105-
self.check_credentials(api_url, api_key, products)
106-
self._set_attr(api_url, api_key, products)
34+
# Load config from file name
35+
if file:
36+
if not os.path.exists(file):
37+
raise IOError('Not file `{}` on directory'.format(file))
38+
39+
with open(file) as config_file:
40+
configs = config_file.read()
41+
42+
try:
43+
configs = json.loads(configs)
44+
except Exception as ex:
45+
raise TypeError('Invalid config file `{}`\n'
46+
'ERROR: {}'.format(file, str(ex)))
47+
48+
(api_url, api_key, products) = (configs.get('apiEndpoint', ''),
49+
configs.get('apiKey', ''),
50+
configs.get('products', ''))
51+
api_url = api_url.encode('utf-8') if not isinstance(api_url, str) else api_url
52+
api_key = api_key.encode('utf-8') if not isinstance(api_key, str) else api_key
53+
products = products.encode('utf-8') if not isinstance(products, (str, list)) else products
54+
55+
# Initialize
56+
self._api_key = api_key
57+
self._api_url = api_url
58+
self._products = [products] \
59+
if isinstance(products, str) \
60+
else products or []
61+
62+
@property
63+
def api_url(self):
64+
return self._api_url
65+
66+
@property
67+
def api_key(self):
68+
return self._api_key
69+
70+
@property
71+
def products(self):
72+
return self._products

connect/resource/base.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,18 @@
1313
from connect.models.exception import ServerErrorException
1414
from .utils import joinurl
1515

16-
config = Config()
17-
1816

1917
class ApiClient(object):
2018

19+
def __init__(self, config):
20+
if not isinstance(config, Config):
21+
raise ValueError('A valid Config object is required to create an ApiClient')
22+
self.config = config
23+
2124
@property
2225
def headers(self):
23-
config.check_credentials(
24-
config.api_url, config.api_key, config.products)
2526
return {
26-
"Authorization": config.api_key,
27+
"Authorization": self.config.api_key,
2728
"Content-Type": "application/json",
2829
}
2930

@@ -64,14 +65,18 @@ def put(self, url, data=None, **kwargs):
6465
class BaseResource(object):
6566
resource = None
6667
limit = 100
67-
api = ApiClient()
68+
api = None
6869
schema = BaseSchema()
6970

70-
def __init__(self, *args, **kwargs):
71-
72-
if self.__class__.resource is None:
71+
def __init__(self, config, *args, **kwargs):
72+
if not self.__class__.resource:
7373
raise AttributeError('Resource name not specified in class {}'.format(
7474
self.__class__.__name__) + '. Add an attribute `resource` name of the resource')
75+
if not isinstance(config, Config):
76+
raise ValueError('A valid Config object is required to create a ' + type(self).__name__)
77+
if not BaseResource.api:
78+
BaseResource.api = ApiClient(config)
79+
self.config = config
7580

7681
def build_filter(self):
7782
res_filter = {}
@@ -82,7 +87,7 @@ def build_filter(self):
8287

8388
@property
8489
def _list_url(self):
85-
return joinurl(config.api_url, self.__class__.resource)
90+
return joinurl(self.config.api_url, self.__class__.resource)
8691

8792
def _obj_url(self, pk):
8893
return joinurl(self._list_url, pk)

connect/resource/fulfillment.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import json
99

10-
from connect.config import Config
1110
from connect.logger import function_log
1211
from connect.models import FulfillmentSchema, Param
1312
from .base import BaseResource
@@ -22,8 +21,8 @@ class FulfillmentResource(BaseResource):
2221

2322
def build_filter(self):
2423
filters = super(FulfillmentResource, self).build_filter()
25-
if Config.products:
26-
filters['product_id'] = Config.products
24+
if self.config.products:
25+
filters['product_id'] = self.config.products
2726

2827
filters['status'] = 'pending'
2928
return filters
@@ -44,7 +43,7 @@ def fail(self, pk, reason):
4443

4544
@function_log
4645
def render_template(self, pk, template_id):
47-
return TemplateResource().render(template_id, pk)
46+
return TemplateResource(self.config).render(template_id, pk)
4847

4948
@function_log
5049
def update_parameters(self, pk, params):

example/example.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
from connect.models import ActivationTemplateResponse, ActivationTileResponse
1212
from connect.models.exception import FulfillmentFail, FulfillmentInquire, Skip
1313

14-
Config(file='config.json')
15-
1614
# set logger level / default level ERROR
1715
logger.setLevel("DEBUG")
1816

@@ -52,5 +50,5 @@ def process_request(self, request):
5250

5351

5452
if __name__ == '__main__':
55-
request = ExampleRequestProcessor()
53+
request = ExampleRequestProcessor(Config(file='config.json'))
5654
request.process()

tests/test_config.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,44 @@ def teardown_module(module):
2828
os.chdir(module.prev_dir)
2929

3030

31-
def teardown_function():
32-
Config.api_url, Config.api_key, Config.products = None, None, None
33-
34-
3531
def test_init_config_with_non_existing_file():
3632
with pytest.raises(IOError):
3733
Config(file='non_existing_config.json')
3834

3935

4036
def test_init_config_with_file():
41-
config = Config(file='config.json')
42-
_assert_config(config)
37+
_assert_config(Config(file='config.json'))
4338

4439

45-
def test_set_config():
46-
config = Config(
40+
def test_init_config_with_arguments():
41+
_assert_config(Config(
4742
api_key=conf_dict.get('apiKey'),
4843
api_url=conf_dict.get('apiEndpoint'),
4944
products=conf_dict.get('products'),
50-
)
51-
_assert_config(config)
52-
53-
54-
def _assert_config(config=None):
55-
assert Config.api_key == conf_dict.get('apiKey')
56-
assert Config.api_url == conf_dict.get('apiEndpoint')
57-
assert isinstance(Config.products, list)
58-
assert len(Config.products) == 1
59-
assert Config.products == [conf_dict.get('products')]
60-
if config:
61-
assert config.api_key == conf_dict.get('apiKey')
62-
assert config.api_url == conf_dict.get('apiEndpoint')
63-
assert isinstance(config.products, list)
64-
assert len(config.products) == 1
65-
assert config.products == [conf_dict.get('products')]
45+
))
46+
47+
48+
def test_init_config_with_invalid_arguments():
49+
with pytest.raises(ValueError):
50+
Config(
51+
api_key='',
52+
api_url='',
53+
products='',
54+
)
55+
56+
57+
def test_config_immutable_properties():
58+
config = Config(file='config.json')
59+
with pytest.raises(AttributeError):
60+
config.api_key = conf_dict.get('apiKey')
61+
with pytest.raises(AttributeError):
62+
config.api_url = conf_dict.get('apiEndpoint')
63+
with pytest.raises(AttributeError):
64+
config.products = [conf_dict.get('products')]
65+
66+
def _assert_config(config):
67+
assert config.api_key == conf_dict.get('apiKey')
68+
assert config.api_url == conf_dict.get('apiEndpoint')
69+
assert isinstance(config.products, list)
70+
assert len(config.products) == 1
71+
assert config.products[0] == conf_dict.get('products')

tests/test_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ def _get_response_ok():
2727

2828
@patch('requests.get', MagicMock(return_value=_get_response_ok()))
2929
def test_create_model_from_response():
30-
Config(api_key='ApiKey XXXX:YYYYY', api_url='http://localhost:8080/api/public/v1/')
30+
config = Config(api_key='ApiKey XXXX:YYYYY', api_url='http://localhost:8080/api/public/v1/')
3131

32-
requests = FulfillmentResource().list()
33-
request_obj = FulfillmentResource().get(pk='PR-000-000-000')
32+
requests = FulfillmentResource(config).list()
33+
request_obj = FulfillmentResource(config).get(pk='PR-000-000-000')
3434

3535
assert requests[0].id == request_obj.id
3636
content = json.loads(response.content)[0]

0 commit comments

Comments
 (0)