Skip to main content

Easy and quick html builder with natural syntax correspondence (python->html). No templates needed. Serves pure pythonic library with no dependencies.

Project description

Airium

Bidirectional HTML-python translator.

PyPI version pipeline status coverage report PyPI pyversion PyPI license PyPI status

Key features:

  • simple, straight-forward
  • template-less (just the python, you may say goodbye to all the templates)
  • DOM structure is strictly represented by python indentation (with context-managers)
  • gives much cleaner HTML than regular templates
  • equipped with reverse translator: HTML to python
  • can output either pretty (default) or minified HTML code

Generating HTML code in python using airium

Basic HTML page (hello world)

from airium import Airium

a = Airium()

a('<!DOCTYPE html>')
with a.html(lang="pl"):
    with a.head():
        a.meta(charset="utf-8")
        a.title(_t="Airium example")

    with a.body():
        with a.h3(id="id23409231", klass='main_header'):
            a("Hello World.")

html = str(a)  # casting to string extracts the value
# or directly to UTF-8 encoded bytes:
html_bytes = bytes(a)  # casting to bytes is a shortcut to str(a).encode('utf-8')

print(html)

Prints such a string:

<!DOCTYPE html>
<html lang="pl">
  <head>
    <meta charset="utf-8" />
    <title>Airium example</title>
  </head>
  <body>
    <h3 id="id23409231" class="main_header">
      Hello World.
    </h3>
  </body>
</html>

In order to store it as a file, just:

with open('that/file/path.html', 'wb') as f:
    f.write(bytes(html))

Simple image in a div

from airium import Airium

a = Airium()

with a.div():
    a.img(src='source.png', alt='alt text')
    a('the text')

html_str = str(a)
print(html_str)
<div>
    <img src="source.png" alt="alt text"/>
    the text
</div>

Table

from airium import Airium

a = Airium()

with a.table(id='table_372'):
    with a.tr(klass='header_row'):
        a.th(_t='no.')
        a.th(_t='Firstname')
        a.th(_t='Lastname')

    with a.tr():
        a.td(_t='1.')
        a.td(id='jbl', _t='Jill')
        a.td(_t='Smith')  # can use _t or text

    with a.tr():
        a.td(_t='2.')
        a.td(_t='Roland', id='rmd')
        a.td(_t='Mendel')

table_str = str(a)
print(table_str)

# To store it to a file:
with open('/tmp/airium_www.example.com.py') as f:
    f.write(table_str)

Now table_str contains such a string:

<table id="table_372">
  <tr class="header_row">
    <th>no.</th>
    <th>Firstname</th>
    <th>Lastname</th>
  </tr>
  <tr>
    <td>1.</td>
    <td id="jbl">Jill</td>
    <td>Smith</td>
  </tr>
  <tr>
    <td>2.</td>
    <td id="rmd">Roland</td>
    <td>Mendel</td>
  </tr>
</table>

Chaining shortcut for elements with only one child

New in version 0.2.2

Having a structure with large number of with statements:

from airium import Airium

a = Airium()

with a.article():
    with a.table():
        with a.thead():
            with a.tr():
                a.th(_t='Column 1')
                a.th(_t='Column 2')
        with a.tbody():
            with a.tr():
                with a.td():
                    a.strong(_t='Value 1')
                a.td(_t='Value 2')

table_str = str(a)
print(table_str)

You may use a shortcut that is equivalent to:

from airium import Airium

a = Airium()

with a.article().table():
    with a.thead().tr():
        a.th(_t="Column 1")
        a.th(_t="Column 2")
    with a.tbody().tr():
        a.td().strong(_t="Value 1")
        a.td(_t="Value 2")

table_str = str(a)
print(table_str)
<article>
  <table>
    <thead>
      <tr>
        <th>Column 1</th>
        <th>Column 2</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>
          <strong>Value 1</strong>
        </td>
        <td>Value 2</td>
      </tr>
    </tbody>
  </table>
</article>

Options

Pretty or Minify

By default, airium biulds HTML code indented with spaces and with line breaks being line feed \n characters. It can be changed while creating an Airium instance. In general all avaliable arguments whit their default values are:

a = Airium(
    base_indent='  ',  # str
    current_level=0,  # int
    source_minify=False,  # bool
    source_line_break_character="\n",  # str
)

minify

