Skip to content

Commit c8cfc76

Browse files
Merge pull request #21 from BrunoSilvaAndrade/develop
Develop
2 parents b570395 + 9411646 commit c8cfc76

File tree

7 files changed

+104
-25
lines changed

7 files changed

+104
-25
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ venv
44
dist
55
build
66
wheel
7-
.coverage
7+
.coverage
8+
.pytest_cache

.pre-commit-config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v3.4.0
4+
hooks:
5+
- id: check-merge-conflict
6+
- id: trailing-whitespace
7+
- id: end-of-file-fixer
8+
- id: check-yaml
9+
- repo: local
10+
hooks:
11+
- id: pytest
12+
name: pytest
13+
entry: pytest
14+
language: python
15+
types: [python]

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ HOW TO USE
3030
---
3131
By default, the config file will look for the following config files in the `.config` directory: `config.json`, `config.yaml`, `config.yml`.
3232

33-
You can also pass a config directory of your preference (assuming your current directory).
33+
You can also pass a config directory and or config file of your preference (assuming your current directory).
34+
35+
```python
36+
from pyconfigparser import configparser
37+
38+
configparser.get_config(CONFIG_SCHEMA, config_dir='your_config_dir_path', file_name='your_config_file_name')
39+
```
3440

3541
Schema validation
3642
---
@@ -98,7 +104,7 @@ A json config file would be something like:
98104
}
99105
```
100106

101-
The instance of Config Class:
107+
The config instance
102108
```python
103109
from pyconfigparser import configparser, ConfigError
104110
import logging
@@ -155,6 +161,18 @@ from pyconfigparser import configparser
155161
configparser.hold_an_instance = False
156162
```
157163

164+
Environment Variables Interpolation
165+
---
166+
If the process does not find a value already set to your env variables
167+
It will raise a ConfigError. But you can disable this behavior, and the parser will set `None` to these unresolved env vars
168+
169+
```python
170+
from pyconfigparser import configparser
171+
172+
configparser.ignore_unset_env_vars = True
173+
config = configparser.get_config()
174+
```
175+
158176
CONTRIBUTE
159177
---
160178
---

pyconfigparser.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class ConfigParser:
4545
def __init__(self):
4646
self.__instance = None
4747
self.__hold_an_instance = True
48+
self.__ignore_unsetted_env_vars = False
4849

4950
@property
5051
def hold_an_instance(self):
@@ -56,15 +57,22 @@ def hold_an_instance(self, value):
5657
raise ValueError('value must be a bool')
5758
self.__hold_an_instance = value
5859

59-
def get_config(self, schema: dict = None, config_dir: str = 'config', file_name: Any = DEFAULT_CONFIG_FILES):
60+
@property
61+
def ignore_unset_env_vars(self):
62+
return self.__ignore_unsetted_env_vars
6063

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
65-
else:
66-
return instance
67-
return self.__instance
64+
@ignore_unset_env_vars.setter
65+
def ignore_unset_env_vars(self, value):
66+
if type(value) is not bool:
67+
raise ValueError('value must be a bool')
68+
self.__ignore_unsetted_env_vars = value
69+
70+
def get_config(self, schema: dict = None, config_dir: str = 'config', file_name: Any = DEFAULT_CONFIG_FILES):
71+
if self.__hold_an_instance:
72+
if self.__instance is None:
73+
self.__instance = self.__create_new_instance(schema, config_dir, file_name)
74+
return self.__instance
75+
return self.__create_new_instance(schema, config_dir, file_name)
6876

6977
def __create_new_instance(self, schema, config_dir, file_name):
7078
file_path = self.__get_file_path(config_dir, file_name)
@@ -125,19 +133,21 @@ def __dict_2_obj(self, data: Any):
125133
return self.__interpol_variable(data)
126134
return data
127135

136+
def __interpol_variable(self, data):
137+
try:
138+
return os.environ[self.__extract_env_variable_key(data)]
139+
except KeyError:
140+
if self.__ignore_unsetted_env_vars:
141+
return None
142+
raise ConfigError(f'Environment variable {data} was not found')
143+
128144
def __is_a_valid_object_key(self, key):
129145
if re.search(ENTITY_NAME_PATTERN, key) is None:
130146
raise ConfigError(f'The key {key} is invalid. The entity keys only may have words, number and underscores.')
131147

132148
def __is_variable(self, data):
133149
return type(data) is str and re.search(VARIABLE_PATTERN, data) is not None
134150

135-
def __interpol_variable(self, data):
136-
try:
137-
return os.environ[self.__extract_env_variable_key(data)]
138-
except KeyError:
139-
raise ConfigError(f'Environment variable {data} was not found')
140-
141151
def __extract_env_variable_key(self, variable):
142152
variable = variable[1:]
143153
if variable[0] == '{':

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pre-commit>=1.2.2

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
schema>=0.7.1
2-
PyYaml>=3.12.0
2+
PyYaml>=3.12.0

test_configparser.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pyconfigparser import configparser, 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,25 +9,32 @@
99

1010
class ConfigTestCase(unittest.TestCase):
1111
def setUp(self) -> None:
12-
configparser.hold_an_instance = False
1312
os.environ['DATE_FORMAT_TEST'] = DT_FMT_TEST
1413
os.environ['LOG_LEVEL_TEST'] = VAR_LOG_LEVEL_INFO
1514

1615
def test_schema_checking(self):
16+
configparser = ConfigParser()
1717
self.assertRaises(ConfigError, configparser.get_config, 1)
1818

1919
def test_config_without_file(self):
20+
configparser = ConfigParser()
2021
self.assertRaises(ConfigFileNotFoundError, configparser.get_config, SIMPLE_SCHEMA_CONFIG,
2122
'config',
2223
'some_non_exists_file.json')
2324

2425
def test_undefined_env_var(self):
2526
try:
27+
configparser = ConfigParser()
2628
configparser.get_config(file_name='config.yaml')
2729
except Exception as e:
2830
self.assertIn('Environment', str(e))
2931

32+
configparser = ConfigParser()
33+
configparser.ignore_unset_env_vars = True
34+
configparser.get_config(file_name='config.yaml')
35+
3036
def test_to_access_attr_from_config(self):
37+
configparser = ConfigParser()
3138
config = configparser.get_config(SIMPLE_SCHEMA_CONFIG)
3239
self.assertEqual(VAR_LOG_LEVEL_INFO, config.core.logging.level)
3340
self.assertEqual(DT_FMT_TEST, config.core.logging.datefmt)
@@ -36,28 +43,55 @@ def test_to_access_attr_from_config(self):
3643
self.assertEqual('Mike', config.core.obj_list[0]['name']) # <- using subscriptable access
3744

3845
def test_access_fake_attr(self):
46+
configparser = ConfigParser()
3947
config = configparser.get_config(SIMPLE_SCHEMA_CONFIG)
4048
self.assertRaises(AttributeError, lambda: config.fake_attr)
4149

4250
def test_unsupported_object_key(self):
51+
configparser = ConfigParser()
4352
self.assertRaises(ConfigError, configparser.get_config, UNSUPPORTED_OBJECT_KEYS_SCHEMA,
4453
file_name='unsupported_object_key.json')
4554

46-
def test_set_hold_an_invalid_instance(self):
47-
def assign_a_bad_type():
48-
configparser.hold_an_instance = []
49-
self.assertRaises(ValueError, assign_a_bad_type)
50-
5155
def test_config_with_wrong_json_model(self):
56+
configparser = ConfigParser()
5257
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='wrong_model.json')
5358

5459
def test_config_file_with_unsupported_extension(self):
60+
configparser = ConfigParser()
5561
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='config.bad_extension')
5662

5763
def test_bad_decoder_error(self):
64+
configparser = ConfigParser()
5865
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='bad_content.json')
5966
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='bad_content.yaml')
6067

68+
def test_caching_instance(self):
69+
configparser = ConfigParser()
70+
config1 = configparser.get_config()
71+
config2 = configparser.get_config()
72+
self.assertIs(config1, config2)
73+
configparser.hold_an_instance = False
74+
75+
config2 = configparser.get_config()
76+
self.assertIsNot(config1, config2)
77+
78+
def test_configparser_config_switches(self):
79+
configparser = ConfigParser()
80+
81+
def assign_a_bad_type_hold_an_instance():
82+
configparser.hold_an_instance = []
83+
84+
def assign_a_bad_type_ignore_unsetted_env_vars():
85+
configparser.ignore_unset_env_vars = []
86+
87+
self.assertRaises(ValueError, assign_a_bad_type_hold_an_instance)
88+
self.assertRaises(ValueError, assign_a_bad_type_ignore_unsetted_env_vars)
89+
configparser.hold_an_instance = False
90+
configparser.ignore_unset_env_vars = True
91+
self.assertIs(configparser.hold_an_instance, False)
92+
self.assertIs(configparser.ignore_unset_env_vars, True)
93+
self.assertIsInstance(configparser.ignore_unset_env_vars, bool)
94+
6195

6296
if __name__ == '__main__':
6397
unittest.main()

0 commit comments

Comments
 (0)