A7.0 What This Teaches
- Stateless web forms - no session history is needed
- Reading textarea input with
request.form - Rendering API output safely inside an HTML page
- Using Jinja2 template escaping to prevent XSS
- Passing data between Flask routes and templates
A7.1 Why Stateless
A7.2 Complete app.py
# app.py - stateless code review web app.
# GET / renders the submission form.
# POST / calls Claude and renders the review.
from flask import Flask, render_template, request
import anthropic
app = Flask(__name__)
client = anthropic.Anthropic()
SYSTEM = (
"You are an expert code reviewer. "
"Review the submitted code for correctness, style, and potential bugs. "
"Structure your response as: (1) Summary, (2) Issues Found, (3) Suggestions."
)
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "GET":
return render_template("index.html")
code = request.form.get("code", "").strip()
language = request.form.get("language", "code")
if not code:
return render_template("index.html", error="Please paste some code before submitting.")
prompt = f"Review this {language} code:\n\n```{language}\n{code}\n```"
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=SYSTEM,
messages=[{"role": "user", "content": prompt}],
)
review = response.content[0].text
return render_template("review.html", review=review, code=code, language=language)
if __name__ == "__main__":
app.run(debug=True)
A7.3 The Form Template
name attributes on each field match what request.form
reads in the route.
<!-- templates/index.html - code submission form -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Code Review</title>
<style>
body { font-family: sans-serif; max-width: 700px; margin: 2rem auto; }
textarea { width: 100%; height: 300px; font-family: monospace; }
select, button { margin-top: 0.5rem; }
</style>
</head>
<body>
<h1>Code Review</h1>
{% if error %}<p style="color:red">{{ error }}</p>{% endif %}
<form method="POST">
<label>Language:
<select name="language">
<option value="Python">Python</option>
<option value="JavaScript">JavaScript</option>
<option value="C++">C++</option>
<option value="Rust">Rust</option>
<option value="C#">C#</option>
</select>
</label>
<br>
<textarea name="code" placeholder="Paste your code here..."></textarea>
<br>
<button type="submit">Review</button>
</form>
</body>
</html>
A7.4 The Review Template
<pre> block
and the review text below it. The Jinja2 | e filter escapes HTML
characters in the user-supplied code so it cannot inject markup into the page.
<!-- templates/review.html - displays review results -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Review Result</title>
<style>
body { font-family: sans-serif; max-width: 700px; margin: 2rem auto; }
pre { background: #f4f4f4; padding: 1rem; overflow-x: auto; }
.review { white-space: pre-wrap; }
</style>
</head>
<body>
<h1>Review: {{ language }}</h1>
<h2>Submitted Code</h2>
<pre>{{ code | e }}</pre>
<h2>Review</h2>
<div class="review">{{ review | e }}</div>
<br>
<a href="/">← Review another file</a>
</body>
</html>
{{ review | e }} escapes any HTML in the API response too, which prevents
a malicious model response from injecting script tags.
A7.5 Complete Code
app.py (shown in A7.2),
templates/index.html (shown in A7.3), and
templates/review.html (shown in A7.4). They are complete and runnable
as shown - no additional code is needed.
pip install flask anthropic
A7.6 Running and Testing
cd web_review
python app.py
http://127.0.0.1:5000. Paste a small Python function, select
Python from the dropdown, and click Review. The review page loads with the submitted
code above and the review below.
* Running on http://127.0.0.1:5000
* Debug mode: on
A7.7 Exercise
Exercise
Add a download link on the review page that lets the user save the review as a
plain text file.
In the route, set the response headers:
- Add a route
GET /downloadthat reads atextquery parameter and returns it as a plain text file download using Flask'smake_response. - On
review.html, add a link:<a href="/download?text=...">Download Review</a>. URL-encode the review text in the link using Jinja2'surlencodefilter or a hidden form with a POST route.
Content-Disposition: attachment; filename="review.txt" and
Content-Type: text/plain.
A7.8 Common Mistakes
Not escaping user code in the HTML template
{{ code }} instead of
{{ code | e }}, a user who pastes <script>alert(1)</script>
will see that script execute in the browser. Always use | e (or
Jinja2 autoescape) for any user-supplied content rendered in HTML.
Not handling empty textarea submission
code is an empty string and you send it to Claude, the model
returns a puzzled response about having nothing to review. Check
if not code before calling the API and return a helpful error
message instead.
Adding conversation history to a stateless form
messages list to the stateless review app
thinking it improves context. It does not - the user submits one piece of code
and receives one review. Adding history adds complexity without benefit here.
Use the Chat app (A6) when multi-turn conversation is genuinely needed.
Key Terms
| Term | Meaning |
|---|---|
| stateless | Each request carries all the data it needs; the server keeps no memory between requests |
| request.form | Flask dict-like object containing HTML form fields submitted via POST |
| render_template | Renders a Jinja2 HTML template with values passed as keyword arguments |
| Jinja2 | e filter | HTML-escapes a string, converting < > & to safe entities |
| textarea | HTML element for multi-line text input; value is read via request.form["name"] |
| make_response | Flask function for building an HTTP response with custom headers and body |
| XSS | Cross-Site Scripting - injecting malicious script tags via unescaped user input |