Flask 3 app factory + Flask-SQLAlchemy: working versions (tested 2026-10-15)
Flask application factory with Flask-SQLAlchemy 3, a JSON API and pytest fixtures using an in-memory database.
passed2 passed · 0 failed2026-10-15 13:15 UTC- Status
- passed
- Last passed
- 2026-10-15 13:15 UTC
- Tests
- 2 / 2
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38740169402
The code
Exactly the files that passed. Your agent can pull them with get_recipe("flask-sqlalchemy-app-factory") (install), or see them on GitHub.
flask==3.1.3 flask-sqlalchemy==3.1.1 sqlalchemy==2.1.4 pytest==9.1.1
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
db = SQLAlchemy(model_class=Base)
class Note(db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
text: Mapped[str]
def create_app(config: dict | None = None) -> Flask:
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
app.config.update(config or {})
db.init_app(app)
with app.app_context():
db.create_all()
@app.post("/notes")
def add_note():
text = (request.get_json(silent=True) or {}).get("text")
if not text:
return jsonify(error="text required"), 400
note = Note(text=text)
db.session.add(note)
db.session.commit()
return jsonify(id=note.id, text=note.text), 201
@app.get("/notes")
def list_notes():
notes = db.session.execute(db.select(Note).order_by(Note.id)).scalars()
return jsonify([{"id": n.id, "text": n.text} for n in notes])
return app
import pytest
from app import create_app
@pytest.fixture
def client():
return create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": "sqlite://"}).test_client()
def test_add_and_list(client):
assert client.post("/notes", json={"text": "hi"}).status_code == 201
assert client.get("/notes").get_json() == [{"id": 1, "text": "hi"}]
def test_validation(client):
assert client.post("/notes", json={}).status_code == 400
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Flask API with Flask-SQLAlchemy and marshmallow-sqlalchemy, validation errors as 422
A Flask JSON API that loads and dumps SQLAlchemy models through SQLAlchemyAutoSchema, returns validation errors as 422, and is tested offline with the Flask test client on in-memory sqlite.
Flask-SQLAlchemy models with Flask-Migrate upgrades on sqlite in tests
Define Flask-SQLAlchemy models, version the schema with Flask-Migrate, and test upgrade, downgrade and model/migration drift offline on a temporary sqlite file.
Flask-Login sessions with Flask-SQLAlchemy users and werkzeug password hashing
Login, login_required redirects, logout and a user_loader built on db.session.get, with the User model in SQLite memory and tests run through the Flask test client.
Alembic autogenerate compare_metadata drift check against SQLite
Declare SQLAlchemy 2 models with a naming convention, then detect schema drift offline by diffing the live SQLite schema against the metadata with Alembic compare_metadata.
All Flask recipesAll Flask-SQLAlchemy recipesAll SQLAlchemy recipes