Issue #65 resolved

Make CherryPy use keys to find objects

Anonymous created an issue

For my project, I need to use URLs with only digits for some of the names, like "/foo/bar/42/baz/". Currently, this is impossible without using default, but default doesn't allow seperate objects to be used. It would be helpful if the publishing mechanism would try looking for keys in addition to attributes, so that the URL "/foo/bar/baz/" would try cpg.root.foo['bar']['baz'], cpg.root.foo['bar'].baz, etc.

Reported by eurleif@ecritters.biz

Comments (14)

  1. Anonymous

    There is another sollution to this problem.

    for a /foo/bar/42/baz like setup you could use:

    class Root(object):
            def foo(self,bar,nr,other):
                    return "[bar,nr,other]: %s" % `[bar,nr,other]`
            foo.exposed = True
    
            def fooExt(self,*p):
                    return "params: %s " % (`p`)
            fooExt.exposed = True
    

    CherryPy searches from right to left in the object chain. So if you don't provide anything after the foo, foo is the first to be found. The rest of the path will be split and passed as parameters if you have exactly enough positional parameters in the function, or accept all parameters using *p as a parameter..

    With the above code:

    • /foo/bar/43/biz and /foo/bar/43/biz/ will both map to foo
    • /foo/bar/2 or something like /foo/ or /foo/bar/34/biz/even/more/parameters/ will fail
    • /fooExt/whatever/parameters/you/enter/they/are/always/excepted well..

    Hope this helps..

  2. Anonymous

    That workaround would work in some cases, but wouldn't really do what I want. Here's the general design I'm looking for:

    class Bar(object):
        def __init__(self, id):
            self.id = id
        def index(self):
            # Do something here
        def baz(self):
            # Do something here
    
    class Foo(object):
        def index(self):
            # HTML listing the various Bar pages
        def __getitem__(self, id):
            return Bar(id)
    

    In other words, I want each /foo/12 URL to map to a certain Bar object, and not just to one of Foo's methods.

  3. Anonymous

    You can do the delegation yourself, by using something like this. You even still have full control, for parameters that are not allowed, you can return a specialised error msg.

    But i will put your question on the mailing list anyway.

    class Bar(object):
        def __init__(self, id):
            self.id = id
        def index(self):
            # Do something here
        def baz(self):
            # Do something here
    
    class Foo(object):
        def index(self,*p):
            if p:
                bar = Bar(p[0]) 
                func = getattr(bar,p[1])
                funcpars =  p[2:]
                if func:
                   return func(*funcpars)
                else:
                   return 'Object not found'
            else:
                # HTML listing the various Bar pages
    
  4. Anonymous

    It's an interesting generalization. getitem checking also works on dicts, so /foo/anything could be mapped to cpg.root.foo[anything] (''where foo is a dict''). We have to keep in mind that this may also cause some undesirable side effects as far as the current lookup works; if we can be sure that no such side effect exists, I'm +1 on it. If there's any side effect, -1.

    On the other hand, it can be argued that the user himself can convert the objects published to support getattr; for example, nothing stops a UserList or UserDict to support getattr with similar results to the ones achieved by getitem with similar arguments.

    This ticket also raises an interesting discussion: today it's difficult for a user to override the existing object mapping function. The current function is inside a module, and the only way to override it is to do some black magic and rebind the symbol inside the module to a new function. I guess we could make it a little bit easier to register such functions. One possibility is to bind such utility symbols directly to the cpg structure, at its top level; in this way it would be easier for the user to customize or override it. For example:

    cpg.mapPathToObject = myObjMapperFunction

    Please not that I'm not talking about making the mapPathToObject() function into a "special" function as filters; there should be only one such function, not one per published object. What I'm proposing is just to bind the symbol to the cpg structure so it can be easily bound to another function by the site programmer.

  5. Anonymous

    Workaround

    The CherryPy library can provide a utility ExposeItems class. It takes any getitem-aware class and publishes it. It's a simple wrapper. The following code is a starting point:

    class ExposeItems:
        exposed = True
        def __init__(self, obj):
            self.obj = obj
        def __getattr__(self, key):
            return self.obj[key]
    

    If this proposal is accepted, this class would be available from the CP library, and this ticket will be marked as 'fixed' or 'wontfix'. The use case is:

    from cherrypy.lib import ExposeItems
    ...
    cpg.root.foo = ExposeItems(mylist)
    cpg.root.bar = ExposeItems(mydict)
    

    Of course, this code assumes that the members of mylist and mydict are itself exposed objects (it only publishes the *list* or *dict*, not it's members). The programmer still have to publishing the members by himself.

  6. Anonymous

    nice sollution.. .

    you could even try :

    from cherrypy.lib import ExposeItems as ExposedItems
    class Root:
       @cpg.exposed
       @ExposedItems
       def foo(self,...):
          ...
    
  7. Anonymous

    Fixed in revision [117], using the ExposeItems workaround. Read the comments on the class docstring itself for hints on usage (also available on the ExposeItems wiki page).

  8. Log in to comment