diff options
author | romangraef <romangraef@loves.dicksinhisan.us> | 2018-07-06 10:35:54 +0200 |
---|---|---|
committer | romangraef <romangraef@loves.dicksinhisan.us> | 2018-07-06 10:35:54 +0200 |
commit | cbb9f2c3622ec96caf4ec9e58e84c8fbd3f45d23 (patch) | |
tree | bfe6aa26d7cab12334ae79b353ba2ec365f2b582 /configlib | |
download | configlib-cbb9f2c3622ec96caf4ec9e58e84c8fbd3f45d23.tar.gz configlib-cbb9f2c3622ec96caf4ec9e58e84c8fbd3f45d23.tar.bz2 configlib-cbb9f2c3622ec96caf4ec9e58e84c8fbd3f45d23.zip |
Initial commit
Diffstat (limited to 'configlib')
-rw-r--r-- | configlib/__init__.py | 6 | ||||
-rw-r--r-- | configlib/model.py | 41 | ||||
-rw-r--r-- | configlib/model_impl.py | 59 | ||||
-rw-r--r-- | configlib/util.py | 18 | ||||
-rw-r--r-- | configlib/version.py | 16 |
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) |