Merging pecan-env into master
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,3 +17,4 @@ pip-log.txt
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
htmlcov
|
||||
|
||||
@@ -25,8 +25,6 @@ def proxy(key):
|
||||
class ObjectProxy(object):
|
||||
def __getattr__(self, attr):
|
||||
obj = getattr(state, key)
|
||||
if attr == 'validation_errors':
|
||||
return getattr(obj, attr, {})
|
||||
return getattr(obj, attr)
|
||||
def __setattr__(self, attr, value):
|
||||
obj = getattr(state, key)
|
||||
@@ -50,9 +48,10 @@ def override_template(template, content_type=None):
|
||||
:param content_type: a valid MIME type to use for the response.
|
||||
'''
|
||||
|
||||
request.override_template = template
|
||||
request.pecan['override_template'] = template
|
||||
if content_type:
|
||||
request.override_content_type = content_type
|
||||
request.pecan['override_content_type'] = content_type
|
||||
|
||||
|
||||
def abort(status_code=None, detail='', headers=None, comment=None):
|
||||
'''
|
||||
@@ -98,11 +97,9 @@ def error_for(field):
|
||||
:param field: The name of the field to get the error for.
|
||||
'''
|
||||
|
||||
if not request.validation_errors:
|
||||
return ''
|
||||
return request.validation_errors.get(field, '')
|
||||
return request.pecan['validation_errors'].get(field, '')
|
||||
|
||||
|
||||
|
||||
def static(name, value):
|
||||
'''
|
||||
When using ``htmlfill`` validation support, this function indicates
|
||||
@@ -129,16 +126,7 @@ def render(template, namespace):
|
||||
:param namespace: The namespace to use for rendering the template, as a dictionary.
|
||||
'''
|
||||
|
||||
renderer = state.app.renderers.get(state.app.default_renderer, state.app.template_path)
|
||||
if template == 'json':
|
||||
renderer = state.app.renderers.get('json', state.app.template_path)
|
||||
else:
|
||||
namespace['error_for'] = error_for
|
||||
namespace['static'] = static
|
||||
if ':' in template:
|
||||
renderer = state.app.renderers.get(template.split(':')[0], state.app.template_path)
|
||||
template = template.split(':')[1]
|
||||
return renderer.render(template, namespace)
|
||||
return state.app.render(template, namespace)
|
||||
|
||||
|
||||
class ValidationException(ForwardRequestException):
|
||||
@@ -156,10 +144,10 @@ class ValidationException(ForwardRequestException):
|
||||
location = cfg['error_handler']
|
||||
if callable(location):
|
||||
location = location()
|
||||
merge_dicts(request.validation_errors, errors)
|
||||
merge_dicts(request.pecan['validation_errors'], errors)
|
||||
if 'pecan.params' not in request.environ:
|
||||
request.environ['pecan.params'] = dict(request.str_params)
|
||||
request.environ['pecan.validation_errors'] = request.validation_errors
|
||||
request.environ['pecan.validation_errors'] = request.pecan['validation_errors']
|
||||
if cfg.get('htmlfill') is not None:
|
||||
request.environ['pecan.htmlfill'] = cfg['htmlfill']
|
||||
request.environ['REQUEST_METHOD'] = 'GET'
|
||||
@@ -214,7 +202,11 @@ class Pecan(object):
|
||||
except NonCanonicalPath, e:
|
||||
if self.force_canonical and not _cfg(e.controller).get('accept_noncanonical', False):
|
||||
if request.method == 'POST':
|
||||
raise RuntimeError, "You have POSTed to a URL '%s' which requires a slash. Most browsers will not maintain POST data when redirected. Please update your code to POST to '%s/' or set force_canonical to False" % (request.routing_path, request.routing_path)
|
||||
raise RuntimeError, "You have POSTed to a URL '%s' which '\
|
||||
'requires a slash. Most browsers will not maintain '\
|
||||
'POST data when redirected. Please update your code '\
|
||||
'to POST to '%s/' or set force_canonical to False" % \
|
||||
(request.pecan['routing_path'], request.pecan['routing_path'])
|
||||
raise exc.HTTPFound(add_slash=True)
|
||||
return e.controller, e.remainder
|
||||
|
||||
@@ -265,9 +257,9 @@ class Pecan(object):
|
||||
args.append(im_self)
|
||||
|
||||
# grab the routing args from nested REST controllers
|
||||
if hasattr(request, 'routing_args'):
|
||||
remainder = request.routing_args + list(remainder)
|
||||
delattr(request, 'routing_args')
|
||||
if 'routing_args' in request.pecan:
|
||||
remainder = request.pecan['routing_args'] + list(remainder)
|
||||
del request.pecan['routing_args']
|
||||
|
||||
# handle positional arguments
|
||||
if valid_args and remainder:
|
||||
@@ -304,6 +296,18 @@ class Pecan(object):
|
||||
|
||||
return args, kwargs
|
||||
|
||||
def render(self, template, namespace):
|
||||
renderer = self.renderers.get(self.default_renderer, self.template_path)
|
||||
if template == 'json':
|
||||
renderer = self.renderers.get('json', self.template_path)
|
||||
else:
|
||||
namespace['error_for'] = error_for
|
||||
namespace['static'] = static
|
||||
if ':' in template:
|
||||
renderer = self.renderers.get(template.split(':')[0], self.template_path)
|
||||
template = template.split(':')[1]
|
||||
return renderer.render(template, namespace)
|
||||
|
||||
def validate(self, schema, params, json=False, error_handler=None,
|
||||
htmlfill=None, variable_decode=None):
|
||||
'''
|
||||
@@ -318,7 +322,6 @@ class Pecan(object):
|
||||
:param variable_decode: Indicates whether or not to decode variables when using htmlfill.
|
||||
'''
|
||||
|
||||
request.validation_errors = {}
|
||||
try:
|
||||
to_validate = params
|
||||
if json:
|
||||
@@ -331,7 +334,7 @@ class Pecan(object):
|
||||
if variable_decode is not None:
|
||||
kwargs['encode_variables'] = True
|
||||
kwargs.update(variable_decode)
|
||||
request.validation_errors = e.unpack_errors(**kwargs)
|
||||
request.pecan['validation_errors'] = e.unpack_errors(**kwargs)
|
||||
if error_handler is not None:
|
||||
raise ValidationException()
|
||||
if json:
|
||||
@@ -347,21 +350,20 @@ class Pecan(object):
|
||||
state.hooks = self.determine_hooks()
|
||||
|
||||
# store the routing path to allow hooks to modify it
|
||||
request.routing_path = request.path
|
||||
request.pecan['routing_path'] = request.path
|
||||
|
||||
# handle "on_route" hooks
|
||||
self.handle_hooks('on_route', state)
|
||||
|
||||
# lookup the controller, respecting content-type as requested
|
||||
# by the file extension on the URI
|
||||
path = request.routing_path
|
||||
path = request.pecan['routing_path']
|
||||
|
||||
if state.content_type is None and '.' in path.split('/')[-1]:
|
||||
if not request.pecan['content_type'] and '.' in path.split('/')[-1]:
|
||||
path, extension = splitext(path)
|
||||
|
||||
request.extension = extension
|
||||
request.pecan['extension'] = extension
|
||||
# preface with a letter to ensure compat for 2.5
|
||||
state.content_type = guess_type('x' + extension)[0]
|
||||
request.pecan['content_type'] = guess_type('x' + extension)[0]
|
||||
|
||||
controller, remainder = self.route(self.root, path)
|
||||
cfg = _cfg(controller)
|
||||
@@ -381,8 +383,11 @@ class Pecan(object):
|
||||
state.controller = controller
|
||||
|
||||
# if unsure ask the controller for the default content type
|
||||
if state.content_type is None:
|
||||
state.content_type = cfg.get('content_type', 'text/html')
|
||||
if not request.pecan['content_type']:
|
||||
request.pecan['content_type'] = cfg.get('content_type', 'text/html')
|
||||
elif cfg.get('content_type') is not None and \
|
||||
request.pecan['content_type'] not in cfg.get('content_types', {}):
|
||||
raise exc.HTTPNotFound
|
||||
|
||||
# get a sorted list of hooks, by priority
|
||||
state.hooks = self.determine_hooks(controller)
|
||||
@@ -402,7 +407,7 @@ class Pecan(object):
|
||||
variable_decode=cfg.get('variable_decode')
|
||||
)
|
||||
elif 'pecan.validation_errors' in request.environ:
|
||||
request.validation_errors = request.environ.pop('pecan.validation_errors')
|
||||
request.pecan['validation_errors'] = request.environ.pop('pecan.validation_errors')
|
||||
|
||||
# fetch the arguments for the controller
|
||||
args, kwargs = self.get_args(
|
||||
@@ -423,17 +428,17 @@ class Pecan(object):
|
||||
raw_namespace = result
|
||||
|
||||
# pull the template out based upon content type and handle overrides
|
||||
template = cfg.get('content_types', {}).get(state.content_type)
|
||||
template = cfg.get('content_types', {}).get(request.pecan['content_type'])
|
||||
|
||||
# check if for controller override of template
|
||||
template = getattr(request, 'override_template', template)
|
||||
state.content_type = getattr(request, 'override_content_type', state.content_type)
|
||||
template = request.pecan.get('override_template', template)
|
||||
request.pecan['content_type'] = request.pecan.get('override_content_type', request.pecan['content_type'])
|
||||
|
||||
# if there is a template, render it
|
||||
if template:
|
||||
if template == 'json':
|
||||
state.content_type = 'application/json'
|
||||
result = render(template, result)
|
||||
request.pecan['content_type'] = 'application/json'
|
||||
result = self.render(template, result)
|
||||
|
||||
# pass the response through htmlfill (items are popped out of the
|
||||
# environment even if htmlfill won't run for proper cleanup)
|
||||
@@ -442,8 +447,8 @@ class Pecan(object):
|
||||
_htmlfill = request.environ.pop('pecan.htmlfill')
|
||||
if 'pecan.params' in request.environ:
|
||||
params = request.environ.pop('pecan.params')
|
||||
if request.validation_errors and _htmlfill is not None and state.content_type == 'text/html':
|
||||
errors = getattr(request, 'validation_errors', {})
|
||||
if request.pecan['validation_errors'] and _htmlfill is not None and request.pecan['content_type'] == 'text/html':
|
||||
errors = request.pecan['validation_errors']
|
||||
result = htmlfill.render(result, defaults=params, errors=errors, **_htmlfill)
|
||||
|
||||
# If we are in a test request put the namespace where it can be
|
||||
@@ -461,8 +466,8 @@ class Pecan(object):
|
||||
response.body = result
|
||||
|
||||
# set the content type
|
||||
if state.content_type:
|
||||
response.content_type = state.content_type
|
||||
if request.pecan['content_type']:
|
||||
response.content_type = request.pecan['content_type']
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
'''
|
||||
@@ -471,15 +476,15 @@ class Pecan(object):
|
||||
|
||||
# create the request and response object
|
||||
state.request = Request(environ)
|
||||
state.content_type = None
|
||||
state.response = Response()
|
||||
state.hooks = []
|
||||
state.app = self
|
||||
|
||||
# handle the request
|
||||
try:
|
||||
# add context to the request
|
||||
# add context and environment to the request
|
||||
state.request.context = {}
|
||||
state.request.pecan = dict(content_type=None, validation_errors={})
|
||||
|
||||
self.handle_request()
|
||||
except Exception, e:
|
||||
@@ -502,7 +507,6 @@ class Pecan(object):
|
||||
return state.response(environ, start_response)
|
||||
finally:
|
||||
# clean up state
|
||||
del state.content_type
|
||||
del state.hooks
|
||||
del state.request
|
||||
del state.response
|
||||
|
||||
@@ -49,9 +49,12 @@ class GenericJSON(JSONEncoder):
|
||||
props[key] = getattr(obj, key)
|
||||
return props
|
||||
elif isinstance(obj, ResultProxy):
|
||||
return dict(rows=list(obj), count=obj.rowcount)
|
||||
props = dict(rows=list(obj), count=obj.rowcount)
|
||||
if props['count'] < 0:
|
||||
props['count'] = len(props['rows'])
|
||||
return props
|
||||
elif isinstance(obj, RowProxy):
|
||||
return dict(rows=dict(obj), count=1)
|
||||
return dict(obj)
|
||||
elif isinstance(obj, (MultiDict, UnicodeMultiDict)):
|
||||
return obj.mixed()
|
||||
else:
|
||||
|
||||
@@ -57,7 +57,7 @@ class RestController(object):
|
||||
|
||||
# get the args to figure out how much to chop off
|
||||
args = getargspec(getattr(self, method))
|
||||
fixed_args = len(args[0][1:]) - len(getattr(request, 'routing_args', []))
|
||||
fixed_args = len(args[0][1:]) - len(request.pecan.get('routing_args', []))
|
||||
var_args = args[1]
|
||||
|
||||
# attempt to locate a sub-controller
|
||||
@@ -166,7 +166,4 @@ class RestController(object):
|
||||
_handle_put = _handle_post
|
||||
|
||||
def _set_routing_args(self, args):
|
||||
if hasattr(request, 'routing_args'):
|
||||
request.routing_args.extend(args)
|
||||
else:
|
||||
setattr(request, 'routing_args', args)
|
||||
request.pecan.setdefault('routing_args', []).extend(args)
|
||||
|
||||
@@ -11,9 +11,9 @@ class SampleForm(Schema):
|
||||
class RootController(object):
|
||||
@expose('index.html')
|
||||
def index(self, name='', age=''):
|
||||
return dict(errors=request.validation_errors, name=name, age=age)
|
||||
return dict(errors=request.pecan['validation_errors'], name=name, age=age)
|
||||
|
||||
@expose('success.html', schema=SampleForm(), error_handler='index')
|
||||
@expose('success.html', schema=SampleForm(), error_handler='/index')
|
||||
def handle_form(self, name, age):
|
||||
return dict(name=name, age=age)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from paste.recursive import ForwardRequestException
|
||||
from unittest import TestCase
|
||||
from webtest import TestApp
|
||||
|
||||
from pecan import Pecan, expose, request, response, redirect, abort, make_app, override_template
|
||||
from pecan import Pecan, expose, request, response, redirect, abort, make_app, override_template, render
|
||||
from pecan.templating import _builtin_renderers as builtin_renderers, error_formatters
|
||||
from pecan.decorators import accept_noncanonical
|
||||
|
||||
@@ -438,6 +438,10 @@ class TestBase(TestCase):
|
||||
def internal(self):
|
||||
redirect('/testing', internal=True)
|
||||
|
||||
@expose()
|
||||
def bad_internal(self):
|
||||
redirect('/testing', internal=True, code=301)
|
||||
|
||||
@expose()
|
||||
def permanent(self):
|
||||
redirect('/testing', code=301)
|
||||
@@ -446,21 +450,25 @@ class TestBase(TestCase):
|
||||
def testing(self):
|
||||
return 'it worked!'
|
||||
|
||||
app = TestApp(Pecan(RootController()))
|
||||
app = TestApp(make_app(RootController(), debug=True))
|
||||
r = app.get('/')
|
||||
assert r.status_int == 302
|
||||
r = r.follow()
|
||||
assert r.status_int == 200
|
||||
assert r.body == 'it worked!'
|
||||
|
||||
self.assertRaises(ForwardRequestException, app.get, '/internal')
|
||||
r = app.get('/internal')
|
||||
assert r.status_int == 200
|
||||
assert r.body == 'it worked!'
|
||||
|
||||
self.assertRaises(ValueError, app.get, '/bad_internal')
|
||||
|
||||
r = app.get('/permanent')
|
||||
assert r.status_int == 301
|
||||
r = r.follow()
|
||||
assert r.status_int == 200
|
||||
assert r.body == 'it worked!'
|
||||
|
||||
|
||||
def test_streaming_response(self):
|
||||
import StringIO
|
||||
class RootController(object):
|
||||
@@ -511,10 +519,10 @@ class TestBase(TestCase):
|
||||
Test extension splits
|
||||
"""
|
||||
class RootController(object):
|
||||
@expose()
|
||||
@expose(content_type=None)
|
||||
def _default(self, *args):
|
||||
from pecan.core import request
|
||||
return request.extension
|
||||
return request.pecan['extension']
|
||||
|
||||
app = TestApp(Pecan(RootController()))
|
||||
r = app.get('/index.html')
|
||||
@@ -544,6 +552,117 @@ class TestBase(TestCase):
|
||||
|
||||
app = make_app(RootController(), wrap_app=wrap, debug=True)
|
||||
assert len(wrapped_apps) == 1
|
||||
|
||||
def test_bad_content_type(self):
|
||||
class RootController(object):
|
||||
@expose()
|
||||
def index(self):
|
||||
return '/'
|
||||
|
||||
app = TestApp(Pecan(RootController()))
|
||||
r = app.get('/')
|
||||
assert r.status_int == 200
|
||||
assert r.body == '/'
|
||||
|
||||
r = app.get('/index.html', expect_errors=True)
|
||||
assert r.status_int == 200
|
||||
assert r.body == '/'
|
||||
|
||||
r = app.get('/index.txt', expect_errors=True)
|
||||
assert r.status_int == 404
|
||||
|
||||
def test_canonical_index(self):
|
||||
class ArgSubController(object):
|
||||
@expose()
|
||||
def index(self, arg):
|
||||
return arg
|
||||
class AcceptController(object):
|
||||
@accept_noncanonical
|
||||
@expose()
|
||||
def index(self):
|
||||
return 'accept'
|
||||
class SubController(object):
|
||||
@expose()
|
||||
def index(self):
|
||||
return 'subindex'
|
||||
class RootController(object):
|
||||
@expose()
|
||||
def index(self):
|
||||
return 'index'
|
||||
|
||||
sub = SubController()
|
||||
arg = ArgSubController()
|
||||
accept = AcceptController()
|
||||
|
||||
app = TestApp(Pecan(RootController()))
|
||||
|
||||
r = app.get('/')
|
||||
assert r.status_int == 200
|
||||
assert 'index' in r.body
|
||||
|
||||
r = app.get('/index')
|
||||
assert r.status_int == 200
|
||||
assert 'index' in r.body
|
||||
|
||||
# for broken clients
|
||||
r = app.get('', status=302)
|
||||
assert r.status_int == 302
|
||||
|
||||
r = app.get('/sub/')
|
||||
assert r.status_int == 200
|
||||
assert 'subindex' in r.body
|
||||
|
||||
r = app.get('/sub', status=302)
|
||||
assert r.status_int == 302
|
||||
|
||||
try:
|
||||
r = app.post('/sub', dict(foo=1))
|
||||
raise Exception, "Post should fail"
|
||||
except Exception, e:
|
||||
assert isinstance(e, RuntimeError)
|
||||
|
||||
r = app.get('/arg/index/foo')
|
||||
assert r.status_int == 200
|
||||
assert r.body == 'foo'
|
||||
|
||||
r = app.get('/accept/')
|
||||
assert r.status_int == 200
|
||||
assert 'accept' == r.body
|
||||
|
||||
r = app.get('/accept')
|
||||
assert r.status_int == 200
|
||||
assert 'accept' == r.body
|
||||
|
||||
app = TestApp(Pecan(RootController(), force_canonical=False))
|
||||
r = app.get('/')
|
||||
assert r.status_int == 200
|
||||
assert 'index' in r.body
|
||||
|
||||
r = app.get('/sub')
|
||||
assert r.status_int == 200
|
||||
assert 'subindex' in r.body
|
||||
|
||||
r = app.post('/sub', dict(foo=1))
|
||||
assert r.status_int == 200
|
||||
assert 'subindex' in r.body
|
||||
|
||||
r = app.get('/sub/')
|
||||
assert r.status_int == 200
|
||||
assert 'subindex' in r.body
|
||||
|
||||
def test_proxy(self):
|
||||
class RootController(object):
|
||||
@expose()
|
||||
def index(self):
|
||||
request.testing = True
|
||||
assert request.testing == True
|
||||
del request.testing
|
||||
assert hasattr(request, 'testing') == False
|
||||
return '/'
|
||||
|
||||
app = TestApp(make_app(RootController(), debug=True))
|
||||
r = app.get('/')
|
||||
assert r.status_int == 200
|
||||
|
||||
|
||||
class TestEngines(object):
|
||||
@@ -652,88 +771,18 @@ class TestEngines(object):
|
||||
assert 'Override' in r.body
|
||||
assert r.content_type == 'text/plain'
|
||||
|
||||
def test_canonical_index(self):
|
||||
class ArgSubController(object):
|
||||
@expose()
|
||||
def index(self, arg):
|
||||
return arg
|
||||
class AcceptController(object):
|
||||
@accept_noncanonical
|
||||
@expose()
|
||||
def index(self):
|
||||
return 'accept'
|
||||
class SubController(object):
|
||||
@expose()
|
||||
def index(self):
|
||||
return 'subindex'
|
||||
def test_render(self):
|
||||
|
||||
#if 'mako' not in builtin_renderers:
|
||||
# return
|
||||
|
||||
class RootController(object):
|
||||
@expose()
|
||||
def index(self):
|
||||
return 'index'
|
||||
|
||||
sub = SubController()
|
||||
arg = ArgSubController()
|
||||
accept = AcceptController()
|
||||
|
||||
app = TestApp(Pecan(RootController()))
|
||||
|
||||
r = app.get('/')
|
||||
assert r.status_int == 200
|
||||
assert 'index' in r.body
|
||||
|
||||
r = app.get('/index')
|
||||
assert r.status_int == 200
|
||||
assert 'index' in r.body
|
||||
def index(self, name='Jonathan'):
|
||||
return render('mako.html', dict(name=name))
|
||||
return dict(name=name)
|
||||
|
||||
# for broken clients
|
||||
r = app.get('', status=302)
|
||||
assert r.status_int == 302
|
||||
|
||||
r = app.get('/sub/')
|
||||
assert r.status_int == 200
|
||||
assert 'subindex' in r.body
|
||||
|
||||
r = app.get('/sub', status=302)
|
||||
assert r.status_int == 302
|
||||
|
||||
try:
|
||||
r = app.post('/sub', dict(foo=1))
|
||||
raise Exception, "Post should fail"
|
||||
except Exception, e:
|
||||
assert isinstance(e, RuntimeError)
|
||||
|
||||
r = app.get('/arg/index/foo')
|
||||
assert r.status_int == 200
|
||||
assert r.body == 'foo'
|
||||
|
||||
r = app.get('/accept/')
|
||||
assert r.status_int == 200
|
||||
assert 'accept' == r.body
|
||||
|
||||
r = app.get('/accept')
|
||||
assert r.status_int == 200
|
||||
assert 'accept' == r.body
|
||||
|
||||
app = TestApp(Pecan(RootController(), force_canonical=False))
|
||||
app = TestApp(Pecan(RootController(), template_path=self.template_path))
|
||||
r = app.get('/')
|
||||
assert r.status_int == 200
|
||||
assert 'index' in r.body
|
||||
|
||||
r = app.get('/sub')
|
||||
assert r.status_int == 200
|
||||
assert 'subindex' in r.body
|
||||
|
||||
r = app.post('/sub', dict(foo=1))
|
||||
assert r.status_int == 200
|
||||
assert 'subindex' in r.body
|
||||
|
||||
r = app.get('/sub/')
|
||||
assert r.status_int == 200
|
||||
assert 'subindex' in r.body
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
assert "<h1>Hello, Jonathan!</h1>" in r.body
|
||||
|
||||
@@ -125,3 +125,7 @@ class TestConf(TestCase):
|
||||
conf = configuration.Config({'a':1})
|
||||
self.assertEqual(['a'], dir(conf))
|
||||
|
||||
def test_config_bad_key(self):
|
||||
conf = configuration.Config({'a': 1})
|
||||
assert conf.a == 1
|
||||
self.assertRaises(AttributeError, getattr, conf, 'b')
|
||||
|
||||
@@ -696,7 +696,7 @@ class TestHooks(object):
|
||||
password_confirm,
|
||||
age):
|
||||
run_hook.append('inside')
|
||||
return str(len(request.validation_errors) > 0)
|
||||
return str(len(request.pecan['validation_errors']) > 0)
|
||||
|
||||
@expose(schema=RegistrationSchema(), error_handler='/errors')
|
||||
def with_handler(self, first_name,
|
||||
@@ -707,7 +707,7 @@ class TestHooks(object):
|
||||
password_confirm,
|
||||
age):
|
||||
run_hook.append('inside')
|
||||
return str(len(request.validation_errors) > 0)
|
||||
return str(len(request.pecan['validation_errors']) > 0)
|
||||
|
||||
# test that the hooks get properly run with no validation errors
|
||||
app = TestApp(make_app(RootController(), hooks=[SimpleHook()]))
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
try:
|
||||
from simplejson import loads
|
||||
from simplejson import loads
|
||||
except:
|
||||
from json import loads
|
||||
from unittest import TestCase
|
||||
from json import loads
|
||||
try:
|
||||
from sqlalchemy import orm, schema, types
|
||||
from sqlalchemy.engine import create_engine
|
||||
except ImportError:
|
||||
create_engine = None
|
||||
from unittest import TestCase
|
||||
|
||||
from pecan.jsonify import jsonify, encode
|
||||
from pecan import Pecan, expose, request
|
||||
from webtest import TestApp
|
||||
from webob.multidict import MultiDict, UnicodeMultiDict
|
||||
from pecan.jsonify import jsonify, encode, ResultProxy, RowProxy
|
||||
from pecan import Pecan, expose, request
|
||||
from webtest import TestApp
|
||||
from webob.multidict import MultiDict, UnicodeMultiDict
|
||||
|
||||
def make_person():
|
||||
class Person(object):
|
||||
@@ -106,3 +111,93 @@ class TestJsonifyGenericEncoder(TestCase):
|
||||
class Foo(object): pass
|
||||
|
||||
self.assertRaises(TypeError, encode, Foo())
|
||||
|
||||
class TestJsonifySQLAlchemyGenericEncoder(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
if not create_engine:
|
||||
self.create_fake_proxies()
|
||||
else:
|
||||
self.create_sa_proxies()
|
||||
|
||||
def create_fake_proxies(self):
|
||||
|
||||
# create a fake SA object
|
||||
class FakeSAObject(object):
|
||||
def __init__(self):
|
||||
self._sa_class_manager = object()
|
||||
self._sa_instance_state = 'awesome'
|
||||
self.id = 1
|
||||
self.first_name = 'Jonathan'
|
||||
self.last_name = 'LaCour'
|
||||
|
||||
# create a fake result proxy
|
||||
class FakeResultProxy(ResultProxy):
|
||||
def __init__(self):
|
||||
self.rowcount = -1
|
||||
self.rows = []
|
||||
def __iter__(self):
|
||||
return iter(self.rows)
|
||||
def append(self, row):
|
||||
self.rows.append(row)
|
||||
|
||||
# create a fake row proxy
|
||||
class FakeRowProxy(RowProxy):
|
||||
def __init__(self, arg=None):
|
||||
self.row = dict(arg)
|
||||
def __getitem__(self, key):
|
||||
return self.row.__getitem__(key)
|
||||
def keys(self):
|
||||
return self.row.keys()
|
||||
|
||||
# get the SA objects
|
||||
self.sa_object = FakeSAObject()
|
||||
self.result_proxy = FakeResultProxy()
|
||||
self.result_proxy.append(FakeRowProxy([('id', 1), ('first_name', 'Jonathan'), ('last_name', 'LaCour')]))
|
||||
self.result_proxy.append(FakeRowProxy([('id', 2), ('first_name', 'Yoann'), ('last_name', 'Roman')]))
|
||||
self.row_proxy = FakeRowProxy([('id', 1), ('first_name', 'Jonathan'), ('last_name', 'LaCour')])
|
||||
|
||||
def create_sa_proxies(self):
|
||||
|
||||
# create the table and mapper
|
||||
metadata = schema.MetaData()
|
||||
user_table = schema.Table('user', metadata,
|
||||
schema.Column('id', types.Integer, primary_key=True),
|
||||
schema.Column('first_name', types.Unicode(25)),
|
||||
schema.Column('last_name', types.Unicode(25)))
|
||||
class User(object):
|
||||
pass
|
||||
orm.mapper(User, user_table)
|
||||
|
||||
# create the session
|
||||
engine = create_engine('sqlite:///:memory:')
|
||||
metadata.bind = engine
|
||||
metadata.create_all()
|
||||
session = orm.sessionmaker(bind=engine)()
|
||||
|
||||
# add some dummy data
|
||||
user_table.insert().execute([
|
||||
{'first_name': u'Jonathan', 'last_name': u'LaCour'},
|
||||
{'first_name': u'Yoann', 'last_name': u'Roman'}
|
||||
])
|
||||
|
||||
# get the SA objects
|
||||
self.sa_object = session.query(User).first()
|
||||
select = user_table.select()
|
||||
self.result_proxy = select.execute()
|
||||
self.row_proxy = select.execute().fetchone()
|
||||
|
||||
def test_sa_object(self):
|
||||
result = encode(self.sa_object)
|
||||
assert loads(result) == {'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'}
|
||||
|
||||
def test_result_proxy(self):
|
||||
result = encode(self.result_proxy)
|
||||
assert loads(result) == {'count': 2, 'rows': [
|
||||
{'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'},
|
||||
{'id': 2, 'first_name': 'Yoann', 'last_name': 'Roman'}
|
||||
]}
|
||||
|
||||
def test_row_proxy(self):
|
||||
result = encode(self.row_proxy)
|
||||
assert loads(result) == {'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'}
|
||||
|
||||
@@ -95,7 +95,7 @@ class TestValidation(object):
|
||||
|
||||
@expose()
|
||||
def errors(self, *args, **kwargs):
|
||||
assert len(request.validation_errors) > 0
|
||||
assert len(request.pecan['validation_errors']) > 0
|
||||
return 'There was an error!'
|
||||
|
||||
@expose(schema=RegistrationSchema())
|
||||
@@ -106,7 +106,7 @@ class TestValidation(object):
|
||||
password,
|
||||
password_confirm,
|
||||
age):
|
||||
assert len(request.validation_errors) > 0
|
||||
assert len(request.pecan['validation_errors']) > 0
|
||||
return 'Success!'
|
||||
|
||||
@expose(schema=RegistrationSchema(), error_handler='/errors')
|
||||
@@ -117,17 +117,17 @@ class TestValidation(object):
|
||||
password,
|
||||
password_confirm,
|
||||
age):
|
||||
assert len(request.validation_errors) > 0
|
||||
assert len(request.pecan['validation_errors']) > 0
|
||||
return 'Success!'
|
||||
|
||||
@expose(json_schema=RegistrationSchema())
|
||||
def json(self, data):
|
||||
assert len(request.validation_errors) > 0
|
||||
assert len(request.pecan['validation_errors']) > 0
|
||||
return 'Success!'
|
||||
|
||||
@expose(json_schema=RegistrationSchema(), error_handler='/errors')
|
||||
def json_with_handler(self, data):
|
||||
assert len(request.validation_errors) > 0
|
||||
assert len(request.pecan['validation_errors']) > 0
|
||||
return 'Success!'
|
||||
|
||||
|
||||
@@ -193,13 +193,13 @@ class TestValidation(object):
|
||||
|
||||
@expose()
|
||||
def errors(self, *args, **kwargs):
|
||||
return 'Error with %s!' % ', '.join(request.validation_errors.keys())
|
||||
return 'Error with %s!' % ', '.join(request.pecan['validation_errors'].keys())
|
||||
|
||||
@expose(schema=ColorSchema(),
|
||||
variable_decode=True)
|
||||
def index(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@@ -207,16 +207,16 @@ class TestValidation(object):
|
||||
error_handler='/errors',
|
||||
variable_decode=True)
|
||||
def with_handler(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@expose(json_schema=ColorSchema(),
|
||||
variable_decode=True)
|
||||
def json(self, data):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@@ -224,16 +224,16 @@ class TestValidation(object):
|
||||
error_handler='/errors',
|
||||
variable_decode=True)
|
||||
def json_with_handler(self, data):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@expose(schema=ColorSchema(),
|
||||
variable_decode=dict())
|
||||
def custom(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@@ -241,16 +241,16 @@ class TestValidation(object):
|
||||
error_handler='/errors',
|
||||
variable_decode=dict())
|
||||
def custom_with_handler(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@expose(json_schema=ColorSchema(),
|
||||
variable_decode=dict())
|
||||
def custom_json(self, data):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@@ -258,16 +258,16 @@ class TestValidation(object):
|
||||
error_handler='/errors',
|
||||
variable_decode=dict())
|
||||
def custom_json_with_handler(self, data):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@expose(schema=ColorSchema(),
|
||||
variable_decode=dict(dict_char='-', list_char='.'))
|
||||
def alternate(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@@ -275,16 +275,16 @@ class TestValidation(object):
|
||||
error_handler='/errors',
|
||||
variable_decode=dict(dict_char='-', list_char='.'))
|
||||
def alternate_with_handler(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@expose(json_schema=ColorSchema(),
|
||||
variable_decode=dict(dict_char='-', list_char='.'))
|
||||
def alternate_json(self, data):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@@ -292,8 +292,8 @@ class TestValidation(object):
|
||||
error_handler='/errors',
|
||||
variable_decode=dict(dict_char='-', list_char='.'))
|
||||
def alternate_json_with_handler(self, data):
|
||||
if request.validation_errors:
|
||||
return ', '.join(request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return ', '.join(request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return 'Success!'
|
||||
|
||||
@@ -507,7 +507,7 @@ class TestValidation(object):
|
||||
schema=ColorSchema(),
|
||||
variable_decode=True)
|
||||
def index(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
if request.pecan['validation_errors']:
|
||||
return dict()
|
||||
else:
|
||||
return dict(data=kwargs)
|
||||
@@ -542,8 +542,8 @@ class TestValidation(object):
|
||||
schema=NameSchema(),
|
||||
htmlfill=dict(auto_insert_errors=True))
|
||||
def json(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
return dict(error_with=request.validation_errors.keys())
|
||||
if request.pecan['validation_errors']:
|
||||
return dict(error_with=request.pecan['validation_errors'].keys())
|
||||
else:
|
||||
return kwargs
|
||||
|
||||
@@ -639,7 +639,7 @@ class TestValidation(object):
|
||||
@expose(template='mako:form_login.html',
|
||||
schema=LoginSchema())
|
||||
def index(self, **kwargs):
|
||||
if request.validation_errors:
|
||||
if request.pecan['validation_errors']:
|
||||
return dict()
|
||||
else:
|
||||
return dict(data=kwargs)
|
||||
|
||||
Reference in New Issue
Block a user