Building an API with Flask
A JSON blueprint
from flask import Blueprint, jsonify, request, url_for
from pydantic import BaseModel, Field, ValidationError
api = Blueprint("api_v1", __name__, url_prefix="/v1")
class ProjectIn(BaseModel):
title: str = Field(min_length=1, max_length=200)
description: str = Field(default="", max_length=5000)
category: str
def project_out(p):
return {"id": str(p.id), "title": p.title, "description": p.description,
"category": p.category, "created_at": p.created_at.isoformat() + "Z"}
Pydantic validates input and gives detailed error messages; a plain function controls exactly what goes out, so internal fields (password hashes, internal notes) never leak.
Endpoints
@api.get("/projects")
def list_projects():
limit = min(int(request.args.get("limit", 20)), 100)
after = request.args.get("cursor", type=int)
stmt = select(Project).where(Project.is_published.is_(True)).order_by(Project.id.desc())
if after:
stmt = stmt.where(Project.id < after)
rows = db.session.scalars(stmt.limit(limit + 1)).all()
has_more = len(rows) > limit
rows = rows[:limit]
return jsonify(data=[project_out(p) for p in rows],
has_more=has_more,
next_cursor=str(rows[-1].id) if has_more else None)
@api.post("/projects")
@require_api_key(scope="projects:write")
def create_project():
try:
data = ProjectIn.model_validate(request.get_json(force=False) or {})
except ValidationError as exc:
return api_error(422, "validation_failed", "Invalid project.",
fields={e["loc"][0]: e["msg"] for e in exc.errors()})
project = Project(**data.model_dump(), owner_id=g.api_user.id)
db.session.add(project)
db.session.commit()
resp = jsonify(project_out(project))
resp.status_code = 201
resp.headers["Location"] = url_for("api_v1.get_project", project_id=project.id)
return resp
Central error handling
def api_error(status, code, message, **extra):
resp = jsonify(error={"code": code, "message": message, **extra})
resp.status_code = status
return resp
@api.errorhandler(404)
def not_found(_):
return api_error(404, "not_found", "Resource not found.")
@api.errorhandler(Exception)
def unexpected(exc):
current_app.logger.exception("API error")
return api_error(500, "internal_error", "Something went wrong.")
CSRF and APIs
Session-cookie-based browser forms need CSRF protection. Token-authenticated APIs (API keys or bearer tokens in the Authorization header) are not vulnerable to CSRF in the same way, so API blueprints are often exempted from CSRF only when they do not accept cookie sessions.
CORS
Browsers block cross-origin requests unless the API allows them. Allow only the origins that need access (flask-cors with an explicit origin list); never combine * with credentials.
Exercise
Implement list, get, create, update (PATCH) and delete endpoints for projects with Pydantic validation, cursor pagination, a consistent error format and tests for each status code.
Check yourself
- Why serialise with an explicit output function?
- When can an API blueprint skip CSRF protection?
- Why not allow CORS from
*with credentials?
ההתקדמות שלכם נשמרת בדפדפן הזה בלבד.
שליחה לבדיקה (10 נקודות)