Generating HTML
Building web pages in Python
Concepts
- HTML is a tree of nested elements
- htpy represents that tree as nested Python function calls, so the structure of your Python code mirrors the structure of the page
- Show me what a simple HTML page looks like written with htpy versus as a raw HTML string
- Cross-site scripting (XSS)
- if you embed user-supplied text directly into an HTML string,
a malicious user can inject
<script>tags that run in other visitors' browsers - htpy escapes special characters automatically to prevent this
- Show me an example of a cross-site scripting attack
- if you embed user-supplied text directly into an HTML string,
a malicious user can inject
- HTML attributes like
class,id, andhrefare passed as keyword arguments to htpy element functions- Hyphens in attribute names become underscores (e.g.,
data_valuebecomesdata-value) - Show me examples of adding attributes to elements with htpy?
- Hyphens in attribute names become underscores (e.g.,
- A
Content-Type: text/htmlheader tells the browser to render the response body as a web page rather than displaying it as raw text- What happens in the browser if the server sends HTML without setting the correct Content-Type header?
- Reusable layout functions wrap page content in a consistent way
(navigation, footer, stylesheet link) without repeating code in every handler
- What are the common elements of a simple page layout?
- The
lang,meta charset, andviewportattributes are accessibility and compatibility requirementslanghelps screen readerscharsetprevents encoding errorsviewportmakes pages usable on mobile devices- What are three short tutorials I should read about web accessibility?
htpy basics
- Tags as Python callables
- Show me how to create a paragraph, a link, and a div using htpy
- Attributes as keyword arguments
- Why does htpy use parentheses for some things and square brackets for others?
- Nesting
- Show me how to nest htpy elements to create an unordered list with three items
- Generating a complete HTML page with head, body, and content
- Generate a complete HTML page with head, title, body, and a heading using htpy
Building a page layout
- A reusable layout function that wraps content with nav bar, main area, and footer
- Write a Python function using htpy that wraps page content in a layout with a nav bar, main content area, and footer
Serving htpy output from Litestar
- Returning an HTML response from a handler
- Show me how to return an htpy-generated HTML page from a Litestar route handler
- Setting the correct content type
- Show me how to set the Content-Type header to text/html in a Litestar response
- Viewing the page in a browser
- How do I verify that Litestar is returning valid HTML?
Reviewing the generated code
- What the LLM got right and what it omitted
- Review this htpy page template and suggest three small improvements
Check for Understanding
Why is f"<p>{user_input}</p>" dangerous if user_input comes from a form field?
If user_input is <script>alert('xss')</script>,
the resulting HTML contains an executable script tag.
Anyone who views the page will have that script run in their browser,
potentially stealing session cookies or redirecting them to a malicious site.
htpy escapes < and > to < and > so that
the text is displayed literally rather than interpreted as markup.
How does htpy represent an HTML attribute like class="nav-bar"?
As a Python keyword argument to the element function.
Since class is a reserved word in Python,
htpy uses class_ instead.
Hyphens in attribute names become underscores (e.g., data-value is written data_value=…).
What is the difference between returning HTMLResponse and returning a plain string from a Litestar handler?
Returning a plain string sends a response with Content-Type: text/plain,
so the browser displays raw text without rendering markup.
Returning HTMLResponse sets Content-Type: text/html,
which tells the browser to parse and render the content as a web page.
The body bytes may be identical; the header controls interpretation.
The lesson mentions missing lang, meta charset, and viewport attributes. What does each of these do?
lang on the <html> element tells screen readers and search engines
the primary language of the page (e.g., lang="en").
meta charset declares the character encoding (almost always utf-8) so the browser decodes bytes correctly.
The viewport meta tag (content="width=device-width, initial-scale=1")
prevents mobile browsers from shrinking the page to fit a desktop-sized layout.
Exercises
Add an about page
Prompt the LLM to add a /about route
that returns an HTML page describing the snail survey project in two or three sentences.
Use the shared layout function so the page has the same navigation and footer as the main page.
Visit the page in your browser and verify the navigation link to it works.
Trigger an XSS attempt
Write a small test where user_input is the string <script>alert('xss')</script>.
Render it using htpy and print the resulting HTML.
Verify that the output contains <script> rather than an executable tag.
Then try the same experiment with an f-string and compare the results.
Inspect the response headers
Use curl -i http://localhost:8000/ to fetch the home page
and display the response headers alongside the body.
Find the Content-Type header and confirm it says text/html.
Then modify the handler temporarily to return a plain string instead of an HTMLResponse
and run curl -i again.
Note which header changes and what the browser does differently when you visit the page.
Render a list of species
Prompt the LLM to add a /snails route that retrieves a hardcoded Python list of snails
and renders it as an HTML unordered list.
Each species name should be a <li> element inside a <ul>.
Look at the code the LLM generates for the list
and note how it turns a Python list into a sequence of htpy elements.
What would you need to change to switch from <ul> to <ol>?
Conditional page elements
Modify the code created above so that the Python list has snails and sizes,
and that snails above a certain size are marked in bold using <strong>…</strong>.