Generating HTML

Goals

Three Ways to Do It

What are the main ways to generate HTML from Python, and what are the tradeoffs?

Show an example of how htpy works.

from htpy import ul, li
print(str(ul[li["item one"], li["item two"]]))
<ul><li>item one</li><li>item two</li></ul>

Building the Basic Page

Rewrite htmlcss/basic_page.html as a Python script using htpy.

How do I add an id attribute to an element in htpy?

i
from htpy import a, body, em, h1, h2, head, html, li, p, title, ul

page = html(lang="en")[
    head[title["Sasquatch Sightings"]],
    body[
        h1["Sasquatch Sightings in British Columbia"],
        p[
            "This page records recent sightings of ",
            em["Gigantopithecus canadensis"],
            " and ",
            em["G. horribilus"],
            " in British Columbia. "
            "Data was collected by volunteers & researchers in early 2024.",
        ],
        h2(id="recent")["Recent Sightings"],
        ul[
            li["2024-01-15: ", em["G. canadensis"], " near Hope, BC. Weight > 200 kg."],
            li[
                "2024-02-03: ",
                em["G. horribilus"],
                " near Whistler. Female; weight < 150 kg.",
            ],
            li[
                "2024-03-21: ",
                em["G. canadensis"],
                ' in Manning Park. Color: "reddish-brown".',
            ],
        ],
        h2(id="about")["About This Project"],
        p[
            "The Sasquatch Observation Registry collects verified sightings "
            "from trained volunteers across British Columbia. "
            "Jump back to ",
            a(href="#recent")["recent sightings"],
            " or visit ",
            a(href="https://example.com/sasquatch")["the project website"],
            ".",
        ],
        p["\u00a9 2024 Sasquatch Research Institute"],
    ],
]

print(str(page))

Building the Table Page

Rewrite htmlcss/table_page.html as a Python script using htpy. Store the sightings data in a list of tuples.

i
from htpy import a, body, em, h1, h2, head, html, p, table, td, th, title, tr

SIGHTINGS = [
    (
        "2024-01-15",
        "G. canadensis",
        "Near Hope, BC",
        "Weight > 200 kg; sex not recorded",
    ),
    ("2024-02-03", "G. horribilus", "Whistler area", "Female; weight < 150 kg"),
    (
        "2024-03-21",
        "G. canadensis",
        "Manning Park",
        'Male; color described as "reddish-brown"',
    ),
]

page = html(lang="en")[
    head[title["Sasquatch Sightings"]],
    body[
        h1["Sasquatch Sightings in British Columbia"],
        p[
            "This page records recent sightings of ",
            em["Gigantopithecus canadensis"],
            " and ",
            em["G. horribilus"],
            " in British Columbia. "
            "Data was collected by volunteers & researchers in early 2024.",
        ],
        h2(id="recent")["Recent Sightings"],
        table[
            tr[th["Date"], th["Species"], th["Location"], th["Notes"]],
            [
                tr[td[date], td[species], td[location], td[notes]]
                for date, species, location, notes in SIGHTINGS
            ],
        ],
        h2(id="about")["About This Project"],
        p[
            "The Sasquatch Observation Registry collects verified sightings "
            "from trained volunteers across British Columbia. "
            "Jump back to ",
            a(href="#recent")["recent sightings"],
            " or visit ",
            a(href="https://example.com/sasquatch")["the project website"],
            ".",
        ],
        p["\u00a9 2024 Sasquatch Research Institute"],
    ],
]

print(str(page))

Adding a Stylesheet

Rewrite htmlcss/styled_page.html as a Python script using htpy. Include the CSS stylesheet and apply the note and copyright classes.

i
# CSS as a plain string; htpy places it inside <style> without escaping
CSS = """
    body { font-family: sans-serif; max-width: 40em; margin: 1em auto; padding: 0 1em; }
    h1 { text-align: center; color: #333333; }
    h2 { border-bottom: 1px solid #cccccc; }
    table { border-collapse: collapse; width: 100%; }
    th, td { border: 1px solid #cccccc; padding: 0.4em 0.8em; text-align: left; }
    th { background-color: #eeeeee; }
    .note { font-style: italic; color: #666666; }
    .copyright { text-align: center; font-size: 0.85em; color: #999999; }
"""

