[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