Skip to main content

Spyce will not waste your time: code reuse

Most frameworks today have pretty good support for code/markup reuse towards providing a common look to a site without the "include a header, include a footer" clunkers you used to see. (Spyce uses "parent tags," which are as elegant as any and more powerful than most.)

But what I want to talk about today is code/markup reuse in the small, at a level that corresponds to functions or methods in Python.

Most frameworks today still suck at this. Approaches vary, but the important thing they have in common is not letting you use the same techniques you use in the rest of the framework as well as generally being clunky.

For instance, with Rails, you define functions which you can then use from your views/templates/presentation layer, but within those functions you're back in 1996, stringing HTML together manually. Something like

def faq_html(question, answers)
    html = '''
    <table class="faq">
    <thead>
      <tr><th>Q.</th><th>%s</th></tr>
    </thead>
    ''' % question
    for a in answers:
        html += '<tr><td>A.</td><td>%s</td></tr>' % a
    html += '</table>'
    return html

(Edit: Rails also has a code reuse mechanism called "partials," which behave a lot more like normal rhtml code.)

In Spyce, you can still do things this old-fashioned way if you really want. But a much better way is defining "active tags," which take parameters like functions but are defined with the same tools you build the rest of your page with, i.e., Python and other tags. Such as:

[[.begin name=html singleton=True ]]
[[.attr name=question ]]
[[.attr name=answers ]]

<spy:table class="faq" data="(('A.', a) for a in answers)">
<thead>
  <tr><th>Q.</th><th>[[= question ]]</th></tr>
</thead>
</spy:table>

[[.end ]]

In a tag library named "faq", this active tag could then be used as follows: (the "=" sign means, "evaluate this parameter as a Python expression instead of as a string)

<faq:html question="=question.body" answers="=[a.body for a in answers]" />

The more complex your code becomes, the more you'll appreciate the Spyce way. You'll find that with the increased programmer-friendliness active tags provide, you'll abstract more common code than you would have with clumsier tools, with the all the improved maintainability that entails over code-by-copy-and-paste.

But what's really cool is you can encapsulate handlers with your tags, creating self-contained bundles of render and control logic. This is crucial: there's no way to do this with the traditional "helper function" approach. Splitting functionality out into active tag components like this allows both reuse and encapsulation, just as classes provide similar benefits in pure Python code.

(We talked about Spyce handlers yesterday. Briefly: Spyce allows you to attach a python function to a form submit action.)

To continue our FAQ example, let's add a "Create a new FAQ" handler, like this:

[[!
def faq_new(self, api, question, answer):
    q = api.db.questions.insert(body=question)
    api.db.answers.insert(question_id=q.id, body=answer)
    api.db.flush()
]]

[[.begin name=faq_html singleton=True ]]
[[.attr name=question ]]
[[.attr name=answers ]]

<spy:table class="faq" data="(('A.', a) for a in answers)">
<thead>
  <tr><th>Q.</th><th>[[= question ]]</th></tr>
</thead>
</spy:table>

<h2>New FAQ</h2>
<div><f:text name=question label="Question:" /></div>
<div><f:text name=answer label="Answer:" /></div>
<div><f:submit handler=self.faq_new value="Create" /></div>
[[.end ]]

Spyce provides friendlier and more powerful tools for code reuse than either other view-oriented frameworks or MVC frameworks. Give it a try; you won't want to go back.

Tomorrow I'll explain the Spyce authentication system.

Comments

Anonymous said…
Two comments:

Piecing together an html page from HTML fragments is not what most templating languages do ... your argument that Spyce does it better thus is lacks convincing power ... I pity the fool who generates html by adding via +=

If you want to convice people that the Spyce way is better, compare it to Cheetah, Django or Kid.

just an opinion
Jonathan Ellis said…
It sounds like you're saying, "Compare yourself to these other things, but limit your comparison to the things they can do too." Not a very interesting comparison, to my mind.

If you've never felt the need to reuse a section of template -- or split it up into sections that "do one thing well" -- the way you reuse or split Python code into functions or classes, all I can say is, try it, you've been missing out. :)
Tim Lesher said…
I think what "anonymous" was trying to say is that most templating languages nowadays provide better mechanisms than helper functions requiring manual HTML assembly.

