aboutsummaryrefslogtreecommitdiff
path: root/configlib
diff options
context:
space:
mode:
authorromangraef <romangraef@loves.dicksinhisan.us>2018-07-06 10:35:54 +0200
committerromangraef <romangraef@loves.dicksinhisan.us>2018-07-06 10:35:54 +0200
commitcbb9f2c3622ec96caf4ec9e58e84c8fbd3f45d23 (patch)
treebfe6aa26d7cab12334ae79b353ba2ec365f2b582 /configlib
downloadconfiglib-cbb9f2c3622ec96caf4ec9e58e84c8fbd3f45d23.tar.gz
configlib-cbb9f2c3622ec96caf4ec9e58e84c8fbd3f45d23.tar.bz2
configlib-cbb9f2c3622ec96caf4ec9e58e84c8fbd3f45d23.zip
Initial commit
Diffstat (limited to 'configlib')
-rw-r--r--configlib/__init__.py6
-rw-r--r--configlib/model.py41
-rw-r--r--configlib/model_impl.py59
-rw-r--r--configlib/util.py18
-rw-r--r--configlib/version.py16
5 files changed, 140 insertions, 0 deletions
diff --git a/configlib/__init__.py b/configlib/__init__.py
new file mode 100644
index 0000000..c0f25aa
--- /dev/null
+++ b/configlib/__init__.py
@@ -0,0 +1,6 @@
+from .model import Config, ConfigValueMissingException, InvalidConfigTypingException
+from .model_impl import BaseConfig
+from .version import VersionInfo, version
+
+__all__ = ['ConfigValueMissingException', 'Config', 'InvalidConfigTypingException',
+ 'BaseConfig', 'VersionInfo', 'version']
diff --git a/configlib/model.py b/configlib/model.py
new file mode 100644
index 0000000..6d79464
--- /dev/null
+++ b/configlib/model.py
@@ -0,0 +1,41 @@
+import json
+from abc import abstractmethod, ABC
+from typing import TypeVar, Type, Union, AnyStr, TextIO
+
+_T = TypeVar('_T', bound='Config')
+
+
+class InvalidConfigTypingException(Exception):
+ pass
+
+
+class ConfigValueMissingException(Exception):
+ pass
+
+
+class Config(ABC):
+
+ @classmethod
+ @abstractmethod
+ def get_name(cls) -> str:
+ pass
+
+ @classmethod
+ @abstractmethod
+ def parse_dict(cls: Type[_T], data: dict) -> _T:
+ pass
+
+ @classmethod
+ def load(cls: Type[_T], file: Union[AnyStr, TextIO]) -> _T:
+ if hasattr(file, 'read'):
+ return cls.loads(file.read())
+ with open(file) as fp:
+ return cls.load(fp)
+
+ @classmethod
+ def loads(cls: Type[_T], text: str) -> _T:
+ return cls.parse_dict(json.loads(text))
+
+ @classmethod
+ def get_instance(cls: Type[_T]) -> _T:
+ return cls
diff --git a/configlib/model_impl.py b/configlib/model_impl.py
new file mode 100644
index 0000000..4b7a866
--- /dev/null
+++ b/configlib/model_impl.py
@@ -0,0 +1,59 @@
+from typing import TypeVar, Type, Dict, List
+
+from .model import Config, ConfigValueMissingException, InvalidConfigTypingException
+from .util import snake_case
+
+_T = TypeVar('_T', bound=Config)
+
+_T0 = TypeVar('_T0')
+
+
+def parse_list_impl(cls: Type[_T0], data, path=''):
+ lis = []
+ for i, item in enumerate(data):
+ list.append(parse_single_item(cls, item, path + '[' + str(i) + ']'))
+ return lis
+
+
+def parse_obj_impl(cls: Type[_T0], data, path='Config') -> _T0:
+ obj = cls()
+ annotations: Dict[str, Type] = obj.__annotations__
+ for key, val_type in annotations.items():
+ if key not in data.keys():
+ raise ConfigValueMissingException(path + key)
+ val = data[key]
+ setattr(obj, key, parse_single_item(val_type, val, path + '.' + key))
+ return obj
+
+
+def parse_dict_impl(val_type: Type[_T0], val, path) -> _T0:
+ dic = {}
+ for key, value in val.items():
+ dic[key] = parse_single_item(val_type, value, path + '[' + repr(key) + ']')
+ return dic
+
+
+def parse_single_item(val_type: Type[_T0], val, path) -> _T0:
+ if issubclass(val_type, (str, int, float)):
+ return val
+ if isinstance(val_type, List):
+ if len(val_type.__args__) != 1:
+ raise InvalidConfigTypingException(path + ': List must be supplied exactly one type')
+ return parse_list_impl(val_type.__args__[0], val, path)
+ if isinstance(val_type, Dict):
+ if len(val_type.__args__) != 2:
+ raise InvalidConfigTypingException(path + ': Dict must be supplied exactly two types')
+ if val_type.__args__[0] != str:
+ raise InvalidConfigTypingException(path + ': Dict must have `str` as indexing')
+ return parse_dict_impl(val_type.__args__[1], val, path)
+ return parse_obj_impl(val_type, val, path)
+
+
+class BaseConfig(Config):
+ @classmethod
+ def get_name(cls) -> str:
+ return snake_case(cls.__name__).upper()
+
+ @classmethod
+ def parse_dict(cls: Type[_T], data: dict) -> _T:
+ return parse_obj_impl(cls, data)
diff --git a/configlib/util.py b/configlib/util.py
new file mode 100644
index 0000000..3c7618d
--- /dev/null
+++ b/configlib/util.py
@@ -0,0 +1,18 @@
+import re
+from typing import List
+
+
+def parse_case(any_case: str) -> List[str]:
+ if '_' in any_case:
+ return any_case.lower().split('_')
+ if '-' in any_case:
+ return any_case.lower().split('-')
+ return [word.lower() for word in re.split('(?<=[a-z0-9])(?=[A-Z])', any_case)]
+
+
+def snake_case(any_case: str) -> str:
+ return '_'.join(parse_case(any_case))
+
+
+def pascal_case(any_case: str) -> str:
+ return ''.join(word.capitalize() for word in parse_case(any_case))
diff --git a/configlib/version.py b/configlib/version.py
new file mode 100644
index 0000000..475dc6e
--- /dev/null
+++ b/configlib/version.py
@@ -0,0 +1,16 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class VersionInfo:
+ major: int
+ minor: int
+ build: int
+ level: str
+ serial: int
+
+ def __str__(self):
+ return '{major}.{minor}.{build}{level}{serial}'.format(**self.__dict__)
+
+
+version = VersionInfo(1, 0, 0, 'a', 0)