Skip to main content

Composable HTML generation in python

Project description

html-compose

✨ Composable HTML generation in Python 3.10+ with extensive type hinting 📚

from html_compose import a, article, body, br, head, html, p, strong, title

>>> username = "github wanderer"
>>> print(
  html()[
    head[title[f"Welcome, {username}!"]],
    body[
        article[
            p["Welcome to the internet ", strong[username], "!"],
            br(),
            p[
                "Have you checked out this cool thing called a ",
                a(href="https://google.com")["search engine"],
                "?",
            ],
        ]
    ],
  ].render()
)
<html><head><title>Welcome, github wanderer!</title></head><body><article><p>Welcome to the internet <strong>github wanderer</strong>!</p><br/><p>Have you checked out this cool thing called a <a href="https://google.com">search engine</a>?</p></article></body></html>

Full autocomplete

a([tab]
  attrs= 
  id= 
  class_= 
  download= 
  href= 
  hreflang= 
  ping= 
  referrerpolicy= 
  rel= 
  target= 
  type= 
  accesskey= 
  autocapitalize= 
  autocorrect= 
  autofocus= 
  contenteditable= 
  dir= 
  draggable= 
  enterkeyhint= 
  hidden= 
  inert= 
  inputmode= 
  is_= 
  itemid= 
  itemprop= 
  itemref= 
  itemscope= 
  itemtype= 
  lang= 
  nonce= 
  popover= 
  slot= 
  spellcheck= 
  style= 
  tabindex= 
  title= 
  translate= 
  writingsuggestions= 

Features ✨

  • ⚡ Lazy evaluation leading to performance gains
  • 🛏 Define children via [] syntax:

    • 💡 It works by running append, then returning self so it can be chained
    from html_compose import p, strong
    
    p()["a ", strong()["bold"], "statement"]
    
    # <p>a <strong>bold</strong> statement</p>
    
    # The above is identical to
    para = p()
    bold_text = strong()
    bold_text.append("bold")
    
    para.append(["a ", bold_text, "statement"])
    
  • 🧩 Skip constructor via same [] syntax for elements with no attributes.

    from html_compose import ul, li, p, strong
    
    ul[
        li["Look ma!"],
        li["No constructor!"],
        li["This feels natural"],
        li["for text elements"]
    ]
    
    p["a ", strong["bold"], " statement"]
    
  • 🌐 Define attributes in a variety of ways:

    from html_compose import div
    
    ## With type hints
    div(tabindex=1)
    div(attrs=[div.hint.tabindex(1)])
    # <div tabindex=1></div>
    
    ## Positionally
    div([div.hint.tabindex(1)])
    # <div tabindex=1></div>
    div({"data-for-something": "foo"})
    # <div data-for-something="foo"></div>
    ## With class dictionary resolution
    is_dark_mode = False
    div(class_={"dark-mode": is_dark_mode, "flex": True})
    # <div class="flex"></div>
    
    ## Combine the two
    div(attrs=[div.class_("flex")], class_={"dark-mode": True})
    # <div class="flex dark-mode"></div>
    
  • 🎭 Type hints for the editor generated from WhatWG spec

  • ⚡ Live Reload server for rapid development
    Run your Python webserver (i.e. Flask, FastAPI, anything!) with live-reload superpowers powered by livereload-js. See browser updates in real-time!

    Note: This feature requires optional dependencies. pip install html-compose[live-reload] or pip install html-compose[full]. The feature also fetches livereload-js from a CDN.

    livereload.py

    import html_compose.live as live
    
    live.server(
        daemon=live.ShellCommand("flask --app ./src/my_webserver run"),
        daemon_delay=1,
        conds=[
            live.WatchCond(path_glob="**/*.py", action=live.ShellCommand("date")),
            live.WatchCond(
                path_glob="./static/sass/**/*.scss",
                action=live.ShellCommand(
                    ["sass", "--update", "static/sass:static/css"]
                ),
                reload=False,  # Nobody reads -these- files so we don't need to reload the server
            ),
            live.WatchCond(
                path_glob="./static/css/", 
                action=None, # There's no action to take on css but this will cause the browser to update
                delay=0.5
            ),
        ],
        host="localhost",
        port=51353
    )
    

Goals 🛠️

  • Be a stable layer for further abstraction of client-server model applications and libraries
  • Put web developer documentation in the hands of developers via their IDE
  • 🚀 Opinionate as few things as possible favoring expression; stay out of the way
  • Clearly mark any potentially breaking changes through discovered development optimizations in changelog

Magic decisions 🪄

The code base is littered with "Magic" decisions to make your life easier, but the keen developer will want to know exactly what these are.

Children 🌟

The children iterator/resolver makes some decisions to marshal input into strings:

  • 🔒 All text elements and attribute values are escaped by default to prevent XSS
    • To inject unsafe text it must explicitly be marked unsafe via html_compose.unsafe_text.
  • 💡 Bools are translated to string true/false
  • ✨ Floats passed as-is are converted to strings by rounding to a fixed precision. The default is defined in ElementBase.FLOAT_PRECISION and can be overridden in two ways:
    • Set ElementBase.FLOAT_PRECISION to the desired value - global
    • Set YourElement.FLOAT_PRECISION to the desired value - applies just to the element. i.e. td.FLOAT_PRECISION
  • 🏷️ Children can be callables, like functions, lambdas, or classes that implement __call__. These are resolved at render time.

Inspiration ✨

Inspiration and motivation for this library are listed below.

  • Throw out your templates by Tavis Rudd
  • The Principle from pydantic FastUI
  • htpy for its syntax ideas, which itself is inspired by projects not in this list
  • htmx Transition library to a "dumb client" model
  • alpinejs Another "dumb client" transition library
  • hyperaxe Similar tool for JavaScript
  • flexx A Python super toolkit for developing user applications
  • lit and the web component engine that it wraps

The WhatWG spec 📝

The Web Hypertext Application Technology Working Group (WHATWG) is a community of people interested in evolving HTML and related technologies.

They produce a document which defines the HTML spec.

https://html.spec.whatwg.org

We parse this document to produce code-generated type hints and annotations.

Generating ⚙️

For maintainers.

In the virtual environment, run python tools/spec_generator.py followed by python tools/generate_attributes.py or python tools/generate_elements.py

This will update the tools/generated directory.

We track this in git so we can see 1:1 changes to our generation.

The generated code is moved into the actual src directory and then the repo tooling is run over it:

  • rye lint --fix
  • rye fmt

Maintaining ⚖️

Elements or attributes may be slightly different from the live package. These should be merged in.

Code generation was used as a trick to bootstrap this package quickly, but the web spec changes quickly, as tools such as the popover API were recently added.

Maintainers will run the generating step, which will update the tools/generated/ classes.

Updates pertaining to those changes should be shipped into the actual module under src.

Dependencies

  • PalletsProjects markupsafe for text escaping. Its "fast" implementation saves significant cycles in the sanitization process.
  • Optional: beautifulsoup4 to beautify HTML

Development tools ⚙️

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

html_compose-0.10.0.tar.gz (256.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

html_compose-0.10.0-py3-none-any.whl (591.8 kB view details)

Uploaded Python 3

File details

Details for the file html_compose-0.10.0.tar.gz.

File metadata

  • Download URL: html_compose-0.10.0.tar.gz
  • Upload date:
  • Size: 256.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.13.2

File hashes

Hashes for html_compose-0.10.0.tar.gz
Algorithm Hash digest
SHA256 502cd516a7eac8e6bed5230fe09f24f1fa248a2097d548c112b730ca78849f8e
MD5 1d99f6bc3ee4249c8933ce7b17597229
BLAKE2b-256 3aaac01daf4bc5355c3e32473b58e95acc3196420eeee9fda3dea9d99b1f112c

See more details on using hashes here.

File details

Details for the file html_compose-0.10.0-py3-none-any.whl.

File metadata

  • Download URL: html_compose-0.10.0-py3-none-any.whl
  • Upload date:
  • Size: 591.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.13.2

File hashes

Hashes for html_compose-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e7d6712cbab57002c15675580f32717541697bc3a484cb1c92d461ec8aad1aa6
MD5 4306f8c9d0e1d4011d6c86def6ed60cd
BLAKE2b-256 19fd19582d071636ead8f0e8603613e5500ed17e892e611e0ad9889f22769a7a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page