Kemmering¶
Overview¶
Kemmering is a lightweight library for producing XML and HTML5 snippets in Python code with a minimum of fuss and bother. The snippets can be templated, or not. Kemmering is not an XML parser, doesn’t know anything about SAX, DOM, XPATH, or namespaces, and generally doesn’t try to think too hard about what an XML document is in the abstract.
Kemmering isn’t intended as a wholesale replacement for template languages. If you’re editing a whole page of HTML, you may as well edit it as HTML, not Python code. Kemmering is intended to make it easy to produce little bits of HTML or other XMLish markup and glue them together in convenient ways. Imagine you’re writing a form library, for instance, that involves a lot of little snippets of HTML that get composed together to make a form. You could have each widget in a separate template file, with dozens of files each containing a few lines of markup. Or you could put your widgets in a single Python file, which may be more convenient.
One thing I recently used this for was producing SVG from a completely different drawing format. This would have been a pain in the neck using a template language, but was pretty straightforward using Kemmer and a functional programming style.
Maybe you’ll find this useful. YMMV, y’all.
Installation¶
Install using setuptools, e.g. (within a virtualenv):
$ $VENV/bin/pip install kemmering
Tags¶
The heart and soul of Kemmering is the tag
class:
>>> from kemmering import tag
>>> hello_world = tag('hello', id='salutation')('world')
>>> str(hello_world)
'<hello id="salutation">world</hello>'
In a nutshell, the first and only positional argument is the name of the tag, any keyword arguments are attributes, and calling the result adds children, where children can be more tags or text elements. Use str to realize a snippet as a string.
Appending a forward slash (/) character to the end of the tag name creates a self-closing tag.
>>> from kemmering import tag
>>> str(tag('hello/'))
'<hello/>'
If you are still using Python 2, you’ll want to use unicode instead of str:
>>> from kemmering import tag
>>> hello_world = tag('hello', id='salutation')('world')
>>> unicode(hello_world)
u'<hello id="salutation">world</hello>'
A tag may be called more than once to add children to it:
>>> from kemmering import tag
>>> ul = tag('ul')
>>> ul(tag('li')('one'))
tag('ul')(tag('li')('one'))
>>> ul(tag('li')('two'))
tag('ul')(tag('li')('one'), tag('li')('two'))
>>> str(ul)
'<ul><li>one</li><li>two</li></ul>'
Partial Pattern¶
A pattern some might find useful is to use partials to create tag-specific callables. For example:
>>> from functools import partial
>>> from kemmering import tag
>>> div = partial(tag, 'div')
>>> str(div()('Hello World!'))
'<div>Hello World!</div>'
HTML Tags¶
The partial pattern is the basic strategy used by the vast majority of the
kemmering.html
package, which contains all valid HTML5 tags.
>>> from kemmering import html as h
>>> snippet = h.dl(class_='fruits')(
... h.dt()('Pomegranate'),
... h.dd()('It has ', h.em()('lots'), ' of seeds!'),
... h.dt()('Kiwi'),
... h.dd()('It tastes like strawberry.'),
... )
>>> str(snippet)
'<dl class="fruits"><dt>Pomegranate</dt><dd>It has <em>lots</em> of seeds!</dd><dt>Kiwi</dt><dd>It tastes like strawberry.</dd></dl>'
Two tags, doc
and
style
, have different behavior from the standard
tag. See their respective API docs for more information.
Make it pretty¶
The html
package also as a
pretty
function that will render a snippet
with line breaks and indentation, which can be useful for debugging.
>>> from kemmering import html as h
>>> print(h.pretty(
... h.doc(h.html()(
... h.head()(
... h.title()('Hello World!')
... ),
... h.body()(
... h.p(class_='salutation')('Hello World!')
... )
... ))
... ))
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<p class="salutation">Hello World!</p>
</body>
</html>
Reserved Word Attributes¶
You might have noticed in the example above that the class attribute for the <p> element is specified using class_. Because class is a reserved word in Python, you wouldn’t be able to say h.p(class=’salutation’). For this reason, any attribute name can end with an underscore character, and the trailing underscore will be elided from the ultimate attribute name.
>>> from kemmering import html as h
>>> snippet = h.label(for_='full_name')('Your full legal name')
>>> str(snippet)
'<label for="full_name">Your full legal name</label>'
Templates¶
defer and bind¶
Using defer
and bind
you
can create and render templates. defer allows you to create an element that
is realized at a later time with information not available when the snippet is
being created. bind is used to realize the snippet later. defer accepts a
single argument which is a function that is called at bind time. The deferred
function accepts a single argument, context, which should contain any
information needed to realize the snippet at bind time. In theory, the
context can be any object, but the standard set of template helpers provided
mostly assume the context is a dictionary or an object which provides a
dictionary interface. The deferred function returns either a tag instance or a
string which replaces the deferred function in the bound snippet.
The signature of defer allows it to be used as a decorator, if you’re into that kind of thing:
>>> from kemmering import bind, defer
>>> from kemmering import html as h
>>> @defer
... def things(context):
... ul = h.ul()
... for thing in context['things']:
... ul(h.li()(thing))
... return ul
>>> snippet = h.div()(things)
>>> bound = bind(snippet, {'things': ['bat', 'glove']})
>>> str(bound)
'<div><ul><li>bat</li><li>glove</li></ul></div>'
notag¶
notag
is useful if you’d like for a deferred to
provide more than one child at the level it is placed in the snippet:
>>> from kemmering import bind, defer, notag
>>> from kemmering import html as h
>>> @defer
... def address(context):
... return notag(
... context['name'], h.br(),
... context['address'], h.br(),
... context['city'], ', ',
... context['state'], ' ',
... context['zip'],
... )
>>> snippet = h.p()(address)
>>> bound = bind(snippet, {'name': 'Joe Blow', 'address': '123 Main St.',
... 'city': 'Minsk', 'state': 'MS', 'zip': '12345'})
>>> str(bound)
'<p>Joe Blow<br/>123 Main St.<br/>Minsk, MS 12345</p>'
Templating attributes¶
Attributes can be templated as well as tag children. For obvious reasons, a deferred function being used for an attribute can’t return a tag. A deferred function used for an attribute may return a string, or it may return None. If an attribute is set to None that attribute is omitted from the final bound snippet.
>>> from kemmering import bind, defer
>>> from kemmering import html as h
>>> def selected(context):
... if context['option'] == context['selected']:
... return ''
>>> snippet = h.option(selected=defer(selected))(
... defer(lambda context: context['option']))
>>> bound = bind(snippet, {'option': 'fish', 'selected': 'fish'})
>>> str(bound)
'<option selected="">fish</option>'
>>> bound = bind(snippet, {'option': 'medicine', 'selected': 'fish'})
>>> str(bound)
'<option>medicine</option>'
Template Helpers¶
The examples above which use defer
are a bit
contrived. In practice, it will probably be rare to use the general purpose
defer, although it is general enough it should cover any use case. The most
common use cases for defer are covered by the helpers described below, which
should cover the vast majority of cases for defer.
from_context¶
from_context
substitutes a value from the
context:
>>> from kemmering import bind, from_context
>>> from kemmering import html as h
>>> snippet = h.p()("Hello ", from_context('name'), '!')
>>> bound = bind(snippet, {'name': 'Edgar'})
>>> str(bound)
'<p>Hello Edgar!</p>'
from_context accepts an optional default argument:
>>> from kemmering import bind, from_context
>>> from kemmering import html as h
>>> snippet = h.p()("Hello ", from_context('name', 'World'), '!')
>>> bound = bind(snippet, {'name': 'Edgar'})
>>> str(bound)
'<p>Hello Edgar!</p>'
>>> bound = bind(snippet, {})
>>> str(bound)
'<p>Hello World!</p>'
in_context¶
in_context
works very simlarly to
from_context but accepts a sequence of keys as an argument and can traverse a
seris of nested dictionaries to retrieve a value from the context:
>>> from kemmering import bind, in_context
>>> from kemmering import html as h
>>> snippet = h.p()("Hello ", in_context(['user', 'name']), '!')
>>> bound = bind(snippet, {'user': {'name': 'Edgar'}})
>>> str(bound)
'<p>Hello Edgar!</p>'
in_context also accepts an optional default argument:
>>> from kemmering import bind, in_context
>>> from kemmering import html as h
>>> snippet = h.p()("Hello ", in_context(['user', 'name'], 'World'), '!')
>>> bound = bind(snippet, {'user': {'name': 'Edgar'}})
>>> str(bound)
'<p>Hello Edgar!</p>'
>>> bound = bind(snippet, {})
>>> str(bound)
'<p>Hello World!</p>'
format_context¶
format_context
is passed a format string,
s, and returns the equivalent of s.format(**context) when it is bound:
>>> from kemmering import bind, format_context
>>> from kemmering import html as h
>>> snippet = h.p()(format_context('Hello {name}!'))
>>> bound = bind(snippet, {'name': 'Edgar'})
>>> str(bound)
'<p>Hello Edgar!</p>'
cond¶
cond
includes a sub-snippet or not conditionally.
The first argument is a function, condition, which accepts a single argument,
context, and returns a boolean. The second argument is the snippet to
include if condition returns True.
>>> from kemmering import bind, cond, format_context
>>> from kemmering import html as h
>>> def logged_in(context):
... return 'user' in context
>>> snippet = h.div()(
... 'Welcome to Acme!',
... cond(logged_in, format_context(' Hello {user[name]}!')))
>>> bound = bind(snippet, {'user': {'name': 'Fred'}})
>>> str(bound)
'<div>Welcome to Acme! Hello Fred!</div>'
>>> bound = bind(snippet, {})
>>> str(bound)
'<div>Welcome to Acme!</div>'
cond also accepts an optional third argument, which is a snippet to include if condition returns False.
>>> from kemmering import bind, cond, format_context
>>> from kemmering import html as h
>>> def logged_in(context):
... return 'user' in context
>>> snippet = h.div()(
... 'Welcome to Acme!',
... cond(logged_in, format_context(' Hello {user[name]}!'),
... ' You are not logged in!'))
>>> bound = bind(snippet, {'user': {'name': 'Fred'}})
>>> str(bound)
'<div>Welcome to Acme! Hello Fred!</div>'
>>> bound = bind(snippet, {})
>>> str(bound)
'<div>Welcome to Acme! You are not logged in!</div>'
loop¶
loop
allows for repeating of a sub-snippet inside of
another snippet by looping over items in a sequence. The first argument to
loop is the name of a key to maintain in the context with the value of the
current item being iterated over. This argument can optionally be a list or
tuple of key names, which will cause sequence values to be unpacked. The
second argument is either a function, which accepts the context as an
argument and returns a sequence, or the name of a key inside of context whose
value is the sequence to iterate over. The third argument is the snippet to be
repeated for each item in the sequence.
>>> from kemmering import bind, from_context, loop
>>> from kemmering import html as h
>>> snippet = h.ul()(
... loop('fruit', 'fruits', h.li()(from_context('fruit'))))
>>> bound = bind(snippet, {'fruits': ['apple', 'pear', 'banana']})
>>> str(bound)
'<ul><li>apple</li><li>pear</li><li>banana</li></ul>'
More Information¶
Reporting Bugs / Development Versions¶
Visit http://github.com/chrisrossi/kemmering to download development or tagged versions.
Visit http://github.com/chrisrossi/kemmering/issues to report bugs.