1. Problem

The set of legal Python identifiers is a subset of the set of legal URI path component names. Thus, "/1foo/2bar/foo-bar" is a perfectly legal (part of a) URI, but neither "1foo", nor "2bar", nor "foo-bar" are legal Python identifiers; that is, attempts to define classes, functions, methods, or variables with leading digits or containing dash/hyphen characters (or spaces, for that matter) will result in SyntaxError.

So if you want or need to use these illegal Python names as components of Quixote URIs, what can you do?

2. Solution

You have a few options:

  • provide a mapping between the URL name and the Python name in _q_exports (new to Quixote 1.0)
  • use _q_lookup()
  • use _q_resolve()
  • play around with Python internals

3. Discussion

Let's make a URI for a Quixote app with an illegal Python name, something like "http://foo/rdf-model".

3.1. Providing a mapping in _q_exports

_q_exports = [
    ("rdf-model", "rdf_model"),
    ]

3.2. Using _q_lookup

Another solution is to define a _q_lookup():

def rdf_model(request): pass

def _q_lookup(request, component):
    if component == "rdf-model":
          return rdf_model(request)

That works and is obvious. And Quixote developers suggest that it won't be a performance problem unless your're serving hundreds of requests per second. That being said, it's a bit hackish, since we're not *really* doing a dynamic lookup.

3.3. Using _q_resolve

The other solution, especially if this illegally named component isn't really dynamic, is to define a _q_resolve():

def rdf_model(request): pass

def _q_resolve(component):
    if component == "rdf-model":
          return rdf_model

The difference compared to the _q_lookup solution is that _q_resolve doesn't dynamically lookup the component for each request; but, rather, does it once for all, storing whatever is returned on the object. As Andrew Kuchling said when he described this approach: "The first time _q_resolve is run, the publisher will do setattr(module, 'rdf-model', <return value>), and _q_resolve won't be called again."

3.4. Python internals

You can always try to muck about with Python internals directly, though I suspect this approach doesn't offer much advantage over the previous two, and it may just make things blow up in your face.

3.4.1. Changing globals

You can always add the appropriate entry to globals():

def rdf_model(request): pass

globals()['rdf-model'] = rdf_model

(Suggested by Neil Schemenauer.)

3.4.2. Changing modules

It's also possible -- this was suggested by Jason Sibre -- to muck about with sys.modules:

def rdf_model(request): pass

_q_exports = ["rdf-model"] #put the illegal name in the exports

import sys
mod = sys.modules[__name__]
setattr(mod, "rdf-name", rdf_model)

Though, near as I can tell, this is never to be preferred to the _q_resolve solution.


CategoryCookbook