Skip to content

Commit dd6b6eb

Browse files
Merge pull request #16 from JaviCerveraIngram/master
Reducing dependencies from global objects: Config instance is now passed on creation of resources.
2 parents a1392bf + a83e332 commit dd6b6eb

File tree

7 files changed

+135
-126
lines changed

7 files changed

+135
-126
lines changed

README.md

Lines changed: 3 additions & 5 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

@@ -68,12 +66,12 @@ class ExampleRequestProcessor(FulfillmentAutomation):
6866
return ActivationTileResponse(tile='\n # Welcome to Fallball!\n\nYes, '
6967
'you decided to have an account in our amazing service!')
7068
# or
71-
# return TemplateResource().render(pk='TEMPLATE_ID', request_id=request.id)
69+
# return TemplateResource(self.config).render(pk='TEMPLATE_ID', request_id=request.id)
7270

7371
# aprrove by Template
7472
return ActivationTemplateResponse(template_id="TL-497-535-242")
7573
# or
76-
# return TemplateResource().get(pk='TEMPLATE_ID')
74+
# return TemplateResource(self.config).get(pk='TEMPLATE_ID')
7775

7876
elif request.type == 'change':
7977
# fail
@@ -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: 51 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -10,97 +10,72 @@
1010

1111

1212
class Config(object):
13-
api_url = None
14-
api_key = None
15-
products = []
13+
instance = None # Global instance
1614

1715
def __init__(
1816
self,
1917
api_url=None,
2018
api_key=None,
2119
products=None,
22-
file=None,
23-
**kwargs
20+
file=None
2421
):
2522
"""
2623
initialization config for public api
2724
:param api_url: Public api url
2825
:param api_key: Service user ApiKey
2926
:param products (optional): Id products
3027
:param file: Config file path
31-
:param kwargs:
3228
"""
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
63-
"""
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-
29+
# Check arguments
30+
if not file and not any([api_key, api_url]):
31+
raise ValueError('Filename or api_key and api_url are expected'
32+
'in Config initialization')
6833
if products and not isinstance(products, (str, list)):
6934
raise TypeError('Products can be string or string list. Found type '
7035
+ type(products).__name__)
7136

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)
37+
# Load config from file name
38+
if file:
39+
if not os.path.exists(file):
40+
raise IOError('Not file `{}` on directory'.format(file))
41+
42+
with open(file) as config_file:
43+
configs = config_file.read()
44+
45+
try:
46+
configs = json.loads(configs)
47+
except Exception as ex:
48+
raise TypeError('Invalid config file `{}`\n'
49+
'ERROR: {}'.format(file, str(ex)))
50+
51+
(api_url, api_key, products) = (configs.get('apiEndpoint', ''),
52+
configs.get('apiKey', ''),
53+
configs.get('products', ''))
54+
api_url = api_url.encode('utf-8') if not isinstance(api_url, str) else api_url
55+
api_key = api_key.encode('utf-8') if not isinstance(api_key, str) else api_key
56+
products = products.encode('utf-8') \
57+
if not isinstance(products, (str, list)) \
58+
else products
59+
60+
# Initialize
61+
self._api_key = api_key
62+
self._api_url = api_url
63+
self._products = [products] \
64+
if isinstance(products, str) \
65+
else products or []
66+
67+
# Store first created instance
68+
if not Config.instance:
69+
Config.instance = self
70+
71+
@property
72+
def api_url(self):
73+
return self._api_url
74+
75+
@property
76+
def api_key(self):
77+
return self._api_key
78+
79+
@property
80+
def products(self):
81+
return self._products

connect/resource/base.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@
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=None):
20+
# Assign passed config or globally configured instance
21+
self.config = config or Config.instance
22+
23+
# Assert data
24+
if not isinstance(self.config, Config):
25+
raise ValueError('A valid Config object is required to create an ApiClient')
26+
2127
@property
2228
def headers(self):
23-
config.check_credentials(
24-
config.api_url, config.api_key, config.products)
2529
return {
26-
"Authorization": config.api_key,
30+
"Authorization": self.config.api_key,
2731
"Content-Type": "application/json",
2832
}
2933

@@ -64,14 +68,22 @@ def put(self, url, data=None, **kwargs):
6468
class BaseResource(object):
6569
resource = None
6670
limit = 100
67-
api = ApiClient()
71+
api = None
6872
schema = BaseSchema()
6973

70-
def __init__(self, *args, **kwargs):
74+
def __init__(self, config=None, *args, **kwargs):
75+
# Assign passed config or globally configured instance
76+
self.config = config or Config.instance
7177

72-
if self.__class__.resource is None:
78+
# Assert data
79+
if not self.__class__.resource:
7380
raise AttributeError('Resource name not specified in class {}'.format(
7481
self.__class__.__name__) + '. Add an attribute `resource` name of the resource')
82+
if not isinstance(self.config, Config):
83+
raise ValueError('A valid Config object must be passed or globally configured '
84+
'to create a ' + type(self).__name__)
85+
if not BaseResource.api:
86+
BaseResource.api = ApiClient(config)
7587

7688
def build_filter(self):
7789
res_filter = {}
@@ -82,7 +94,7 @@ def build_filter(self):
8294

8395
@property
8496
def _list_url(self):
85-
return joinurl(config.api_url, self.__class__.resource)
97+
return joinurl(self.config.api_url, self.__class__.resource)
8698

8799
def _obj_url(self, pk):
88100
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: 3 additions & 5 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

@@ -36,12 +34,12 @@ def process_request(self, request):
3634
return ActivationTileResponse(tile='\n # Welcome to Fallball!\n\nYes, you decided '
3735
'to have an account in our amazing service!')
3836
# or
39-
# return TemplateResource().render(pk='TEMPLATE_ID', request_id=request.id)
37+
# return TemplateResource(self.config).render(pk='TEMPLATE_ID', request_id=request.id)
4038

4139
# aprrove by Template
4240
return ActivationTemplateResponse(template_id="TL-497-535-242")
4341
# or
44-
# return TemplateResource().get(pk='TEMPLATE_ID')
42+
# return TemplateResource(self.config).get(pk='TEMPLATE_ID')
4543

4644
elif request.type == 'change':
4745
# fail
@@ -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: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,65 @@ 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+
config.api_url = conf_dict.get('apiEndpoint')
62+
config.products = [conf_dict.get('products')]
63+
64+
65+
def test_global_config():
66+
Config.instance = None # Reset global config
67+
Config(file='config.json')
68+
_assert_global_config()
69+
70+
71+
def test_global_config_immutable_properties():
72+
Config(file='config.json')
73+
with pytest.raises(AttributeError):
74+
Config.instance.api_key = conf_dict.get('apiKey')
75+
Config.instance.api_url = conf_dict.get('apiEndpoint')
76+
Config.instance.products = [conf_dict.get('products')]
77+
78+
79+
def _assert_config(config):
80+
assert config.api_key == conf_dict.get('apiKey')
81+
assert config.api_url == conf_dict.get('apiEndpoint')
82+
assert isinstance(config.products, list)
83+
assert len(config.products) == 1
84+
assert config.products[0] == conf_dict.get('products')
85+
86+
87+
def _assert_global_config():
88+
assert Config.instance.api_key == conf_dict.get('apiKey')
89+
assert Config.instance.api_url == conf_dict.get('apiEndpoint')
90+
assert isinstance(Config.instance.products, list)
91+
assert len(Config.instance.products) == 1
92+
assert Config.instance.products[0] == conf_dict.get('products')

0 commit comments

Comments
 (0)