Skip to content

Commit

Permalink
Merge pull request #21 from BrunoSilvaAndrade/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
BrunoSilvaAndrade authored Jul 15, 2022
2 parents b570395 + 9411646 commit c8cfc76
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 25 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ venv
dist
build
wheel
.coverage
.coverage
.pytest_cache
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: check-merge-conflict
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest
language: python
types: [python]
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ HOW TO USE
---
By default, the config file will look for the following config files in the `.config` directory: `config.json`, `config.yaml`, `config.yml`.

You can also pass a config directory of your preference (assuming your current directory).
You can also pass a config directory and or config file of your preference (assuming your current directory).

```python
from pyconfigparser import configparser

configparser.get_config(CONFIG_SCHEMA, config_dir='your_config_dir_path', file_name='your_config_file_name')
```

Schema validation
---
Expand Down Expand Up @@ -98,7 +104,7 @@ A json config file would be something like:
}
```

The instance of Config Class:
The config instance
```python
from pyconfigparser import configparser, ConfigError
import logging
Expand Down Expand Up @@ -155,6 +161,18 @@ from pyconfigparser import configparser
configparser.hold_an_instance = False
```

Environment Variables Interpolation
---
If the process does not find a value already set to your env variables
It will raise a ConfigError. But you can disable this behavior, and the parser will set `None` to these unresolved env vars

```python
from pyconfigparser import configparser

configparser.ignore_unset_env_vars = True
config = configparser.get_config()
```

CONTRIBUTE
---
---
Expand Down
38 changes: 24 additions & 14 deletions pyconfigparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class ConfigParser:
def __init__(self):
self.__instance = None
self.__hold_an_instance = True
self.__ignore_unsetted_env_vars = False

@property
def hold_an_instance(self):
Expand All @@ -56,15 +57,22 @@ def hold_an_instance(self, value):
raise ValueError('value must be a bool')
self.__hold_an_instance = value

def get_config(self, schema: dict = None, config_dir: str = 'config', file_name: Any = DEFAULT_CONFIG_FILES):
@property
def ignore_unset_env_vars(self):
return self.__ignore_unsetted_env_vars

if self.__instance is None:
instance = self.__create_new_instance(schema, config_dir, file_name)
if self.__hold_an_instance:
self.__instance = instance
else:
return instance
return self.__instance
@ignore_unset_env_vars.setter
def ignore_unset_env_vars(self, value):
if type(value) is not bool:
raise ValueError('value must be a bool')
self.__ignore_unsetted_env_vars = value

def get_config(self, schema: dict = None, config_dir: str = 'config', file_name: Any = DEFAULT_CONFIG_FILES):
if self.__hold_an_instance:
if self.__instance is None:
self.__instance = self.__create_new_instance(schema, config_dir, file_name)
return self.__instance
return self.__create_new_instance(schema, config_dir, file_name)

def __create_new_instance(self, schema, config_dir, file_name):
file_path = self.__get_file_path(config_dir, file_name)
Expand Down Expand Up @@ -125,19 +133,21 @@ def __dict_2_obj(self, data: Any):
return self.__interpol_variable(data)
return data

def __interpol_variable(self, data):
try:
return os.environ[self.__extract_env_variable_key(data)]
except KeyError:
if self.__ignore_unsetted_env_vars:
return None
raise ConfigError(f'Environment variable {data} was not found')

def __is_a_valid_object_key(self, key):
if re.search(ENTITY_NAME_PATTERN, key) is None:
raise ConfigError(f'The key {key} is invalid. The entity keys only may have words, number and underscores.')

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

def __interpol_variable(self, data):
try:
return os.environ[self.__extract_env_variable_key(data)]
except KeyError:
raise ConfigError(f'Environment variable {data} was not found')

def __extract_env_variable_key(self, variable):
variable = variable[1:]
if variable[0] == '{':
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pre-commit>=1.2.2
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
schema>=0.7.1
PyYaml>=3.12.0
PyYaml>=3.12.0
48 changes: 41 additions & 7 deletions test_configparser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pyconfigparser import configparser, ConfigError, ConfigFileNotFoundError
from pyconfigparser import ConfigParser, ConfigError, ConfigFileNotFoundError
from config.schemas import SIMPLE_SCHEMA_CONFIG, UNSUPPORTED_OBJECT_KEYS_SCHEMA
import unittest
import os
Expand All @@ -9,25 +9,32 @@

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

def test_schema_checking(self):
configparser = ConfigParser()
self.assertRaises(ConfigError, configparser.get_config, 1)

def test_config_without_file(self):
configparser = ConfigParser()
self.assertRaises(ConfigFileNotFoundError, configparser.get_config, SIMPLE_SCHEMA_CONFIG,
'config',
'some_non_exists_file.json')

def test_undefined_env_var(self):
try:
configparser = ConfigParser()
configparser.get_config(file_name='config.yaml')
except Exception as e:
self.assertIn('Environment', str(e))

configparser = ConfigParser()
configparser.ignore_unset_env_vars = True
configparser.get_config(file_name='config.yaml')

def test_to_access_attr_from_config(self):
configparser = ConfigParser()
config = configparser.get_config(SIMPLE_SCHEMA_CONFIG)
self.assertEqual(VAR_LOG_LEVEL_INFO, config.core.logging.level)
self.assertEqual(DT_FMT_TEST, config.core.logging.datefmt)
Expand All @@ -36,28 +43,55 @@ def test_to_access_attr_from_config(self):
self.assertEqual('Mike', config.core.obj_list[0]['name']) # <- using subscriptable access

def test_access_fake_attr(self):
configparser = ConfigParser()
config = configparser.get_config(SIMPLE_SCHEMA_CONFIG)
self.assertRaises(AttributeError, lambda: config.fake_attr)

def test_unsupported_object_key(self):
configparser = ConfigParser()
self.assertRaises(ConfigError, configparser.get_config, UNSUPPORTED_OBJECT_KEYS_SCHEMA,
file_name='unsupported_object_key.json')

def test_set_hold_an_invalid_instance(self):
def assign_a_bad_type():
configparser.hold_an_instance = []
self.assertRaises(ValueError, assign_a_bad_type)

def test_config_with_wrong_json_model(self):
configparser = ConfigParser()
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='wrong_model.json')

def test_config_file_with_unsupported_extension(self):
configparser = ConfigParser()
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='config.bad_extension')

def test_bad_decoder_error(self):
configparser = ConfigParser()
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='bad_content.json')
self.assertRaises(ConfigError, configparser.get_config, SIMPLE_SCHEMA_CONFIG, file_name='bad_content.yaml')

def test_caching_instance(self):
configparser = ConfigParser()
config1 = configparser.get_config()
config2 = configparser.get_config()
self.assertIs(config1, config2)
configparser.hold_an_instance = False

config2 = configparser.get_config()
self.assertIsNot(config1, config2)

def test_configparser_config_switches(self):
configparser = ConfigParser()

def assign_a_bad_type_hold_an_instance():
configparser.hold_an_instance = []

def assign_a_bad_type_ignore_unsetted_env_vars():
configparser.ignore_unset_env_vars = []

self.assertRaises(ValueError, assign_a_bad_type_hold_an_instance)
self.assertRaises(ValueError, assign_a_bad_type_ignore_unsetted_env_vars)
configparser.hold_an_instance = False
configparser.ignore_unset_env_vars = True
self.assertIs(configparser.hold_an_instance, False)
self.assertIs(configparser.ignore_unset_env_vars, True)
self.assertIsInstance(configparser.ignore_unset_env_vars, bool)


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

0 comments on commit c8cfc76

Please sign in to comment.