diff --git a/README.md b/README.md index 36dc912..01131c7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The app is served below `/fatima` by default. Configure the external path in flask: url_prefix: "/fatima" log_file: "/tmp/fatima.log" + database: "instance/calendar.sqlite" ``` Set `URL_PREFIX` in the environment to override this value at deployment time. @@ -83,6 +84,11 @@ For deployments, `APP_CONFIG` can point to a YAML file elsewhere. `SECRET_KEY`, override the corresponding values. `DATABASE` should be an absolute path to the SQLite file used in production. +The YAML `flask.database` path is resolved relative to the directory containing +the selected config file. If it is omitted, the app uses +`instance/calendar.sqlite` beside `flask_fatima.py`; it no longer relies on +Flask's environment-dependent inferred instance directory. + At startup, the app prints a database summary to the Waitress log containing the resolved path, amount and status totals, distinct recorded-date total, and date range. Compare that path and those totals with the importer's output to confirm diff --git a/config.example.yaml b/config.example.yaml index 0089807..38c80c2 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -5,6 +5,8 @@ flask: url_prefix: "/fatima" # Startup database summary log. log_file: "/tmp/fatima.log" + # Relative paths are resolved from the directory containing this config file. + database: "instance/calendar.sqlite" auth: username: "your-username" diff --git a/flask_fatima.py b/flask_fatima.py index 5369891..31d6215 100644 --- a/flask_fatima.py +++ b/flask_fatima.py @@ -39,7 +39,7 @@ class UrlPrefixMiddleware: def load_yaml_config(path): - config_path = Path(path) + config_path = Path(path).expanduser().resolve() if not config_path.is_file(): raise RuntimeError( f"Configuration file not found: {config_path}. " @@ -59,6 +59,14 @@ def load_yaml_config(path): "URL_PREFIX": normalize_url_prefix(flask_config.get("url_prefix", "/fatima")), "LOG_FILE": flask_config.get("log_file", "/tmp/fatima.log"), } + configured_database = flask_config.get("database") + if configured_database is not None: + if not isinstance(configured_database, str) or not configured_database.strip(): + raise RuntimeError(f"{config_path}: flask.database must be a non-empty path") + database_path = Path(configured_database).expanduser() + if not database_path.is_absolute(): + database_path = config_path.parent / database_path + required["DATABASE"] = str(database_path.resolve()) missing = [ name for name, value in required.items() if name != "URL_PREFIX" and (not isinstance(value, str) or not value) @@ -119,7 +127,10 @@ def create_app(test_config=None): app = Flask(__name__) config_path = os.environ.get("APP_CONFIG", Path(app.root_path) / "config.yaml") app.config.from_mapping(load_yaml_config(config_path)) - app.config.setdefault("DATABASE", str(Path(app.instance_path) / "calendar.sqlite")) + app.config.setdefault( + "DATABASE", + str(Path(__file__).resolve().parent / "instance" / "calendar.sqlite"), + ) # Environment variables remain useful when deploying without a local file. for name in ("SECRET_KEY", "APP_USERNAME", "APP_PASSWORD", "DATABASE", "LOG_FILE"): diff --git a/tests/test_app.py b/tests/test_app.py index 3f89bad..1240b58 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,7 +1,7 @@ import tempfile from pathlib import Path -from flask_fatima import create_app, log_database_summary +from flask_fatima import create_app, load_yaml_config, log_database_summary URL_PREFIX = "/fatima" @@ -44,6 +44,27 @@ def test_database_summary_is_appended_to_log(capsys): assert "Fatima database:" in capsys.readouterr().out +def test_database_path_is_resolved_relative_to_config_file(): + with tempfile.TemporaryDirectory() as directory: + config_path = Path(directory) / "production.yaml" + config_path.write_text( + """ +flask: + secret_key: test-secret + database: data/calendar.sqlite +auth: + username: user + password: pass +""", + encoding="utf-8", + ) + + config = load_yaml_config(config_path) + assert config["DATABASE"] == str( + (Path(directory) / "data" / "calendar.sqlite").resolve() + ) + + def test_prefix_applies_to_generated_links_and_unprefixed_routes_are_rejected(): test_client = client() test_client.post(url("/login"), data={"username": "user", "password": "pass"})