| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- import json
- import pathlib
- import functools
- from .config_exceptions import key_not_implemented
- from .config_exceptions import bad_type_loaded
- from .config_exceptions import invalid_config_processor
- from .config_exceptions import bad_key_type
- from .config_exceptions import bad_value_type
- from .config_exceptions import config_file_not_exists
- from .config_exceptions import config_file_not_readable
- class config_getter:
- """
- That is config getter, it is builded by config processor. It is read
- only and is usefull to getting keys from config. When an key is not
- definied in configuration file, then default value is returned. Keys
- could be accessed by "get(key: str)" method, as attribute of instance
- of that class or as dict keys.
- """
- def __init__(self, content: dict) -> None:
- """
- That create new instance of getter. It would be used only by config
- processor.
- Parameters
- ----------
- content : dict
- Content of the config.
- """
- self.__content = dict(content)
-
- @functools.cache
- def get(self, key: str) -> str | int | float | bool:
- """
- That function return given key from config.
- Parameters
- ----------
- key : str
- Name of the key to return.
- Returns
- -------
- str | float | bool | int
- Value of the key.
- Raises
- ------
- key_not_implemented
- When tried to load key which not exists.
- """
- if not key in self.__content:
- raise key_not_implemented(key)
- return self.__content[key]
- def __getattr__(self, key: str) -> str | int | float | bool:
- """
- That is alias of "get(key: str)" function for getting keys as
- attributes of that class instance.
- """
- return self.get(key)
- def __getitem__(self, key: str) -> str | int | float | bool:
- """
- That is alias of "get(key: str)" function for getting keys as
- keys of dict.
- """
- return self.get(key)
- class config_processor:
- """
- That class is processor for configuration. It load defaults config
- options, then config loader addng keys from config file. That class
- validating options, it key must exists in the defaults config, and
- also type of default config value must be same as loaded value for
- that key.
- """
- def __init__(self) -> None:
- """
- It create new config processor. Content is set to default. Adding
- new keys replace defaults values by new ones from config loader.
- """
- self.__content = self.get_default_config()
- self.__validate_defaults()
- def __validate_defaults(self) -> None:
- """
- That function validate dictionary of defaults config. It could
- find typical issues with configuration.
- Raises
- ------
- bad_key_type
- When key in the configuration dict is not string.
- bad_value_type
- When value for key in the configuration is in invalid type. Valid
- types are str, int, float and bool.
- """
- for key, value in self.get_default_config().items():
- if type(key) is not str:
- raise bad_key_type(key)
- if type(value) is str or type(value) is int:
- continue
-
- if type(value) is float or type(value) is bool:
- continue
- raise bad_value_type(key, value)
- def add(self, key: str, value: str | int | float | bool) -> None:
- """
- It load new key to config. Key must exists in the default config. It
- replace value from default configuration by new value, loaded from
- configuration file.
- Parameters
- ----------
- key : str
- Name of the key from configuration.
- value : str | int | float | bool
- Value for that key.
- Raises
- ------
- key_not_implemented
- When key not exists in the default configuration.
- bad_type_loaded
- When type loaded from configuration is different from
- type in default config for that key.
- """
- if not key in self.get_default_config():
- raise key_not_implemented(key)
- default = self.get_default_config()[key]
-
- if type(default) is not type(value):
- raise bad_type_loaded(key, type(default), type(value))
-
- self.__content[key] = value
- def result(self) -> config_getter:
- """
- It return new config getter. Config getter is object which make
- configuration read only.
- Returns
- -------
- config_getter
- New config getter from configuration.
- """
- return config_getter(self.__content)
- @functools.cache
- def get_default_config(self) -> dict:
- """
- It return default configuration. It must be overwriten, it is
- abstract function.
- Returns
- -------
- dict <str, str | int | float | bool>
- Default configuration to load.
- """
- raise NotImplementedError()
- class config_loader:
- def __new__(
- cls,
- processor_class: type,
- location: pathlib.Path | None = None
- ) -> config_getter:
- """
- This load configuration from config file. It require configuration
- processor which could handle options from file. Processor must be
- given as class, which is subclass of config_processor. That subclass
- overwrite defaults config.
- Parameters
- ----------
- processor_class : type
- Subclass of config_processor with defaults config.
-
- location : pathlib.Path | None
- Location of config file, or none. When give none, then
- default location from that class is used. To overwrite default
- location create subclass of config_loader.
- Raises
- ------
- invalid_config_processor
- When config processor is not valid subclass.
- config_file_not_exists
- When config file does not exists.
- config_file_not_readable
- When config file contains syntax error, or file is
- not readable because of filesystem error.
- Returns
- -------
- config_getter
- Config getter created by config processor, and loaded from config
- file.
- """
- if type(processor_class) is not type:
- raise invalid_config_processor(processor_class)
- if not issubclass(processor_class, config_processor):
- raise invalid_config_processor(processor_class)
- processor = processor_class()
- default = cls.get_default_location()
- if location is None:
- location = default
- if not location.exists() or not location.is_file():
- raise config_file_not_exists(location)
- try:
- return cls.__load(processor, location)
- except Exception as error:
- raise config_file_not_readable(error)
- @classmethod
- def __load(
- cls,
- processor: config_processor,
- location: pathlib.Path
- ) -> config_getter:
- """
- This function load config from configuration file. It open file,
- and adding all keys to the config processor. Finally it create
- config_getter to make config readonly.
- Parameters
- ----------
- processor : config_processor
- Config processor to work with.
-
- location : pathlib.Path
- Location of the config file.
- Returns
- -------
- config_getter
- Result of config file.
- """
- with location.open() as handler:
- for key, value in json.loads(handler.read()).items():
- processor.add(key, value)
-
- return processor.result()
- @classmethod
- @functools.cache
- def get_default_location(cls) -> pathlib.Path:
- """
- It is default location of config file. It is abstract function and
- must being overwrite by subclass to make config loader useable.
- Returns
- -------
- pathlib.Path
- Path of the default config file.
- """
- raise NotImplementedError()
|