That's a mode when size of the code is minimized, i.e. contains as less whitespaces as it's possible. The option can be enabled with source_minify argument, i.e.:

a = Airium(source_minify=True)

In case if you need to explicitly add a line break in the source code (not the <br/>):

a = Airium(source_minify=True)
a.h1(_t="Here's your table")
with a.table():
    with a.tr():
        a.break_source_line()
        a.th(_t="Cell 11")
        a.th(_t="Cell 12")
    with a.tr():
        a.break_source_line()
        a.th(_t="Cell 21")
        a.th(_t="Cell 22")
    a.break_source_line()
a.p(_t="Another content goes here")

Will result with such a code:

<h1>Here's your table</h1><table><tr>
<th>Cell 11</th><th>Cell 12</th></tr><tr>
<th>Cell 21</th><th>Cell 22</th></tr>
</table><p>Another content goes here</p>

Note that the break_source_line cannot be used in context manager chains.

indent style

The default indent of the generated HTML code has two spaces per each indent level. You can change it to \t or 4 spaces by setting Airium constructor argument, e.g.:

a = Airium(base_indent="\t")  # one tab symbol
a = Airium(base_indent="    ")  # 4 spaces per each indentation level
a = Airium(base_indent=" ")  # 1 space per one level
# pick one of the above statements, it can be mixed with other arguments

Note that this setting is ignored when source_minify argument is set to True (see above).

There is a special case when you set the base indent to empty string. It would disable indentation, but line breaks will be still added. In order to get rid of line breaks, check the source_minify argument.

indent level

The current_level being an integer can be set to non-negative value, wich will cause airium to start indentation with level offset given by the number.

line break character

By default, just a line feed (\n) is used for terminating lines of the generated code. You can change it to different style, e.g. \r\n or \r by setting source_line_break_character to the desired value.

a = Airium(source_line_break_character="\r\n")  # windows' style

Note that the setting has no effect when source_minify argument is set to True (see above).

Using airium with web-frameworks

Airium can be used with frameworks like Flask or Django. It can completely replace template engines, reducing code-files scater, which may bring better code organization, and some other reasons.

Here is an example of using airium with django. It implements reusable basic_body and a view called index.

# file: your_app/views.py
import contextlib
import inspect

from airium import Airium
from django.http import HttpResponse


@contextlib.contextmanager
def basic_body(a: Airium, useful_name: str = ''):
    """Works like a Django/Ninja template."""

    a('<!DOCTYPE html>')
    with a.html(lang='en'):
        with a.head():
            a.meta(charset='utf-8')
            a.meta(content='width=device-width, initial-scale=1', name='viewport')
            # do not use CSS from this URL in a production, it's just for an educational purpose
            a.link(href='https://unpkg.com/@picocss/pico@1.4.1/css/pico.css', rel='stylesheet')
            a.title(_t=f'Hello World')

        with a.body():
            with a.div():
                with a.nav(klass='container-fluid'):
                    with a.ul():
                        with a.li():
                            with a.a(klass='contrast', href='./'):
                                a.strong(_t="⌨ Foo Bar")
                    with a.ul():
                        with a.li():
                            a.a(klass='contrast', href='#', **{'data-theme-switcher': 'auto'}, _t='Auto')
                        with a.li():
                            a.a(klass='contrast', href='#', **{'data-theme-switcher': 'light'}, _t='Light')
                        with a.li():
                            a.a(klass='contrast', href='#', **{'data-theme-switcher': 'dark'}, _t='Dark')

                with a.header(klass='container'):
                    with a.hgroup():
                        a.h1(_t=f"You're on the {useful_name}")
                        a.h2(_t="It's a page made by our automatons with a power of steam engines.")

            with a.main(klass='container'):
                yield  # This is the point where main content gets inserted

            with a.footer(klass='container'):
                with a.small():
                    margin = 'margin: auto 10px;'
                    a.span(_t='© Airium HTML generator example', style=margin)

            # do not use JS from this URL in a production, it's just for an educational purpose
            a.script(src='https://picocss.com/examples/js/minimal-theme-switcher.js')


