Skill

SkillsSoftware Development › Backend & APIs

flask

**Status**: Production Ready

Freerisk: low
flaskpythonsqlite

Tools: flask,config,flask_sqlalchemy,flask_login,dotenv,app,flask_wtf,wtforms

The full skill

— name: flask description: | Build Python web applications with Flask, using the application factory pattern, Blueprints, and Flask-SQLAlchemy. Covers project structure, authentication, and configuration management. Use when: creating Flask projects, organizing with blueprints, implementing authentication, or troubleshooting circular imports, application context errors, or blueprint registration. — # Flask Skill Production-tested patterns for Flask with the application factory pattern, Blueprints, and Flask-SQLAlchemy. **Latest Versions** (verified December 2025): – Flask: 3.1.2 – Flask-SQLAlchemy: 3.1.1 – Flask-Login: 0.6.3 – Flask-WTF: 1.2.2 – Werkzeug: 3.1.3 — ## Quick Start ### Project Setup with uv “`bash # Create project uv init my-flask-app cd my-flask-app # Add dependencies uv add flask flask-sqlalchemy flask-login flask-wtf python-dotenv # Run development server uv run flask –app app run –debug “` ### Minimal Working Example “`python # app.py from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return {"message": "Hello, World!"} if __name__ == "__main__": app.run(debug=True) “` Run: `uv run flask –app app run –debug` — ## Project Structure (Application Factory) For maintainable applications, use the factory pattern with blueprints: “` my-flask-app/ ├── pyproject.toml ├── config.py # Configuration classes ├── run.py # Entry point │ ├── app/ │ ├── __init__.py # Application factory (create_app) │ ├── extensions.py # Flask extensions (db, login_manager) │ ├── models.py # SQLAlchemy models │ │ │ ├── main/ # Main blueprint │ │ ├── __init__.py │ │ └── routes.py │ │ │ ├── auth/ # Auth blueprint │ │ ├── __init__.py │ │ ├── routes.py │ │ └── forms.py │ │ │ ├── templates/ │ │ ├── base.html │ │ ├── main/ │ │ └── auth/ │ │ │ └── static/ │ ├── css/ │ └── js/ │ └── tests/ ├── conftest.py └── test_main.py “` — ## Core Patterns ### Application Factory “`python # app/__init__.py from flask import Flask from app.extensions import db, login_manager from config import Config def create_app(config_class=Config): """Application factory function.""" app = Flask(__name__) app.config.from_object(config_class) # Initialize extensions db.init_app(app) login_manager.init_app(app) # Register blueprints from app.main import bp as main_bp from app.auth import bp as auth_bp app.register_blueprint(main_bp) app.register_blueprint(auth_bp, url_prefix="/auth") # Create database tables with app.app_context(): db.create_all() return app “` **Key Benefits**: – Multiple app instances with different configs (testing) – Avoids circular imports – Extensions initialized once, bound to app later ### Extensions Module “`python # app/extensions.py from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager db = SQLAlchemy() login_manager = LoginManager() login_manager.login_view = "auth.login" login_manager.login_message_category = "info" “` **Why separate file?**: Prevents circular imports – models can import `db` without importing `app`. ### Configuration “`python # config.py import os from dotenv import load_dotenv load_dotenv() class Config: """Base configuration.""" SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-key") SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///app.db") SQLALCHEMY_TRACK_MODIFICATIONS = False class DevelopmentConfig(Config): """Development configuration.""" DEBUG = True class TestingConfig(Config): """Testing configuration.""" TESTING = True SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" WTF_CSRF_ENABLED = False class ProductionConfig(Config): """Production configuration.""" DEBUG = False “` ### Entry Point “`python # run.py from app import create_app app = create_app() if __name__ == "__main__": app.run() “` Run: `flask –app run run –debug` — ## Blueprints ### Creating a Blueprint “`python # app/main/__init__.py from flask import Blueprint bp = Blueprint("main", __name__) from app.main import routes # Import routes after bp is created! “` “`python # app/main/routes.py from flask import render_template, jsonify from app.main import bp @bp.route("/") def index(): return render_template("main/index.html") @bp.route("/api/health") def health(): return jsonify({"status": "ok"}) “` ### Blueprint with Templates “`python # app/auth/__init__.py from flask import Blueprint bp = Blueprint( "auth", __name__, template_folder="templates", # Blueprint-specific templates static_folder="static", # Blueprint-specific static files ) from app.auth import routes “` — ## Database Models “`python # app/models.py from datetime import datetime from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from app.extensions import db, login_manager class User(UserMixin, db.Model): """User model for authentication.""" __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(120), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(256), nullable=False) is_active = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def __repr__(self): return f"<User {self.email}>" @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) “` — ## Authentication with Flask-Login ### Auth Forms “`python # app/auth/forms.py from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError from app.models import User class LoginForm(FlaskForm): email = StringField("Email", validators=[DataRequired(), Email()]) password = PasswordField("Password", validators=[DataRequired()]) remember = BooleanField("Remember Me") submit = SubmitField("Login") class RegistrationForm(FlaskForm): email = StringField("Email", validators=[DataRequired(), Email()]) password = PasswordField("Password", validators=[DataRequired(), Length(min=8)]) confirm = PasswordField("Confirm Password", validators=[ DataRequired(), EqualTo("password", message="Passwords must match") ]) submit = SubmitField("Register") def validate_email(self, field): if User.query.filter_by(email=field.data).first(): raise ValidationError("Email already registered.") “` ### Auth Routes “`python # app/auth/routes.py from flask import render_template, redirect, url_for, flash, request from flask_login import login_user, logout_user, login_required, current_user from app.auth import bp from app.auth.forms import LoginForm, RegistrationForm from app.extensions import db from app.models import User @bp.route("/register", methods=["GET", "POSTf:T48d,"""Application configuration classes.""" import os from dotenv import load_dotenv load_dotenv() class Config: """Base configuration.""" SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-key-change-in-production") SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///app.db") SQLALCHEMY_TRACK_MODIFICATIONS = False class DevelopmentConfig(Config): """Development configuration.""" DEBUG = True SQLALCHEMY_ECHO = True class TestingConfig(Config): """Testing configuration.""" TESTING = True SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" WTF_CSRF_ENABLED = False class ProductionConfig(Config): """Production configuration.""" DEBUG = False @classmethod def init_app(cls, app): """Production-specific initialization.""" # Ensure SECRET_KEY is set assert cls.SECRET_KEY != "dev-secret-key-change-in-production", \ "Set SECRET_KEY environment variable for production!" # Config mapping config = { "development": DevelopmentConfig, "testing": TestingConfig, "production": ProductionConfig, "default": DevelopmentConfig, } 20:T4d2,"""Authentication forms.""" from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError from app.models import User class LoginForm(FlaskForm): """User login form.""" email = StringField("Email", validators=[DataRequired(), Email()]) password = PasswordField("Password", validators=[DataRequired()]) remember = BooleanField("Remember Me") submit = SubmitField("Log In") class RegistrationForm(FlaskForm): """User registration form.""" email = StringField("Email", validators=[DataRequired(), Email()]) password = PasswordField( "Password", validators=[DataRequired(), Length(min=8, message="Password must be at least 8 characters")] ) confirm = PasswordField( "Confirm Password", validators=[DataRequired(), EqualTo("password", message="Passwords must match")] ) submit = SubmitField("Register") def validate_email(self, field): """Check if email is already registered.""" if User.query.filter_by(email=field.data).first(): raise ValidationError("This email is already registered.") 21:T471,"""Database models.""" from datetime import datetime from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from app.extensions import db, login_manager class User(UserMixin, db.Model): """User model for authentication.""" __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(120), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(256), nullable=False) is_active = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) def set_password(self, password): """Hash and set the user's password.""" self.password_hash = generate_password_hash(password) def check_password(self, password): """Check if the provided password matches the hash.""" return check_password_hash(self.password_hash, password) def __repr__(self): return f"<User {self.email}>" @login_manager.user_loader def load_user(user_id): """Load user by ID for Flask-Login.""" return User.query.get(int(user_id)) 22:T6d3,"""Authentication routes.""" from flask import render_template, redirect, url_for, flash, request from flask_login import login_user, logout_user, login_required, current_user from app.auth import bp from app.auth.forms import LoginForm, RegistrationForm from app.extensions import db from app.models import User @bp.route("/register", methods=["GET", "POST:T4b9,"""Tests for authentication routes.""" def test_register_page(client): """Test registration page loads.""" response = client.get("/auth/register") assert response.status_code == 200 def test_login_page(client): """Test login page loads.""" response = client.get("/auth/login") assert response.status_code == 200 def test_register_user(client): """Test user registration.""" response = client.post("/auth/register", data={ "email": "[email protected]", "password": "testpass123", "confirm": "testpass123", }, follow_redirects=True) assert response.status_code == 200 def test_login_user(client, user): """Test user login.""" response = client.post("/auth/login", data={ "email": "[email protected]", "password": "testpassword123", }, follow_redirects=True) assert response.status_code == 200 def test_login_invalid_password(client, user): """Test login with wrong password.""" response = client.post("/auth/login", data={ "email": "[email protected]", "password": "wrongpassword", }) assert response.status_code == 200 assert b"Invalid email or password" in response.data 9:[["$","$L1b",null,{"locale":"en","skill":{"id":"jezweb-claude-skills-skills-flask-skill-md","name":"flask","author":"jezweb","authorAvatarUrl":"https://avatars.githubusercontent.com/u/13249054?v=4"}}],["$","$L1c",null,{"locale":"en","skill":{"id":"jezweb-claude-skills-skills-flask-skill-md","name":"flask","author":"jezweb","authorAvatar":"https://avatars.githubusercontent.com/u/13249054?v=4","description":"**Status**: Production Ready","githubUrl":"https://github.com/jezweb/claude-skills/tree/main/skills/flask","skillContent":null,"hasMarketplaceJson":true},"files":[{"id":"9643a706-e253-429f-8de5-f14a8a35cebb","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":".claude-plugin","name":".claude-plugin","type":"directory","size":null,"content":null,"isBinary":false},{"id":"99f32cff-693f-483e-9a69-b5282f7b2297","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":"templates/app","name":"app","type":"directory","size":null,"content":null,"isBinary":false},{"id":"b5d05c20-fe16-4b8c-8521-e84f5101dc33","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":"templates/app/auth","name":"auth","type":"directory","size":null,"content":null,"isBinary":false},{"id":"4a402086-b3fc-4400-b889-c36b9031daf8","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":"templates/app/main","name":"main","type":"directory","size":null,"content":null,"isBinary":false},{"id":"5cafa951-633d-4b56-a63e-afaa2db32d4c","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":"templates","name":"templates","type":"directory","size":null,"content":null,"isBinary":false},{"id":"9cb3b2ef-9ffa-493c-88da-9c13