From d0e245d63e014d268310976fc45429f08d2cbbe9 Mon Sep 17 00:00:00 2001 From: Dennis Fink Date: Wed, 19 Aug 2026 19:49:55 +0200 Subject: refactor(core): improve typing and test coverage Restructure database and user CLI commands, make passwords write-only model properties, and use the UTC datetime constant throughout the application. Add PEP 287-style documentation and expand receiver, scanner, sender, and URL security tests to cover error paths and edge cases. --- webmentions_ssg/__init__.py | 97 +++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 39 deletions(-) (limited to 'webmentions_ssg/__init__.py') diff --git a/webmentions_ssg/__init__.py b/webmentions_ssg/__init__.py index b799678..3928195 100644 --- a/webmentions_ssg/__init__.py +++ b/webmentions_ssg/__init__.py @@ -11,6 +11,7 @@ import click import coloredlogs import sqlalchemy as sa from flask import Flask +from flask.cli import AppGroup from flask_bootstrap import Bootstrap5 from flask_migrate import Migrate, upgrade from flask_sqlalchemy import SQLAlchemy @@ -35,14 +36,25 @@ DATABASE = SQLAlchemy(model_class=Base) MIGRATE = Migrate() HUEY = Huey() +USERS_CLI = AppGroup("users", help="Handle users") +DB_CLI = AppGroup("db", help="Handle database") + def create_app(config_class: type[DefaultConfig] | None = None) -> Flask: - """Creates and configures the Flask application.""" + """ + Create and configure the Flask application. + + The application configuration is selected based on the development + environment unless an explicit configuration class is provided. Logging, + extensions, authentication, Huey, blueprints, and CLI commands are then + initialized. - development = os.environ.get( - "WEBMENTIONS_SSG_DEV", - "", - ).lower() in { + :param config_class: Configuration class to use, or ``None`` to select the + development or production configuration automatically. + :return: Configured Flask application. + """ + + development = os.environ.get("WEBMENTIONS_SSG_DEV", "").lower() in { "1", "true", "yes", @@ -53,8 +65,7 @@ def create_app(config_class: type[DefaultConfig] | None = None) -> Flask: config_class = DevelopmentConfig if development else ProductionConfig app = Flask( - __name__, - instance_path=str(Path("testing").absolute()) if development else None, + __name__, instance_path=str(Path("testing").absolute()) if development else None ) app.config.from_object(config_class) @@ -84,9 +95,7 @@ def create_app(config_class: type[DefaultConfig] | None = None) -> Flask: werkzeug_logger.addHandler(log_file_handler) coloredlogs.install( - level=app.config["LOG_LEVEL"], - logger=app.logger, - fmt=app.config["LOG_FORMAT"], + level=app.config["LOG_LEVEL"], logger=app.logger, fmt=app.config["LOG_FORMAT"] ) coloredlogs.install( level=app.config["LOG_LEVEL"], @@ -106,44 +115,54 @@ def create_app(config_class: type[DefaultConfig] | None = None) -> Flask: @app.context_processor def inject_version(): - return dict(VERSION=VERSION) + """ + Add the application version to the template context. + + :return: Template context containing the application version. + """ + return {"VERSION": VERSION} from .views import root_page app.register_blueprint(root_page) - @app.cli.command("create-db") - def create_db() -> None: - """Create or update the database to the latest migration.""" - upgrade() + app.cli.add_command(USERS_CLI) - @app.cli.command("create-user") - @click.argument("username") - @click.password_option( - confirmation_prompt=True, - ) - def create_user( - username: str, - password: str, - ) -> None: - """Create a user.""" + return app - from .models import User - if ( - DATABASE.session.scalar(sa.select(User).where(User.username == username)) - is not None - ): - raise click.ClickException(f"User {username!r} already exists.") +@USERS_CLI.command("create", help="Create or add users") +@click.option("--username", prompt=True) +@click.password_option(confirmation_prompt=True) +def create_user(username: str, password: str) -> None: + """ + Create a new user. - user = User( - username=username, - ) - user.set_password(password) + :param username: Username of the user to create. + :param password: Plain-text password to hash and store for the user. + :raises click.ClickException: If a user with the given username already + exists. + """ - DATABASE.session.add(user) - DATABASE.session.commit() + from .models import User - click.echo(f"Created user {username!r}.") + if ( + DATABASE.session.scalar(sa.select(User).where(User.username == username)) + is not None + ): + raise click.ClickException(f"User {username!r} already exists.") - return app + user = User(username=username, password=password) + + DATABASE.session.add(user) + DATABASE.session.commit() + + click.echo(f"Created user {username!r}.") + + +@DB_CLI.command("create") +def create_db() -> None: + """ + Upgrade the database to the latest migration revision. + """ + upgrade() -- cgit v1.3.1