Rebuild configuration loading mechanisms with Flask tools
- and extract Flask app initialization mostly into modulemaster
parent
ffc0550342
commit
cf66f47b2e
@ -0,0 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from flask import (
|
||||
Flask,
|
||||
)
|
||||
|
||||
from .web.config import apply_config
|
||||
|
||||
|
||||
def create_app() -> Flask:
|
||||
app = Flask(
|
||||
import_name=__name__,
|
||||
instance_relative_config=True,
|
||||
root_path=str(Path(__file__).parent.parent),
|
||||
)
|
||||
apply_config(app.config)
|
||||
print(app.static_folder)
|
||||
return app
|
||||
|
||||
|
||||
__all__ = [
|
||||
"create_app",
|
||||
]
|
@ -0,0 +1,16 @@
|
||||
from .load import load_config
|
||||
from .validate import validate_config
|
||||
|
||||
from flask import (
|
||||
Config,
|
||||
)
|
||||
|
||||
|
||||
def apply_config(config: Config) -> None:
|
||||
load_config(config)
|
||||
validate_config(config)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"apply_config",
|
||||
]
|
@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class _Config:
|
||||
pass
|
||||
|
||||
|
||||
class DebugConfig(_Config):
|
||||
PONY_CREATE_DB = True
|
||||
PONY_FILENAME = "./db.sqlite"
|
||||
PONY_PROVIDER = "sqlite"
|
||||
|
||||
|
||||
class TestingConfig(_Config):
|
||||
pass
|
||||
|
||||
|
||||
class ProductionConfig(_Config):
|
||||
pass
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DebugConfig",
|
||||
"ProductionConfig",
|
||||
"TestingConfig",
|
||||
]
|
@ -0,0 +1,48 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import (
|
||||
Config,
|
||||
)
|
||||
|
||||
from .defaults import (
|
||||
DebugConfig,
|
||||
ProductionConfig,
|
||||
TestingConfig,
|
||||
)
|
||||
|
||||
|
||||
FLASK_DEBUG_KEY = "DEBUG"
|
||||
FLASK_TESTING_KEY = "TESTING"
|
||||
|
||||
ENV_FLASK_PREFIX = "FLASK"
|
||||
ENV_OTHER_NAMESPACES = [
|
||||
"CELERY",
|
||||
"PONY",
|
||||
]
|
||||
|
||||
|
||||
def load_config(config: Config) -> None:
|
||||
_load_defaults(config)
|
||||
_load_from_env(config)
|
||||
|
||||
|
||||
def _load_defaults(config: Config) -> None:
|
||||
defaults = (
|
||||
DebugConfig
|
||||
if config.get(FLASK_DEBUG_KEY, False)
|
||||
else TestingConfig
|
||||
if config.get(FLASK_TESTING_KEY, False)
|
||||
else ProductionConfig
|
||||
)
|
||||
config.from_object(defaults())
|
||||
|
||||
|
||||
def _load_from_env(config: Config) -> None:
|
||||
config.from_prefixed_env(
|
||||
prefix="FLASK",
|
||||
)
|
||||
for prefix in ENV_OTHER_NAMESPACES:
|
||||
config.from_prefixed_env(
|
||||
prefix=prefix,
|
||||
trim_prefix=False,
|
||||
)
|
@ -0,0 +1,106 @@
|
||||
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),
|
||||
)
|
Loading…
Reference in New Issue