Skip to main content

Library for generating HTML through the use of syntactic macros in python

Project description

Pyml

Pyml is a python library that allows developers to generate non-static HTML through the use of a custom implementation of syntactic macros for python. By leveraging the power of syntatic macros, pyml offers the familiarity and full feature richness of python, with minimal syntactical overhead.

Pyml is framework agnostic and can be used with any web framework/server that supports the returning HTML strings as a response.

Pyml is in a very early stage of developmenet. Any feedback, contribuitions or bug reports will be greatly appreciated.

Installation

Pyml can be installed using pip:

pip install pyml3

Using pyml

Pyml relies on the concept of syntactic macros to allow embedding of markup within python code. At the time of writing, python does not offer builtin support for syntactic macros (although that might change in the future). The library implements macros by relying on a custom implementation that uses import hooks and a custom file extension.

There are two steps to using pyml:

  1. Generate html in .pyxp files using the pysx macro
  2. Import the .pyxp file and access the pysx output

Example:

### views.pyxp
"""
A .pyxp files is just like a normal python source file, but with one caveat, they can
contain macro expressions. A macro expression is just like a normal function call, but
if the function name is followed by a '!', the expression is parsed as a macro call.
"""

from pyml import pysx

title = pysx!( 
    h1 {
        "hello world"
    }
)


### app.py
import pyml.activate # 'pysx.activate' must appear before importing any .pyxp module
from  .views import title

print(title) # <h1>hello world</h1>

Elements

The syntax used with the pysx macro is html-like and was inspired by rsx. In pysx, elements are referenced by the name of the element, followed by their attributes and children in between curly braces:

h1 = pysx!(h1 {
    style: "color:blue", 
    "Hello world"
}) # expands to: <h1 style="color: blue">Hello world</h1>
    

Void elements like input are checked during expansion to make ensure that they don't have children:

h1 = pysx!(input { "bad child"}) # Error: Void element 'input' cannot have children

Expressions

Pysx has the following types of expressions:

  • Numbers: 1, 2, 1.5, 8
  • Strings: "Foo", 'Bar'
  • Identifiers (Variable reference):py_var, some_var

Expressions can be used as attribute values and as the children of elements. Identifiers are resolved using python's name resolution rules:

hello_word = "Hello world in a variable"

print(
    pysx!(div {
        hello_word
    })
) # expands to: <div>Hello world in variable</div>

The pyelement allows the use of any arbitary python expression within a pysx block:

pysx!(
    h1{
        "Even numbers from 1-100: ", 
        div {
            py { ", ".join([str(i) for i in range(1, 101) if i % 2 == 0]) }
        }
    }
)

Conditional rendering

You can use if statements within rsx to render content conditionally:

### if_stmt.pyxp
from pyml import pysx

is_visible = False

page = pysx!(
    div {
        if is_visible {
            "hello world" 
        },
    }
) 
print(page) # <div></div>


# You can also use elif and else statements
lit_type = "string"
page = pysx!(
    div {
        if lit_type == "list_lit" {
            "[]"
        }
        elif lit_type == "float" {
            "3.14"
        }
        elif lit_type == "string" {
            "Hello world"
        } else {
            "unknown type"
        }
    }
) 
print(page) # <div>Hello world</div>

Loops

You can also use for loops within pysx:

### for_stmt.pyxp
from pyml import pysx

word_list = ["hello world", "bye world"]
pysx!(
    div {
        h1 {
            "List content" 
        }, 
        for i, word in enumerate(word_list) {
            b {
                i
            }, 
            b {
                word
            }
        } 
    }
)

Components

Components are just functions decorated with the pyml.component.component decorator that return pysx.
Components are invoked with the props arg, which is an object that contains all the props that the component was initialized with. The children prop is a special prop that can be used to render the child elements
passed to the component:

### views.pyxp
from pyml import pysx, component, Props

@component
def Scaffold(props: Props):
    return pysx!(
        "<!DOCTYPE html>", 
        html {
            head {
                meta {
                    charset: "utf-8"
                }, 
                meta {
                    name: "viewport", 
                    content: "width=device-width", 
                    initial-scale: 1
                } 
            }, 
            body{  
                style: py { f"background-color: {props.bg_color}" }
                py { props.children }
            }, 
        }
    )

def index(): 
    return pysx!(
        Scaffold {
            bg_color: "red",
            h1 {
                "hello world"
            }
        }
    )

Trying to access an unknown prop will result in a runtime error:

### views.pyxp
from pyml import pysx, component, Props

@component
def Scaffold(props: Props):
    return pysx!(
        h1 {
            py { props.message }
        }
    )

def index(): 
    return pysx!(
        Scaffold {  
        } # raises AttributeError: 'Props' object has no attribute 'message'. 
    )

print(index())

The above example fails because the Scaffold component attempts to get the message prop even though it wasn't invoked with it.

Debugging pysx

If you would like to see the expanded version of a .pyxp for debugging or other purposes, without needing to import in another file, you can use the expan.main script:

python -m expan.main path/to/pyxp/file.pyxp

The above command prints the expanded source to stdout. If you would like send the output to another file, just use the -o option:

python -m expan.main path/to/pyxp/file.pyxp -o path/to/output/file.py

Example(s)

To see examples of pyml in use, check out the tiny app(s) in the examples folder.

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

pyml3-0.1.1.tar.gz (16.6 kB view details)

Uploaded Source

Built Distribution

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

pyml3-0.1.1-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

Details for the file pyml3-0.1.1.tar.gz.

File metadata

  • Download URL: pyml3-0.1.1.tar.gz
  • Upload date:
  • Size: 16.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.3.2 CPython/3.10.4 Linux/5.4.0-150-generic

File hashes

Hashes for pyml3-0.1.1.tar.gz
Algorithm Hash digest
SHA256 d49c458c5cd05ae45b67f42cf59e380487520e24dbd952f835b952fbc4df1126
MD5 ea11d9c69c6299b9ea22eef417d83f7b
BLAKE2b-256 9af074eba2d521754f00bf11145469cd7603dfa3e065968a5b28c7478ee009de

See more details on using hashes here.

File details

Details for the file pyml3-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pyml3-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 16.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.3.2 CPython/3.10.4 Linux/5.4.0-150-generic

File hashes

Hashes for pyml3-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 46ce93c7bfe3630f9a6b5f0e2038b6be76c326b6e5b14e6d43a14f15c4431bbb
MD5 c052c669c9f7cea845954cc5dd70c049
BLAKE2b-256 9913f8e4207b0331542cebd5e3d58d659a8531a2055433606a7318292d8b57db

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