本文へ移動
cccskills
無料GitHub で公開

flask-conventions

Flask web framework conventions: app factory pattern with create_app(), Blueprint registration with url_prefix, MethodView for class-based API views, Marshmallow schema validation, WTForms for HTML forms, Flask-Login for session auth, flask-jwt-extended for stateless API auth, Jinja2 template conventions, error handlers, and extension initialization. Activated automatically by flask-plugin/stack.md. Use this skill to: - Structure Flask applications with the app factory and per-feature Blueprints. - Validate JSON request data with Marshmallow and HTML forms with WTForms. - Implement session-based auth with Flask-Login or token auth with flask-jwt-extended. - Render Jinja2 templates safely or return JSON responses for API mode. - Register global error handlers for consistent error responses. Do NOT use this skill for: - SQLAlchemy ORM model patterns and Flask-Migrate — see flask-plugin:sqlalchemy-patterns. - Python language idioms — see python-foundation:python-conventions. - Testing patterns — see python-foundation:pytest-testing.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md13.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Flask Conventions

Detection

Read pyproject.toml or requirements.txt before writing any Flask code:

grep -E "flask|Flask" requirements.txt pyproject.toml

Determine the installed extensions:

CheckMeaning
flask-login presentSession-based auth — use @login_required, login_user(), logout_user()
flask-jwt-extended presentToken-based auth — use @jwt_required(), create_access_token()
marshmallow or flask-marshmallow presentJSON API validation — use Schema.load() / Schema.dump()
flask-wtf presentHTML form validation — use FlaskForm with validate_on_submit()
flask-sqlalchemy presentORM — use db.Model, db.session
flask-migrate presentMigrations — flask db migrate, flask db upgrade

App factory

Define create_app() in app/__init__.py. Never instantiate Flask at module level in a way that creates side effects — the factory pattern allows multiple app instances for testing.

# app/__init__.py
from flask import Flask

from app.config import config_by_name
from app.extensions import db, login_manager, migrate


def create_app(config_name: str = "development") -> Flask:
    app = Flask(__name__)
    app.config.from_object(config_by_name[config_name])

    _init_extensions(app)
    _register_blueprints(app)
    _register_error_handlers(app)

    return app


def _init_extensions(app: Flask) -> None:
    db.init_app(app)
    migrate.init_app(app, db)
    login_manager.init_app(app)


def _register_blueprints(app: Flask) -> None:
    from app.auth.views import auth_bp
    from app.users.views import users_bp
    from app.orders.views import orders_bp

    app.register_blueprint(auth_bp)
    app.register_blueprint(users_bp)
    app.register_blueprint(orders_bp)


def _register_error_handlers(app: Flask) -> None:
    from app.errors import register_error_handlers
    register_error_handlers(app)

Initialize extensions at module level in app/extensions.py, then call .init_app(app) in the factory. This avoids circular imports.

# app/extensions.py
from flask_login import LoginManager
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
login_manager.login_view = "auth.login"

Config classes split per environment:

# app/config.py
import os


class BaseConfig:
    SECRET_KEY = os.environ["SECRET_KEY"]
    SQLALCHEMY_TRACK_MODIFICATIONS = False


class DevelopmentConfig(BaseConfig):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.environ.get(
        "DATABASE_URL", "sqlite:///dev.db"
    )


class ProductionConfig(BaseConfig):
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]


class TestingConfig(BaseConfig):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
    WTF_CSRF_ENABLED = False


config_by_name = {
    "development": DevelopmentConfig,
    "production": ProductionConfig,
    "testing": TestingConfig,
}

Never hardcode SECRET_KEY. If the env var is missing, os.environ["SECRET_KEY"] raises KeyError at startup — fail fast rather than silently using a weak default.


Blueprints

One Blueprint per feature with a url_prefix:

# app/orders/views.py
from flask import Blueprint, jsonify, request
from flask_login import login_required

from app.orders.schemas import OrderSchema, OrderCreateSchema
from app.orders.service import get_order, create_order

orders_bp = Blueprint("orders", __name__, url_prefix="/orders")
order_schema = OrderSchema()
order_create_schema = OrderCreateSchema()


