[Twisted-web] CorePost - a tiny Flask-style REST microframework for twisted.web

Jacek Furmankiewicz jacek99 at gmail.com
Mon Sep 5 13:41:48 EDT 2011


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 at 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 at 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 at twistedmatrix.com
> > http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-web
> >
> >
> > _______________________________________________
> > Twisted-web mailing list
> > Twisted-web at twistedmatrix.com
> > http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-web
>
>
> _______________________________________________
> Twisted-web mailing list
> Twisted-web at twistedmatrix.com
> http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-web
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://twistedmatrix.com/pipermail/twisted-web/attachments/20110905/13393a58/attachment-0001.htm 


More information about the Twisted-web mailing list