And the re-designed version 0.0.6 is out on PyPI.

More detailed docs on github:
https://github.com/jacek99/corepost

Cheers,
Jacek

On Mon, Sep 5, 2011 at 1:41 PM, Jacek Furmankiewicz <jacek99@gmail.com> wrote:
Hi Ian, I think the problem though is that your solution relies on static class-level methods and variables, whereas I need more instance-level.

Here's my solution (seems to be working so far)

def route(url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache=True):
    '''
    Main decorator for registering REST functions
    '''
    def decorator(f):
        def wrap(*args,**kwargs):
            return f
        router = RequestRouter(f, url, methods, accepts, produces, cache)
        setattr(wrap,'corepostRequestRouter',router)
        
        return wrap
    return decorator

and then the following method is called from the instance constructor:

   def __registerRouters(self):
        from types import FunctionType
        for _,func in self.__class__.__dict__.iteritems():
            if type(func) == FunctionType and hasattr(func,'corepostRequestRouter'):
                rq = func.corepostRequestRouter
                for method in rq.methods:
                    self.__urls[method][rq.url] = rq
                    self.__routers[func] = rq # needed so that we can lookup the router for a specific function

I am updating docs and examples, 0.0.6 should be out soon with these changes, 
here's what the API looks like now with these changes:

class HomeApp(CorePost):

    @route("/")
    def home_root(self,request,**kwargs):
        return "HOME %s" % kwargs

class Module1(CorePost):

    @route("/",Http.GET)
    def module1_get(self,request,**kwargs):
        return request.path
    
    @route("/sub",Http.GET)
    def module1e_sub(self,request,**kwargs):
        return request.path

class Module2(CorePost):
    
    @route("/",Http.GET)
    def module2_get(self,request,**kwargs):
        return request.path
    
    @route("/sub",Http.GET)
    def module2_sub(self,request,**kwargs):
        return request.path

def run_app_multi():
    app = Resource()
    app.putChild('', HomeApp())
    app.putChild('module1',Module1())
    app.putChild('module2',Module2())

    factory = Site(app)
    reactor.listenTCP(8081, factory)  
    reactor.run()                   

Jacek


On Mon, Sep 5, 2011 at 1:25 PM, Ian Rees <ian@ianrees.net> wrote:
Actually, it turns out our solution was a little more complicated :P Here is our basic implementation (our actual class is a bit more involved and allows multiple routes per method, regular expressions, etc..). The view's method decorators are evaluated before the view's class decorator. The class decorator looks at all the methods and sees which have been decorated with the route.

class Routing(object):
       routes = {}

       @classmethod
       def register(cls, viewclass):
               # Check a class for any methods that have routes as attributes
               #       and add these to the routing dictionary
               for k,v in viewclass.__dict__.items():
                       if hasattr(v, 'route'):
                               print "Registering %s method %s with route %s"%(viewclass, v.__name__, v.route)
                               cls.routes[v.route] = (viewclass, v.__name__)
               return cls

       @classmethod
       def addroute(cls, route):
               # This decorator adds the specified routes as attributes to the methods.
               # These routes will be found when the view class is registered.
               def wrap(handler):
                       handler.route = route
                       return handler
               return wrap

       @classmethod
       def resolve(cls, route):
               for r in cls.routes:
                       if r == route:
                               print "Found handler for %s:"%route, cls.routes[r]
                               view, method = cls.routes[r]
                               inst = view()
                               return getattr(inst, method)


@Routing.register
class View(object):
       @Routing.addroute(r'/blog/post/')
       def dosomething(self, title=None, body=None):
               print "Adding blog post:", title, body


handler = Routing.resolve('/blog/post/')
handler(title="Test", body="ok")



Thanks,
Ian

On Sep 5, 2011, at 11:14 AM, Jacek Furmankiewicz wrote:

> Hi Glyph,
>
> I looked at your suggestion, but unfortunately the implementation is very complex, if not impossible.
>
> The main problem is that
> a) a class method with a decorator "forgets" its class, so it's impossible from the decorator which class it belongs to.
> The function has not been bound to a class yet when the decorator is called for the first time, so there is no way for it to notify the containing class that this function defines a route for it
>
> b) is is next to impossible for a class to scan its own function and find their decorators. I've seen some hacks on StackOverflow
> where it actually parses the source code, but that is an ugly hack to say the least (and probably prone to many bugs)
>
> In general, it seems decorators on class methods are missing such crucial functionality as finding out which class the method belongs to.
> Sort of a key requirement, if you ask me (at least after lots of experience with Java or .Net reflection, where getting this sort of info is trivial).
>
> if you have any suggestions on how to accomplish your recommendation, I would greatly appreciate it.
>
> The decorator in question that I would need to take out of the CorePost class and make it a standalone function looks like this:
>
>     def route(self,url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache=True):
>         """Main decorator for registering REST functions """
>         def wrap(f,*args,**kwargs):
>             self.__registerFunction(f, url, methods, accepts, produces,cache)
>             return f
>         return wrap
>
> it's obtaining the reference to 'self' when it is not a class method any more is the problem. Not sure how to get around it.
>
> Cheers,
> Jacek
>
> On Sun, Sep 4, 2011 at 12:01 AM, Glyph Lefkowitz <glyph@twistedmatrix.com> wrote:
>
> On Sep 3, 2011, at 8:28 PM, Jacek Furmankiewicz wrote:
>
>> Any feedback is welcome
>
> Hi Jacek,
>
> Great to see more development going into Twisted-based web stuff! :)
>
> However, I do have one question.  Maybe I'm missing something about the way Flask does things, but it seems very odd to me that the decorators you're using are applied to global functions, rather than instances of an object.  For example, instead of:
>
> app = CorePost()
> ...
> @app.route("/validate/<int:rootId>/schema",Http.POST)
> @validate(schema=TestSchema)
> def postValidateSchema(request,rootId,childId,**kwargs):
>     '''Validate using a common schema'''
>     return "%s - %s - %s" % (rootId,childId,kwargs)
>
> You could do:
>
> class MyPost(CorePost):
>     @route("/validate/<int:rootId>/schema",Http.POST)
>     @validate(schema=TestSchema)
>     def postValidateSchema(self,request,rootId,childId,**kwargs):
>         '''Validate using a common schema'''
>         return "%s - %s - %s" % (rootId,childId,kwargs)
>
> This would allow for re-usable objects; for example, rather than having a "blog article create" API (sorry for the uninspired example, it's late) for your entire site, you would have a "article create" API on a "Blog", which would enable you to have multiple Blog objects (perhaps with different authors, in different permission domains, etc).  This would also make re-using the relevant objects between different applications easier.
>
> In other words, global variables are bad, and this looks like it depends rather heavily on them.
>
> Any thoughts on this?  Am I missing the point?
>
> Thanks,
>
> -glyph
>
>
> _______________________________________________
> Twisted-web mailing list
> Twisted-web@twistedmatrix.com
> http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-web
>
>
> _______________________________________________
> Twisted-web mailing list
> Twisted-web@twistedmatrix.com
> http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-web


_______________________________________________
Twisted-web mailing list
Twisted-web@twistedmatrix.com
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-web