State & Relational Persistence (SQLite)
architect, critique, revise, codeModule 4: State & Relational Persistence (SQLite)
Goal: Store your task data securely in a local relational database using raw SQL queries.
What you need to know first
State Persistence: In previous modules, tasks were only saved in memory variables (which are erased when the server stops). To keep data permanently, we must write it to a database file.
Relational Databases (SQLite):
- Think of a database like an Excel Spreadsheet Workbook:
- Tables are like individual Worksheets inside the workbook (e.g. a
tasksworksheet and ausersworksheet). - Columns are the Headers specifying the data types (e.g.,
idis an integer,titleis text). - Rows are the actual Records of data.
- Tables are like individual Worksheets inside the workbook (e.g. a
- SQLite is a lightweight engine that stores this entire spreadsheet structure in a single local file (e.g.
tasks.db). - ⚠️ SQLite File Lock Warning: Because SQLite is a simple local file, it only allows one writer connection at a time. If you open
tasks.dbin a database viewer app (like DB Browser for SQLite) to inspect your tables and try to run writing tests at the same time, the viewer might lock the file, causing uvicorn or pytest to crash with adatabase is lockederror. Keep your GUI viewers closed or in read-only mode during tests.
SQL Injection (SQLi): A severe vulnerability where malicious code inside user input is executed as SQL.
- Vulnerable (Concatenation):
Ifquery = f"INSERT INTO tasks (title) VALUES ('{user_input}')"user_inputisTask', '1'); DROP TABLE tasks; --, the database will execute the DROP statement! - Secure (Parameter Binding):
The database treats the input strictly as a text value, preventing code execution.cursor.execute("INSERT INTO tasks (title) VALUES (?)", (user_input,))
The SOLO idea
Security is not a final step; it is a core feature. Following Pillar 03: Security-First, we never write concatenation-based query logic, even in temporary labs. Every query must use parameter placeholders. We plan our data layer and connection states before wiring them to endpoints.
Lab 4: SQLite CRUD
Estimated AI conversations: 3
- Create a database initialize script
db.pyin your project folder. - Step A: Plan the database interface. Open Copilot Chat and type:
Follow #file:workflows/architect.md — Plan the database helper in
db.pythat initializes an SQLite database filetasks.dbwith a tabletaskscontaining columns:id(INTEGER PRIMARY KEY AUTOINCREMENT),title(TEXT), andhours(REAL).- Plan a generator function
get_db()that yields a database connection and closes it when finished. - Configure the connection to use
sqlite3.Rowas itsrow_factoryso that row records can be accessed like dictionaries (row["title"]).
- Plan a generator function
- Step B: Critique database connections. In the same conversation, type:
Follow #file:workflows/critique.md — Critique this connection helper strategy. Will uvicorn lock the database if multiple requests run concurrently? Are we handling connections inside a
try...finallyblock? - Step C: Revise database helper.
Follow #file:workflows/revise.md — Revise the plan to address database locking risks and connection safety (ensuring
conn.close()always executes insidefinally). - Step D: Implement database helper.
Follow #file:workflows/code.md — Write the finalized code in
db.py. - Initialize your database:
- Mac/Linux/Windows:
python -c "import db; conn = db.sqlite3.connect('tasks.db') # or trigger helper creation"
- Mac/Linux/Windows:
- Step E: Plan, critique, and code the endpoints. Follow the planning loop for endpoints:
- Plan: Run
/architectto map out replacing the stub functions inmain.pyusing dependency injection withDepends(get_db)to acquire the database connection. - Critique: Run
/critiqueto audit for SQL Injection. Verify every SQL command (SELECT,INSERT,UPDATE,DELETE) uses?bindings and never string interpolation. - Code: Run
/codeto replace route logic.
- Plan: Run
- Verify your API operations in the
/docsUI. Confirm tasks are saved permanently intasks.dbeven if you restart the server. - Commit:
git add . git commit -m "Module 4: SQLite CRUD implemented with parameterized SQL and get_db generator" git push
Checkpoint
Create checkpoint_04.md:
- List the SQL query statement templates used in your routes (e.g.
INSERT INTO tasks (title, hours) VALUES (?, ?)). - What does setting
conn.row_factory = sqlite3.Rowdo under the hood, and why is it useful when returning data as JSON in FastAPI? - What does the SQL placeholder
?accomplish from a security perspective?