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 11:35 UTC- Status
- passed
- Last passed
- 2026-10-15 11:35 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 38728226240
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.
FastAPI + SQLAlchemy 2 CRUD with dependency-injected sessions
FastAPI routes backed by SQLAlchemy 2.x typed models, a per-request session dependency, and TestClient tests on SQLite.
FastAPI with async SQLAlchemy 2 on aiosqlite, tested offline with per-test in-memory DBs
A FastAPI app using AsyncSession and async_sessionmaker on aiosqlite, tested with httpx.AsyncClient over ASGITransport, overriding the session dependency with a fresh in-memory database for each test.
Flask 3 + Flask-JWT-Extended access and refresh tokens
Login, protected routes, refresh tokens and custom claims in Flask 3 using Flask-JWT-Extended, with an app factory.
All Flask recipesAll Flask-SQLAlchemy recipesAll SQLAlchemy recipes