diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..275a047 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +CLIENT_ID = '' +CLIENT_SECRET = '' diff --git a/config.py b/config.py new file mode 100644 index 0000000..fdbfbb3 --- /dev/null +++ b/config.py @@ -0,0 +1,99 @@ +import logging +import os +import typing +from pathlib import Path +from typing import Any, Dict, Tuple + +import toml +from pydantic import BaseSettings as PydanticBaseSettings +from pydantic.env_settings import SettingsSourceCallable + +log = logging.getLogger(__name__) + +CONFIG_PATHS: list = [ + f"{os.getcwd()}/config.toml", + f"{os.getcwd()}/site/config.toml", + "./config.toml", +] + +DEFAULT_CONFIG_PATHS = [os.path.join(os.path.dirname(__file__), "config-default.toml")] + + +def determine_file_path( + paths: typing.Union[list, tuple], config_type: str = "default" +) -> typing.Union[str, None]: + """Determine the location of a configuration file, given a list of paths.""" + path = None + for file_path in paths: + config_file = Path(file_path) + if (config_file).exists(): + path = config_file + log.debug(f"Found {config_type} config at {file_path}") + break + return path or None + + +DEFAULT_CONFIG_PATH = determine_file_path(DEFAULT_CONFIG_PATHS) +USER_CONFIG_PATH = determine_file_path(CONFIG_PATHS, config_type="") + + +def toml_default_config_source(settings: PydanticBaseSettings) -> Dict[str, Any]: + """ + A simple settings source that loads variables from a toml file. + + from within the module's source folder. + """ + if DEFAULT_CONFIG_PATH is not None: + return dict(**toml.load(DEFAULT_CONFIG_PATH)) + return dict() + + +def toml_user_config_source(settings: PydanticBaseSettings) -> Dict[str, Any]: + """ + A simple settings source that loads variables from a toml file. + + from within the module's source folder. + """ + if USER_CONFIG_PATH is not None: + return dict(**toml.load(USER_CONFIG_PATH)) + return dict() + + +class BaseSettings(PydanticBaseSettings): + """ + Custom Pydantic base class for settings, with a Config that also loads directly from toml. + + Loading methods are from toml config files, class defaults, passed arguments, and env variables. + """ + + class Config: + """BaseSettings custom default Config.""" + + extra = "ignore" + env_file_encoding = "utf-8" + + @classmethod + def customise_sources( + cls, + init_settings: SettingsSourceCallable, + env_settings: SettingsSourceCallable, + file_secret_settings: SettingsSourceCallable, + ) -> Tuple[SettingsSourceCallable, ...]: + """Modify Pydantic default settings to add toml as a source and priortise env variables.""" + return ( + env_settings, + init_settings, + file_secret_settings, + toml_user_config_source, + toml_default_config_source, + ) + + +class SiteConfig(BaseSettings): + """Pydantic model for Site Settings.""" + + CLIENT_ID: str + CLIENT_SECRET: str + + +CONFIG = SiteConfig(_env_file="./.env") diff --git a/main.py b/main.py index 5326743..589ce07 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,7 @@ from starlette.staticfiles import StaticFiles from starlette.templating import Jinja2Templates +from .config import CONFIG # noqa: F401 This should be removed once configuration is used. templates = Jinja2Templates(directory="templates") diff --git a/poetry.lock b/poetry.lock index 5c2dd76..c2e533b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -475,7 +475,7 @@ toml = ">=0.10.0,<0.11.0" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -562,7 +562,7 @@ uvicorn = [] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "dcabd48254eeae86bbbd0b21d52b22884406da788dd5c86629de543716d1cf1a" +content-hash = "75bd9f5ef2d5f6e3345e95b2eaa9c9a8785b98c4b6c0d001ecd0837fffc46fac" [metadata.files] aiofiles = [ diff --git a/pyproject.toml b/pyproject.toml index 92b703d..b75d765 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,12 +9,13 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.8" +aiofiles = "^0.7.0" coloredlogs = "^15.0" -pydantic = "^1.8.2" -starlette = "^0.14.2" jinja2 = "^3.0.1" -aiofiles = "^0.7.0" +pydantic = "^1.8.2" python-dotenv = "^0.17.1" +starlette = "^0.14.2" +toml = "^0.10.2" uvicorn = {extras = ["standard"], version = "^0.13.4"} [tool.poetry.extras]