From 0b3b6f50feec2e10e4d9c7db9a6a700b34e28e3a Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Sat, 1 Aug 2026 01:08:41 +0900 Subject: [PATCH] Add unconnected database support Also adds a helper for doing nested config parsing since we're getting more and more --- .gitignore | 1 + src/config/__init__.py | 20 +++++++++----------- src/config/database.py | 16 ++++++++++++++++ src/config/parse.py | 13 ++++++++++++- src/infra/db.py | 15 +++++++++++++++ 5 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 src/config/database.py create mode 100644 src/infra/db.py diff --git a/.gitignore b/.gitignore index ebb8a8d..676ddc8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ __pycache__/* config.json result todo.md +*.db diff --git a/src/config/__init__.py b/src/config/__init__.py index c83bc3e..32b37b3 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -4,7 +4,8 @@ from typing import Any from .logging import Logging from .email import Email -from .parse import ConfigParseError, assert_key_of_type +from .database import Database +from .parse import assert_key_of_type, parse_nested_config @dataclass @@ -13,6 +14,7 @@ class Config: port: int | None logging: Logging email: Email + database: Database @classmethod def from_dict(cls, config: dict[str, Any]) -> 'Config': @@ -20,17 +22,13 @@ class Config: assert_key_of_type(config, 'email', dict) assert_key_of_type(config, 'host', str) assert_key_of_type(config, 'port', int) - try: - log_config = Logging.from_dict(config['logging']) - except ConfigParseError as e: - raise ConfigParseError(['logging', *e.keypath], e.issue) from e + log_config = parse_nested_config(config, 'logging', Logging.from_dict) + email_config = parse_nested_config(config, 'email', Email.from_dict) + db_config = parse_nested_config(config, 'database', Database.from_dict) - try: - email_config = Email.from_dict(config['email']) - except ConfigParseError as e: - raise ConfigParseError(['email', *e.keypath], e.issue) from e - - return Config(host=config['host'], port=config['port'], email=email_config, logging=log_config) + return Config( + host=config['host'], port=config['port'], email=email_config, logging=log_config, database=db_config + ) def parse_config(filename: str) -> Config: diff --git a/src/config/database.py b/src/config/database.py new file mode 100644 index 0000000..625473d --- /dev/null +++ b/src/config/database.py @@ -0,0 +1,16 @@ +from src.config.parse import assert_key_of_type +from typing import Any +from dataclasses import dataclass + + +@dataclass +class Database: + url: str + echo: bool + + @classmethod + def from_dict(cls, config: dict[str, Any]) -> 'Database': + assert_key_of_type(config, 'url', str) + assert_key_of_type(config, 'echo', bool) + + return Database(url=config['url'], echo=config['echo']) diff --git a/src/config/parse.py b/src/config/parse.py index c940175..c50df3f 100644 --- a/src/config/parse.py +++ b/src/config/parse.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, TypeVar, Callable class ConfigParseError(Exception): @@ -18,3 +18,14 @@ def assert_key_of_type(config: dict[str, Any], key: str, kind: Any): if not isinstance(config[key], kind): raise ConfigParseError([key], f'type of "{key}" was {type(config[key])}, expected {kind}') + + +T = TypeVar('T') + + +def parse_nested_config(config: dict[str, Any], key: str, parse: Callable[[dict[str, Any]], T]) -> T: + assert_key_of_type(config, key, dict) + try: + return parse(config[key]) + except ConfigParseError as e: + raise ConfigParseError([key, *e.keypath], e.issue) diff --git a/src/infra/db.py b/src/infra/db.py new file mode 100644 index 0000000..841cbb8 --- /dev/null +++ b/src/infra/db.py @@ -0,0 +1,15 @@ +from sqlalchemy.orm import DeclarativeBase +from src.config.database import Database as DatabaseConfig +from sqlalchemy import create_engine + + +class Base(DeclarativeBase): + pass + + +def get_database(config: DatabaseConfig): + engine = create_engine(config.url, echo=config.echo) + # Ensure that all tables exist in the database + Base.metadata.create_all(engine) + + return engine