From a61124cec59590cb57ea5e95b37ffea5c021d57b Mon Sep 17 00:00:00 2001 From: Andreas Pelme Date: Mon, 18 Mar 2024 22:20:50 +0100 Subject: [PATCH] Deployed 17a9fef with MkDocs version: 1.5.3 --- common-patterns/index.html | 101 +++++++++++++++++++++++-------------- search/search_index.json | 2 +- sitemap.xml.gz | Bin 127 -> 127 bytes 3 files changed, 63 insertions(+), 40 deletions(-) diff --git a/common-patterns/index.html b/common-patterns/index.html index 89cdd68..8ffcfc2 100644 --- a/common-patterns/index.html +++ b/common-patterns/index.html @@ -487,65 +487,74 @@

File/module structure

HTML.

Using a file named components.py can be a good idea. If you have many components, you may create a components package instead.

-

Your component functions can accept arbitrary argument with the required data:

-
views.py
from django.http import HttpResponse
+

Your component functions can accept arbitrary argument with the required data. +It is a good idea to only use keyword arguments (put a * in the argument list +to force keyword arguments):

+
views.py
from django.http import HttpRequest, HttpResponse
 
-from . import components
+from .components import greeting_page
 
-def greeting(request):
-    return HttpResponse(components.greeting(
+def greeting(request: HttpRequest) -> HttpResponse:
+    return HttpResponse(greeting_page(
         name=request.GET.get("name", "anonymous"),
     ))
 
-
components.py
def greeting(*, name):
-    return html[body[f"hi {name}!"]]
+
components.py
from htpy import html, body, h1
+
+def greeting_page(*, name: str) -> Element:
+    return html[body[h1[f"hi {name}!"]]]
 

Creating a base layout

A common feature of template languages is to "extend" a base/parent template and specify placeholders. This can be achieved with a base_layout function:

components.py
import datetime
 
-from htpy import body, div, h1, head, html, p, title
+from htpy import body, div, h1, head, html, p, title, Node, Element
 
 
-def base_layout(*, page_title=None, extra_head=None, content=None, body_class=None):
-    return html[
-        head[title[page_title], extra_head],
-        body(class_=body_class)[
-            content,
-            div("#footer")[f"Copyright {datetime.date.today().year} by Foo Inc."],
-        ],
-    ]
-
-
-def index_page():
-    return base_layout(
-        page_title="Welcome!",
-        body_class="green",
-        content=[
-            h1["Welcome to my site!"],
-            p["Hello and welcome!"],
-        ],
-    )
-
-
-def about_page():
-    return base_layout(
-        page_title="About us",
-        content=[
-            h1["About us"],
-            p["We love creating web sites!"],
-        ],
-    )
+def base_layout(*,
+    page_title: str | None = None,
+    extra_head: Node = None,
+    content: Node = None,
+    body_class: str | None = None,
+) -> Element:
+    return html[
+        head[title[page_title], extra_head],
+        body(class_=body_class)[
+            content,
+            div("#footer")[f"Copyright {datetime.date.today().year} by Foo Inc."],
+        ],
+    ]
+
+
+def index_page() -> Element:
+    return base_layout(
+        page_title="Welcome!",
+        body_class="green",
+        content=[
+            h1["Welcome to my site!"],
+            p["Hello and welcome!"],
+        ],
+    )
+
+
+def about_page() -> Element:
+    return base_layout(
+        page_title="About us",
+        content=[
+            h1["About us"],
+            p["We love creating web sites!"],
+        ],
+    )
 

UI components

Creating higher level wrappers for common UI components can be a good idea to reduce repitition.

Wrapping Bootstrap Modal could be achieved with a function like this:

Creating wrapper for Bootstrap Modal
from markupsafe import Markup
 
-from htpy import button, div, h5, span
+from htpy import Element, Node, button, div, h5, span
 
 
-def bootstrap_modal(*, title, body=None, footer=None):
+def bootstrap_modal(*, title: str, body: Node = None, footer: Node = None) -> Element:
     return div(".modal", tabindex="-1", role="dialog")[
         div(".modal-dialog", role="document")[
             div(".modal-content")[
@@ -566,6 +575,20 @@ 

UI components

] ]
+

You would then use it like this: +

from htpy import button, p
+
+print(
+    bootstrap_modal(
+        title="Modal title",
+        body=p["Modal body text goes here."],
+        footer=[
+            button(".btn.btn-primary", type="button")["Save changes"],
+            button(".btn.btn-secondary", type="button")["Close"],
+        ],
+    )
+)
+

diff --git a/search/search_index.json b/search/search_index.json index 5fd3e26..e916ae2 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":""},{"location":"#htpy-html-in-python","title":"htpy - HTML in Python","text":"

htpy is a library that makes writing HTML in Python fun and efficient, without the need for a template language.

  • Define HTML elements in Python...

    from htpy import html, body, h1, img\n\nis_cool = True\n\nprint(html[\n  body(class_={\"cool\": is_cool})[\n    h1(\"#hi\")[\"Welcome to htpy!\"],\n    img(src=\"cat.jpg\"),\n  ]\n])\n
  • ...and render it as HTML.

    <!doctype html>\n<html>\n  <body class=\"cool\">\n    <h1 id=\"hi\">Welcome to htpy!</h1>\n    <img src=\"cat.jpg\">\n  </body>\n</html>\n

"},{"location":"#introduction","title":"Introduction","text":"

At Personalkollen, where htpy was originally developed we often found ourselves hitting walls when using classic templates. htpy was created to improve the productiveness and experience of generating HTML from a Python backend.

"},{"location":"#key-features","title":"Key features","text":"
  • Leverage static types: - Use mypy or pyright to type check your code.

  • Great debugging: Avoid cryptic stack traces from templates. Use your favorite Python debugger.

  • Easy to extend: There is no special way to define template tags/filters. Just call regular functions.

  • Create reusable components: Define components, snippets, complex layouts/pages as regular Python variables or functions.

  • Familiar concepts from React: React helped make it popular writing HTML with a programming language. htpy uses a lot of similar constructs.

"},{"location":"common-patterns/","title":"Common patterns","text":"

htpy itself is a library that does not impose any particular structure for your code. You have the full power of Python functions, classes and modules at your disposal.

General programming practices on how to structure modules, functions and classes apply to HTML generation with htpy.

This page describes common scenarios and patterns that may help you structure your own project in a good way.

"},{"location":"common-patterns/#filemodule-structure","title":"File/module structure","text":"

It is generally a good idea to keep your HTML pages/components separate from HTTP request handling and \"business logic\".

In Django, this means that the view function should not directly generate the HTML.

Using a file named components.py can be a good idea. If you have many components, you may create a components package instead.

Your component functions can accept arbitrary argument with the required data:

views.py
from django.http import HttpResponse\n\nfrom . import components\n\ndef greeting(request):\n    return HttpResponse(components.greeting(\n        name=request.GET.get(\"name\", \"anonymous\"),\n    ))\n
components.py
def greeting(*, name):\n    return html[body[f\"hi {name}!\"]]\n
"},{"location":"common-patterns/#creating-a-base-layout","title":"Creating a base layout","text":"

A common feature of template languages is to \"extend\" a base/parent template and specify placeholders. This can be achieved with a base_layout function:

components.py
import datetime\n\nfrom htpy import body, div, h1, head, html, p, title\n\n\ndef base_layout(*, page_title=None, extra_head=None, content=None, body_class=None):\n    return html[\n        head[title[page_title], extra_head],\n        body(class_=body_class)[\n            content,\n            div(\"#footer\")[f\"Copyright {datetime.date.today().year} by Foo Inc.\"],\n        ],\n    ]\n\n\ndef index_page():\n    return base_layout(\n        page_title=\"Welcome!\",\n        body_class=\"green\",\n        content=[\n            h1[\"Welcome to my site!\"],\n            p[\"Hello and welcome!\"],\n        ],\n    )\n\n\ndef about_page():\n    return base_layout(\n        page_title=\"About us\",\n        content=[\n            h1[\"About us\"],\n            p[\"We love creating web sites!\"],\n        ],\n    )\n
"},{"location":"common-patterns/#ui-components","title":"UI components","text":"

Creating higher level wrappers for common UI components can be a good idea to reduce repitition.

Wrapping Bootstrap Modal could be achieved with a function like this:

Creating wrapper for Bootstrap Modal
from markupsafe import Markup\n\nfrom htpy import button, div, h5, span\n\n\ndef bootstrap_modal(*, title, body=None, footer=None):\n    return div(\".modal\", tabindex=\"-1\", role=\"dialog\")[\n        div(\".modal-dialog\", role=\"document\")[\n            div(\".modal-content\")[\n                div(\".modal-header\")[\n                    div(\".modal-title\")[\n                        h5(\".modal-title\")[title],\n                        button(\n                            \".close\",\n                            type=\"button\",\n                            data_dismiss=\"modal\",\n                            aria_label=\"Close\",\n                        )[span(aria_hidden=\"true\")[Markup(\"&times;\")]],\n                    ]\n                ],\n                div(\".modal-body\")[body],\n                footer and div(\".modal-footer\")[footer],\n            ]\n        ]\n    ]\n
"},{"location":"django/","title":"Usage with Django","text":"

htpy is not tied to any specific web framework. Nonetheless, htpy works great when combined with Django. This page contains information and useful techniques on how to combine htpy and Django.

"},{"location":"django/#returning-a-htpy-response","title":"Returning a htpy response","text":"

htpy elements can be passed directly to HttpResponse:

views.py
from django.http import HttpResponse\nfrom htpy import html, body, div\n\ndef my_view(request):\n    return HttpResponse(html[body[div[\"Hi Django!\"]]])\n
"},{"location":"django/#using-htpy-as-part-of-an-existing-django-template","title":"Using htpy as part of an existing Django template","text":"

htpy elements are marked as \"safe\" and can be injected directly into Django templates:

base.html
<html>\n    <head>\n        <title>My Django Site</title>\n    </head>\n    <body>{{ content }}</body>\n</html>\n
views.py
from django.shortcuts import render\n\nfrom htpy import h1\n\n\ndef index(request):\n    return render(request, \"base.html\", {\"content\": h1[\"Welcome to my site!\"]})\n
"},{"location":"django/#render-a-django-form-with-htpy","title":"Render a Django form with htpy","text":"

CSRF token, form widgets and errors can be directly used within htpy elements:

forms.py
from django import forms\n\n\nclass MyForm(forms.Form):\n    name = forms.CharField()\n
views.py
from django.http import HttpResponse\n\nfrom . import components\nfrom .forms import MyForm\n\n\ndef my_form(request):\n    form = MyForm(request.POST or None)\n    if form.is_valid():\n        return HttpResponse(components.my_form_success())\n\n    return HttpResponse(components.my_form(request, form))\n
components.py
from django.template.backends.utils import csrf_input\n\nfrom htpy import body, button, form, h1, head, html, title\n\n\ndef base(page_title, content):\n    return html[head[title[page_title]], body[content]]\n\n\ndef my_form(request, my_form):\n    return base(\n        \"My form\",\n        form(method=\"post\")[\n            csrf_input(request),\n            my_form.errors,\n            my_form[\"name\"],\n            button[\"Submit!\"],\n        ],\n    )\n\n\ndef my_form_success():\n    return base(\n        \"Success!\",\n        h1[\"Success! The form was valid!\"],\n    )\n
"},{"location":"django/#implement-custom-form-widgets-with-htpy","title":"Implement custom form widgets with htpy","text":"

You can implement a custom form widget directly with htpy like this:

widgets.py
from django.forms import widgets\n\nfrom htpy import sl_input\n\n\nclass ShoelaceInput(widgets.Widget):\n    \"\"\"\n    A form widget using Shoelace's <sl-input> element.\n    More info: https://shoelace.style/components/input\n    \"\"\"\n\n    def render(self, name, value, attrs=None, renderer=None):\n        return str(sl_input(attrs, name=name, value=value))\n
"},{"location":"faq/","title":"FAQ","text":""},{"location":"faq/#how-does-htpy-performance-compare-to-django-or-jinja-templates","title":"How does htpy performance compare to Django or Jinja templates?","text":"

The performance of HTML rendering is rarely the bottleneck in most web application. It is usually fast enough regardless of what method of constructing the HTML is being used.

Given that it has been fast enough, there has not been much effort in optimizing htpy. It should be possible to significantly increase the effectiveness and we are open to contributions with benchmarks and speed improvements.

That said, htpy is currently on par with Django templates when it comes to speed. Jinja2 is currently significantly faster than both Django templates and htpy. There is a small benchmark script in the repo that generates a table with 50 000 rows.

"},{"location":"faq/#can-htpy-generate-xmlxhtml","title":"Can htpy generate XML/XHTML?","text":"

No. Generating XML/XHTML is out of scope for this project. Use a XML library if you are looking to generate XML.

htpy generates HTML, therefore \"void elements\" such as <br> does not include a trailing /.

"},{"location":"faq/#does-not-generating-html-from-python-mean-mixing-concerns-between-presentation-and-business-logic","title":"Does not generating HTML from Python mean mixing concerns between presentation and business logic?","text":"

With a template language, create HTML markup in separate files is enforced by design. Avoiding logic in the presentation layer is also mostly done by making the language very restrictive.

It takes a little bit of planning and effort, but it is possible to have a nicely separated presentation layer that is free from logic. See Common patterns for more details on how you can structure your project.

"},{"location":"faq/#what-kind-of-black-magic-makes-from-htpy-import-whatever_element-work","title":"What kind of black magic makes from htpy import whatever_element work?","text":"

htpy uses the module level __getattr__. It was introduced in Python 3.7. It allows creating Element instances for any elements that are imported.

"},{"location":"faq/#why-does-htpy-not-provide-html-like-tag-syntax-with-angle-brackets-like-pyxl-and-jsx","title":"Why does htpy not provide HTML like tag syntax with angle brackets like pyxl and JSX?","text":"

htpy must be compatible with standard Python code formatters, editors and static type checkers. Unfortunately, it is not possible to support those workflows with a custom syntax without a massive effort to change those tools to support that syntax.

"},{"location":"references/","title":"References","text":"

htpy was heavily inspired by many other libraries and articles. This page lists some of them.

"},{"location":"references/#similar-libraries-and-tools","title":"Similar libraries and tools","text":"
  • JSX/React - Made writing HTML in a programming language popular.
  • pyxl, pyxl3, pyxl4 - Write HTML in Python with JSX-like syntax. Not actively maintained.
  • htbuilder - Very similar to htpy but does not currently support automatic escaping.
  • breve - An early implementation of HTML in Python. Using getattr [] syntax for children. Not actively maintained.
  • hyperscript - JavaScript library that also uses CSS selector-like syntax for specifying id and classes.
  • hyperpython - A Python interpretation of hyperscript. Not actively maintained.
  • h by Adam Johnson - Similar to htpy, uses call syntax (()) for attributes and getitem ([]) for children.
"},{"location":"references/#articles-about-html-generation-without-templates","title":"Articles about HTML generation without templates","text":"
  • Jeff Atwood - You're Doing It Wrong - Stack Overflow co-founder Jeff Atwood
  • Tavis Rudd - Throw out your templates - Tavis Rudd, creator of Python template language \"Cheetah\" argues for creating HTML without templates.
  • David Ford - 80% of my coding is doing this (or why templates are dead) - Discusses various techniques for rendering HTML.
"},{"location":"usage/","title":"Usage","text":"

Elements are imported directly from the htpy module as their name. HTML attributes are specified by parenthesis (() / \"call\"). Children are specified using square brackets ([] / \"getitem\").

>>> from htpy import div\n>>> print(div(id=\"hi\")[\"Hello!\"])\n<div id=\"hi\">Hello!</div>\n
"},{"location":"usage/#elements","title":"Elements","text":"

Children can be strings, markup, other elements or lists/iterators.

Elements can be arbitrarily nested: Nested elements

>>> from htpy import article, section, p\n>>> print(section[article[p[\"Lorem ipsum\"]]])\n<section><article><p>Lorem ipsum</p></article></section>\n

"},{"location":"usage/#textstrings","title":"Text/strings","text":"

It is possible to pass a string directly: Using a string as children

>>> from htpy import h1\n>>> print(h1[\"Welcome to my site!\"])\n<h1>Welcome to my site!</h1>\n

Strings are automatically escaped to avoid XSS vulnerabilities. It is convenient and safe to directly insert variable data via f-strings:

>>> from htpy import h1\n>>> user_supplied_name = \"bobby </h1>\"\n>>> print(h1[f\"hello {user_supplied_name}\"])\n<h1>hello bobby &lt;/h1&gt;</h1>\n
"},{"location":"usage/#conditional-rendering","title":"Conditional rendering","text":"

None and False will not render anything. This can be useful to conditionally render some content.

Conditional rendering
>>> from htpy import div, b\n>>> error = None\n\n>>> # No <b> tag will be rendered since error is None\n>>> print(div[error and b[error]])\n<div></div>\n\n>>> error = 'Enter a valid email address.'\n>>> print(div[error and b[error]])\n<div><b>Enter a valid email address.</b></div>\n\n# Inline if/else can also be used:\n>>> print(div[b[error] if error else None])\n<div><b>Enter a valid email address.</b></div>\n
"},{"location":"usage/#loops-iterating-over-children","title":"Loops / iterating over children","text":"

You can pass a list, tuple or generator to generate multiple children:

Iterate over a generator
>>> from htpy import ul, li\n>>> print(ul[(li[letter] for letter in \"abc\")])\n<ul><li>a</li><li>b</li><li>c</li></ul>\n

A list can be used similar to a JSX fragment:

Render a list of child elements
>>> from htpy import div, img\n>>> my_images = [img(src=\"a.jpg\"), img(src=\"b.jpg\")]\n>>> print(div[my_images])\n<div><img src=\"a.jpg\"><img src=\"b.jpg\"></div>\n
"},{"location":"usage/#custom-elements-web-components","title":"Custom elements / web components","text":"

Custom elements / web components are HTML elements that contains at least one dash (-). Since - cannot be used in Python identifiers, use underscore (_) instead:

Using custom elements
>>> from htpy import my_custom_element\n>>> print(my_custom_element['hi!'])\n<my-custom-element>hi!</my-custom-element>\n
"},{"location":"usage/#injecting-markup","title":"Injecting markup","text":"

If you have HTML markup that you want to insert without further escaping, wrap it in Markup from the markupsafe library. markupsafe is a dependency of htpy and is automatically installed:

Injecting markup
>>> from htpy import div\n>>> from markupsafe import Markup\n>>> print(div[Markup(\"<foo></foo>\")])\n<div><foo></foo></div>\n

If you are generate Markdown and want to insert it into an element, use Markup: Injecting generated markdown

>>> from markdown import markdown\n>>> from markupsafe import Markup\n>>> from htpy import div\n>>> print(div[Markup(markdown('# Hi'))])\n<div><h1>Hi</h1></div>\n

"},{"location":"usage/#html-doctype","title":"HTML Doctype","text":"

The HTML doctype is automatically prepended to the <html> tag:

>>> from htpy import html\n>>> print(html)\n<!doctype html><html></html>\n
"},{"location":"usage/#attributes","title":"Attributes","text":"

HTML attributes are defined by calling the element. They can be specified in a couple of different ways.

"},{"location":"usage/#elements-without-attributes","title":"Elements without attributes","text":"

Some elements do not have attributes, they can be specified by just the element itself:

>>> from htpy import hr\n>>> print(hr)\n<hr>\n
"},{"location":"usage/#keyword-arguments","title":"Keyword arguments","text":"

Attributes can be specified via keyword arguments:

>>> from htpy import img\n>>> print(img(src=\"picture.jpg\"))\n<img src=\"picture.jpg\">\n

In Python, class and for cannot be used as keyword arguments. Instead, they can be specified as class_ or for_ when using keyword arguments:

>>> from htpy import label\n>>> print(label(for_=\"myfield\"))\n<label for=\"myfield\"></label>\n

Attributes that contains dashes - can be specified using underscores:

>>> from htpy import form\n>>> print(form(hx_post=\"/foo\"))\n<form hx-post=\"/foo\"></form>\n

"},{"location":"usage/#idclass-shorthand","title":"id/class shorthand","text":"

Defining id and class attributes is common when writing HTML. A string shorthand that looks like a CSS selector can be used to quickly define id and classes:

Define id
>>> from htpy import div\n>>> print(div(\"#myid\"))\n<div id=\"myid\"></div>\n
Define multiple classes
>>> from htpy import div\n>>> print(div(\".foo.bar\"))\n<div id=\"foo bar\"></div>\n
Combining both id and classes
>>> from htpy import div\n>>> print(div(\"#myid.foo.bar\"))\n<div id=\"myid\" class=\"foo bar\"></div>\n
"},{"location":"usage/#attributes-as-dict","title":"Attributes as dict","text":"

Attributes can also be specified as a dict. This is useful when using attributes that are reserved Python keywords (like for or class), when the attribute name contains a dash (-) or when you want to define attributes dynamically.

Using an attribute with a dash
>>> from htpy import div\n>>> print(div({\"data-foo\": \"bar\"}))\n<div data-foo=\"bar\"></div>\n
Using an attribute with a reserved keyword
>>> from htpy import label\n>>> print(label({\"for\": \"myfield\"}))\n<label for=\"myfield\"></label>\n
"},{"location":"usage/#boolean-attributes","title":"Boolean attributes","text":"

In HTML, boolean attributes such as disabled are considered \"true\" when they exist. Specifying an attribute as True will make it appear (without a value). False will make it hidden. This is useful and brings the semantics of bool to HTML.

True bool attribute
>>> from htpy import button\n>>> print(button(disabled=True))\n<button disabled></button>\n
False bool attribute
>>> from htpy import button\n>>> print(button(disabled=False))\n<button></button>\n
"},{"location":"usage/#conditionally-mixing-css-classes","title":"Conditionally mixing CSS classes","text":"

To make it easier to mix CSS classes, the class attribute accepts a list of class names or a dict. Falsey values will be ignored.

>>> from htpy import button\n>>> is_primary = True\n>>> print(button(class_=[\"btn\", {\"btn-primary\": is_primary}]))\n<button class=\"btn btn-primary\"></button>\n>>> is_primary = False\n>>> print(button(class_=[\"btn\", {\"btn-primary\": is_primary}]))\n<button class=\"btn\"></button>\n>>>\n
"},{"location":"usage/#combining-modes","title":"Combining modes","text":"

Attributes via id/class shorthand, keyword arguments and dictionary can be combined:

Specifying attribute via multiple arguments
>>> from htyp import label\n>>> print(label(\"#myid.foo.bar\", {'for': \"somefield\"}, name=\"myname\",))\n<label id=\"myid\" class=\"foo bar\" for=\"somefield\" name=\"myname\"></label>\n
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":""},{"location":"#htpy-html-in-python","title":"htpy - HTML in Python","text":"

htpy is a library that makes writing HTML in Python fun and efficient, without the need for a template language.

  • Define HTML elements in Python...

    from htpy import html, body, h1, img\n\nis_cool = True\n\nprint(html[\n  body(class_={\"cool\": is_cool})[\n    h1(\"#hi\")[\"Welcome to htpy!\"],\n    img(src=\"cat.jpg\"),\n  ]\n])\n
  • ...and render it as HTML.

    <!doctype html>\n<html>\n  <body class=\"cool\">\n    <h1 id=\"hi\">Welcome to htpy!</h1>\n    <img src=\"cat.jpg\">\n  </body>\n</html>\n

"},{"location":"#introduction","title":"Introduction","text":"

At Personalkollen, where htpy was originally developed we often found ourselves hitting walls when using classic templates. htpy was created to improve the productiveness and experience of generating HTML from a Python backend.

"},{"location":"#key-features","title":"Key features","text":"
  • Leverage static types: - Use mypy or pyright to type check your code.

  • Great debugging: Avoid cryptic stack traces from templates. Use your favorite Python debugger.

  • Easy to extend: There is no special way to define template tags/filters. Just call regular functions.

  • Create reusable components: Define components, snippets, complex layouts/pages as regular Python variables or functions.

  • Familiar concepts from React: React helped make it popular writing HTML with a programming language. htpy uses a lot of similar constructs.

"},{"location":"common-patterns/","title":"Common patterns","text":"

htpy itself is a library that does not impose any particular structure for your code. You have the full power of Python functions, classes and modules at your disposal.

General programming practices on how to structure modules, functions and classes apply to HTML generation with htpy.

This page describes common scenarios and patterns that may help you structure your own project in a good way.

"},{"location":"common-patterns/#filemodule-structure","title":"File/module structure","text":"

It is generally a good idea to keep your HTML pages/components separate from HTTP request handling and \"business logic\".

In Django, this means that the view function should not directly generate the HTML.

Using a file named components.py can be a good idea. If you have many components, you may create a components package instead.

Your component functions can accept arbitrary argument with the required data. It is a good idea to only use keyword arguments (put a * in the argument list to force keyword arguments):

views.py
from django.http import HttpRequest, HttpResponse\n\nfrom .components import greeting_page\n\ndef greeting(request: HttpRequest) -> HttpResponse:\n    return HttpResponse(greeting_page(\n        name=request.GET.get(\"name\", \"anonymous\"),\n    ))\n
components.py
from htpy import html, body, h1\n\ndef greeting_page(*, name: str) -> Element:\n    return html[body[h1[f\"hi {name}!\"]]]\n
"},{"location":"common-patterns/#creating-a-base-layout","title":"Creating a base layout","text":"

A common feature of template languages is to \"extend\" a base/parent template and specify placeholders. This can be achieved with a base_layout function:

components.py
import datetime\n\nfrom htpy import body, div, h1, head, html, p, title, Node, Element\n\n\ndef base_layout(*,\n    page_title: str | None = None,\n    extra_head: Node = None,\n    content: Node = None,\n    body_class: str | None = None,\n) -> Element:\n    return html[\n        head[title[page_title], extra_head],\n        body(class_=body_class)[\n            content,\n            div(\"#footer\")[f\"Copyright {datetime.date.today().year} by Foo Inc.\"],\n        ],\n    ]\n\n\ndef index_page() -> Element:\n    return base_layout(\n        page_title=\"Welcome!\",\n        body_class=\"green\",\n        content=[\n            h1[\"Welcome to my site!\"],\n            p[\"Hello and welcome!\"],\n        ],\n    )\n\n\ndef about_page() -> Element:\n    return base_layout(\n        page_title=\"About us\",\n        content=[\n            h1[\"About us\"],\n            p[\"We love creating web sites!\"],\n        ],\n    )\n
"},{"location":"common-patterns/#ui-components","title":"UI components","text":"

Creating higher level wrappers for common UI components can be a good idea to reduce repitition.

Wrapping Bootstrap Modal could be achieved with a function like this:

Creating wrapper for Bootstrap Modal
from markupsafe import Markup\n\nfrom htpy import Element, Node, button, div, h5, span\n\n\ndef bootstrap_modal(*, title: str, body: Node = None, footer: Node = None) -> Element:\n    return div(\".modal\", tabindex=\"-1\", role=\"dialog\")[\n        div(\".modal-dialog\", role=\"document\")[\n            div(\".modal-content\")[\n                div(\".modal-header\")[\n                    div(\".modal-title\")[\n                        h5(\".modal-title\")[title],\n                        button(\n                            \".close\",\n                            type=\"button\",\n                            data_dismiss=\"modal\",\n                            aria_label=\"Close\",\n                        )[span(aria_hidden=\"true\")[Markup(\"&times;\")]],\n                    ]\n                ],\n                div(\".modal-body\")[body],\n                footer and div(\".modal-footer\")[footer],\n            ]\n        ]\n    ]\n

You would then use it like this:

from htpy import button, p\n\nprint(\n    bootstrap_modal(\n        title=\"Modal title\",\n        body=p[\"Modal body text goes here.\"],\n        footer=[\n            button(\".btn.btn-primary\", type=\"button\")[\"Save changes\"],\n            button(\".btn.btn-secondary\", type=\"button\")[\"Close\"],\n        ],\n    )\n)\n

"},{"location":"django/","title":"Usage with Django","text":"

htpy is not tied to any specific web framework. Nonetheless, htpy works great when combined with Django. This page contains information and useful techniques on how to combine htpy and Django.

"},{"location":"django/#returning-a-htpy-response","title":"Returning a htpy response","text":"

htpy elements can be passed directly to HttpResponse:

views.py
from django.http import HttpResponse\nfrom htpy import html, body, div\n\ndef my_view(request):\n    return HttpResponse(html[body[div[\"Hi Django!\"]]])\n
"},{"location":"django/#using-htpy-as-part-of-an-existing-django-template","title":"Using htpy as part of an existing Django template","text":"

htpy elements are marked as \"safe\" and can be injected directly into Django templates:

base.html
<html>\n    <head>\n        <title>My Django Site</title>\n    </head>\n    <body>{{ content }}</body>\n</html>\n
views.py
from django.shortcuts import render\n\nfrom htpy import h1\n\n\ndef index(request):\n    return render(request, \"base.html\", {\"content\": h1[\"Welcome to my site!\"]})\n
"},{"location":"django/#render-a-django-form-with-htpy","title":"Render a Django form with htpy","text":"

CSRF token, form widgets and errors can be directly used within htpy elements:

forms.py
from django import forms\n\n\nclass MyForm(forms.Form):\n    name = forms.CharField()\n
views.py
from django.http import HttpResponse\n\nfrom . import components\nfrom .forms import MyForm\n\n\ndef my_form(request):\n    form = MyForm(request.POST or None)\n    if form.is_valid():\n        return HttpResponse(components.my_form_success())\n\n    return HttpResponse(components.my_form(request, form))\n
components.py
from django.template.backends.utils import csrf_input\n\nfrom htpy import body, button, form, h1, head, html, title\n\n\ndef base(page_title, content):\n    return html[head[title[page_title]], body[content]]\n\n\ndef my_form(request, my_form):\n    return base(\n        \"My form\",\n        form(method=\"post\")[\n            csrf_input(request),\n            my_form.errors,\n            my_form[\"name\"],\n            button[\"Submit!\"],\n        ],\n    )\n\n\ndef my_form_success():\n    return base(\n        \"Success!\",\n        h1[\"Success! The form was valid!\"],\n    )\n
"},{"location":"django/#implement-custom-form-widgets-with-htpy","title":"Implement custom form widgets with htpy","text":"

You can implement a custom form widget directly with htpy like this:

widgets.py
from django.forms import widgets\n\nfrom htpy import sl_input\n\n\nclass ShoelaceInput(widgets.Widget):\n    \"\"\"\n    A form widget using Shoelace's <sl-input> element.\n    More info: https://shoelace.style/components/input\n    \"\"\"\n\n    def render(self, name, value, attrs=None, renderer=None):\n        return str(sl_input(attrs, name=name, value=value))\n
"},{"location":"faq/","title":"FAQ","text":""},{"location":"faq/#how-does-htpy-performance-compare-to-django-or-jinja-templates","title":"How does htpy performance compare to Django or Jinja templates?","text":"

The performance of HTML rendering is rarely the bottleneck in most web application. It is usually fast enough regardless of what method of constructing the HTML is being used.

Given that it has been fast enough, there has not been much effort in optimizing htpy. It should be possible to significantly increase the effectiveness and we are open to contributions with benchmarks and speed improvements.

That said, htpy is currently on par with Django templates when it comes to speed. Jinja2 is currently significantly faster than both Django templates and htpy. There is a small benchmark script in the repo that generates a table with 50 000 rows.

"},{"location":"faq/#can-htpy-generate-xmlxhtml","title":"Can htpy generate XML/XHTML?","text":"

No. Generating XML/XHTML is out of scope for this project. Use a XML library if you are looking to generate XML.

htpy generates HTML, therefore \"void elements\" such as <br> does not include a trailing /.

"},{"location":"faq/#does-not-generating-html-from-python-mean-mixing-concerns-between-presentation-and-business-logic","title":"Does not generating HTML from Python mean mixing concerns between presentation and business logic?","text":"

With a template language, create HTML markup in separate files is enforced by design. Avoiding logic in the presentation layer is also mostly done by making the language very restrictive.

It takes a little bit of planning and effort, but it is possible to have a nicely separated presentation layer that is free from logic. See Common patterns for more details on how you can structure your project.

"},{"location":"faq/#what-kind-of-black-magic-makes-from-htpy-import-whatever_element-work","title":"What kind of black magic makes from htpy import whatever_element work?","text":"

htpy uses the module level __getattr__. It was introduced in Python 3.7. It allows creating Element instances for any elements that are imported.

"},{"location":"faq/#why-does-htpy-not-provide-html-like-tag-syntax-with-angle-brackets-like-pyxl-and-jsx","title":"Why does htpy not provide HTML like tag syntax with angle brackets like pyxl and JSX?","text":"

htpy must be compatible with standard Python code formatters, editors and static type checkers. Unfortunately, it is not possible to support those workflows with a custom syntax without a massive effort to change those tools to support that syntax.

"},{"location":"references/","title":"References","text":"

htpy was heavily inspired by many other libraries and articles. This page lists some of them.

"},{"location":"references/#similar-libraries-and-tools","title":"Similar libraries and tools","text":"
  • JSX/React - Made writing HTML in a programming language popular.
  • pyxl, pyxl3, pyxl4 - Write HTML in Python with JSX-like syntax. Not actively maintained.
  • htbuilder - Very similar to htpy but does not currently support automatic escaping.
  • breve - An early implementation of HTML in Python. Using getattr [] syntax for children. Not actively maintained.
  • hyperscript - JavaScript library that also uses CSS selector-like syntax for specifying id and classes.
  • hyperpython - A Python interpretation of hyperscript. Not actively maintained.
  • h by Adam Johnson - Similar to htpy, uses call syntax (()) for attributes and getitem ([]) for children.
"},{"location":"references/#articles-about-html-generation-without-templates","title":"Articles about HTML generation without templates","text":"
  • Jeff Atwood - You're Doing It Wrong - Stack Overflow co-founder Jeff Atwood
  • Tavis Rudd - Throw out your templates - Tavis Rudd, creator of Python template language \"Cheetah\" argues for creating HTML without templates.
  • David Ford - 80% of my coding is doing this (or why templates are dead) - Discusses various techniques for rendering HTML.
"},{"location":"usage/","title":"Usage","text":"

Elements are imported directly from the htpy module as their name. HTML attributes are specified by parenthesis (() / \"call\"). Children are specified using square brackets ([] / \"getitem\").

>>> from htpy import div\n>>> print(div(id=\"hi\")[\"Hello!\"])\n<div id=\"hi\">Hello!</div>\n
"},{"location":"usage/#elements","title":"Elements","text":"

Children can be strings, markup, other elements or lists/iterators.

Elements can be arbitrarily nested: Nested elements

>>> from htpy import article, section, p\n>>> print(section[article[p[\"Lorem ipsum\"]]])\n<section><article><p>Lorem ipsum</p></article></section>\n

"},{"location":"usage/#textstrings","title":"Text/strings","text":"

It is possible to pass a string directly: Using a string as children

>>> from htpy import h1\n>>> print(h1[\"Welcome to my site!\"])\n<h1>Welcome to my site!</h1>\n

Strings are automatically escaped to avoid XSS vulnerabilities. It is convenient and safe to directly insert variable data via f-strings:

>>> from htpy import h1\n>>> user_supplied_name = \"bobby </h1>\"\n>>> print(h1[f\"hello {user_supplied_name}\"])\n<h1>hello bobby &lt;/h1&gt;</h1>\n
"},{"location":"usage/#conditional-rendering","title":"Conditional rendering","text":"

None and False will not render anything. This can be useful to conditionally render some content.

Conditional rendering
>>> from htpy import div, b\n>>> error = None\n\n>>> # No <b> tag will be rendered since error is None\n>>> print(div[error and b[error]])\n<div></div>\n\n>>> error = 'Enter a valid email address.'\n>>> print(div[error and b[error]])\n<div><b>Enter a valid email address.</b></div>\n\n# Inline if/else can also be used:\n>>> print(div[b[error] if error else None])\n<div><b>Enter a valid email address.</b></div>\n
"},{"location":"usage/#loops-iterating-over-children","title":"Loops / iterating over children","text":"

You can pass a list, tuple or generator to generate multiple children:

Iterate over a generator
>>> from htpy import ul, li\n>>> print(ul[(li[letter] for letter in \"abc\")])\n<ul><li>a</li><li>b</li><li>c</li></ul>\n

A list can be used similar to a JSX fragment:

Render a list of child elements
>>> from htpy import div, img\n>>> my_images = [img(src=\"a.jpg\"), img(src=\"b.jpg\")]\n>>> print(div[my_images])\n<div><img src=\"a.jpg\"><img src=\"b.jpg\"></div>\n
"},{"location":"usage/#custom-elements-web-components","title":"Custom elements / web components","text":"

Custom elements / web components are HTML elements that contains at least one dash (-). Since - cannot be used in Python identifiers, use underscore (_) instead:

Using custom elements
>>> from htpy import my_custom_element\n>>> print(my_custom_element['hi!'])\n<my-custom-element>hi!</my-custom-element>\n
"},{"location":"usage/#injecting-markup","title":"Injecting markup","text":"

If you have HTML markup that you want to insert without further escaping, wrap it in Markup from the markupsafe library. markupsafe is a dependency of htpy and is automatically installed:

Injecting markup
>>> from htpy import div\n>>> from markupsafe import Markup\n>>> print(div[Markup(\"<foo></foo>\")])\n<div><foo></foo></div>\n

If you are generate Markdown and want to insert it into an element, use Markup: Injecting generated markdown

>>> from markdown import markdown\n>>> from markupsafe import Markup\n>>> from htpy import div\n>>> print(div[Markup(markdown('# Hi'))])\n<div><h1>Hi</h1></div>\n

"},{"location":"usage/#html-doctype","title":"HTML Doctype","text":"

The HTML doctype is automatically prepended to the <html> tag:

>>> from htpy import html\n>>> print(html)\n<!doctype html><html></html>\n
"},{"location":"usage/#attributes","title":"Attributes","text":"

HTML attributes are defined by calling the element. They can be specified in a couple of different ways.

"},{"location":"usage/#elements-without-attributes","title":"Elements without attributes","text":"

Some elements do not have attributes, they can be specified by just the element itself:

>>> from htpy import hr\n>>> print(hr)\n<hr>\n
"},{"location":"usage/#keyword-arguments","title":"Keyword arguments","text":"

Attributes can be specified via keyword arguments:

>>> from htpy import img\n>>> print(img(src=\"picture.jpg\"))\n<img src=\"picture.jpg\">\n

In Python, class and for cannot be used as keyword arguments. Instead, they can be specified as class_ or for_ when using keyword arguments:

>>> from htpy import label\n>>> print(label(for_=\"myfield\"))\n<label for=\"myfield\"></label>\n

Attributes that contains dashes - can be specified using underscores:

>>> from htpy import form\n>>> print(form(hx_post=\"/foo\"))\n<form hx-post=\"/foo\"></form>\n

"},{"location":"usage/#idclass-shorthand","title":"id/class shorthand","text":"

Defining id and class attributes is common when writing HTML. A string shorthand that looks like a CSS selector can be used to quickly define id and classes:

Define id
>>> from htpy import div\n>>> print(div(\"#myid\"))\n<div id=\"myid\"></div>\n
Define multiple classes
>>> from htpy import div\n>>> print(div(\".foo.bar\"))\n<div id=\"foo bar\"></div>\n
Combining both id and classes
>>> from htpy import div\n>>> print(div(\"#myid.foo.bar\"))\n<div id=\"myid\" class=\"foo bar\"></div>\n
"},{"location":"usage/#attributes-as-dict","title":"Attributes as dict","text":"

Attributes can also be specified as a dict. This is useful when using attributes that are reserved Python keywords (like for or class), when the attribute name contains a dash (-) or when you want to define attributes dynamically.

Using an attribute with a dash
>>> from htpy import div\n>>> print(div({\"data-foo\": \"bar\"}))\n<div data-foo=\"bar\"></div>\n
Using an attribute with a reserved keyword
>>> from htpy import label\n>>> print(label({\"for\": \"myfield\"}))\n<label for=\"myfield\"></label>\n
"},{"location":"usage/#boolean-attributes","title":"Boolean attributes","text":"

In HTML, boolean attributes such as disabled are considered \"true\" when they exist. Specifying an attribute as True will make it appear (without a value). False will make it hidden. This is useful and brings the semantics of bool to HTML.

True bool attribute
>>> from htpy import button\n>>> print(button(disabled=True))\n<button disabled></button>\n
False bool attribute
>>> from htpy import button\n>>> print(button(disabled=False))\n<button></button>\n
"},{"location":"usage/#conditionally-mixing-css-classes","title":"Conditionally mixing CSS classes","text":"

To make it easier to mix CSS classes, the class attribute accepts a list of class names or a dict. Falsey values will be ignored.

>>> from htpy import button\n>>> is_primary = True\n>>> print(button(class_=[\"btn\", {\"btn-primary\": is_primary}]))\n<button class=\"btn btn-primary\"></button>\n>>> is_primary = False\n>>> print(button(class_=[\"btn\", {\"btn-primary\": is_primary}]))\n<button class=\"btn\"></button>\n>>>\n
"},{"location":"usage/#combining-modes","title":"Combining modes","text":"

Attributes via id/class shorthand, keyword arguments and dictionary can be combined:

Specifying attribute via multiple arguments
>>> from htyp import label\n>>> print(label(\"#myid.foo.bar\", {'for': \"somefield\"}, name=\"myname\",))\n<label id=\"myid\" class=\"foo bar\" for=\"somefield\" name=\"myname\"></label>\n
"}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 90369468c471439febb9141492f2c6538008f186..cdbc5d4865f778c11fa5114fb2f508054f3e97e4 100644 GIT binary patch delta 13 Ucmb=gXP58h;4s?oV