xhpy

XHP for Python


Keywords
xhp, html, xml
License
Apache-2.0
Install
pip install xhpy==0.81

Documentation

XHPy: XHP for Python

XHPy extends Python syntax such that XML document fragments become valid Python expressions. It is based off XHP, a similar framework for PHP.

Advantages

  • 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.

An example

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.

Syntax

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>.

How it works

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.