Skip to main content

Generate HTML with Python functions.

Project description

DashML - Functional HTML Generation

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!).
  • markupsafe to prevent injection attacks (like React does!)
  • 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:

License

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

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

dashml-0.1.0.tar.gz (6.5 kB view hashes)

Uploaded Source

Built Distribution

dashml-0.1.0-py3-none-any.whl (7.2 kB view hashes)

Uploaded Python 3

Supported by

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