Package: pyml3
PymlPyml 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.InstallationPyml can be installed using pip:pipinstallpyml3Using pymlPyml relies on the concept ofsyntactic macrosto 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 thefuture).
The library implements macros by relying on a custom implementation that usesimport hooksand
a custom file extension.There are two steps to using pyml:Generate html in.pyxpfiles using thepysxmacroImport the .pyxp file and access the pysx outputExample:### views.pyxp"""A .pyxp files is just like a normal python source file, but with one caveat, they cancontain macro expressions. A macro expression is just like a normal function call, butif the function name is followed by a '!', the expression is parsed as a macro call."""frompymlimportpysxtitle=pysx!(h1{"hello world"})### app.pyimportpyml.activate# 'pysx.activate' must appear before importing any .pyxp modulefrom.viewsimporttitleprint(title)# <h1>hello world</h1>ElementsThe syntax used with the pysx macro is html-like and was inspired byrsx.
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 likeinputare checked during expansion to make ensure that they don't have children:h1=pysx!(input{"bad child"})# Error: Void element 'input' cannot have childrenExpressionsPysx has the following types of expressions:Numbers:1, 2, 1.5, 8Strings:"Foo", 'Bar'Identifiers (Variable reference):py_var, some_varExpressions can be used as attribute values and as the children of elements.Identifiersare 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>Thepyelement allows the use of any arbitary python expression
within a pysx block:pysx!(h1{"Even numbers from 1-100: ",div{py{", ".join([str(i)foriinrange(1,101)ifi%2==0])}}})Conditional renderingYou can use if statements within rsx to render content conditionally:### if_stmt.pyxpfrompymlimportpysxis_visible=Falsepage=pysx!(div{ifis_visible{"hello world"},})print(page)# <div></div># You can also use elif and else statementslit_type="string"page=pysx!(div{iflit_type=="list_lit"{"[]"}eliflit_type=="float"{"3.14"}eliflit_type=="string"{"Hello world"}else{"unknown type"}})print(page)# <div>Hello world</div>LoopsYou can also use for loops within pysx:### for_stmt.pyxpfrompymlimportpysxword_list=["hello world","bye world"]pysx!(div{h1{"List content"},fori,wordinenumerate(word_list){b{i},b{word}}})ComponentsComponents are just functions decorated with thepyml.component.componentdecorator that return pysx.Components are invoked with thepropsarg, which is an object that contains
all the props that the component was initialized with. Thechildrenprop
is a special prop that can be used to render the child elementspassed to the component:### views.pyxpfrompymlimportpysx,component,Props@componentdefScaffold(props:Props):returnpysx!("<!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}},})defindex():returnpysx!(Scaffold{bg_color:"red",h1{"hello world"}})Trying to access an unknown prop will result in a runtime error:### views.pyxpfrompymlimportpysx,component,Props@componentdefScaffold(props:Props):returnpysx!(h1{py{props.message}})defindex():returnpysx!(Scaffold{}# raises AttributeError: 'Props' object has no attribute 'message'.)print(index())The above example fails because theScaffoldcomponent attempts to get themessageprop
even though it wasn't invoked with it.Debugging pysxIf 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 theexpan.mainscript:python-mexpan.mainpath/to/pyxp/file.pyxpThe above command prints the expanded source to stdout. If you would like send the output to another file, just
use the-ooption:python-mexpan.mainpath/to/pyxp/file.pyxp-opath/to/output/file.pyExample(s)To see examples of pyml in use, check out the tiny app(s) in theexamplesfolder.