Skip to main content

An html templating library for Python

Project description

Annandale

Welcome to the Annandale HTML Components library. I created this library because I needed a way of distributing HTML templates across a number of users of the Anacostia Pipeline. I love Jinja2 templates but I needed a more user-friendly approach to distribute these templates to users of Anacostia.

What I used to do was I packaged the Jinja2 templates into the Anacostia Pipeline package as package_data and then publish the package onto PyPI. From there, users of Anacostia would have to use the pkg_resources library to extract the templates from the package and then create their own child templates. The architecture and development philosophy of Anactostia emphasized enabling developers to build their own Anacostia nodes and the custom frontend for their nodes. Enabling this development philosophy would require a server-side rendering approach where developers would create a FastAPI sub-application that would interact with their node(s), render the information of the nodes in an html fragment, and then the html fragment would be inserted into the DOM via htmx. This approach was promising but FastAPI presented a problem: only one directory can be defined for the main application, thus, users simply could not just create their own templates directory and then mount their templates directory into their sub-application which would then be recognized by the main application. Therefore, a more user-friendly approach was needed.

The solution I decided upon was to create a library to render HTML fragments using only python which would not require the use of Jinja2 nor any templating directories that needed to be mounted into the FastAPI sub-application. Distribution of the fragments could be done by packaging the code for the fragments inside the Anacostia Pipeline package where the user could simply import the fragments into their python development environment. From there, the FastAPI sub-applications could simply render the templates during runtime and return the html snippets as the response to htmx requests.

###The Annandale Library: There are only three important files (i.e., modules) in this library: #####base.py There is not much to this library. The Component class in base.py is the base class that is responsible for constructing the tag for the element. The Component class simply takes two arguments children (a list of child components) and attributes (a dictionary of HTML attributes to put on the element). The Component simply recursively calls the str() function to render all of its child components and then places the attributes into the element.

#####default.py A library of all of the basic HTML5 elements codified as components.

#####utils.py A library that provides functionalities such as formatting the rendered HTML into a more aethetically pleasing format.

###Example Usage: This is how you can use Annandale to create your own HTML components:

class CustomComponent(Component):
    def __init__(
        self, 
        child1: Union[str, 'Component', List['Component']], 
        child2: Union[str, 'Component', List['Component']], 
        properties: Dict[str, str] = {}
    ) -> None:
        super().__init__([
            div([
                p("Hello, World 1!"),
                child1
            ], {"class": "container"}),
            div([
                p("Hello, World 2!"),
                child2
            ], {"class": "container"})
        ], properties=properties)

    def __str__(self) -> str:
        return super().__str__() 

if __name__ == "__main__":
    component = html([
        head([
            title("Hello, World!"),
            meta({"charset": "UTF-8", "name": "viewport", "content": "width=device-width, initial-scale=1.0"}),
            link({"rel": "stylesheet", "href": "style.css"}),
            script("", {"src": "script.js"})
        ]),
        body([
            CustomComponent(
                p("Hello, World 3!"), 
                p("Hello, World 4!"), 
                {"id": "custom-component1"}
            ),
            CustomComponent(
                p("Hello, World 5!"), 
                p("Hello, World 6!"), 
                {"id": "custom-component2"}
            ),
            script("console.log('Hello, World!');")
        ])
    ])

    print(format_html(component))

Output HTML:

<!DOCTYPE html>
<html>
 <head>
  <title>
   Hello, World!
  </title>
  <meta charset="utf-8" content="width=device-width, initial-scale=1.0" name="viewport"/>
  <link href="style.css" rel="stylesheet"/>
  <script src="script.js">
  </script>
 </head>
 <body>
  <customcomponent id="custom-component1">
   <div class="container">
    <p>
     Hello, World 1!
    </p>
    <p>
     Hello, World 3!
    </p>
   </div>
   <div class="container">
    <p>
     Hello, World 2!
    </p>
    <p>
     Hello, World 4!
    </p>
   </div>
  </customcomponent>
  <customcomponent id="custom-component2">
   <div class="container">
    <p>
     Hello, World 1!
    </p>
    <p>
     Hello, World 5!
    </p>
   </div>
   <div class="container">
    <p>
     Hello, World 2!
    </p>
    <p>
     Hello, World 6!
    </p>
   </div>
  </customcomponent>
  <script>
   console.log('Hello, World!');
  </script>
 </body>
</html>

Example usage #2:

fragments = format_html([
    CustomComponent(
        p("Hello, World 3!"), 
        p("Hello, World 4!"), 
        {"id": "custom-component1"}
    ),
    CustomComponent(
        p("Hello, World 5!"), 
        p("Hello, World 6!"), 
        {"id": "custom-component2"}
    )
])
print(fragments)

Output:

<customcomponent id="custom-component1">
 <div class="container">
  <p>
   Hello, World 1!
  </p>
  <p>
   Hello, World 3!
  </p>
 </div>
 <div class="container">
  <p>
   Hello, World 2!
  </p>
  <p>
   Hello, World 4!
  </p>
 </div>
</customcomponent>
<customcomponent id="custom-component2">
 <div class="container">
  <p>
   Hello, World 1!
  </p>
  <p>
   Hello, World 5!
  </p>
 </div>
 <div class="container">
  <p>
   Hello, World 2!
  </p>
  <p>
   Hello, World 6!
  </p>
 </div>
</customcomponent>

As you can see, not need for Jinja2 or any templating library necessary.

Advantages of using Annandale:

  1. Extremely lightweight library: Annandale has only 1 dependency, BeautifulSoup4, that is it.
  2. Easy to understand: it is fairly easy to see what HTML code will be generated from Annandale Components. Obviously it is not as easy to see the HTML code that will be created by custom components so as a best practice, create custom components that are simple. In short, create and use custom components at your own peril.
  3. No need to create template files: all rendering is done in python code.

#####Jinja2 vs Annandale Comparison: for loops:

{% for record in records %}
<div id="record.id">{{ record.content }}</div>
{% endfor }
format_html(
    [div(record.content, {"id": record.id}) for record in records]
)

conditionals:

{% if is_open == True %}
    <button class=".open_btn">open</button>
{% else %}
    <button class=".close_btn">close</button>
{% endif %}
format_html(
    button("open", {"class": ".open_btn"}) if is_open is True else button("close", {"class": ".close_btn"})
)

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

whitehouse-0.1.0.tar.gz (7.2 kB view details)

Uploaded Source

Built Distribution

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

whitehouse-0.1.0-py3-none-any.whl (5.4 kB view details)

Uploaded Python 3

File details

Details for the file whitehouse-0.1.0.tar.gz.

File metadata

  • Download URL: whitehouse-0.1.0.tar.gz
  • Upload date:
  • Size: 7.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.3

File hashes

Hashes for whitehouse-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1551a1da18d81997a384780793bd5ccefdb64bc9625c91b5999293cf7a2df4be
MD5 37c499b661bf1a87ce258b50373c566d
BLAKE2b-256 1c1947406bb7e704fe3ac77a39099be460ba421d119078096e7d7f172c32c165

See more details on using hashes here.

File details

Details for the file whitehouse-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: whitehouse-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 5.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.3

File hashes

Hashes for whitehouse-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cf9042c673c0598a97b78592ddbb9e3811d5f92d51ad2ffe90f9325caca839ad
MD5 ee8286f72d5ca30d5d5768e5e518717b
BLAKE2b-256 47415cb22780f9fe31122c074593f31baa5d891b5dc4b217a545cc34a4e1f212

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