0% found this document useful (0 votes)
2 views20 pages

03-python-flask

The document outlines a preparation guide for a Python Flask written test, focusing on building or extending a REST API. Key areas include app structure, request handling, database interaction with SQLAlchemy, error handling, authentication, and testing. It emphasizes best practices such as input validation, proper use of HTTP methods, and the importance of structuring larger applications with Blueprints.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views20 pages

03-python-flask

The document outlines a preparation guide for a Python Flask written test, focusing on building or extending a REST API. Key areas include app structure, request handling, database interaction with SQLAlchemy, error handling, authentication, and testing. It emphasizes best practices such as input validation, proper use of HTTP methods, and the importance of structuring larger applications with Blueprints.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Python Flask — Written Test

Prep

How this section usually works


You're typically asked to build or extend a small
REST API — define routes, handle requests, talk
to a database via an ORM, and handle errors
properly. Graders check:

1. Correct route definitions and HTTP


methods/status codes
2. Proper use of the request object (JSON body,
query params, path params)
3. Database interaction via SQLAlchemy done
correctly (sessions, relationships, avoiding
N+1 queries)
4. Sensible error handling (not letting a stack
trace leak to the client)
5. Basic security awareness (input validation,
not trusting client input blindly)
1. Basic app structure and routing

from flask import Flask, request,


jsonify

app = Flask(__name__)

@[Link]("/")
def home():
return "Hello, Flask!"

@[Link]("/users/<int:user_id>") #
converter enforces int, auto-404s on
non-numeric
def get_user(user_id):
return jsonify({"id": user_id,
"name": "Sample User"})

@[Link]("/users", methods=["POST"])
def create_user():
data = request.get_json()
return jsonify(data), 201

if __name__ == "__main__":
[Link](debug=True)

Detail worth stating: using <int:user_id> (a


route converter) instead of <user_id> means
Flask itself rejects non-numeric values with a
404 before your function body even runs — this is
cheaper and more consistent than a manual
try: int(user_id) check inside every handler.
debug=True should never be used in production
— it exposes an interactive debugger that can
execute arbitrary code if reached.

2. Request handling — query


params, form data, JSON,
headers

@[Link]("/search")
def search():
query = [Link]("q", "")
# query string: /search?q=phone
page = [Link]("page", 1,
type=int) # with default + type
coercion
return jsonify({"query": query,
"page": page})

@[Link]("/submit", methods=["POST"])
def submit():
if request.is_json:
data = request.get_json()
else:
data = [Link].to_dict()
# standard form POST (not JSON)
return jsonify(received=data)

