From b86a8e5083d069f028b4e4c40a2952ddc1c0b8cd Mon Sep 17 00:00:00 2001 From: Ethan Paul Date: Tue, 8 Feb 2022 22:49:01 -0500 Subject: [PATCH] Add config structure and database init function --- section7/configuration.py | 111 ++++++++++++++++++++++++++++++++++++++ section7/constants.py | 16 +++++- section7/database.py | 49 +++++++++++++++++ 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 section7/configuration.py diff --git a/section7/configuration.py b/section7/configuration.py new file mode 100644 index 0000000..fff9f4c --- /dev/null +++ b/section7/configuration.py @@ -0,0 +1,111 @@ +import os +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path +from typing import Optional + +from section7 import constants + + +@dataclass +class DatabaseSqliteConfig: + """SQLite database backend configuration options + + :param path: Path to the SQLite database file + """ + + path: Path = Path.cwd() / "section7.db" + + @classmethod + def from_env(cls): + """Build dataclass from environment""" + return cls( + path=Path(os.environ.get("SECTION7_DATABASE_SQLITE_PATH", cls.path)) + .expanduser() + .resolve(), + ) + + +@dataclass +class DatabaseMariaConfig: + """MariaDB database backend configuration options + + :param hostname: Hostname or IP address of the host running the database server + :param username: Username of the account to use for connecting to the database server + :param password: Password for the account to use for connecting to the database server + :param port: Port on the host that the database server is listening on + :param schema: Database schema that the application should use + """ + + hostname: str = "localhost" + username: str = "root" + password: Optional[str] = None + port: int = 3306 + schema: str = "section7" + + @classmethod + def from_env(cls): + """Build dataclass from environment""" + + return cls( + hostname=os.getenv("SECTION7_DATABASE_MARIADB_HOSTNAME", cls.hostname), + username=os.getenv("SECTION7_DATABASE_MARIADB_USERNAME", cls.username), + password=os.environ.get("SECTION7_DATABASE_MARIADB_PASSWORD", cls.password), + port=int(os.environ.get("SECTION7_DATABASE_MARIADB_PORT", cls.port)), + schema=os.getenv("SECTION7_DATABASE_MARIADB_SCHEMA", cls.schema), + ) + + +@dataclass +class DatabaseConfig: + """Database backend configuration + + :param backend: Enum selecting the backend to use for storing data + :param sqlite: Container of SQLite settings + :param mariadb: Container of MariaDB settings + """ + + backend: constants.DatabaseBackend = constants.DatabaseBackend.SQLITE + sqlite: DatabaseSqliteConfig = field(default_factory=DatabaseSqliteConfig.from_env) + mariadb: DatabaseMariaConfig = field(default_factory=DatabaseMariaConfig.from_env) + + @classmethod + def from_env(cls): + """Build dataclass from environment""" + + return cls( + backend=constants.DatabaseBackend[ + os.environ["SECTION7_DATABASE_BACKEND"].upper() + ] + if "SECTION7_DATABASE_BACKEND" in os.environ + else cls.backend, + ) + + +@dataclass +class Section7Config: + """Global application configuration settings + + :param database: Container of database backend settings + :param contact_email: Public administrative contact email for the site + """ + + database: DatabaseConfig = field(default_factory=DatabaseConfig.from_env) + contact_email: str = "admin@enp.one" + + @classmethod + def from_env(cls): + """Build dataclass from environment""" + return cls(contact_email=os.getenv("SECTION7_CONTACT_EMAIL", cls.contact_email)) + + +def load() -> Section7Config: + """Load the application configuration from environment variables + + :returns: Populated environment configuration + """ + + try: + return Section7Config.from_env() + except (ValueError, TypeError, IndexError, KeyError) as err: + raise RuntimeError("Failed to load application configuration") from err diff --git a/section7/constants.py b/section7/constants.py index da94e78..4b8bda1 100644 --- a/section7/constants.py +++ b/section7/constants.py @@ -1,7 +1,17 @@ +"""Application constants""" import enum +class DatabaseBackend(enum.Enum): + """Enum of supported database backends""" + + MARIADB = enum.auto() + SQLITE = enum.auto() + + class PayRate(enum.Enum): + """Enum of payment interval options""" + ANNUALY = enum.auto() QUARTERLY = enum.auto() MONTHLY = enum.auto() @@ -12,14 +22,16 @@ class PayRate(enum.Enum): class Industry(enum.Enum): - pass + """Enum of industires records can be associated with""" class Currency(enum.Enum): - pass + """Enum of currency options""" class Country(enum.Enum): + """Enum of country options""" + AF = "Afghanistan" AX = "Ã…land Islands" AL = "Albania" diff --git a/section7/database.py b/section7/database.py index ad5a0a8..8b9b37d 100644 --- a/section7/database.py +++ b/section7/database.py @@ -1,15 +1,64 @@ import datetime +import logging import uuid +from typing import Optional import peewee import peewee_plus +from section7 import configuration from section7 import constants INTERFACE = peewee.DatabaseProxy() +def initialize(config: Optional[configuration.Section7Config] = None): + """Initialize the database interface + + Defining the database as an + `unconfigured proxy object `_ + allows it to be configured at runtime based on the config values. + + :param config: Populated configuration container object + """ + + logger = logging.getLogger(__name__) + + config = config or configuration.load() + + if config.database.backend == constants.DatabaseBackend.SQLITE: + logger.debug("Using SQLite database backend") + logger.debug(f"Applying SQLite pragmas: {peewee_plus.SQLITE_DEFAULT_PRAGMAS}") + database = peewee.SqliteDatabase( + config.database.sqlite.path, pragmas=peewee_plus.SQLITE_DEFAULT_PRAGMAS + ) + elif config.database.backend == constants.DatabaseBackend.MARIADB: + logger.debug("Using MariaDB database backend") + logger.debug( + "Configuring MariaDB:" + f" {config.database.mariadb.username}@{config.database.mariadb.hostname}:{config.database.mariadb.port}," + f" with database '{config.database.mariadb.schema}'" + ) + database = peewee.MySQLDatabase( + config.database.mariadb.schema, + host=config.database.mariadb.hostname, + port=config.database.mariadb.port, + user=config.database.mariadb.username, + password=config.database.mariadb.password, + charset="utf8mb4", + ) + else: + raise RuntimeError( + f"Invalid storage backend in configuration: {config.database.backend.name}" + ) + + INTERFACE.initialize(database) + + with INTERFACE.atomic(): + INTERFACE.create_tables([DisclosureRecord]) + + class Section7Model(peewee.Model): """Base model for defining common fields and attaching database"""