@orders_bp.route("/", methods=["GET"])
@login_required
def list_orders():
    orders = get_orders_for_current_user()
    return jsonify(order_schema.dump(orders, many=True))


@orders_bp.route("/", methods=["POST"])
@login_required
def create_order_view():
    data = order_create_schema.load(request.get_json())
    order = create_order(data)
    return jsonify(order_schema.dump(order)), 201

For class-based API views, use MethodView:

from flask.views import MethodView


class OrderResource(MethodView):
    decorators = [login_required]

    def get(self, order_id: int):
        order = get_order(order_id)
        return jsonify(order_schema.dump(order))

    def put(self, order_id: int):
        data = order_schema.load(request.get_json(), partial=True)
        order = update_order(order_id, data)
        return jsonify(order_schema.dump(order))

    def delete(self, order_id: int):
        delete_order(order_id)
        return "", 204


orders_bp.add_url_rule(
    "/<int:order_id>",
    view_func=OrderResource.as_view("order_resource"),
)

Marshmallow validation

Use Marshmallow for JSON API request validation and response serialization. Separate input and output schemas when field sets differ.

# app/users/schemas.py
from marshmallow import Schema, ValidationError, fields, post_load, validates


class UserCreateSchema(Schema):
    email = fields.Email(required=True)
    password = fields.String(required=True, load_only=True)
    display_name = fields.String(required=True)

    @validates("password")
    def validate_password(self, value: str) -> None:
        if len(value) < 8:
            raise ValidationError("Password must be at least 8 characters.")

    @post_load
    def make_user_data(self, data: dict, **kwargs) -> dict:
        return data


class UserReadSchema(Schema):
    id = fields.Int(dump_only=True)
    email = fields.Email(dump_only=True)
    display_name = fields.String(dump_only=True)
    created_at = fields.DateTime(dump_only=True)

Usage in a view:

user_create_schema = UserCreateSchema()
user_read_schema = UserReadSchema()


@users_bp.route("/", methods=["POST"])
def register():
    try:
        data = user_create_schema.load(request.get_json())
    except ValidationError as err:
        return jsonify({"errors": err.messages}), 422
    user = create_user(data)
    return jsonify(user_read_schema.dump(user)), 201

WTForms

Use WTForms for HTML form validation (server-rendered Jinja2 mode). WTForms provides CSRF protection via Flask-WTF. Use validate_on_submit() which checks both POST and CSRF validity.

# app/auth/forms.py
from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired, Email, Length


class LoginForm(FlaskForm):
    email = StringField("Email", validators=[DataRequired(), Email()])
    password = PasswordField("Password", validators=[DataRequired(), Length(min=8)])
    submit = SubmitField("Log In")

Usage in a view:

@auth_bp.route("/login", methods=["GET", "POST"])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = authenticate_user(form.email.data, form.password.data)
        if user:
            login_user(user)
            return redirect(url_for("users.profile"))
        form.email.errors.append("Invalid credentials.")
    return render_template("auth/login.html", form=form)

When to use WTForms vs Marshmallow:

  • WTForms: HTML form submissions (Content-Type: application/x-www-form-urlencoded), Jinja2 templates, CSRF protection needed.
  • Marshmallow: JSON request/response (Content-Type: application/json), API mode, SPA frontends.

Flask-Login

Use Flask-Login for session-based auth (browser clients, Jinja2 mode).

# app/extensions.py
from flask_login import LoginManager

login_manager = LoginManager()
login_manager.login_view = "auth.login"  # redirect for @login_required


@login_manager.user_loader
def load_user(user_id: str):
    from app.users.models import User
    return User.query.get(int(user_id))

The User model must implement UserMixin:

# app/users/models.py
from flask_login import UserMixin
from app.extensions import db


class User(UserMixin, db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True, nullable=False)
    hashed_password = db.Column(db.String(255), nullable=False)

    def get_id(self) -> str:
        return str(self.id)

Auth view functions:

from flask_login import login_required, login_user, logout_user


