Rendering With hyperpython

As mentioned at the start, htm.py is only half the equation. It generates a tree of operations. For “server side rendering” (SSR), something needs to take that and generate an HTML string.

The hyperpython package can do that. The output of htm.py – the VDOM tree – is the input to hyperpython.

Let’s take a look.

Hello World

In Hello World we saw a snippet that generated the VDOM for a static “Hello World”. Let’s re-do it, but with rendering to a string:

from htm import htm
from hyperpython import h


@htm
def html(tag, props, children):
    return h(tag, props, children)


result01 = str(html("""
  <div>Hello World</div>
"""))

What’s different? We import the h function from hyperpython and have our html function pass the htm stuff into it. Then, we get the string representation at the end, which does the conversion into a string:

As such, instead of a Python tuple of VDOM thingies, we get an HTML string:

>>> result01
'<div>Hello World</div>'

Rendered Subcomponent

Let’s now recreate another example: Conditional Rendering. But this time, rendering a string.

message = 'Say Howdy'
names = ['World', 'Universe', 'Galaxy']


def greeting(name):
    return html('<li>Hello {name}</li>')


result02 = str(html("""
  <ul title="{message}">
    {[greeting(name) for name in names]}
  </li>
"""))

What’s different? We import the h function from hyperpython and have our html function pass the htm stuff into it. Then, we get the string representation at the end, which does the conversion into a string.

As such, instead of a Python tuple of VDOM thingies, we get an HTML string:

>>> result02
'<ul title="Say Howdy"><li>Hello World</li><li>Hello Universe</li><li>Hello Galaxy</li></ul>'

Let’s use that as our foray into some more features.

Real Component

That’s not a real component, it’s just a Python function call in the middle of an expression. htm and thus htm.py support a first-class concept of a component, in the grammar, which gets compiled.

Components have a curly brace around them. Let’s do a simple Greeting, but first, without passing an argument:


@htm
def html(tag, props, children):
    if callable(tag):
        return tag()
    return h(tag, props, children)


def Heading():
    return html('<header>Hello World</header>')


result03 = str(html("""
    <{Heading}><//>
"""))

React popularized having lots of small, reusable, functional components. Here’s how the Heading renders:

>>> result03
'<header>Hello World</header>'

Component “Props”

JSX uses “props”, which are a series of HTML attribute-looking things. Our html function is currently acting like a factory. Let’s make it smarter about gathering up props.


@htm
def html(tag, props, children):
    if callable(tag):
        # In this case:
        #   props={'header': 'My Components'}
        return tag_factory(tag, children=children, **props)
    return h(tag, props, children)


def tag_factory(tag_callable, **kwargs):
    return tag_callable(**kwargs)


def Heading(header='Some Default', children=()):
    return html('<header>Hello {header}</header>')


result04 = str(html("""
    <{Heading} header='My Components'><//>
"""))

Rather than call the “tag” immediately, we instead need to pass the props to a function which will:

  • Pick them apart

  • Pass them as arguments to the callable at tag

  • Return the result

This is the kind of thing factories such as html are good for. Here’s the result we get:

>>> result04
'<header>Hello My Components</header>'

Argument Sniffing

Our factory does a good job of forwarding along arguments from the “props” to the callable. But what if the props provide something the callable doesn’t want?

Let’s make the tag_factory a little smarter, by having it sniff at the function arguments. Then, get the requested args from the props before calling:


@htm
def html(tag, props, children):
    if callable(tag):
        # In this case:
        #   props={'header': 'My Components'}
        return tag_factory(tag, children=children, **props)
    return h(tag, props, children)


def tag_factory(tag_callable, **kwargs):
    sig = signature(tag_callable)
    parameters = sig.parameters

    # Pick through the callable's signature and get
    # what is needed
    if not any(p.kind == p.VAR_KEYWORD for p in parameters.values()):
        extra_key = "_"
        while extra_key in parameters:
            extra_key += "_"

        sig = sig.replace(
            parameters=[*parameters.values(),
                        Parameter(extra_key, Parameter.VAR_KEYWORD)
                        ]
        )
        kwargs = dict(sig.bind(**kwargs).arguments)
        kwargs.pop(extra_key, None)

    return tag_callable(**kwargs)


def Heading(header='Some Default'):
    return html('<header>Hello {header}</header>')


result05 = str(html("""
    <{Heading} header='My Components' unused=9><//>
"""))

Note that our usage of Heading included a prop unused which is ignored, since we are sniffing the Heading callable’s arguments. As an additional feature, our Heading callable doesn’t have to ask for children when it isn’t using it.

>>> result05
'<header>Hello My Components</header>'

You can also pass other Python values as props by using curly brackets:


@htm
def html(tag, props, children):
    if callable(tag):
        # In this case:
        #   props={'header': 'My Components'}
        return tag_factory(tag, children=children, **props)
    return h(tag, props, children)


def tag_factory(tag_callable, **kwargs):
    sig = signature(tag_callable)
    parameters = sig.parameters

    # Pick through the callable's signature and get
    # what is needed
    if not any(p.kind == p.VAR_KEYWORD for p in parameters.values()):
        extra_key = "_"
        while extra_key in parameters:
            extra_key += "_"

        sig = sig.replace(
            parameters=[*parameters.values(),
                        Parameter(extra_key, Parameter.VAR_KEYWORD)
                        ]
        )
        kwargs = dict(sig.bind(**kwargs).arguments)
        kwargs.pop(extra_key, None)

    return tag_callable(**kwargs)


def Heading(count):
    return html('<header>Count++ {count+1}</header>')


result05b = str(html("""
    <{Heading} count={8}><//>
"""))
>>> result05b
'<header>Count++ 9</header>'

Component Children

A component might expect to receive some children, which it will then place as it wishes. Our tag_factory supports it, so let’s show it:


@htm
def html(tag, props, children):
    if callable(tag):
        return tag_factory(tag, children=children, **props)
    return h(tag, props, children)


def tag_factory(tag_callable, **kwargs):
    sig = signature(tag_callable)
    parameters = sig.parameters

    # Pick through the callable's signature and get
    # what is needed
    if not any(p.kind == p.VAR_KEYWORD for p in parameters.values()):
        extra_key = "_"
        while extra_key in parameters:
            extra_key += "_"

        sig = sig.replace(
            parameters=[*parameters.values(),
                        Parameter(extra_key, Parameter.VAR_KEYWORD)
                        ]
        )
        kwargs = dict(sig.bind(**kwargs).arguments)
        kwargs.pop(extra_key, None)

    return tag_callable(**kwargs)


def Heading(children, header='Some Default'):
    return html('<header>Hello {header}</header>{children}')


result06 = str(html("""
    <div>
    <{Heading} header='My Components' unused=9>
        <div>Some children</div>
    <//>    
    </div>
"""))
>>> result06
'<div><header>Hello My Components</header><div>Some children</div></div>'