@[Link]("/protected")
def protected():
auth_header =
[Link]("Authorization")
if not auth_header:
return jsonify(error="Missing
Authorization header"), 401
return jsonify(message="Access
granted")

Q: What's the difference between


[Link] , [Link] , and
request.get_json() ?

[Link] reads query string parameters


( ?key=value in the URL) — always present
regardless of method. [Link] reads
multipart/form-data or application/x-
www-form-urlencoded bodies (classic HTML
form submissions). request.get_json()
parses a JSON body, and returns None (or
raises, depending on force / silent flags) if
the Content-Type header isn't
application/json — a common gotcha
when a client forgets to set that header.
3. Dynamic routes, Blueprints
(structuring a larger app)

# blueprints/[Link]
from flask import Blueprint, jsonify

users_bp = Blueprint("users", __name__,


url_prefix="/users")

@users_bp.route("/<int:user_id>")
def get_user(user_id):
return jsonify({"id": user_id})

# [Link]
from flask import Flask
from [Link] import users_bp

app = Flask(__name__)
app.register_blueprint(users_bp)

Why Blueprints matter (a common "how would


you structure this" question): as an app grows
past a handful of routes, keeping everything in
one [Link] becomes unmanageable. Blueprints
let you group related routes (users, orders, auth)
into separate modules, each independently
definable and testable, then wire them together
in the main app file with a single
register_blueprint call — this is the standard
answer to "how do you organize a larger Flask
application."

4. Flask-SQLAlchemy — models,
relationships, queries

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Department([Link]):
id = [Link]([Link],
primary_key=True)
name = [Link]([Link](100),
nullable=False)
employees =
[Link]("Employee",
backref="department", lazy=True)

class Employee([Link]):
id = [Link]([Link],
primary_key=True)
name = [Link]([Link](100),
nullable=False)
salary = [Link]([Link],
nullable=False)
department_id =
[Link]([Link],
[Link]("[Link]"),
nullable=False)

Q: Write an endpoint that returns all employees


in a department, using the relationship.

@[Link]("/departments/<int:dept_id>/
employees")
def department_employees(dept_id):
department =
[Link].get_or_404(dept_id)
return jsonify([
{"id": [Link], "name": [Link],
"salary": [Link]}
for e in [Link]
])

get_or_404 is worth using explicitly — it's a one-


liner that replaces a manual "fetch, check if
None, return 404" block, and is exactly the kind of
Flask-idiomatic detail graders look for.

Q: What's the N+1 query problem, and how


would you avoid it here?
If you queried all departments and then
accessed .employees on each one in a loop,
SQLAlchemy would (by default, with
lazy=True ) issue one query to fetch
departments, then one additional query per
department to fetch its employees — N+1
total queries instead of 2. The fix is eager
loading:

from [Link] import joinedload

departments =
[Link](joinedload(Dep
[Link])).all()

This makes SQLAlchemy fetch departments and


their employees in a single JOIN query. Naming
this problem and its fix unprompted is a strong
signal on an ORM-focused question.

Q: Create a new employee (POST), with basic


validation.

@[Link]("/employees", methods=
["POST"])
def create_employee():
data = request.get_json()
if not data or "name" not in data
or "salary" not in data or
"department_id" not in data:
return jsonify(error="Missing
required fields"), 400

department =
[Link](data["department_i
d"])
if not department:
return
jsonify(error="Department not found"),
404

employee = Employee(
name=data["name"],
salary=data["salary"],

department_id=data["department_id"]
)
[Link](employee)
[Link]()
return jsonify({"id": [Link],
"name": [Link]}), 201

Detail to mention: validate that referenced


foreign keys ( department_id ) actually exist
before inserting — otherwise you either get a raw
database integrity error leaking to the client, or
(on a misconfigured DB without FK constraints)
silently create an orphaned row.

Q: Update and delete an employee.

@[Link]("/employees/<int:emp_id>",
methods=["PUT"])
def update_employee(emp_id):
employee =
[Link].get_or_404(emp_id)
data = request.get_json()
[Link] = [Link]("name",
[Link])
[Link] =
[Link]("salary", [Link])
[Link]()
return jsonify({"id": [Link],
"name": [Link], "salary":
[Link]})

@[Link]("/employees/<int:emp_id>",
methods=["DELETE"])
def delete_employee(emp_id):
employee =
[Link].get_or_404(emp_id)
[Link](employee)
[Link]()
return "", 204 # 204 No Content —
successful, nothing to return
Returning 204 with an empty body for a
successful delete (rather than 200 with a
message) is the REST convention worth stating
explicitly.

5. Error handling

@[Link](404)
def not_found(e):
return jsonify(error="Resource not
found"), 404

@[Link](500)
def server_error(e):
return jsonify(error="Internal
server error"), 500

# Custom exception + handler


class
InsufficientStockError(Exception):
pass

@[Link](InsufficientStockErro
r)
def handle_stock_error(e):
return jsonify(error=str(e)), 400

@[Link]("/orders", methods=["POST"])
def create_order():
data = request.get_json()
if [Link]("quantity", 0) >
get_available_stock(data["product_id"])
:
raise
InsufficientStockError("Not enough
stock available")
# ... proceed with order creation

Why custom exception classes matter: they let


you raise a domain-specific error deep inside
business logic (a service function several layers
down) without that function needing to know
anything about HTTP status codes — the
@[Link] at the top level is the only
place that translates it into an actual HTTP
response. This keeps business logic decoupled
from the web layer, which is a detail worth
stating if asked "how would you structure error
handling in a larger app."

6. Middleware-style hooks:
before_request / after_request
import time

@app.before_request
def start_timer():
request.start_time = [Link]()

@app.after_request
def log_request(response):
duration = [Link]() -
request.start_time
[Link](f"{[Link]}
{[Link]} took {duration:.3f}s")
return response

Use case to mention: before_request is also


where you'd typically implement things that need
to apply to every route without repeating code in
each handler — e.g. checking an API key header,
rejecting requests over a certain size, or opening
a DB connection/session per request.

7. Authentication (JWT-based,
the common pattern)

import jwt
import datetime
from functools import wraps

SECRET_KEY = "your-secret-key" # in
real code: load from environment, never
hardcode

def generate_token(user_id):
payload = {
"user_id": user_id,
"exp":
[Link]() +
[Link](hours=1)
}
return [Link](payload,
SECRET_KEY, algorithm="HS256")

def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token =
[Link]("Authorization",
"").replace("Bearer ", "")
if not token:
return jsonify(error="Token
missing"), 401
try:
payload = [Link](token,
SECRET_KEY, algorithms=["HS256"])
request.user_id =
payload["user_id"]
except
[Link]:
return jsonify(error="Token
expired"), 401
except [Link]:
return
jsonify(error="Invalid token"), 401
return f(*args, **kwargs)
return decorated

@[Link]("/profile")
@token_required
def profile():
return jsonify({"user_id":
request.user_id})

Explain the decorator pattern: token_required


wraps any route function so the token-validation
logic is written once and reused via
@token_required , rather than copy-pasted into
every protected route. @wraps(f) preserves the
original function's name/metadata (without it,
Flask's routing can break when multiple routes
are wrapped by the same decorator, since they'd
all appear to be named decorated ).
Distinguishing ExpiredSignatureError from a
generally invalid token is a detail worth including
— it lets the client tell "log in again" apart from
"something is actually wrong with this token."
8. CORS

from flask_cors import CORS

app = Flask(__name__)
CORS(app, resources={r"/api/*":
{"origins": "[Link]

Q: What is CORS and why does it matter for a


Flask API serving a separate frontend?

CORS (Cross-Origin Resource Sharing) is a


browser security mechanism that blocks
JavaScript on one origin
(domain+port+protocol) from reading
responses from a different origin, unless the
server explicitly allows it via response
headers. If your React frontend runs on
localhost:3000 and your Flask API on
localhost:5000 , the browser blocks the
frontend's fetch calls by default — flask-
cors adds the necessary Access-Control-
Allow-Origin headers. Scoping it to specific
origins/routes (rather than a blanket
origins: "*" ) is worth mentioning as the
more secure default for anything beyond local
development.

9. Testing with pytest and Flask's


test client

import pytest
from app import app, db

@[Link]
def client():
[Link]["TESTING"] = True

[Link]["SQLALCHEMY_DATABASE_URI"] =
"sqlite:///:memory:"
with app.test_client() as client:
with app.app_context():
db.create_all()
yield client

def test_create_employee(client):
response =
[Link]("/employees", json={
"name": "Jane Doe", "salary":
75000, "department_id": 1
})
assert response.status_code in
(201, 404) # 404 if dept doesn't exist
in this test DB

def
test_get_nonexistent_employee(client):
response =
[Link]("/employees/9999")
assert response.status_code == 404

Detail worth mentioning: using an in-memory


SQLite database ( sqlite:///:memory: ) for
tests keeps them fast and fully isolated from
your real database — each test run starts from a
clean slate via db.create_all() , so tests never
leave residue that could make later runs
pass/fail inconsistently.

10. Application context & request


context (a common "gotcha"
question)
Q: Why does code outside a route sometimes
throw "working outside of application context"?

Flask uses two context stacks: the application


context (holds things like current_app , the
DB connection config) and the request
context (holds request , session — only
exists during an actual HTTP request). Code
that touches db or current_app outside of a
route function, a CLI command, or an explicit
with app.app_context(): block has no
active context to attach to, and Flask raises
this error rather than silently guessing which
app/config to use. This commonly bites
people writing a standalone script that
imports models and queries the DB directly —
the fix is wrapping that script's logic in with
app.app_context(): .

General template for any Flask


question
1. Identify the route: path, HTTP method,
path/query params needed.
2. Validate input early — required fields present,
correct types, referenced foreign keys exist —
before touching the database.
3. Use the ORM relationship/eager-loading
correctly — watch for N+1 queries when a
question involves nested/related data.
4. Return the correct status code: 200 OK, 201
Created, 204 No Content (delete), 400 bad
input, 401 unauthorized, 404 not found,
500 only for truly unexpected server errors
(never leak a raw traceback to the client).
5. Mention security/structure concerns
unprompted where relevant — hardcoded
secrets, missing auth checks, N+1 queries, or
a monolithic [Link] that should be split into
Blueprints — these are exactly the "senior"
details a written test is trying to surface.

You might also like