def index(request) -> HttpResponse:
    a = Airium()
    with basic_body(a, f'main page: {request.path}'):
        with a.article():
            a.h3(_t="Hello World from Django running Airium")
            with a.p().small():
                a("This bases on ")
                with a.a(href="https://picocss.com/examples/company/"):
                    a("Pico.css / Company example")

            with a.p():
                a("Instead of a HTML template, airium has been used.")
                a("The whole body is generated by a template "
                  "and the article code looks like that:")

            with a.code().pre():
                a(inspect.getsource(index))

    return HttpResponse(bytes(a))  # from django.http import HttpResponse

Route it in urls.py just like a regular view:

# file: your_app/urls.py
from django.contrib import admin
from django.urls import path

import your_app

urlpatterns = [
    path('index/', your_app.views.index),
    path('admin/', admin.site.urls),
]

The result ing web page on my machine looks like that:

Airium/Django templateless example

Reverse translation

Airium is equipped with a transpiler [HTML -> py]. It generates python code out of a given HTML string.

Using reverse translator as a binary:

Ensure you have installed [parse] extras. Then call in command line:

airium http://www.example.com

That will fetch the document and translate it to python code. The code calls airium statements that reproduce the HTML document given. It may give a clue - how to define HTML structure for a given web page using airium package.

To store the translation's result into a file:

airium http://www.example.com > /tmp/airium_example_com.py

You can also parse local HTML files:

airium /path/to/your_file.html > /tmp/airium_my_file.py

You may also try to parse your Django templates. I'm not sure if it works, but there will be probably not much to fix.

Using reverse translator as python code:

from airium import from_html_to_airium

# assume we have such a page given as a string:
html_str = """\
<!DOCTYPE html>
<html lang="pl">
  <head>
    <meta charset="utf-8" />
    <title>Airium example</title>
  </head>
  <body>
    <h3 id="id23409231" class="main_header">
      Hello World.
    </h3>
  </body>
</html>
"""

# to convert the html into python, just call:

py_str = from_html_to_airium(html_str)

# airium tests ensure that the result of the conversion is equal to the string:
assert py_str == """\
#!/usr/bin/env python
# File generated by reverse AIRIUM translator (version 0.2.7).
# Any change will be overridden on next run.
# flake8: noqa E501 (line too long)

from airium import Airium

a = Airium()

a('<!DOCTYPE html>')
with a.html(lang='pl'):
    with a.head():
        a.meta(charset='utf-8')
        a.title(_t='Airium example')
    with a.body():
        a.h3(klass='main_header', id='id23409231', _t='Hello World.')
"""

Transpiler limitations

so far in version 0.2.2:

  • result of translation does not keep exact amount of leading whitespaces within <pre> tags. They come over-indented in python code.

This is not however an issue when code is generated from python to HTML.

  • although it keeps the proper tags structure, the transpiler does not chain all the with statements, so in some cases the generated code may be much indented.

  • it's not too fast

Installation

If you need a new virtual environment, call:

virtualenv venv
source venv/bin/activate

Having it activated - you may install airium like this:

pip install airium

In order to use reverse translation - two additional packages are needed, run:

pip install airium[parse]

Then check if the transpiler works by calling:

airium --help

Enjoy!

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

airium-0.2.7.tar.gz (20.3 kB view details)

Uploaded Source

Built Distribution

airium-0.2.7-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file airium-0.2.7.tar.gz.

File metadata

  • Download URL: airium-0.2.7.tar.gz
  • Upload date:
  • Size: 20.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.4

File hashes

Hashes for airium-0.2.7.tar.gz
Algorithm Hash digest
SHA256 6b4dd8b69838b7766179ab90078a049dc337b1da4fc0f7754f30f74d6eed706e
MD5 57819dd14545a85e2601de215a0003db
BLAKE2b-256 2cb2b03d50eaf3ddbc258d376e1f8439cf2575afaff93d07a42c9de720792e46

See more details on using hashes here.

File details

Details for the file airium-0.2.7-py3-none-any.whl.

File metadata

  • Download URL: airium-0.2.7-py3-none-any.whl
  • Upload date:
  • Size: 13.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.4

File hashes

Hashes for airium-0.2.7-py3-none-any.whl
Algorithm Hash digest
SHA256 35e3ae334327b17b7c2fc39bb57ab2c48171ca849f8cf3dff11437d1e054952e
MD5 8e6bbdb04797ba27ccdbba5ec9410804
BLAKE2b-256 b7cbd92ab9a9571e82f8107865bda7d7cbdaf43d48eec94ec69f45bff6dc4e4c

See more details on using hashes here.

Supported by

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