{"id":16545551,"url":"https://github.com/keithasaurus/simple_html","last_synced_at":"2026-01-07T02:13:45.261Z","repository":{"id":48445048,"uuid":"212960210","full_name":"keithasaurus/simple_html","owner":"keithasaurus","description":"fast, templateless html generation","archived":false,"fork":false,"pushed_at":"2024-03-24T06:09:28.000Z","size":201,"stargazers_count":32,"open_issues_count":1,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-04-25T05:21:38.085Z","etag":null,"topics":["html","python","typesafe"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/keithasaurus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-10-05T07:20:45.000Z","updated_at":"2024-04-18T01:30:45.000Z","dependencies_parsed_at":"2023-10-16T12:30:51.990Z","dependency_job_id":"cf7587a2-0c5e-4c7d-9406-dfa779836f76","html_url":"https://github.com/keithasaurus/simple_html","commit_stats":{"total_commits":75,"total_committers":4,"mean_commits":18.75,"dds":0.6533333333333333,"last_synced_commit":"eb8542e98ede29529b26bd421e33719458385a23"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fsimple_html","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fsimple_html/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fsimple_html/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fsimple_html/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/keithasaurus","download_url":"https://codeload.github.com/keithasaurus/simple_html/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219859190,"owners_count":16556036,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["html","python","typesafe"],"created_at":"2024-10-11T19:07:04.395Z","updated_at":"2025-10-28T15:31:39.914Z","avatar_url":"https://github.com/keithasaurus.png","language":"Python","funding_links":[],"categories":["Libraries"],"sub_categories":["General HTML Generation"],"readme":"# simple_html\n\n## Why use it?\n- clean syntax\n- fully-typed\n- speed -- often faster than jinja\n- zero dependencies\n- escaped by default\n- usually renders fewer bytes than templating\n\n\n## Installation\n`pip install simple-html`\n\n\n## Usage\n\n```python\nfrom simple_html import h1, render\n\n\nnode = h1(\"Hello World!\")\n\nrender(node)  \n# \u003ch1\u003eHello World!\u003c/h1\u003e \n```\n\nTo add attributes to a tag, pass a dictionary as the first argument: \n```python\nnode = h1({\"id\": \"heading\"}, \"Hello World!\")\n\nrender(node)  \n# \u003ch1 id=\"heading\"\u003eHello World!\u003c/h1\u003e \n```\n\nHere's a fuller-featured example:\n```python\nfrom simple_html import render, DOCTYPE_HTML5, html, head, title, body, h1, div, p, br, ul, li\n\n\nrender(\n    DOCTYPE_HTML5,\n    html(\n        head(\n            title(\"A Great Webpage!\")\n        ),\n        body(\n            h1({\"class\": \"great header\"},\n               \"Welcome!\"),\n            div(\n                p(\"This webpage is great for three reasons:\"),\n                ul(li(f\"{s} reason\") for s in [\"first\", \"second\", \"third\"]),\n            ),\n            br,\n            \"Hope you like it!\"\n        )\n    )\n)\n\n```\n\nThe above renders to a minified version of the following html:\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\n\u003chead\u003e\u003ctitle\u003eA Great Webpage!\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\u003ch1 class=\"great header\"\u003eWelcome!\u003c/h1\u003e\n\u003cdiv\u003e\u003cp\u003eThis webpage is great for three reasons:\u003c/p\u003e\n    \u003cul\u003e\n        \u003cli\u003efirst reason\u003c/li\u003e\n        \u003cli\u003esecond reason\u003c/li\u003e\n        \u003cli\u003ethird reason\u003c/li\u003e\n    \u003c/ul\u003e\n\u003c/div\u003e\n\u003cbr/\u003eHope you like it!\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nAs you might have noticed, there are several ways to use `Tag`s:\n```python\nfrom simple_html import br, div, h1, img, span, render\n\n\n# raw node renders to empty tag\nrender(br)\n# \u003cbr/\u003e\n\n# node with attributes but no children\nrender(\n    img({\"src\": \"/some-image.jpg\", \"alt\": \"a great picture\"})\n)\n# \u003cimg src=\"/some-image.jpg\" alt=\"a great picture\"/\u003e\n\n# nodes with children and (optional) attributes\nrender(\n    div(\n        h1({\"class\": \"neat-class\"}, \n        span(\"cool\"),\n        br)\n    )\n)\n# \u003cdiv\u003e\u003ch1 class=\"neat-class\"\u003e\u003cspan\u003ecool\u003c/span\u003e\u003cbr/\u003e\u003c/h1\u003e\u003c/div\u003e\n```\n### Strings and Things\nStrings, ints, floats, and Decimals are generally rendered as one would expect expect. For safety, `str`s are \nescaped by default; `SafeString`s can be used to bypass escaping.\n\n```python\nfrom simple_html import br, p, SafeString, render\n\n\nnode = p(\"Escaped \u0026 stuff\",\n         br,\n         SafeString(\"Not escaped \u0026 stuff\"))\n\nrender(node)  \n# \u003cp\u003eEscaped \u0026amp; stuff\u003cbr/\u003eNot escaped \u0026 stuff\u003c/p\u003e \n```\n\n### Attributes\n\nTag attributes are defined as simple dictionaries -- typically you'll just use strings for both keys and values. Note \nthat Tag attributes with `None` as the value will only render the attribute name:\n```python\nfrom simple_html import div, render\n\n\nnode = div({\"empty-str-attribute\": \"\", \n            \"key-only-attr\": None})\n\nrender(node)\n# \u003cdiv empty-str-attribute=\"\" key-only-attr\u003e\u003c/div\u003e\n```\n\nString attributes are escaped by default -- both keys and values. You can use `SafeString` to bypass, if needed.\n\n```python\nfrom simple_html import div, render, SafeString\n\n\nrender(\n    div({\"\u003cbad\u003e\":\"\u003c/also bad\u003e\"})\n)\n# \u003cdiv \u0026amp;lt;bad\u0026amp;gt;=\"\u0026amp;lt;/also bad\u0026amp;gt;\"\u003e\u003c/div\u003e\n\nrender(\n    div({SafeString(\"\u003cbad\u003e\"): SafeString(\"\u003c/also bad\u003e\")})\n)  \n# \u003cdiv \u003cbad\u003e=\"\u003c/also bad\u003e\"\u003e\u003c/div\u003e\n```\n\nYou can also use `int`, `float`, and `Decimal` instances for attribute values.\n```python\nfrom decimal import Decimal\nfrom simple_html import div, render, SafeString\n\n\nrender(\n    div({\"x\": 1, \"y\": 2.3, \"z\": Decimal('3.45')})    \n)\n# \u003cdiv x=\"1\" y=\"2.3\" z=\"3.45\"\u003e\u003c/div\u003e\n```\n\n### CSS\n\nYou can render inline CSS styles with `render_styles`:\n```python\nfrom simple_html import div, render, render_styles\n\n\nstyles = render_styles({\"min-width\": \"25px\"})\n\nnode = div({\"style\": styles}, \"cool\")\n\nrender(node)\n# \u003cdiv style=\"min-width:25px;\"\u003ecool\u003c/div\u003e\n\n\n# ints, floats, and Decimals are legal values\nstyles = render_styles({\"padding\": 0, \"flex-grow\": 0.6})\n\nnode = div({\"style\": styles}, \"wow\")\n\nrender(node)\n# \u003cdiv style=\"padding:0;flex-grow:0.6;\"\u003ewow\u003c/div\u003e\n```\n\n### Collections\nYou can pass many items as a `Tag`'s children using `*args`, lists or generators:\n```python\nfrom typing import Generator\nfrom simple_html import div, render, Node, br, p\n\n\ndiv(\n    *[\"neat\", br], p(\"cool\")\n)\n# renders to \u003cdiv\u003eneat\u003cbr/\u003e\u003cp\u003ecool\u003c/p\u003e\u003c/div\u003e\n\n\n# passing the raw list instead of *args \ndiv(\n    [\"neat\", br],\n    p(\"cool\")\n)\n# renders to \u003cdiv\u003eneat\u003cbr/\u003e\u003cp\u003ecool\u003c/p\u003e\u003c/div\u003e\n\n\ndef node_generator() -\u003e Generator[Node, None, None]:\n    yield \"neat\"\n    yield br \n\n\ndiv(node_generator(), p(\"cool\"))\n# renders to \u003cdiv\u003eneat\u003cbr/\u003e\u003cp\u003ecool\u003c/p\u003e\u003c/div\u003e\n```\n\n#### Custom Tags\n\nFor convenience, most common tags are provided, but you can also create your own:\n\n```python\nfrom simple_html import Tag, render\n\n\ncustom_elem = Tag(\"custom-elem\")\n\n# works the same as any other tag\nnode = custom_elem(\n    {\"id\": \"some-custom-elem-id\"},\n    \"Wow\"\n)\n\nrender(node)\n# \u003ccustom-elem id=\"some-custom-elem-id\"\u003eWow\u003c/custom-elem\u003e\n```\n\n### Optimization\n\n#### `prerender`\n\n`prerender` is a very simple function. It just `render`s a `Node` and puts the resulting string inside \na `SafeString` (so its contents won't be escaped again). It's most useful for prerendering at the module level, \nwhich ensures the render operation happens only once. A simple use case might be a website's footer:\n\n```python\nfrom simple_html import SafeString, prerender, footer, div, a, head, body, title, h1, html, render\n\n\nprerendered_footer: SafeString = prerender(\n    footer(\n        div(a({\"href\": \"/about\"}, \"About Us\")),\n        div(a({\"href\": \"/blog\"}, \"Blog\")),\n        div(a({\"href\": \"/contact\"}, \"Contact\"))\n    )\n)\n\n\ndef render_page(page_title: str) -\u003e str:\n    return render(\n        html(\n            head(title(page_title)),\n            body(\n                h1(page_title),\n                prerendered_footer  # this is extremely fast to render\n            )\n        )\n    )\n```\nThis greatly reduces the amount of work `render` needs to do on the prerendered content when outputting HTML.\n\n#### Caching\nYou may want to cache rendered content. This is easy to do; the main thing to keep in \nmind is you'll likely want to return a `SafeString`. For example, here's how you might cache with `lru_cache`:\n\n```python\nfrom simple_html import prerender, SafeString, h1\nfrom functools import lru_cache\n\n\n@lru_cache\ndef greeting(name: str) -\u003e SafeString:\n    return prerender(\n        h1(f\"Hello, {name}\")\n    )\n```\n\nOne thing to remember is that not all variants of `Node` are hashable, and thus cannot be passed directly to a function \nwhere the arguments constitute the cache key -- e.g. lists and generators are not hashable, but they can be \nvalid `Node`s. Another way to use `prerender` in combination with a caching function is to prerender arguments:\n\n```python\nfrom simple_html import prerender, SafeString, h1, div, html, body, head, ul, li\nfrom functools import lru_cache\n\n\n@lru_cache\ndef cached_content(children: SafeString) -\u003e SafeString:\n    return prerender(\n        div(\n            h1(\"This content is cached according to the content of the children\"),\n            children,\n            # presumably this function would have a lot more elements for it to be worth \n            # the caching overhead\n        )\n    )\n\ndef page(words_to_render: list[str]):\n    return html(\n        head,\n        body(\n            cached_content(\n                prerender(ul([\n                    li(word) for word in words_to_render \n                ]))\n            )\n        )\n    )\n```\nKeep in mind that using `prerender` on dynamic content -- not at the module level -- still incurs all the overhead\nof `render` each time that content is rendered, so, for this approach to make sense, the prerendered content should \nbe a small portion of the full content of the `cached_content` function. ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeithasaurus%2Fsimple_html","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeithasaurus%2Fsimple_html","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeithasaurus%2Fsimple_html/lists"}