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

Jacek Furmankiewicz jacek99 at gmail.com
Mon Sep 5 13:46:43 EDT 2011


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 at 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 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/4d641859/attachment.htm 


More information about the Twisted-web mailing list