But even if you are down to a helper function, ElementTree beats string concatenation.
Jonathan Ellis said…
... I've been trying to avoid rubbing others' noses in how Spyce is better than [specific other framework]. Which may be a mistake, but I think it would just sound like sour grapes and turn people off.

Honestly, what I'd like is for some of those other frameworks' designers to see this and think, "Huh, that's a good idea. I'll figure out a way to make my stuff at least that elegant." Then everybody wins.
Jonathan Ellis said…
Tim: perhaps you are right, but his inclusion of Cheetah makes me think not.
Jonathan Ellis said…
Edited the post to make it clear that I didn't mean all non-Spyce frameworks take the same approach.
Anonymous said…
Ruby on Rails has what it calls "Partial templates". They use the same template language as normal page templates, but can be included within a page template. They focus on "doing one thing well" and do not require stringing pieces of HTML together in code.
Jonathan Ellis said…
Aaron, perhaps I am wrong, but my impression from the Kid documentation (http://kid-templating.org/language.html#named-template-functions-py-def) is that named template functions may only be used in the template they are defined in. That's pretty limited reuse.

(Edit: ah, you're supposed to inherit from "utility templates" and use them that way. Okay, Kid gets a pass on that part. :)
Anonymous said…
Just ran across this and it looks intriguing. If I can wrap my brain around it, I'll give it a try. :)

Popular posts from this blog

Python at Mozy.com

At my day job, I write code for a company called Berkeley Data Systems. (They found me through this blog, actually. It's been a good place to work.) Our first product is free online backup at mozy.com . Our second beta release was yesterday; the obvious problems have been fixed, so I feel reasonably good about blogging about it. Our back end, which is the most algorithmically complex part -- as opposed to fighting-Microsoft-APIs complex, as we have to in our desktop client -- is 90% in python with one C extension for speed. We (well, they, since I wasn't at the company at that point) initially chose Python for speed of development, and it's definitely fulfilled that expectation. (It's also lived up to its reputation for readability, in that the Python code has had 3 different developers -- in serial -- with very quick ramp-ups in each case. Python's succinctness and and one-obvious-way-to-do-it philosophy played a big part in this.) If you try it out, pleas

A week of Windows Subsystem for Linux

I first experimented with WSL2 as a daily development environment two years ago. Things were still pretty rough around the edges, especially with JetBrains' IDEs, and I ended up buying a dedicated Linux workstation so I wouldn't have to deal with the pain.  Unfortunately, the Linux box developed a heat management problem, and simultaneously I found myself needing a beefier GPU than it had for working on multi-vector encoding , so I decided to give WSL2 another try. Here's some of the highlights and lowlights. TLDR, it's working well enough that I'm probably going to continue using it as my primary development machine going forward. The Good NVIDIA CUDA drivers just work. I was blown away that I ran conda install cuda -c nvidia and it worked the first try. No farting around with Linux kernel header versions or arcane errors from nvidia-smi. It just worked, including with PyTorch. JetBrains products work a lot better now in remote development mod

A review of 6 Python IDEs

(March 2006: you may also be interested the updated review I did for PyCon -- http://spyced.blogspot.com/2006/02/pycon-python-ide-review.html .) For September's meeting, the Utah Python User Group hosted an IDE shootout. 5 presenters reviewed 6 IDEs: PyDev 0.9.8.1 Eric3 3.7.1 Boa Constructor 0.4.4 BlackAdder 1.1 Komodo 3.1 Wing IDE 2.0.3 (The windows version was tested for all but Eric3, which was tested on Linux. Eric3 is based on Qt, which basically means you can't run it on Windows unless you've shelled out $$$ for a commerical Qt license, since there is no GPL version of Qt for Windows. Yes, there's Qt Free , but that's not exactly production-ready software.) Perhaps the most notable IDEs not included are SPE and DrPython. Alas, nobody had time to review these, but if you're looking for a free IDE perhaps you should include these in your search, because PyDev was the only one of the 3 free ones that we'd consider using. And if you aren