diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1d1f1..947d0e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `logging.py` - `models.py` - `utilities.py` + - `migrate.py` + - `responses.py` - A `get_db` utility function to retrieve a reference to the database (with type hinting) - Minor `DATABASE_URL` check in `models.py` to prevent cryptic connection issues @@ -21,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Migration script now uses `structlog` instead of `print` - Migration script output is tuned to structlog as well. +- Migration names must be at least 9 characters long - Unspecified IPv6 addresses are returned without hiding in `utilities.hide_ip` - Applied `get_db` utility function in all applicable areas. diff --git a/backend/linkpulse/migrate.py b/backend/linkpulse/migrate.py index ca824e4..0d94bdb 100644 --- a/backend/linkpulse/migrate.py +++ b/backend/linkpulse/migrate.py @@ -130,13 +130,17 @@ def main(*args: str) -> None: else: logger.info("No pending migrations to apply.") + # Inspects models and might generate a migration script migration_available = router.show(target_models) + if migration_available is not None: logger.info("A migration is available to be applied:") migrate_text, rollback_text = migration_available def _reformat_text(text: str) -> str: + # Remove empty lines text = [line for line in text.split("\n") if line.strip() != ""] + # Add line numbers, indent, ensure it starts on a new line return "\n" + "\n".join([f"{i:02}:\t{line}" for i, line in enumerate(text)]) logger.info("Migration Content", content=_reformat_text(migrate_text)) @@ -144,11 +148,11 @@ def main(*args: str) -> None: if questionary.confirm("Do you want to create this migration?").ask(): logger.info( - 'Lowercase letters and underscores only (e.g. "create_table", "remove_ipaddress_count").' + 'Minimum length 9, lowercase letters and underscores only (e.g. "create_table", "remove_ipaddress_count").' ) migration_name: Optional[str] = questionary.text( "Enter migration name", - validate=lambda text: re.match("^[a-z_]+$", text) is not None, + validate=lambda text: re.match("^[a-z_]{9,}$", text) is not None, ).ask() if migration_name is None: @@ -157,6 +161,7 @@ def main(*args: str) -> None: migration = router.create(migration_name, auto=target_models) if migration: logger.info(f"Migration created: {migration}") + if len(router.diff) == 1: if questionary.confirm( "Do you want to apply this migration immediately?" @@ -167,8 +172,9 @@ def main(*args: str) -> None: "!!! Commit and push this migration file immediately!" ) else: - logger.error("No changes detected. Something went wrong.") - return + raise RuntimeError( + "Changes anticipated with show() but no migration created with create(), model definition may have reverted." + ) else: logger.info("No database changes detected.") diff --git a/backend/linkpulse/responses.py b/backend/linkpulse/responses.py index 266140b..287553d 100644 --- a/backend/linkpulse/responses.py +++ b/backend/linkpulse/responses.py @@ -1,3 +1,8 @@ +"""responses.py + +This module contains the response models for the FastAPI application. +""" + from pydantic import BaseModel