Commit 446c73bf authored by Jacek Furmankiewicz's avatar Jacek Furmankiewicz

0.0.6: reworked API to be class/method oriented and avoid global objects

parent fa5d53b9
...@@ -3,30 +3,31 @@ Argument extraction tests ...@@ -3,30 +3,31 @@ Argument extraction tests
@author: jacekf @author: jacekf
''' '''
from corepost.web import CorePost, validate from corepost.web import CorePost, validate, route
from corepost.enums import Http from corepost.enums import Http
from formencode import Schema, validators from formencode import Schema, validators
app = CorePost()
class TestSchema(Schema): class TestSchema(Schema):
allow_extra_fields = True allow_extra_fields = True
childId = validators.Regex(regex="^jacekf|test$") childId = validators.Regex(regex="^jacekf|test$")
@app.route("/int/<int:intarg>/float/<float:floatarg>/string/<stringarg>",Http.GET) class ArgumentApp(CorePost):
def test(request,intarg,floatarg,stringarg,**kwargs):
@route("/int/<int:intarg>/float/<float:floatarg>/string/<stringarg>",Http.GET)
def test(self,request,intarg,floatarg,stringarg,**kwargs):
args = (intarg,floatarg,stringarg) args = (intarg,floatarg,stringarg)
return "%s" % map(lambda x: (type(x),x),args) return "%s" % map(lambda x: (type(x),x),args)
@app.route("/validate/<int:rootId>/schema",Http.POST) @route("/validate/<int:rootId>/schema",Http.POST)
@validate(schema=TestSchema) @validate(schema=TestSchema())
def postValidateSchema(request,rootId,childId,**kwargs): def postValidateSchema(self,request,rootId,childId,**kwargs):
return "%s - %s - %s" % (rootId,childId,kwargs) return "%s - %s - %s" % (rootId,childId,kwargs)
@app.route("/validate/<int:rootId>/custom",Http.POST) @route("/validate/<int:rootId>/custom",Http.POST)
@validate(childId=validators.Regex(regex="^jacekf|test$")) @validate(childId=validators.Regex(regex="^jacekf|test$"))
def postValidateCustom(request,rootId,childId,**kwargs): def postValidateCustom(self,request,rootId,childId,**kwargs):
return "%s - %s - %s" % (rootId,childId,kwargs) return "%s - %s - %s" % (rootId,childId,kwargs)
def run_app_arguments(): def run_app_arguments():
app = ArgumentApp()
app.run(8082) app.run(8082)
\ No newline at end of file
...@@ -3,42 +3,46 @@ Server tests ...@@ -3,42 +3,46 @@ Server tests
@author: jacekf @author: jacekf
''' '''
from corepost.web import CorePost from corepost.web import CorePost, route
from corepost.enums import Http from corepost.enums import Http
from twisted.internet import defer from twisted.internet import defer
app = CorePost() class HomeApp(CorePost):
@app.route("/",Http.GET) @route("/",Http.GET)
@defer.inlineCallbacks @defer.inlineCallbacks
def root(request,**kwargs): def root(self,request,**kwargs):
yield 1 yield 1
request.write("%s" % kwargs) request.write("%s" % kwargs)
request.finish() request.finish()
@app.route("/test",Http.GET) @route("/test",Http.GET)
def test(request,**kwargs): def test(self,request,**kwargs):
return "%s" % kwargs return "%s" % kwargs
@app.route("/test/<int:numericid>/resource/<stringid>",Http.GET) @route("/test/<int:numericid>/resource/<stringid>",Http.GET)
def test_get_resources(request,numericid,stringid,**kwargs): def test_get_resources(self,request,numericid,stringid,**kwargs):
return "%s - %s" % (numericid,stringid) return "%s - %s" % (numericid,stringid)
@app.route("/post",(Http.POST,Http.PUT)) @route("/post",(Http.POST,Http.PUT))
def test_post(request,**kwargs): def test_post(self,request,**kwargs):
return "%s" % kwargs return "%s" % kwargs
@app.route("/put",(Http.POST,Http.PUT)) @route("/put",(Http.POST,Http.PUT))
def test_put(request,**kwargs): def test_put(self,request,**kwargs):
return "%s" % kwargs return "%s" % kwargs
@app.route("/postput",(Http.POST,Http.PUT)) @route("/postput",(Http.POST,Http.PUT))
def test_postput(request,**kwargs): def test_postput(self,request,**kwargs):
return "%s" % kwargs return "%s" % kwargs
@app.route("/delete",Http.DELETE) @route("/delete",Http.DELETE)
def test_delete(request,**kwargs): def test_delete(self,request,**kwargs):
return "%s" % kwargs return "%s" % kwargs
def run_app_home(): def run_app_home():
app = HomeApp()
app.run() app.run()
if __name__ == "__main__":
run_app_home()
\ No newline at end of file
'''
Created on 2011-09-02
Misc tests
@author: jacekf
'''
def dec(f):
print "DEC"
def wrap():
print "WRAP"
v = f()
return v
return wrap
@dec
def test():
print "TEST3232"
if __name__ == "__main__":
test()
test()
test()
\ No newline at end of file
...@@ -2,43 +2,43 @@ ...@@ -2,43 +2,43 @@
A CorePost module1 that can be merged into the main CorePost Resource A CorePost module1 that can be merged into the main CorePost Resource
''' '''
from corepost.web import CorePost from corepost.web import CorePost, route
from corepost.enums import Http from corepost.enums import Http
from twisted.web.resource import Resource from twisted.web.resource import Resource
from twisted.internet import reactor from twisted.internet import reactor
from twisted.web.server import Site from twisted.web.server import Site
home = CorePost() class HomeApp(CorePost):
@home.route("/") @route("/")
def home_root(request,**kwargs): def home_root(self,request,**kwargs):
return "HOME %s" % kwargs return "HOME %s" % kwargs
module1 = CorePost('module1') class Module1(CorePost):
@module1.route("/",Http.GET) @route("/",Http.GET)
def module1_get(request,**kwargs): def module1_get(self,request,**kwargs):
return request.path return request.path
@module1.route("/sub",Http.GET) @route("/sub",Http.GET)
def module1e_sub(request,**kwargs): def module1e_sub(self,request,**kwargs):
return request.path return request.path
module2 = CorePost('module2') class Module2(CorePost):
@module2.route("/",Http.GET) @route("/",Http.GET)
def module2_get(request,**kwargs): def module2_get(self,request,**kwargs):
return request.path return request.path
@module2.route("/sub",Http.GET) @route("/sub",Http.GET)
def module2_sub(request,**kwargs): def module2_sub(self,request,**kwargs):
return request.path return request.path
def run_app_multi(): def run_app_multi():
app = Resource() app = Resource()
app.putChild(home.path, home) app.putChild('', HomeApp())
app.putChild(module1.path,module1) app.putChild('module1',Module1())
app.putChild(module2.path,module2) app.putChild('module2',Module2())
factory = Site(app) factory = Site(app)
reactor.listenTCP(8081, factory) #@UndefinedVariable reactor.listenTCP(8081, factory) #@UndefinedVariable
......
...@@ -3,16 +3,18 @@ Main server classes ...@@ -3,16 +3,18 @@ Main server classes
@author: jacekf @author: jacekf
''' '''
import re, copy, exceptions
from twisted.internet import reactor, defer
from twisted.web.resource import Resource
from twisted.web.server import Site, NOT_DONE_YET
from twisted.web.http import parse_qs
from collections import defaultdict from collections import defaultdict
from enums import MediaType
from corepost.enums import Http from corepost.enums import Http
from corepost.utils import getMandatoryArgumentNames from corepost.utils import getMandatoryArgumentNames
from enums import MediaType
from formencode import FancyValidator, Invalid from formencode import FancyValidator, Invalid
from twisted.internet import reactor, defer
from twisted.web.http import parse_qs
from twisted.web.resource import Resource
from twisted.web.server import Site, NOT_DONE_YET
import re
import copy
import exceptions
class RequestRouter: class RequestRouter:
''' Common class for containing info related to routing a request to a function ''' ''' Common class for containing info related to routing a request to a function '''
...@@ -21,17 +23,16 @@ class RequestRouter: ...@@ -21,17 +23,16 @@ class RequestRouter:
__urlRegexReplace = {"":r"(?P<arg>.+)","int":r"(?P<arg>\d+)","float":r"(?P<arg>\d+.?\d*)"} __urlRegexReplace = {"":r"(?P<arg>.+)","int":r"(?P<arg>\d+)","float":r"(?P<arg>\d+.?\d*)"}
__typeConverters = {"int":int,"float":float} __typeConverters = {"int":int,"float":float}
def __init__(self,f,url,method,accepts,produces,cache): def __init__(self,f,url,methods,accepts,produces,cache):
self.__url = url self.__url = url
self.__method = method self.__methods = methods if isinstance(methods,tuple) else (methods,)
self.__accepts = accepts self.__accepts = accepts
self.__produces = produces self.__produces = produces
self.__cache = cache self.__cache = cache
self.__f = f self.__f = f
self.__argConverters = {} # dict of arg names -> group index self.__argConverters = {} # dict of arg names -> group index
self.__schema = None
self.__validators = {} self.__validators = {}
self.__mandatory = getMandatoryArgumentNames(f)[1:] self.__mandatory = getMandatoryArgumentNames(f)[2:]
#parse URL into regex used for matching #parse URL into regex used for matching
m = RequestRouter.__urlMatcher.findall(url) m = RequestRouter.__urlMatcher.findall(url)
...@@ -57,9 +58,12 @@ class RequestRouter: ...@@ -57,9 +58,12 @@ class RequestRouter:
return self.__cache return self.__cache
@property @property
def schema(self): def methods(self):
''''Returns the formencode Schema, if this URL uses custom validation schema''' return self.__methods
return self.__schema
@property
def url(self):
return self.__url
def addValidator(self,fieldName,validator): def addValidator(self,fieldName,validator):
'''Adds additional field-specific formencode validators''' '''Adds additional field-specific formencode validators'''
...@@ -83,12 +87,12 @@ class RequestRouter: ...@@ -83,12 +87,12 @@ class RequestRouter:
else: else:
return None return None
def call(self,request,**kwargs): def call(self,instance,request,**kwargs):
'''Forwards call to underlying method''' '''Forwards call to underlying method'''
for arg in self.__mandatory: for arg in self.__mandatory:
if arg not in kwargs: if arg not in kwargs:
raise TypeError("Missing mandatory argument '%s'" % arg) raise TypeError("Missing mandatory argument '%s'" % arg)
return self.__f(request,**kwargs) return self.__f(instance,request,**kwargs)
class CachedUrl(): class CachedUrl():
''' '''
...@@ -113,7 +117,7 @@ class CorePost(Resource): ...@@ -113,7 +117,7 @@ class CorePost(Resource):
''' '''
isLeaf = True isLeaf = True
def __init__(self,path='',schema=None): def __init__(self,schema=None):
''' '''
Constructor Constructor
''' '''
...@@ -122,13 +126,22 @@ class CorePost(Resource): ...@@ -122,13 +126,22 @@ class CorePost(Resource):
self.__cachedUrls = defaultdict(dict) self.__cachedUrls = defaultdict(dict)
self.__methods = {} self.__methods = {}
self.__routers = {} self.__routers = {}
self.__path = path
self.__schema = schema self.__schema = schema
self.__registerRouters()
@property @property
def path(self): def path(self):
return self.__path return self.__path
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
def __registerFunction(self,f,url,methods,accepts,produces,cache): def __registerFunction(self,f,url,methods,accepts,produces,cache):
if f not in self.__methods.values(): if f not in self.__methods.values():
if not isinstance(methods,(list,tuple)): if not isinstance(methods,(list,tuple)):
...@@ -142,11 +155,8 @@ class CorePost(Resource): ...@@ -142,11 +155,8 @@ class CorePost(Resource):
self.__methods[url] = f self.__methods[url] = f
def route(self,url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache=True): def route(self,url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache=True):
"""Main decorator for registering REST functions """ '''Obsolete'''
def wrap(f,*args,**kwargs): raise RuntimeError("Do not @app.route() any more, as of 0.0.6 API has been re-designed around class methods, see docs and examples")
self.__registerFunction(f, url, methods, accepts, produces,cache)
return f
return wrap
def render_GET(self,request): def render_GET(self,request):
""" Handles all GET requests """ """ Handles all GET requests """
...@@ -202,7 +212,7 @@ class CorePost(Resource): ...@@ -202,7 +212,7 @@ class CorePost(Resource):
#handle Deferreds natively #handle Deferreds natively
try: try:
val = urlrouter.call(request,**allargs) val = urlrouter.call(self,request,**allargs)
if isinstance(val,defer.Deferred): if isinstance(val,defer.Deferred):
# we assume the method will call request.finish() # we assume the method will call request.finish()
...@@ -244,6 +254,20 @@ class CorePost(Resource): ...@@ -244,6 +254,20 @@ class CorePost(Resource):
# #
################################################################################################## ##################################################################################################
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
def validate(schema=None,**vKwargs): def validate(schema=None,**vKwargs):
''' '''
Main decorator for registering additional validators for incoming URL arguments Main decorator for registering additional validators for incoming URL arguments
......
...@@ -40,6 +40,7 @@ Links ...@@ -40,6 +40,7 @@ Links
Changelog Changelog
````````` `````````
* 0.0.6 - redesigned API around classes and methods, rather than functions and global objects (after feedback from Twisted devs)
* 0.0.5 - added FormEncode validation for arguments * 0.0.5 - added FormEncode validation for arguments
* 0.0.4 - path argument extraction, mandatory argument error checking * 0.0.4 - path argument extraction, mandatory argument error checking
...@@ -57,7 +58,7 @@ def read(fname): ...@@ -57,7 +58,7 @@ def read(fname):
setup( setup(
name="CorePost", name="CorePost",
version="0.0.5", version="0.0.6",
author="Jacek Furmankiewicz", author="Jacek Furmankiewicz",
author_email="jacek99@gmail.com", author_email="jacek99@gmail.com",
description=("A Twisted Web REST micro-framework"), description=("A Twisted Web REST micro-framework"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment