[Python-3000] pep 3124 plans

Jonathan LaCour jonathan-lists at cleverdevil.org
Thu Jul 19 22:00:53 CEST 2007


Guido van Rossum wrote:

> FWIW, I think the Turbogears use you're thinking of is jsonify,
> a GF for converting arbitrary Python data into JSON (JavaScript
> Object Notation). But I'm not aware of it using any of the
> advanced features -- it seems to be using just the basic facility
> of overloading on a single argument type, which could be done
> with my own "overloading" example (see the Python subversion
> sandbox). At least that's what I got from skimming the docs:
> http://docs.turbogears.org/1.0/JsonifyDecorator . That article claims
> that TurboGears uses RuleDispatch extensively. I'd love to hear from
> them about how they use the advanced features.

There are several places in TurboGears that we use generic functions:

TurboJSON
---------
TurboGears controllers work by returning dictionaries, which are
then passed to template engines to generate and render responses.
TurboJSON is a Buffet-compatible template plugin that jsonifies data
that is returned from a TurboGears controller.  The jsonify function
is a generic function that is used to perform the serialization,
and is commonly extended to provide custom JSON serialization in a
cross-cutting way:

    # TurboGears Controller
    class PeopleController(controllers.Controller):

        @expose('json')
        def person(self, person_id):
            person = Person.get(person_id)
            return dict(person=person)


    # generic function for JSONifying Person objects
    @jsonify.when('isinstance(obj, Person)')
    def jsonify_person(obj):
        return dict(
            name=person.name,
            age=person.age,
            birthdate=person.birthdate.strftime('%Y-%M-%D')
        )

I use this feature heavily, and find it to be easy to understand once
you get used to the concept of generic functions.

Of course, we don't restrict @jsonify.when() to isinstance checking.
I've seen production code which checks the value of an object before
jsonifying it, or which checks and attribute on the object to determine
how it should be rendered in the JSON.  For example if one of our users
has a bunch of different contacts in a contact object, but she wants
different JSON for contacts who are also leads, she can use predicate
dispatch in the @jsonify.when decorator to do that...


Picking a Template Engine
-------------------------

TurboGears supports a variety of templating engines in a cross-framework
way using a standard API called Buffet.  TurboGears controllers can
specify different templating engines and different templates for a
controller method if they so desire, and we use generic functions to
implement this on the backend so that you can regester multiple template
options for rendering the same controller method.

    class Root(controllers.RootController):
        @expose(template='mako:path.to.mako.template.html')
        def get_mako(self):
            return dict(...)

        @expose("actionflow.templates.tasks")
        @expose("cheetah:actionflow.templates.tasktext",
                accept_format="text/plain")
        @expose("kid:actionflow.templates.taskfeed,
               accept_format="rss")
        @expose("json", accept_format = "text/javascript",
               as_format="json")
        def task(self):
            return dict(...)

Rule dispatch gets used to check what format is requested (either in the
headers, or explicitly via a tg_format parameter) and calls the correct
rendering function in the correct way to turn the dict that's returned
into what the client asked for.  We're going to be improving this and
making it even more powerful in TurboGears 2.0.


Validation and Error Handling
-----------------------------

TurboGears has a built-in framework for validating parameters that are
passed in over HTTP.  This integrates with an underlying widget system
which can be used to generate forms, called ToscaWidgets, that you can
use to validate against.  You can find good documentation and examples
here: http://docs.turbogears.org/1.0/ErrorHandling

Here is an example:

import turbogears
from turbogears import controllers, expose, validate, redirect
from turbogears import exception_handler

class Root(controllers.RootController):
      def vh(self, tg_exceptions=None):
          return dict(
              handling_value=True,
              exception=str(tg_exceptions)
          )

      def ih(self, tg_exceptions=None):
          return dict(
              handling_index=True,
              exception=str(tg_exceptions)
          )

      @expose()
      @exception_handler(vh, "isinstance(tg_exceptions, ValueError)")
      @exception_handler(ih, "isinstance(tg_exceptions, IndexError)")
      def exceptional(self, number=2):
          number = int(number)
          if number < 42:
              raise IndexError("Number too Low!")
          if number == 42:
              raise IndexError("Wise guy, eh?")
          if number > 100:
              raise Exception("This number is exceptionally high!")
          return dict(result="No errors!")


Lots of users are currently making use of this functionality in
TurboGears, and it seems to be fairly well received.  And again, you
can use predicate dispatch to regoster different error_handlers for
different kinds of errors.

I for one, as a committer on TurboGears, would absolutely love to see
a good, solid generic function capability integrated into the standard
library, and find PEP 3124 to completely cover my needs.  There are
certainly things in the PEP that I do not have a use for, but nothing in
the PEP seems to be much of a stretch to me.

Just my 2 cents (or maybe 50 cents...)

--
Jonathan LaCour
http://cleverdevil.org


More information about the Python-3000 mailing list