XHPy extends Python syntax such that XML document fragments become valid Python expressions. It is based off XHP, a similar framework for PHP.
- Simplicity: write UI logic in a simple, expressive syntax without the need for external templates or templating languages.
- Flexibility: use Python expressions freely within XHPy tags, and vice-versa.
- Security: benefit from automatic escaping of text within XHPy tags.
- Reusability: build reusable components by subclassing ❌element.
In bar.py:
from xhpy.init import register_xhpy_module
register_xhpy_module('foo')
import foo
In foo.py:
from xhpy.pylib import *
class :ui:foo(:x:element):
attribute list bar
category %flow
def render(self):
a = <ul />
for b in self.getAttribute('bar'):
a.appendChild(<li>{b}</li>)
return a
print <div class="baz"><ui:foo bar={range(3)} /></div>
We can now run bar.py as a normal Python script:
$ python bar.py <div class="baz"><ul><li>0</li><li>1</li><li>2</li></ul></div>
Congratulations! You just wrote your first snippet of XHPy.
XHPy adds some new syntax to Python. Line by line replay time!
from xhpy.init import register_xhpy_module
This initializes XHPy and allows you to register modules to be interpreted as XHPy.
register_xhpy_module('foo')
Now the foo module in foo.py will be interpreted as XHPy when imported.
If foo were a package, all of its submodules would also be registered; this is
useful for registering UI libraries.
import foo
To actually use XHPy, however, you will probably want the core library:
from xhpy.pylib import *
Now you have access to all the standard HTML 4.0 elements, the :x:element base class
(this is what you build custom components on top of!), and some utilities.
class :ui:foo(:x:element):
Making new components is easy: just subclass :x:element. For your component class to be
registered, it must start with : - this clearly distinguishes your components from
ordinary Python classes.
attribute list bar
This is an attribute declaration, meaning that :ui:foo allows bar attributes on <ui:foo>
tags. Note the
<ui:foo bar={range(3)} />
later on - like XHP, XHPy uses XML attribute syntax.
category %flow
This is a category declaration - :ui:foo is part of the %flow category. Categories are
primarily useful as a way of identifying elements that are similar without using
inheritance; for example, the <a> tag in pylib.html has
children (pcdata | %flow)*
indicating that its children must either contain text or be of the %flow category. (So
we can put <ui:foo> inside <a>!)
def render(self):
When you print an :x:element (or call str on it), the render() method is invoked; this
is where you put your UI logic.
a = <ul />
for b in self.getAttribute('bar'):
a.appendChild(<li>{b}</li>)
return a
Here, <ui:foo> is a thin wrapper around <ul> that allows you to construct an unordered
list out of a Python list. Standard HTML elements like <ul> and <li> are automatically
rendered - except that, in XHPy, you can use Python expressions within tags, so that
{b}
is replaced by the value of b. Note the use of getAttribute() and appendChild():
self.getAttribute('bar')
fetches the value of attribute bar (in this case, range(3)), whereas
a.appendChild(<li>{b}</li>)
adds <li>{b}</li> as a child of a = <ul />.
XHPy is largely based off XHP; for more details on the latter, see the XHP wiki. The syntax has been adapted for Python; in particular:
- there are no semicolons;
- XHPy class names may be used anywhere ordinary Python classes can;
- XHPy tags ignore internal whitespace, but must externally obey indentation and line continuation rules.
More on the last point:
def foo(href):
return <a href={href}></a>
def bar(href):
return\
<a href={href}></a>
are valid, whereas
def foo(href):
return\
<a href={href}>
</a>
is not, as it introduces an extra dedent after </a>.
When you
import xhpy.init
XHPy installs an import hook. This hook traps subsequent import statements, running them through a preprocessor that parses a superset of Python. This preprocessor translates XHPy tags and class names to valid Python, then executes the translated code in module scope.
This is similar to how XHP works, except:
- with, e.g., pythonenv, you can always use XHPy even without access to system-wide Python package installation directories;
- by default, Python compiles bytecode .pyc files from your modules, so the preprocessing only needs to be done once when a module is first imported.