Skip to content

Commit d142809

Browse files
authored
Merge pull request #15 from BrunoSilvaAndrade/develop
fix(api)!: Renaming classes and creating a default package object
2 parents 96df547 + 184dde5 commit d142809

File tree

3 files changed

+79
-83
lines changed

3 files changed

+79
-83
lines changed

README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ MAIN FEATURES
1212
===
1313
---
1414
* Declarative configurations without using .ini files
15-
* Access OOP or subscriptable, which means that you can iterate the config object items
15+
* Access using OOP or subscriptable, which means that you can iterate the config object items
1616
* Runtime validation using [schema](https://github.com/keleshev/schema)
1717
* Automatic environment variables interpolation
1818
* Automatic parser selecting by config file extension
@@ -105,21 +105,23 @@ This config file as a json would be something like:
105105
```
106106

107107
The instance of Config Class:
108+
108109
```python
109-
from pyconfigparser import Config, ConfigError
110+
from pyconfigparser import Configparser, ConfigError
110111
import logging
111112

112113
try:
113-
config = Config.get_config(SCHEMA_CONFIG) # <- Here I'm using that SCHEMA_CONFIG we had declared, and the dir file default value is being used
114+
config = Configparser.get_config(
115+
SCHEMA_CONFIG) # <- Here I'm using that SCHEMA_CONFIG we had declared, and the dir file default value is being used
114116
except ConfigError as e:
115117
print(e)
116118
exit()
117119

118-
#to access your config you need just:
120+
# to access your config you need just:
119121

120122

121-
fmt = config.core.logging.format #look this, at this point I'm already using the config variable
122-
date_fmt = config['core']['logging']['date_fmt'] #here subscriptable access
123+
fmt = config.core.logging.format # look this, at this point I'm already using the config variable
124+
date_fmt = config['core']['logging']['date_fmt'] # here subscriptable access
123125

124126
logging.getLogger(__name__)
125127

@@ -129,38 +131,36 @@ logging.basicConfig(
129131
level=logging.INFO
130132
)
131133

132-
#the list of object example:
134+
# the list of object example:
133135

134136
for client in config.core.allowed_clients:
135137
print(client.ip)
136138
print(client.timeout)
137139

138-
139-
#The config object's parts which is not a list can also be itered but, it'll give you the attribute's names
140-
#So you can access the values by subscriptale access
140+
# The config object's parts which is not a list can also be itered but, it'll give you the attribute's names
141+
# So you can access the values by subscriptale access
141142
for logging_section_attr_key in config.core.logging:
142143
print(config.core.logging[logging_section_attr_key])
143144

144-
#Accessing the environment variable already resolved
145+
# Accessing the environment variable already resolved
145146
print(config.random_env_variable)
146147

147148
```
148149
Since you've already created the first Config's instance this instance will be cached inside Config class,
149150
so after this first creation you can just invoke Config.get_config()
150151

151152
```python
152-
from pyconfigparser import Config
153+
from pyconfigparser import Configparser
153154

154-
config = Config.get_config() #At this point you already have the configuration properties in your config object
155+
config = Configparser.get_config() # At this point you already have the configuration properties in your config object
155156
```
156157

157158
You can also disable the action to cache the instance config
158159

159-
160160
```python
161-
from pyconfigparser import Config
161+
from pyconfigparser import Configparser
162162

163-
Config.set_hold_an_instance(False)
163+
Configparser.hold_an_instance = False
164164
```
165165

166166

pyconfigparser.py

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import re
77

8-
LINUX_KEY_VARIABLE_PATTERN = r'\$([a-zA-Z][\w]+|\{[a-zA-Z][\w]+\})$'
8+
VARIABLE_PATTERN = r'\$([a-zA-Z][\w]+|\{[a-zA-Z][\w]+\})$'
99
DEFAULT_CONFIG_FILES = ('config.json', 'config.yaml', 'config.yml')
1010
ENTITY_NAME_PATTERN = r'^[a-zA-Z][\w]+$'
1111
SUPPORTED_EXTENSIONS = {
@@ -23,7 +23,7 @@ class ConfigFileNotFoundError(ConfigError):
2323
pass
2424

2525

26-
class ConfigValue:
26+
class Config:
2727

2828
def __getitem__(self, item):
2929
return self.__dict__[item]
@@ -41,58 +41,52 @@ def values(self):
4141
return self.__dict__.values()
4242

4343

44-
class Config:
45-
__instance = None
46-
__hold_an_instance = True
44+
class Configparser:
45+
def __init__(self):
46+
self.__instance = None
47+
self.__hold_an_instance = True
4748

48-
@classmethod
49-
def hold_an_instance(cls):
50-
return cls.__hold_an_instance
49+
@property
50+
def hold_an_instance(self):
51+
return self.__hold_an_instance
5152

52-
@classmethod
53-
def set_hold_an_instance(cls, value):
53+
@hold_an_instance.setter
54+
def hold_an_instance(self, value):
5455
if type(value) is not bool:
5556
raise ValueError('value must be a bool')
56-
cls.__hold_an_instance = value
57+
self.__hold_an_instance = value
5758

58-
def __new__(cls, *args, **kwargs):
59-
raise RuntimeError('A instance of config is not allowed, use Config.get_config() instead')
59+
def get_config(self, schema: dict = None, config_dir: str = 'config', file_name: Any = DEFAULT_CONFIG_FILES):
6060

61-
@classmethod
62-
def get_config(cls, schema: dict = None, config_dir: str = 'config', file_name: Any = DEFAULT_CONFIG_FILES):
63-
64-
if cls.__instance is None:
65-
instance = cls.__create_new_instance(schema, config_dir, file_name)
66-
if cls.__hold_an_instance:
67-
cls.__instance = instance
61+
if self.__instance is None:
62+
instance = self.__create_new_instance(schema, config_dir, file_name)
63+
if self.__hold_an_instance:
64+
self.__instance = instance
6865
else:
6966
return instance
70-
return cls.__instance
67+
return self.__instance
7168

72-
@classmethod
73-
def __create_new_instance(cls, schema, config_dir, file_name):
74-
file_path = cls.__get_file_path(config_dir, file_name)
75-
parser = cls.__get_file_parser(file_path)
76-
file_buff = cls.__get_file_buff(file_path)
69+
def __create_new_instance(self, schema, config_dir, file_name):
70+
file_path = self.__get_file_path(config_dir, file_name)
71+
parser = self.__get_file_parser(file_path)
72+
file_buff = self.__get_file_buff(file_path)
7773

7874
try:
79-
config = cls.__validate_schema(schema, parser(file_buff))
80-
return cls.__dict_2_obj(config)
75+
config = self.__validate_schema(schema, parser(file_buff))
76+
return self.__dict_2_obj(config)
8177
except ParseError as e:
8278
raise ConfigError(e)
8379
except SchemaError as e:
8480
raise ConfigError('Schema validation error', e)
8581

86-
@classmethod
87-
def __get_file_parser(cls, file_path):
82+
def __get_file_parser(self, file_path):
8883
try:
8984
extension = file_path.split('.')[-1]
9085
return SUPPORTED_EXTENSIONS[extension]
9186
except KeyError:
9287
raise ConfigError(f'Supported extensions: {list(SUPPORTED_EXTENSIONS.keys())}')
9388

94-
@classmethod
95-
def __get_file_path(cls, config_dir, file_name):
89+
def __get_file_path(self, config_dir, file_name):
9690
file_path = f'{os.getcwd()}/{config_dir}/'
9791
if type(file_name) is str:
9892
file_name = [file_name]
@@ -103,49 +97,52 @@ def __get_file_path(cls, config_dir, file_name):
10397

10498
raise ConfigFileNotFoundError(f'Config file {file_path}{file_name} was not found')
10599

106-
@classmethod
107-
def __validate_schema(cls, schema, config_obj):
100+
def __validate_schema(self, schema, config_obj):
108101
if schema is None:
109102
return config_obj
110103
elif type(schema) not in (dict, list):
111104
raise ConfigError('The first config\'s schema element should be a Map or a List')
112105

113106
return Schema(schema).validate(config_obj)
114107

115-
@classmethod
116-
def __get_file_buff(cls, path_file: str):
108+
def __get_file_buff(self, path_file: str):
117109
with open(path_file, 'r') as f:
118110
return f.read()
119111

120-
@classmethod
121-
def __dict_2_obj(cls, data: Any):
112+
def __dict_2_obj(self, data: Any):
122113
_type = type(data)
123114

124115
if _type is dict:
125-
obj = ConfigValue()
116+
obj = Config()
126117
for key, value in data.items():
127-
if re.search(ENTITY_NAME_PATTERN, key) is None:
128-
raise ConfigError(
129-
f'The key {key} is invalid. The entity keys only may have words, number and underscores.')
130-
setattr(obj, key, cls.__dict_2_obj(value))
118+
self.__is_a_valid_object_key(key)
119+
setattr(obj, key, self.__dict_2_obj(value))
131120
return obj
132121
if _type in (list, set, tuple):
133-
return list(map(lambda v: cls.__dict_2_obj(v), data))
122+
return list(map(lambda v: self.__dict_2_obj(v), data))
134123
else:
135-
if type(data) is str and re.search(LINUX_KEY_VARIABLE_PATTERN, data) is not None:
136-
return cls.interpol_variable(data)
124+
if self.__is_variable(data):
125+
return self.__interpol_variable(data)
137126
return data
138127

139-
@classmethod
140-
def interpol_variable(cls, data):
128+
def __is_a_valid_object_key(self, key):
129+
if re.search(ENTITY_NAME_PATTERN, key) is None:
130+
raise ConfigError(f'The key {key} is invalid. The entity keys only may have words, number and underscores.')
131+
132+
def __is_variable(self, data):
133+
return type(data) is str and re.search(VARIABLE_PATTERN, data) is not None
134+
135+
def __interpol_variable(self, data):
141136
try:
142-
return os.environ[cls.extract_env_variable_key(data)]
137+
return os.environ[self.__extract_env_variable_key(data)]
143138
except KeyError:
144139
raise ConfigError(f'Environment variable {data} was not found')
145140

146-
@classmethod
147-
def extract_env_variable_key(cls, variable):
141+
def __extract_env_variable_key(self, variable):
148142
variable = variable[1:]
149143
if variable[0] == '{':
150144
return variable[1:-1]
151145
return variable
146+
147+
148+
configparser = Configparser()

test_configparser.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pyconfigparser import Config, ConfigError, ConfigFileNotFoundError
1+
from pyconfigparser import configparser, ConfigError, ConfigFileNotFoundError
22
from config.schemas import SIMPLE_SCHEMA_CONFIG, UNSUPPORTED_OBJECT_KEYS_SCHEMA
33
import unittest
44
import os
@@ -9,55 +9,54 @@
99

1010
class ConfigTestCase(unittest.TestCase):
1111
def setUp(self) -> None:
12-
Config.set_hold_an_instance(False)
12+
configparser.hold_an_instance = False
1313
os.environ['DATE_FORMAT_TEST'] = DT_FMT_TEST
1414
os.environ['LOG_LEVEL_TEST'] = VAR_LOG_LEVEL_INFO
1515

16-
def test_new(self):
17-
self.assertRaises(RuntimeError, Config)
18-
1916
def test_schema_checking(self):
20-
self.assertRaises(ConfigError, Config.get_config, 1)
17+
self.assertRaises(ConfigError, configparser.get_config, 1)
2118

2219
def test_config_without_file(self):
23-
self.assertRaises(ConfigFileNotFoundError, Config.get_config, SIMPLE_SCHEMA_CONFIG,
20+
self.assertRaises(ConfigFileNotFoundError, configparser.get_config, SIMPLE_SCHEMA_CONFIG,
2421
'config',
2522
'some_non_exists_file.json')
2623

2724
def test_undefined_env_var(self):
2825
try:
29-
Config.get_config(file_name='config.yaml')
26+
configparser.get_config(file_name='config.yaml')
3027
except Exception as e:
3128
self.assertIn('Environment', str(e))
3229

3330
def test_to_access_attr_from_config(self):
34-
config = Config.get_config(SIMPLE_SCHEMA_CONFIG)
31+
config = configparser.get_config(SIMPLE_SCHEMA_CONFIG)
3532
self.assertEqual(VAR_LOG_LEVEL_INFO, config.core.logging.level)
3633
self.assertEqual(DT_FMT_TEST, config.core.logging.datefmt)
3734
self.assertEqual('format', config.core.logging.format)
3835
self.assertEqual(24, config.core.obj_list[0].age)
3936
self.assertEqual('Mike', config.core.obj_list[0]['name']) # <- using subscriptable access
4037

4138
def test_access_fake_attr(self):
42-
config = Config.get_config(SIMPLE_SCHEMA_CONFIG)
39+
config = configparser.get_config(SIMPLE_SCHEMA_CONFIG)
4340
self.assertRaises(AttributeError, lambda: config.fake_attr)
4441

4542
def test_unsupported_object_key(self):
46-
self.assertRaises(ConfigError, Config.get_config, UNSUPPORTED_OBJECT_KEYS_SCHEMA,
43+
self.assertRaises(ConfigError, configparser.get_config, UNSUPPORTED_OBJECT_KEYS_SCHEMA,
4744
file_name='unsupported_object_key.json')
4845

4946
def test_set_hold_an_invalid_instance(self):
50-
self.assertRaises(ValueError, Config.set_hold_an_instance, [])
47+
def assign_a_bad_type():
48+
configparser.hold_an_instance = []
49+
self.assertRaises(ValueError, assign_a_bad_type)
5150

5251
def test_config_with_wrong_json_model(self):
53-
self.assertRaises(ConfigError, Config.get_config, SIMPLE_SCHEMA_CONFIG, file_name='wrong_model.json')
52+
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='wrong_model.json')
5453

5554
def test_config_file_with_unsupported_extension(self):
56-
self.assertRaises(ConfigError, Config.get_config, SIMPLE_SCHEMA_CONFIG, file_name='config.bad_extension')
55+
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='config.bad_extension')
5756

5857
def test_bad_decoder_error(self):
59-
self.assertRaises(ConfigError, Config.get_config, SIMPLE_SCHEMA_CONFIG, file_name='bad_content.json')
60-
self.assertRaises(ConfigError, Config.get_config, SIMPLE_SCHEMA_CONFIG, file_name='bad_content.yaml')
58+
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='bad_content.json')
59+
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='bad_content.yaml')
6160

6261

6362
if __name__ == '__main__':

0 commit comments

Comments
 (0)