From fbaca54be864225087b09ebe8120222c71f63812 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Wed, 22 Oct 2025 05:20:23 -0600 Subject: [PATCH] Refine Engine configuration wiring --- Data/Engine/CURRENT_STAGE.md | 2 +- Data/Engine/bootstrapper.py | 10 ++-- Data/Engine/config/__init__.py | 13 ++++- Data/Engine/config/environment.py | 77 +++++++++++++++++++++------ Data/Engine/interfaces/ws/__init__.py | 4 +- Data/Engine/server.py | 6 +-- 6 files changed, 84 insertions(+), 28 deletions(-) diff --git a/Data/Engine/CURRENT_STAGE.md b/Data/Engine/CURRENT_STAGE.md index ec0eac9..81b2c1c 100644 --- a/Data/Engine/CURRENT_STAGE.md +++ b/Data/Engine/CURRENT_STAGE.md @@ -5,7 +5,7 @@ - 1.2 Document environment variables/settings expected by the Engine to keep parity with legacy defaults. - 1.3 Verify Engine logging produces `Logs/Server/engine.log` entries alongside the legacy server. -2. Introduce configuration & dependency wiring +[COMPLETED] 2. Introduce configuration & dependency wiring - 2.1 Create `config/environment.py` loaders mirroring legacy defaults (TLS paths, feature flags). - 2.2 Add settings dataclasses for Flask, Socket.IO, and DB paths; inject them via `server.py`. - 2.3 Commit once the Engine can start with equivalent config but no real routes. diff --git a/Data/Engine/bootstrapper.py b/Data/Engine/bootstrapper.py index eabd728..6dcb77e 100644 --- a/Data/Engine/bootstrapper.py +++ b/Data/Engine/bootstrapper.py @@ -29,7 +29,7 @@ def bootstrap() -> EngineRuntime: logger.info("bootstrap-started") app = create_app(settings) register_http_interfaces(app) - socketio = create_socket_server(app, settings) + socketio = create_socket_server(app, settings.socketio) logger.info("bootstrap-complete") return EngineRuntime(app=app, settings=settings, socketio=socketio) @@ -40,14 +40,14 @@ def main() -> None: if socketio is not None: socketio.run( # type: ignore[call-arg] runtime.app, - host=runtime.settings.host, - port=runtime.settings.port, + host=runtime.settings.server.host, + port=runtime.settings.server.port, debug=runtime.settings.debug, ) else: runtime.app.run( - host=runtime.settings.host, - port=runtime.settings.port, + host=runtime.settings.server.host, + port=runtime.settings.server.port, debug=runtime.settings.debug, ) diff --git a/Data/Engine/config/__init__.py b/Data/Engine/config/__init__.py index 8f382e9..f0cb508 100644 --- a/Data/Engine/config/__init__.py +++ b/Data/Engine/config/__init__.py @@ -2,11 +2,22 @@ from __future__ import annotations -from .environment import EngineSettings, load_environment +from .environment import ( + DatabaseSettings, + EngineSettings, + FlaskSettings, + ServerSettings, + SocketIOSettings, + load_environment, +) from .logging import configure_logging __all__ = [ + "DatabaseSettings", "EngineSettings", + "FlaskSettings", "load_environment", + "ServerSettings", + "SocketIOSettings", "configure_logging", ] diff --git a/Data/Engine/config/environment.py b/Data/Engine/config/environment.py index ad8e8b8..56205f5 100644 --- a/Data/Engine/config/environment.py +++ b/Data/Engine/config/environment.py @@ -8,18 +8,47 @@ from pathlib import Path from typing import Iterable, Tuple +@dataclass(frozen=True, slots=True) +class DatabaseSettings: + """SQLite database configuration for the Engine.""" + + path: Path + + +@dataclass(frozen=True, slots=True) +class FlaskSettings: + """Parameters that influence Flask application behavior.""" + + secret_key: str + static_root: Path + cors_allowed_origins: Tuple[str, ...] + + +@dataclass(frozen=True, slots=True) +class SocketIOSettings: + """Configuration for the optional Socket.IO server.""" + + cors_allowed_origins: Tuple[str, ...] + + +@dataclass(frozen=True, slots=True) +class ServerSettings: + """HTTP server binding configuration.""" + + host: str + port: int + + @dataclass(frozen=True, slots=True) class EngineSettings: """Immutable container describing the Engine runtime configuration.""" project_root: Path - database_path: Path - static_root: Path - cors_allowed_origins: Tuple[str, ...] - secret_key: str debug: bool - host: str - port: int + database: DatabaseSettings + flask: FlaskSettings + socketio: SocketIOSettings + server: ServerSettings @property def logs_root(self) -> Path: @@ -27,6 +56,12 @@ class EngineSettings: return self.project_root / "Logs" / "Server" + @property + def database_path(self) -> Path: + """Convenience accessor for the database file path.""" + + return self.database.path + def _resolve_project_root() -> Path: candidate = os.getenv("BOREALIS_ROOT") @@ -61,27 +96,37 @@ def load_environment() -> EngineSettings: """Load Engine settings from environment variables and filesystem hints.""" project_root = _resolve_project_root() - database_path = _resolve_database_path(project_root) - static_root = _resolve_static_root(project_root) + database = DatabaseSettings(path=_resolve_database_path(project_root)) cors_allowed_origins = _parse_origins(os.getenv("BOREALIS_CORS_ALLOWED_ORIGINS")) - secret_key = os.getenv("BOREALIS_FLASK_SECRET_KEY", "change-me") + flask_settings = FlaskSettings( + secret_key=os.getenv("BOREALIS_FLASK_SECRET_KEY", "change-me"), + static_root=_resolve_static_root(project_root), + cors_allowed_origins=cors_allowed_origins, + ) + socket_settings = SocketIOSettings(cors_allowed_origins=cors_allowed_origins) debug = os.getenv("BOREALIS_DEBUG", "false").lower() in {"1", "true", "yes", "on"} host = os.getenv("BOREALIS_HOST", "127.0.0.1") try: port = int(os.getenv("BOREALIS_PORT", "5000")) except ValueError: port = 5000 + server_settings = ServerSettings(host=host, port=port) return EngineSettings( project_root=project_root, - database_path=database_path, - static_root=static_root, - cors_allowed_origins=cors_allowed_origins, - secret_key=secret_key, debug=debug, - host=host, - port=port, + database=database, + flask=flask_settings, + socketio=socket_settings, + server=server_settings, ) -__all__ = ["EngineSettings", "load_environment"] +__all__ = [ + "DatabaseSettings", + "EngineSettings", + "FlaskSettings", + "SocketIOSettings", + "ServerSettings", + "load_environment", +] diff --git a/Data/Engine/interfaces/ws/__init__.py b/Data/Engine/interfaces/ws/__init__.py index 84c7476..322bc90 100644 --- a/Data/Engine/interfaces/ws/__init__.py +++ b/Data/Engine/interfaces/ws/__init__.py @@ -6,7 +6,7 @@ from typing import Optional from flask import Flask -from ...config import EngineSettings +from ...config import SocketIOSettings try: # pragma: no cover - import guard from flask_socketio import SocketIO @@ -14,7 +14,7 @@ except Exception: # pragma: no cover - optional dependency SocketIO = None # type: ignore[assignment] -def create_socket_server(app: Flask, settings: EngineSettings) -> Optional[SocketIO]: +def create_socket_server(app: Flask, settings: SocketIOSettings) -> Optional[SocketIO]: """Create a Socket.IO server bound to *app* if dependencies are available.""" if SocketIO is None: diff --git a/Data/Engine/server.py b/Data/Engine/server.py index dbbdd62..732dfde 100644 --- a/Data/Engine/server.py +++ b/Data/Engine/server.py @@ -20,7 +20,7 @@ def _resolve_static_folder(static_root: Path) -> tuple[str | None, str]: def create_app(settings: EngineSettings) -> Flask: """Create the Flask application instance for the Engine.""" - static_folder, static_url_path = _resolve_static_folder(settings.static_root) + static_folder, static_url_path = _resolve_static_folder(settings.flask.static_root) app = Flask( __name__, static_folder=static_folder, @@ -28,7 +28,7 @@ def create_app(settings: EngineSettings) -> Flask: ) app.config.update( - SECRET_KEY=settings.secret_key, + SECRET_KEY=settings.flask.secret_key, JSON_SORT_KEYS=False, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SECURE=not settings.debug, @@ -41,7 +41,7 @@ def create_app(settings: EngineSettings) -> Flask: CORS( app, - resources={r"/*": {"origins": list(settings.cors_allowed_origins)}}, + resources={r"/*": {"origins": list(settings.flask.cors_allowed_origins)}}, supports_credentials=True, )