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 pyml
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:
- Generate html in .pyxp files using the pysx macro
- 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 pysx.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 py
element 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.