mirror of https://github.com/evilhero/mylar
404 lines
12 KiB
Python
404 lines
12 KiB
Python
|
import cherrypy
|
||
|
from cherrypy._cptree import Application
|
||
|
from cherrypy.test import helper
|
||
|
|
||
|
script_names = ["", "/foo", "/users/fred/blog", "/corp/blog"]
|
||
|
|
||
|
|
||
|
|
||
|
def setup_server():
|
||
|
class SubSubRoot:
|
||
|
def index(self):
|
||
|
return "SubSubRoot index"
|
||
|
index.exposed = True
|
||
|
|
||
|
def default(self, *args):
|
||
|
return "SubSubRoot default"
|
||
|
default.exposed = True
|
||
|
|
||
|
def handler(self):
|
||
|
return "SubSubRoot handler"
|
||
|
handler.exposed = True
|
||
|
|
||
|
def dispatch(self):
|
||
|
return "SubSubRoot dispatch"
|
||
|
dispatch.exposed = True
|
||
|
|
||
|
subsubnodes = {
|
||
|
'1': SubSubRoot(),
|
||
|
'2': SubSubRoot(),
|
||
|
}
|
||
|
|
||
|
class SubRoot:
|
||
|
def index(self):
|
||
|
return "SubRoot index"
|
||
|
index.exposed = True
|
||
|
|
||
|
def default(self, *args):
|
||
|
return "SubRoot %s" % (args,)
|
||
|
default.exposed = True
|
||
|
|
||
|
def handler(self):
|
||
|
return "SubRoot handler"
|
||
|
handler.exposed = True
|
||
|
|
||
|
def _cp_dispatch(self, vpath):
|
||
|
return subsubnodes.get(vpath[0], None)
|
||
|
|
||
|
subnodes = {
|
||
|
'1': SubRoot(),
|
||
|
'2': SubRoot(),
|
||
|
}
|
||
|
class Root:
|
||
|
def index(self):
|
||
|
return "index"
|
||
|
index.exposed = True
|
||
|
|
||
|
def default(self, *args):
|
||
|
return "default %s" % (args,)
|
||
|
default.exposed = True
|
||
|
|
||
|
def handler(self):
|
||
|
return "handler"
|
||
|
handler.exposed = True
|
||
|
|
||
|
def _cp_dispatch(self, vpath):
|
||
|
return subnodes.get(vpath[0])
|
||
|
|
||
|
#--------------------------------------------------------------------------
|
||
|
# DynamicNodeAndMethodDispatcher example.
|
||
|
# This example exposes a fairly naive HTTP api
|
||
|
class User(object):
|
||
|
def __init__(self, id, name):
|
||
|
self.id = id
|
||
|
self.name = name
|
||
|
|
||
|
def __unicode__(self):
|
||
|
return unicode(self.name)
|
||
|
|
||
|
user_lookup = {
|
||
|
1: User(1, 'foo'),
|
||
|
2: User(2, 'bar'),
|
||
|
}
|
||
|
|
||
|
def make_user(name, id=None):
|
||
|
if not id:
|
||
|
id = max(*user_lookup.keys()) + 1
|
||
|
user_lookup[id] = User(id, name)
|
||
|
return id
|
||
|
|
||
|
class UserContainerNode(object):
|
||
|
exposed = True
|
||
|
|
||
|
def POST(self, name):
|
||
|
"""
|
||
|
Allow the creation of a new Object
|
||
|
"""
|
||
|
return "POST %d" % make_user(name)
|
||
|
|
||
|
def GET(self):
|
||
|
keys = user_lookup.keys()
|
||
|
keys.sort()
|
||
|
return unicode(keys)
|
||
|
|
||
|
def dynamic_dispatch(self, vpath):
|
||
|
try:
|
||
|
id = int(vpath[0])
|
||
|
except (ValueError, IndexError):
|
||
|
return None
|
||
|
return UserInstanceNode(id)
|
||
|
|
||
|
class UserInstanceNode(object):
|
||
|
exposed = True
|
||
|
def __init__(self, id):
|
||
|
self.id = id
|
||
|
self.user = user_lookup.get(id, None)
|
||
|
|
||
|
# For all but PUT methods there MUST be a valid user identified
|
||
|
# by self.id
|
||
|
if not self.user and cherrypy.request.method != 'PUT':
|
||
|
raise cherrypy.HTTPError(404)
|
||
|
|
||
|
def GET(self, *args, **kwargs):
|
||
|
"""
|
||
|
Return the appropriate representation of the instance.
|
||
|
"""
|
||
|
return unicode(self.user)
|
||
|
|
||
|
def POST(self, name):
|
||
|
"""
|
||
|
Update the fields of the user instance.
|
||
|
"""
|
||
|
self.user.name = name
|
||
|
return "POST %d" % self.user.id
|
||
|
|
||
|
def PUT(self, name):
|
||
|
"""
|
||
|
Create a new user with the specified id, or edit it if it already exists
|
||
|
"""
|
||
|
if self.user:
|
||
|
# Edit the current user
|
||
|
self.user.name = name
|
||
|
return "PUT %d" % self.user.id
|
||
|
else:
|
||
|
# Make a new user with said attributes.
|
||
|
return "PUT %d" % make_user(name, self.id)
|
||
|
|
||
|
def DELETE(self):
|
||
|
"""
|
||
|
Delete the user specified at the id.
|
||
|
"""
|
||
|
id = self.user.id
|
||
|
del user_lookup[self.user.id]
|
||
|
del self.user
|
||
|
return "DELETE %d" % id
|
||
|
|
||
|
|
||
|
class ABHandler:
|
||
|
class CustomDispatch:
|
||
|
def index(self, a, b):
|
||
|
return "custom"
|
||
|
index.exposed = True
|
||
|
|
||
|
def _cp_dispatch(self, vpath):
|
||
|
"""Make sure that if we don't pop anything from vpath,
|
||
|
processing still works.
|
||
|
"""
|
||
|
return self.CustomDispatch()
|
||
|
|
||
|
def index(self, a, b=None):
|
||
|
body = [ 'a:' + str(a) ]
|
||
|
if b is not None:
|
||
|
body.append(',b:' + str(b))
|
||
|
return ''.join(body)
|
||
|
index.exposed = True
|
||
|
|
||
|
def delete(self, a, b):
|
||
|
return 'deleting ' + str(a) + ' and ' + str(b)
|
||
|
delete.exposed = True
|
||
|
|
||
|
class IndexOnly:
|
||
|
def _cp_dispatch(self, vpath):
|
||
|
"""Make sure that popping ALL of vpath still shows the index
|
||
|
handler.
|
||
|
"""
|
||
|
while vpath:
|
||
|
vpath.pop()
|
||
|
return self
|
||
|
|
||
|
def index(self):
|
||
|
return "IndexOnly index"
|
||
|
index.exposed = True
|
||
|
|
||
|
class DecoratedPopArgs:
|
||
|
"""Test _cp_dispatch with @cherrypy.popargs."""
|
||
|
def index(self):
|
||
|
return "no params"
|
||
|
index.exposed = True
|
||
|
|
||
|
def hi(self):
|
||
|
return "hi was not interpreted as 'a' param"
|
||
|
hi.exposed = True
|
||
|
DecoratedPopArgs = cherrypy.popargs('a', 'b', handler=ABHandler())(DecoratedPopArgs)
|
||
|
|
||
|
class NonDecoratedPopArgs:
|
||
|
"""Test _cp_dispatch = cherrypy.popargs()"""
|
||
|
|
||
|
_cp_dispatch = cherrypy.popargs('a')
|
||
|
|
||
|
def index(self, a):
|
||
|
return "index: " + str(a)
|
||
|
index.exposed = True
|
||
|
|
||
|
class ParameterizedHandler:
|
||
|
"""Special handler created for each request"""
|
||
|
|
||
|
def __init__(self, a):
|
||
|
self.a = a
|
||
|
|
||
|
def index(self):
|
||
|
if 'a' in cherrypy.request.params:
|
||
|
raise Exception("Parameterized handler argument ended up in request.params")
|
||
|
return self.a
|
||
|
index.exposed = True
|
||
|
|
||
|
class ParameterizedPopArgs:
|
||
|
"""Test cherrypy.popargs() with a function call handler"""
|
||
|
ParameterizedPopArgs = cherrypy.popargs('a', handler=ParameterizedHandler)(ParameterizedPopArgs)
|
||
|
|
||
|
Root.decorated = DecoratedPopArgs()
|
||
|
Root.undecorated = NonDecoratedPopArgs()
|
||
|
Root.index_only = IndexOnly()
|
||
|
Root.parameter_test = ParameterizedPopArgs()
|
||
|
|
||
|
Root.users = UserContainerNode()
|
||
|
|
||
|
md = cherrypy.dispatch.MethodDispatcher('dynamic_dispatch')
|
||
|
for url in script_names:
|
||
|
conf = {'/': {
|
||
|
'user': (url or "/").split("/")[-2],
|
||
|
},
|
||
|
'/users': {
|
||
|
'request.dispatch': md
|
||
|
},
|
||
|
}
|
||
|
cherrypy.tree.mount(Root(), url, conf)
|
||
|
|
||
|
class DynamicObjectMappingTest(helper.CPWebCase):
|
||
|
setup_server = staticmethod(setup_server)
|
||
|
|
||
|
def testObjectMapping(self):
|
||
|
for url in script_names:
|
||
|
prefix = self.script_name = url
|
||
|
|
||
|
self.getPage('/')
|
||
|
self.assertBody('index')
|
||
|
|
||
|
self.getPage('/handler')
|
||
|
self.assertBody('handler')
|
||
|
|
||
|
# Dynamic dispatch will succeed here for the subnodes
|
||
|
# so the subroot gets called
|
||
|
self.getPage('/1/')
|
||
|
self.assertBody('SubRoot index')
|
||
|
|
||
|
self.getPage('/2/')
|
||
|
self.assertBody('SubRoot index')
|
||
|
|
||
|
self.getPage('/1/handler')
|
||
|
self.assertBody('SubRoot handler')
|
||
|
|
||
|
self.getPage('/2/handler')
|
||
|
self.assertBody('SubRoot handler')
|
||
|
|
||
|
# Dynamic dispatch will fail here for the subnodes
|
||
|
# so the default gets called
|
||
|
self.getPage('/asdf/')
|
||
|
self.assertBody("default ('asdf',)")
|
||
|
|
||
|
self.getPage('/asdf/asdf')
|
||
|
self.assertBody("default ('asdf', 'asdf')")
|
||
|
|
||
|
self.getPage('/asdf/handler')
|
||
|
self.assertBody("default ('asdf', 'handler')")
|
||
|
|
||
|
# Dynamic dispatch will succeed here for the subsubnodes
|
||
|
# so the subsubroot gets called
|
||
|
self.getPage('/1/1/')
|
||
|
self.assertBody('SubSubRoot index')
|
||
|
|
||
|
self.getPage('/2/2/')
|
||
|
self.assertBody('SubSubRoot index')
|
||
|
|
||
|
self.getPage('/1/1/handler')
|
||
|
self.assertBody('SubSubRoot handler')
|
||
|
|
||
|
self.getPage('/2/2/handler')
|
||
|
self.assertBody('SubSubRoot handler')
|
||
|
|
||
|
self.getPage('/2/2/dispatch')
|
||
|
self.assertBody('SubSubRoot dispatch')
|
||
|
|
||
|
# The exposed dispatch will not be called as a dispatch
|
||
|
# method.
|
||
|
self.getPage('/2/2/foo/foo')
|
||
|
self.assertBody("SubSubRoot default")
|
||
|
|
||
|
# Dynamic dispatch will fail here for the subsubnodes
|
||
|
# so the SubRoot gets called
|
||
|
self.getPage('/1/asdf/')
|
||
|
self.assertBody("SubRoot ('asdf',)")
|
||
|
|
||
|
self.getPage('/1/asdf/asdf')
|
||
|
self.assertBody("SubRoot ('asdf', 'asdf')")
|
||
|
|
||
|
self.getPage('/1/asdf/handler')
|
||
|
self.assertBody("SubRoot ('asdf', 'handler')")
|
||
|
|
||
|
def testMethodDispatch(self):
|
||
|
# GET acts like a container
|
||
|
self.getPage("/users")
|
||
|
self.assertBody("[1, 2]")
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
# POST to the container URI allows creation
|
||
|
self.getPage("/users", method="POST", body="name=baz")
|
||
|
self.assertBody("POST 3")
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
# POST to a specific instanct URI results in a 404
|
||
|
# as the resource does not exit.
|
||
|
self.getPage("/users/5", method="POST", body="name=baz")
|
||
|
self.assertStatus(404)
|
||
|
|
||
|
# PUT to a specific instanct URI results in creation
|
||
|
self.getPage("/users/5", method="PUT", body="name=boris")
|
||
|
self.assertBody("PUT 5")
|
||
|
self.assertHeader('Allow', 'DELETE, GET, HEAD, POST, PUT')
|
||
|
|
||
|
# GET acts like a container
|
||
|
self.getPage("/users")
|
||
|
self.assertBody("[1, 2, 3, 5]")
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
test_cases = (
|
||
|
(1, 'foo', 'fooupdated', 'DELETE, GET, HEAD, POST, PUT'),
|
||
|
(2, 'bar', 'barupdated', 'DELETE, GET, HEAD, POST, PUT'),
|
||
|
(3, 'baz', 'bazupdated', 'DELETE, GET, HEAD, POST, PUT'),
|
||
|
(5, 'boris', 'borisupdated', 'DELETE, GET, HEAD, POST, PUT'),
|
||
|
)
|
||
|
for id, name, updatedname, headers in test_cases:
|
||
|
self.getPage("/users/%d" % id)
|
||
|
self.assertBody(name)
|
||
|
self.assertHeader('Allow', headers)
|
||
|
|
||
|
# Make sure POSTs update already existings resources
|
||
|
self.getPage("/users/%d" % id, method='POST', body="name=%s" % updatedname)
|
||
|
self.assertBody("POST %d" % id)
|
||
|
self.assertHeader('Allow', headers)
|
||
|
|
||
|
# Make sure PUTs Update already existing resources.
|
||
|
self.getPage("/users/%d" % id, method='PUT', body="name=%s" % updatedname)
|
||
|
self.assertBody("PUT %d" % id)
|
||
|
self.assertHeader('Allow', headers)
|
||
|
|
||
|
# Make sure DELETES Remove already existing resources.
|
||
|
self.getPage("/users/%d" % id, method='DELETE')
|
||
|
self.assertBody("DELETE %d" % id)
|
||
|
self.assertHeader('Allow', headers)
|
||
|
|
||
|
|
||
|
# GET acts like a container
|
||
|
self.getPage("/users")
|
||
|
self.assertBody("[]")
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
def testVpathDispatch(self):
|
||
|
self.getPage("/decorated/")
|
||
|
self.assertBody("no params")
|
||
|
|
||
|
self.getPage("/decorated/hi")
|
||
|
self.assertBody("hi was not interpreted as 'a' param")
|
||
|
|
||
|
self.getPage("/decorated/yo/")
|
||
|
self.assertBody("a:yo")
|
||
|
|
||
|
self.getPage("/decorated/yo/there/")
|
||
|
self.assertBody("a:yo,b:there")
|
||
|
|
||
|
self.getPage("/decorated/yo/there/delete")
|
||
|
self.assertBody("deleting yo and there")
|
||
|
|
||
|
self.getPage("/decorated/yo/there/handled_by_dispatch/")
|
||
|
self.assertBody("custom")
|
||
|
|
||
|
self.getPage("/undecorated/blah/")
|
||
|
self.assertBody("index: blah")
|
||
|
|
||
|
self.getPage("/index_only/a/b/c/d/e/f/g/")
|
||
|
self.assertBody("IndexOnly index")
|
||
|
|
||
|
self.getPage("/parameter_test/argument2/")
|
||
|
self.assertBody("argument2")
|
||
|
|