You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
107 lines
2.4 KiB
Python
107 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import (
|
|
Mapping,
|
|
Optional,
|
|
Tuple,
|
|
Type,
|
|
TypeAlias,
|
|
)
|
|
|
|
|
|
# type aliases
|
|
|
|
|
|
TypeSet: TypeAlias = Tuple[Type, ...]
|
|
TypeLike: TypeAlias = Type | TypeSet
|
|
|
|
|
|
# exceptions
|
|
|
|
|
|
class ConfigValidationError(Exception):
|
|
key: str
|
|
|
|
|
|
class ConfigMissingRequiredError(ConfigValidationError):
|
|
def __init__(self, *, key: str) -> None:
|
|
super().__init__(f"Missing configuration value for key {key!r}")
|
|
self.key = key
|
|
|
|
|
|
class ConfigInvalidTypeError(ConfigValidationError):
|
|
key: str
|
|
types: TypeSet
|
|
|
|
def __init__(self, *, key: str, types: TypeLike, got_type: Type) -> None:
|
|
self.key = key
|
|
self.types = self.__parse_type_like(types)
|
|
super().__init__(
|
|
f"Required configuration value for key {key!r} to be of either {self.type_list}; got type {got_type}."
|
|
)
|
|
|
|
@staticmethod
|
|
def __parse_type_like(types: TypeLike) -> TypeSet:
|
|
if isinstance(types, tuple):
|
|
return types
|
|
return (types,)
|
|
|
|
@property
|
|
def type_list(self) -> str:
|
|
return ", ".join(str(t) for t in self.types)
|
|
|
|
|
|
# methods
|
|
|
|
|
|
_REQUIRED_KEYS: Mapping[str, Optional[TypeLike]] = {
|
|
"PONY_PROVIDER": str,
|
|
"SECRET_KEY": (bytes, str),
|
|
}
|
|
|
|
|
|
_OPTIONAL_KEYS: Mapping[str, Optional[TypeLike]] = {
|
|
"PONY_CHARSET": str,
|
|
"PONY_CREATE_DB": bool,
|
|
"PONY_DATABASE": str,
|
|
"PONY_DB": str,
|
|
"PONY_DSN": str,
|
|
"PONY_FILENAME": str,
|
|
"PONY_HOST": str,
|
|
"PONY_PASSWD": str,
|
|
"PONY_PASSWORD": str,
|
|
"PONY_PORT": int,
|
|
"PONY_USER": str,
|
|
}
|
|
|
|
|
|
def validate_config(config: Mapping) -> None:
|
|
for required, key_list in {True: _REQUIRED_KEYS, False: _OPTIONAL_KEYS}.items():
|
|
for key, types in key_list.items():
|
|
check_value(
|
|
config=config,
|
|
key=key,
|
|
types=types,
|
|
required=required,
|
|
)
|
|
|
|
|
|
def check_value(
|
|
config: Mapping, key: str, types: Optional[TypeLike] = None, required: bool = True
|
|
) -> None:
|
|
if key not in config:
|
|
if required:
|
|
raise ConfigMissingRequiredError(
|
|
key=key,
|
|
)
|
|
return
|
|
if types is None:
|
|
return
|
|
value = config[key]
|
|
if not isinstance(value, types):
|
|
raise ConfigInvalidTypeError(
|
|
key=key,
|
|
types=types,
|
|
got_type=type(value),
|
|
)
|