page = html(lang="en")[
    head[
        title["Sasquatch Sightings"],
        style[CSS],
    ],
i
    body[
        h1["Sasquatch Sightings in British Columbia"],
        p[
            "This page records recent sightings of ",
            em["Gigantopithecus canadensis"],
            " and ",
            em["G. horribilus"],
            " in British Columbia. "
            "Data was collected by volunteers & researchers between January and March 2024.",
        ],
        h2(id="recent")["Recent Sightings"],
        table[
            tr[th["Date"], th["Species"], th["Location"], th["Notes"]],
            [
                tr[td[date], td[species], td[location], td(class_="note")[notes]]
                for date, species, location, notes in SIGHTINGS
            ],
        ],
        h2(id="about")["About This Project"],
        p[
            "The Sasquatch Observation Registry collects verified sightings "
            "from trained volunteers across British Columbia. "
            "Jump back to ",
            a(href="#recent")["recent sightings"],
            " or visit ",
            a(href="https://example.com/sasquatch")["the project website"],
            ".",
        ],
        p(class_="copyright")["\u00a9 2024 Sasquatch Research Institute"],
    ],
]

print(str(page))

Check Understanding

See [htpy2025] for the full htpy documentation and [mdn-html2024] for reference on HTML elements and attributes.

What does htpy use instead of class="..." as a keyword argument, and why?

htpy uses class_ (with a trailing underscore) because class is a reserved word in Python---it is used to define new classes and cannot appear as a function argument name. Adding an underscore is a standard Python convention for working around name conflicts with built-in keywords.

You write td["Weight > 200 kg"] in htpy. What does the browser receive, and why?

The browser receives <td>Weight &gt; 200 kg</td>. htpy automatically escapes > as &gt; in text content so the browser does not mistake it for the start of a closing tag. This happens for < (becomes &lt;) and & (becomes &amp;) as well.

The script below raises a TypeError. What is wrong and how do you fix it?
from htpy import ul, li
page = ul(li["first"], li["second"])
print(str(page))

The round brackets (...) pass arguments to configure the element's attributes. To give an element its children, use square brackets [...]. The fix is:

page = ul[li["first"], li["second"]]
What is the advantage of storing sightings data in a list of tuples (as in table_page.py) rather than hard-coding the table rows directly in the htpy expression?

Separating data from structure means you can change one without touching the other. If the sightings list comes from a database or a file later in the tutorial, you only need to change where SIGHTINGS comes from, not the code that builds the table. It also makes the table-building code shorter and easier to read.

A classmate writes this to center a heading: h1(style="text-align: center")["Title"]. It works, but is it a good idea? Why or why not?

It works for a single heading, but it is the same problem as inline styles in HTML: if you later decide to change the alignment, you have to find and edit every element that has the style attribute. A better approach is to define .centered { text-align: center; } in the CSS string and apply it with h1(class_="centered")["Title"], so one change in the CSS affects every element that uses the class.

The SIGHTINGS list in table_page.py contains the string 'Male; color described as "reddish-brown"'. The original table_page.html uses &quot; for those double-quotes. What does htpy put in the output: &quot;, &#34;, the literal ", or something else?

htpy outputs &#34;, the numeric character reference for ". The original HTML file used &quot;, which is the named entity for the same character. Both are valid HTML and display identically in a browser. Double-quotes do not actually need escaping in text content (only inside attribute values), but htpy escapes them anyway using the numeric form. You can verify this yourself by running python table_page.py and searching the output.

Exercises

Add a Caption

HTML tables support an optional <caption> element placed immediately after the opening <table> tag. Import caption from htpy and add a caption to the table in table_page.py. Add a CSS rule to styled_page.py that centers the caption and makes it italic.

Generate the List from Data

Rewrite basic_page.py so the three sightings in the <ul> are generated from a list of tuples, using the same pattern as table_page.py. Verify that the HTML output is identical to what the original script produced.

Add a Navigation Bar

Add an unordered list at the top of the <body> in styled_page.py with links to the #recent and #about anchors. Add a CSS rule to the stylesheet string so the list items appear side by side rather than stacked.

Extract a Page Builder Function

Define a Python function make_page(heading, rows) that returns a complete html[...] element given a heading string and a list of row tuples. Call it from a short __main__ block to produce the same output as styled_page.py.

Color-Code by Species

Edit styled_page.py so each data row has a CSS class that matches its species (canadensis or horribilus). Add CSS rules to the stylesheet string so each species has a different background color.