Skip to main content

DashML - HTML Components in Python

PyPI GitHub issues License: MIT Code Style: Black

Create functions to build HTML in Python- inspired by the "component movement" in Javascript.

>>> from dashml import _, render
>>>
>>> render(_.p("Hello, world!"))
'<p>Hello, world!</p>'

Why DashML?

JavaScript frameworks like React or Vue took over the frontend landscape because of the ease of creating reusable components. React especially thrives with the fact that components are code, and can be manipulated as such.

Meanwhile, server-side languages are stuck with clunky template languages to generate HTML. It's hard to extract components out of Jinja2 or Django templates to re-use.

DashML expands on existing Python libraries to create an ergonomic way to generate HTML in Python.

  • Built on lxml (and in turn built on C) for extreme speed- check out the benchmarks (or run them yourself!).
  • A minimal API you can pick up in ~15 minutes- DashML is so simple, you could have written it yourself!

Adopting DashML in your project allows you to create traditional server rendered webpages faster, while embracing the good parts of component driven design.

Get Started

To get started with DashML, you will need:

  • Python 3.6+
  • pip
  • About 5 minutes.

Install From PyPi

Like almost every Python package, you can install DashML from PyPi:

pip install dashml

Wow, that was really simple.

Writing Components

DashML components are just functions that return lxml Element objects. You can write one like this:

# greeter.py

from dashml import _

def greeter(name: str) -> 'Element':
    """My first DashML component!!"""
    return _.p(f"Hello, {name}!")

The function greeter returns an Element, which is a special representation of an xml document from the library lxml. lxml uses C, so it's really fast to create and modify elements.

You can get a string out of an Element by using the DashML function render.

# greeter.py

from dashml import render, _

# ...snip

print(render(greeter("Maddie")))

If you save this to greeter.py and run it, you can see your component get rendered out as a string:

python greeter.py
<p>Hello, Maddie!</p>

Adding Attributes

HTML documents almost always have attributes on some elements. You can add in attributes to DashML functions as keyword arguments.

# greeter.py

from dashml import _

def greeter(name: str) -> 'Element':
    """My first DashML component!!"""
    return _.p(f"Hello, {name}!", id="my-greeter", class_name="my-greeter")

DashML attributes are always snake case. The attributes class and for are replaced with class_name and html_for. Additionally, attributes prefixed with data_ or aria_ are converted to use dashes instead of underscores. If you've used React, this probably feels familiar!

:warning: Unsafe Usage :warning:

By default, DashML prevents XSS attacks by escaping all text that goes into any elements. If you really want to use a raw HTML string, you can import unsafe_from_string and create raw elements that way.

from dashml import unsafe_from_string

element = unsafe_from_string('<script>alert("Unsafe!")</script>')

Remember, this is not safe for untrusted strings, and should only be used as an escape hatch if absolutely necessary. To stay safe, stick to normal usage of DashML.

DashML Tips

Congrats! You've completed a tour of the DashML API. Here are some friendly suggestions of how to use DashML effectively in your projects.

Create composable, reusable functions.

Writing out element names by hand everywhere is no better than just writing plain HTML, and doesn't use the composable and reusable powers DashML has.

For example, rather than typing out _.h1(...) everywhere, create a function called header that is specific to your project and creates the elements you need:

def header(text: str) -> 'Element':
    """Creates a header for a page."""
    return _.header(
        _.h1(text, class_name="header__title"),
        class_name="header"
    )

Composing functions that create elements is expressive, and a lot cleaner:

page(
    header("HTML in Python? More likely than you think."),
    content(),
    footer(),
)

Now that's component driven code!

Work With Your Data Structures

A great strategy with DashML components is to have them accept your data objects as arguments, and just control display logic.

For example, if you're working with an ORM object for a blog post, creating a component called blog_post provides an easy way to render out your database records:

# components.py

def blog_post(post: BlogPost) -> 'Element':
    return page(
        header(post.title),
        content(post.text),
        footer(),
    )

Keep DashML Components Separate

Consider keeping your DashML code in a different module from your code that contains business logic (and everything else). Keeping a separate components.py file in each module with DashML components keeps your intent clear and code clean:

mymodule/
|- components.py
|- models.py
|- views.py

Additionally, it allows easier mirroring of objects in other modules. From the blog post example above:

# views.py

from . import components

def my_view(request, post_id: int):
    # ...snip
    post = BlogPost.get(post_id)
    return components.blog_post(post)

...And Keep Business Logic out of Components

Components should be as slim as possible, and only handle presentation logic. It's an anti-pattern to bundle up business logic in your DashML components.

# BAD: Don't do this!
def render_post(pk: int):
    post = get_or_404(BlogPost, pk)
    return _.p(post.content)

# GOOD: Just pass in objects to render!
def render_post(post: BlogPost):
    return _.p(post.content)

Additionally, every DashML component is ideally a pure function, that will always return the same result for a given input.

Consider Functional CSS

If you want to bring functional styling into your DashML components, consider using a css library like Tachyons for styling.

def my_text(text: str):
    return _.p(text, class_name="f5 f4-l lh-copy athelas")

Atomic/functional CSS blends exceptionally well with DashML, and allows the embracing of functional components in styles and markup.

Benchmarks

Since DashML relies on lxml for components, it's fast. Very, very fast. Rendering components is generally a sub-millisecond operation if no external action happens in the component.

===============================================================
Benchmark: 'Simple HTML document.'
===============================================================
344.982 miliseconds for 5000 renders of 'Simple HTML document.'
Avg 0.069 miliseconds for one render.

Obviously, adding things like HTTP requests, database queries, or complex calculations to renders will slow them down. But why are you doing that, keep logic separate!.

You can run the benchmarks yourself locally by cloning and installing the project:

git clone https://github.com/madelyneriksen/dashml/
cd dashml
python -m venv .env && source .env/bin/activate && pip install -r requirements.dev.txt
python bench.py # or `make bench`

Special Thanks

DashML could not be built without these libraries:

  • lxml for creating a fast XML library.

License

Copyright 2019 under terms of the MIT license. See LICENSE for details.

Release files for dashml 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dashml 0.1.1
File Size Uploaded
dashml-0.1.1.tar.gz 8.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dashml 0.1.1
File Interpreter ABI Platform
dashml-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 18.5 kB

Release files / dashml-0.1.1.tar.gz

Download URL dashml-0.1.1.tar.gz
Size 8.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7c1b2e0bad1515fbf11a0d40276fd937d906dfdb954e4c3fd45d004f52fd4a30
BLAKE2b-256 checksum
How to use checksums
ba97518ac9e1e3522c77b6de54b5c2563542fb578e68bf49952caf53e02c3bf7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/42.0.2 requests-toolbelt/0.9.1 tqdm/4.41.0 CPython/3.6.9

Release files / dashml-0.1.1-py3-none-any.whl

Download URL dashml-0.1.1-py3-none-any.whl
Size 10.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0f2d6cbf6bb57543e370295a63cfe471de0d1e03804c7532cb6a1af0c0e13650
BLAKE2b-256 checksum
How to use checksums
f78b48544f6cbd3a77e166c8bf166010b9eced5554e01685af0ebae979114dff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/42.0.2 requests-toolbelt/0.9.1 tqdm/4.41.0 CPython/3.6.9

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page