Skip to content

Commit

Permalink
Merge pull request #23 from BrunoSilvaAndrade/22-modulenotfounderror-…
Browse files Browse the repository at this point in the history
…no-module-named-parsers

fix(package): Unifying the whole code in a unique file.
  • Loading branch information
BrunoSilvaAndrade authored Aug 15, 2022
2 parents e42f8d8 + a7e12b0 commit 1c83c7e
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 94 deletions.
20 changes: 0 additions & 20 deletions parsers.py

This file was deleted.

180 changes: 106 additions & 74 deletions pyconfigparser.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,111 @@
from parsers import ParseError, json_parser, yaml_parser
from schema import Schema, SchemaError
from typing import Any
from os import path
import json
import yaml
import os
import re

VARIABLE_PATTERN = r'\$([a-zA-Z][\w]+|\{[a-zA-Z][\w]+\})$'
DEFAULT_CONFIG_FILES = ('config.json', 'config.yaml', 'config.yml')
ENTITY_NAME_PATTERN = r'^[a-zA-Z][\w]+$'
SUPPORTED_EXTENSIONS = {
'json': json_parser,
'yaml': yaml_parser,
'yml': yaml_parser
}
__all__ = [
"ConfigError",
"ConfigFileNotFoundError",
"Config",
"ConfigParser",
"configparser"
]


class ConfigError(Exception):
pass


class ConfigFileNotFoundError(ConfigError):
class ConfigFileNotFoundError(Exception):
pass


def _json_parser(file_buff):
try:
return json.loads(file_buff)
except json.JSONDecodeError as e:
raise ConfigError('Unable to decode config file using json', e)


def _yaml_parser(file_buff):
try:
return yaml.safe_load(file_buff)
except yaml.YAMLError as e:
raise ConfigError('Unable to decode config file using yaml', e)


def _validate_schema(schema, config_obj):
if schema is None:
return config_obj
elif type(schema) not in (dict, list):
raise ConfigError('The first config\'s schema element should be a Map or a List')

return Schema(schema).validate(config_obj)


def _get_file_buff(path_file: str):
with open(path_file, 'r') as f:
return f.read()


def _get_file_parser(file_path):
try:
extension = file_path.split('.')[-1]
return _SUPPORTED_EXTENSIONS[extension]
except KeyError:
raise ConfigError(f'Supported extensions: {list(_SUPPORTED_EXTENSIONS.keys())}')


def _get_file_path(config_dir, file_name):
file_path = f'{os.getcwd()}/{config_dir}/'
if type(file_name) is str:
file_name = [file_name]

for f_name in file_name:
if path.isfile(file_path + f_name):
return file_path + f_name

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


def _is_a_valid_object_key(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(data):
return type(data) is str and re.search(_VARIABLE_PATTERN, data) is not None


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


def _extract_env_variable_key(variable):
variable = variable[1:]
if variable[0] == '{':
return variable[1:-1]
return variable


_VARIABLE_PATTERN = r'\$([a-zA-Z][\w]+|\{[a-zA-Z][\w]+\})$'
_DEFAULT_CONFIG_FILES = ('config.json', 'config.yaml', 'config.yml')
_ENTITY_NAME_PATTERN = r'^[a-zA-Z][\w]+$'
_SUPPORTED_EXTENSIONS = {
'json': _json_parser,
'yaml': _yaml_parser,
'yml': _yaml_parser
}


class Config:

def __getitem__(self, item):
Expand All @@ -45,7 +128,7 @@ class ConfigParser:
def __init__(self):
self.__instance = None
self.__hold_an_instance = True
self.__ignore_unsetted_env_vars = False
self.__ignore_unset_env_vars = False

@property
def hold_an_instance(self):
Expand All @@ -59,100 +142,49 @@ def hold_an_instance(self, value):

@property
def ignore_unset_env_vars(self):
return self.__ignore_unsetted_env_vars
return self.__ignore_unset_env_vars

@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
self.__ignore_unset_env_vars = value

def get_config(self, schema: dict = None, config_dir: str = 'config', file_name: Any = DEFAULT_CONFIG_FILES):
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)
parser = self.__get_file_parser(file_path)
file_buff = self.__get_file_buff(file_path)
file_path = _get_file_path(config_dir, file_name)
parser = _get_file_parser(file_path)
file_buff = _get_file_buff(file_path)

try:
config = self.__validate_schema(schema, parser(file_buff))
config = _validate_schema(schema, parser(file_buff))
return self.__dict_2_obj(config)
except ParseError as e:
raise ConfigError(e)
except SchemaError as e:
raise ConfigError('Schema validation error', e)

def __get_file_parser(self, file_path):
try:
extension = file_path.split('.')[-1]
return SUPPORTED_EXTENSIONS[extension]
except KeyError:
raise ConfigError(f'Supported extensions: {list(SUPPORTED_EXTENSIONS.keys())}')

def __get_file_path(self, config_dir, file_name):
file_path = f'{os.getcwd()}/{config_dir}/'
if type(file_name) is str:
file_name = [file_name]

for f_name in file_name:
if path.isfile(file_path + f_name):
return file_path + f_name

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

def __validate_schema(self, schema, config_obj):
if schema is None:
return config_obj
elif type(schema) not in (dict, list):
raise ConfigError('The first config\'s schema element should be a Map or a List')

return Schema(schema).validate(config_obj)

def __get_file_buff(self, path_file: str):
with open(path_file, 'r') as f:
return f.read()

def __dict_2_obj(self, data: Any):
_type = type(data)

if _type is dict:
obj = Config()
for key, value in data.items():
self.__is_a_valid_object_key(key)
_is_a_valid_object_key(key)
setattr(obj, key, self.__dict_2_obj(value))
return obj

if _type in (list, set, tuple):
return list(map(lambda v: self.__dict_2_obj(v), data))

else:
if self.__is_variable(data):
return self.__interpol_variable(data)
if _is_variable(data):
return _interpol_variable(data, self.__ignore_unset_env_vars)
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 not re.search(ENTITY_NAME_PATTERN, key):
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)

def __extract_env_variable_key(self, variable):
variable = variable[1:]
if variable[0] == '{':
return variable[1:-1]
return variable


configparser = ConfigParser()

0 comments on commit 1c83c7e

Please sign in to comment.