Advanced Demo¶
This annotated example combines soft deletes, nested writes and custom callbacks.
The code lives in demo/advanced_features/app.py.
1from __future__ import annotations
2
3import datetime
4from typing import Any
5
6from flask import Flask
7from flask_sqlalchemy import SQLAlchemy
8from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String
9from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
10
11from flarchitect import Architect
12
13
14class BaseModel(DeclarativeBase):
15 """Base model with timestamp and soft delete columns."""
16
17 created: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow)
18 updated: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
19 deleted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
20
21 def get_session(*args: Any, **kwargs: Any):
22 """Return the current database session."""
23
24 return db.session
25
26
27db = SQLAlchemy(model_class=BaseModel)
28
29
30class Author(db.Model):
31 """Author of one or more books."""
32
33 __tablename__ = "author"
34
35 class Meta:
36 tag = "Author"
37 tag_group = "People"
38
39 id: Mapped[int] = mapped_column(Integer, primary_key=True)
40 name: Mapped[str] = mapped_column(String(80))
41 books: Mapped[list[Book]] = relationship(back_populates="author")
42
43
44class Book(db.Model):
45 """Book written by an author."""
46
47 __tablename__ = "book"
48
49 class Meta:
50 tag = "Book"
51 tag_group = "Content"
52 allow_nested_writes = True
53 add_callback = staticmethod(lambda obj, model: _add_callback(obj))
54
55 id: Mapped[int] = mapped_column(Integer, primary_key=True)
56 title: Mapped[str] = mapped_column(String(120))
57 author_id: Mapped[int] = mapped_column(ForeignKey("author.id"))
58 author: Mapped[Author] = relationship(back_populates="books")
59
60
61def _add_callback(obj: Book) -> Book:
62 """Ensure book titles are capitalised before saving."""
63
64 obj.title = obj.title.title()
65 return obj
66
67
68def return_callback(model: type[BaseModel], output: dict[str, Any], **kwargs: Any) -> dict[str, Any]:
69 """Attach a debug flag to every response.
70
71 Args:
72 model: Model class being processed.
73 output: Response payload.
74
75 Returns:
76 Modified response dictionary.
77 """
78
79 output["debug"] = True
80 return {"output": output}
81
82
83def create_app() -> Flask:
84 """Build the Flask application and initialise flarchitect.
85
86 Returns:
87 Configured Flask application.
88 """
89
90 app = Flask(__name__)
91 app.config.update(
92 SQLALCHEMY_DATABASE_URI="sqlite:///:memory:",
93 API_TITLE="Advanced API",
94 API_VERSION="1.0",
95 API_BASE_MODEL=db.Model,
96 API_ALLOW_NESTED_WRITES=True,
97 API_SOFT_DELETE=True,
98 API_SOFT_DELETE_ATTRIBUTE="deleted",
99 API_SOFT_DELETE_VALUES=(False, True),
100 API_RETURN_CALLBACK=return_callback,
101 )
102
103 db.init_app(app)
104 with app.app_context():
105 db.create_all()
106 Architect(app)
107
108 return app
109
110
111if __name__ == "__main__":
112 create_app().run(debug=True)
Key points¶
Soft deletes are enabled via
API_SOFT_DELETEand thedeletedcolumn onBaseModel.Nested writes allow creating related objects in one request.
Book.Meta.allow_nested_writesturns it on for books.Custom callbacks modify behaviour:
return_callbackinjects adebugflag into every response andBook.Meta.add_callbacktitle-cases book names before saving.
Run the demo¶
python demo/advanced_features/app.py
curl -X POST http://localhost:5000/api/book \
-H "Content-Type: application/json" \
-d '{"title": "my book", "author": {"name": "Alice"}}'
curl http://localhost:5000/api/book?include_deleted=true