@auth_bp.route("/login", methods=["POST"])
def login():
    user = authenticate_user(email, password)
    if user:
        login_user(user, remember=form.remember.data)
        return redirect(url_for("users.profile"))
    return jsonify({"error": "Invalid credentials"}), 401


@auth_bp.route("/logout", methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect(url_for("auth.login"))

flask-jwt-extended

Use flask-jwt-extended for token-based auth (JSON API mode, SPA frontends).

# app/extensions.py
from flask_jwt_extended import JWTManager

jwt = JWTManager()

Initialize in the factory:

jwt.init_app(app)

Token creation in the login view:

from flask_jwt_extended import create_access_token, create_refresh_token


@auth_bp.route("/login", methods=["POST"])
def login():
    data = login_schema.load(request.get_json())
    user = authenticate_user(data["email"], data["password"])
    if not user:
        return jsonify({"error": "Invalid credentials"}), 401
    access_token = create_access_token(identity=str(user.id))
    refresh_token = create_refresh_token(identity=str(user.id))
    return jsonify({"access_token": access_token, "refresh_token": refresh_token})

Protecting routes and getting the current identity:

from flask_jwt_extended import get_jwt_identity, jwt_required


@users_bp.route("/me", methods=["GET"])
@jwt_required()
def get_current_user():
    user_id = get_jwt_identity()
    user = User.query.get(int(user_id))
    return jsonify(user_read_schema.dump(user))


@auth_bp.route("/refresh", methods=["POST"])
@jwt_required(refresh=True)
def refresh_token():
    user_id = get_jwt_identity()
    new_access_token = create_access_token(identity=user_id)
    return jsonify({"access_token": new_access_token})

Jinja2 conventions

Organize templates under app/templates/. Use a base template with blocks:

{# app/templates/base.html #}
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}My App{% endblock %}</title>
</head>
<body>
    <nav>
        {% if current_user.is_authenticated %}
            <a href="{{ url_for('auth.logout') }}">Log out</a>
        {% else %}
            <a href="{{ url_for('auth.login') }}">Log in</a>
        {% endif %}
    </nav>
    {% block content %}{% endblock %}
</body>
</html>
{# app/templates/users/profile.html #}
{% extends "base.html" %}
{% block title %}Profile — {{ user.display_name }}{% endblock %}
{% block content %}
<h1>{{ user.display_name }}</h1>
<p>{{ user.email }}</p>
{% endblock %}

Key Jinja2 rules:

  • {{ user.name }} — auto-escaped. Safe for all user-controlled strings.
  • {{ content|safe }} — bypasses auto-escaping. Only use for content sanitized with bleach.clean().
  • {{ url_for('orders.list_orders') }} — always use url_for() for URLs, never hardcode paths.
  • Never use Markup(user_input) — only use Markup() for static, developer-controlled HTML fragments.

Error handlers

Register error handlers in the factory. Detect the request's preferred response format from request.accept_mimetypes:

# app/errors.py
from flask import Flask, jsonify, render_template, request


def register_error_handlers(app: Flask) -> None:
    @app.errorhandler(404)
    def not_found(error):
        if request.accept_mimetypes.best == "application/json":
            return jsonify({"error": "not found", "status": 404}), 404
        return render_template("errors/404.html"), 404

    @app.errorhandler(422)
    def unprocessable(error):
        return jsonify({"error": "unprocessable entity", "status": 422}), 422

    @app.errorhandler(500)
    def internal_error(error):
        if request.accept_mimetypes.best == "application/json":
            return jsonify({"error": "internal server error", "status": 500}), 500
        return render_template("errors/500.html"), 500

Checklist

Before handing off to flask-migrate-specialist:

  • All Blueprints registered via app.register_blueprint() in the factory?
  • All extensions initialized with init_app(app)?
  • SECRET_KEY read from os.environ, never hardcoded?
  • No flask db migrate or flask db upgrade called?
  • No {{ var|safe }} on user-controlled data in templates?
  • No Markup(user_input) calls?
  • Every state-changing route protected by @login_required or @jwt_required() (or explicitly BA-approved for anonymous access)?
  • ruff format . run and output is clean?

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する