Permalink
Browse files

Merge with latest parent repo

  • Loading branch information...
2 parents 30df9a7 + f136b2a commit 8b9f71c04b2fcfdb3de07125bea3daea94464e79 @chbrown chbrown committed Jan 24, 2010
Showing with 155 additions and 100 deletions.
  1. +68 −7 tg/controllers/dispatcher.py
  2. +13 −61 tg/controllers/restcontroller.py
  3. +1 −1 tg/release.py
  4. +73 −31 tg/tests/test_tg_controller_dispatch.py
View
75 tg/controllers/dispatcher.py
@@ -138,16 +138,22 @@ def _remove_argspec_params_from_params(self, func, params, remainder):
# replace the existing required variables with the values that come in
# from params these could be the parameters that come off of validation.
- for i,var in enumerate(required_vars):
- remainder[i] = params[var]
- del params[var]
+ remainder = list(remainder)
+ for i, var in enumerate(required_vars):
+# remainder[i] = params[var]
+ if i < len(remainder):
+ remainder[i] = params[var]
+ elif params.get(var):
+ remainder.append(params[var])
+ if var in params:
+ del params[var]
#remove the optional vars from the params until we run out of remainder
for var in optional_vars:
if var in params:
del params[var]
- return params, remainder
+ return params, tuple(remainder)
def _dispatch(self, state, remainder):
"""override this to define how your controller should dispatch.
@@ -310,6 +316,59 @@ def _is_exposed(self, controller, name):
if hasattr(controller, name) and ismethod(getattr(controller, name)):
return True
+ def _method_matches_args(self, method, state, remainder):
+ """
+ This method matches the params from the request along with the remainder to the
+ method's function signiture. If the two jive, it returns true.
+
+ It is very likely that this method would go into ObjectDispatch in the future.
+ """
+ argspec = self._get_argspec(method)
+ #skip self,
+ argvars = argspec[0][1:]
+ argvals = argspec[3]
+
+ required_vars = argvars
+ if argvals:
+ required_vars = argvars[:-len(argvals)]
+ else:
+ argvals = []
+
+ #remove the appropriate remainder quotient
+ if len(remainder)<len(required_vars):
+ #pull the first few off with the remainder
+ required_vars = required_vars[len(remainder):]
+ else:
+ #there is more of a remainder than there is non optional vars
+ required_vars = []
+
+ #remove vars found in the params list
+ params = state.params
+ for var in required_vars[:]:
+ if var in params:
+ required_vars.pop(0)
+ else:
+ break;
+
+ var_in_params = 0
+ for var in argvars:
+ if var in params:
+ var_in_params+=1
+
+ #make sure all of the non-optional-vars are there
+ if not required_vars:
+ var_args = argspec[0][1:]
+ #there are more args in the remainder than are available in the argspec
+ if len(var_args)<len(remainder) and not argspec[1]:
+ return False
+ defaults = argspec[3] or []
+ var_args = var_args[len(remainder):-len(defaults)]
+ for arg in var_args:
+ if arg not in state.params:
+ return False
+ return True
+ return False
+
def _is_controller(self, controller, name):
"""
Override this function to define how an object is determined to be a
@@ -407,9 +466,11 @@ def _dispatch(self, state, remainder):
#an exposed method matching the path is found
if self._is_exposed(current_controller, current_path):
- state.add_method(getattr(
- current_controller, current_path), remainder[1:])
- return state
+ #check to see if the argspec jives
+ controller = getattr(current_controller, current_path)
+ if self._method_matches_args(controller, state, remainder[1:]):
+ state.add_method(controller, remainder[1:])
+ return state
#another controller is found
if hasattr(current_controller, current_path):
View
74 tg/controllers/restcontroller.py
@@ -18,54 +18,6 @@ class RestDispatcher(ObjectDispatcher):
def _setup_wsgiorg_routing_args(self, url_path, remainder, params):
pylons.request.environ['wsgiorg.routing_args'] = (tuple(remainder), params)
- def _method_matches_args(self, method, state, remainder):
- """
- This method matches the params from the request along with the remainder to the
- method's function signiture. If the two jive, it returns true.
-
- It is very likely that this method would go into ObjectDispatch in the future.
- """
- argspec = self._get_argspec(method)
- #skip self,
- argvars = argspec[0][1:]
- argvals = argspec[3]
-
- required_vars = argvars
- if argvals:
- required_vars = argvars[:-len(argvals)]
- else:
- argvals = []
-
- #remove the appropriate remainder quotient
- if len(remainder)<len(required_vars):
- #pull the first few off with the remainder
- required_vars = required_vars[len(remainder):]
- else:
- #there is more of a remainder than there is non optional vars
- required_vars = []
-
- #remove vars found in the params list
- params = state.params
- for var in required_vars[:]:
- if var in params:
- required_vars.pop(0)
- else:
- break;
-
- var_in_params = 0
- for var in argvars:
- if var in params:
- var_in_params+=1
-
- #make sure all of the non-optional-vars are
- if not required_vars:
- var_args = argspec[1]
- len_rem = len(remainder) + var_in_params
- if (len_rem >= len(required_vars) and len_rem <= len(argvars)) or\
- (len_rem >= len(required_vars) and var_args):
- return True
- return False
-
def _handle_put_or_post(self, method, state, remainder):
current_controller = state.controller
if remainder:
@@ -98,7 +50,7 @@ def _handle_delete(self, method, state, remainder):
#you may not send a delete request to a non-delete function
if remainder and self._is_exposed(current_controller, remainder[0]):
abort(405)
-
+
# there might be a sub-controller with a delete method, let's go see
if remainder:
sub_controller = getattr(current_controller, remainder[0], None)
@@ -110,7 +62,7 @@ def _handle_delete(self, method, state, remainder):
if r:
return r
return self._dispatch_first_found_default_or_lookup(state, remainder)
-
+
def _check_for_sub_controllers(self, state, remainder):
current_controller = state.controller
method = None
@@ -145,7 +97,7 @@ def _handle_delete_edit_or_new(self, state, remainder):
method_name = 'get_delete'
current_controller = state.controller
-
+
if self._is_exposed(current_controller, method_name):
method = getattr(current_controller, method_name)
new_remainder = remainder[:-1]
@@ -169,26 +121,26 @@ def _handle_get(self, method, state, remainder):
#test for "edit" or "new"
r = self._handle_delete_edit_or_new(state, remainder)
- if r:
+ if r:
return r
-
+
current_path = remainder[0]
if self._is_exposed(current_controller, current_path):
state.add_method(getattr(current_controller, current_path), remainder[1:])
return state
-
+
if self._is_controller(current_controller, current_path):
current_controller = getattr(current_controller, current_path)
return self._dispatch_controller(current_path, current_controller, state, remainder[1:])
-
- if self._is_exposed(current_controller, 'get_one'):
- method = current_controller.get_one
+
+ if self._is_exposed(current_controller, 'get_one') or self._is_exposed(current_controller, 'get'):
+ method = current_controller.get_one if self._is_exposed(current_controller, 'get_one') else current_controller.get
if method and self._method_matches_args(method, state, remainder):
state.add_method(method, remainder)
return state
return self._dispatch_first_found_default_or_lookup(state, remainder)
-
+
_handler_lookup = {
'put':_handle_put_or_post,
'post':_handle_put_or_post,
@@ -202,7 +154,7 @@ def _dispatch(self, state, remainder):
if not hasattr(state, 'http_method'):
method = pylons.request.method.lower()
params = state.params
-
+
#conventional hack for handling methods which are not supported by most browsers
request_method = params.get('_method', None)
if request_method:
@@ -214,13 +166,13 @@ def _dispatch(self, state, remainder):
abort(405)
method = request_method
state.http_method = method
-
+
r = self._check_for_sub_controllers(state, remainder)
if r:
return r
r = self._handler_lookup[state.http_method](self, state.http_method, state, remainder)
-
+
#clear out the method hack
if '_method' in pylons.request.POST:
del pylons.request.POST['_method']
View
2 tg/release.py
@@ -1,5 +1,5 @@
"""TurboGears project related information"""
-version = "2.1a3"
+version = "2.1b1"
description = "Next generation TurboGears built on Pylons"
long_description="""
TurboGears brings together a best of breed python tools
View
104 tg/tests/test_tg_controller_dispatch.py
@@ -33,7 +33,7 @@ def wsgi_app(environ, start_response):
return resp(environ, start_response)
class BeforeController(TGController):
-
+
def __before__(self, *args, **kw):
pylons.tmpl_context.var = '__my_before__'
def __after__(self, *args, **kw):
@@ -49,22 +49,22 @@ def _before(self, *args, **kw):
pylons.tmpl_context.var = '__my_before__'
def _after(self, *args, **kw):
global_craziness = '__my_after__'
-
+
@expose()
def index(self):
assert pylons.tmpl_context.var
return pylons.tmpl_context.var
class SubController(object):
mounted_app = WSGIAppController(wsgi_app)
-
+
before = BeforeController()
newbefore = NewBeforeController()
@expose('genshi')
def unknown_template(self):
return "sub unknown template"
-
+
@expose()
def foo(self,):
return 'sub_foo'
@@ -104,28 +104,32 @@ def list(self, **kw):
return "hello list"
class LookupHelper:
-
+
def __init__(self, var):
self.var = var
-
+
@expose()
def index(self):
return self.var
-
+
class LookupHelperWithArgs:
-
+
@expose()
def get_here(self, *args):
return "%s"%args
+ @expose()
+ def post_with_mixed_args(self, arg1, arg2, **kw):
+ return "%s%s" % (arg1, arg2)
+
class LoookupControllerWithArgs(TGController):
-
+
@expose()
def _lookup(self, *args):
return LookupHelperWithArgs(), args
class LoookupController(TGController):
-
+
@expose()
def _lookup(self, a, *args):
return LookupHelper(a), args
@@ -136,7 +140,7 @@ def errors_here(self, *args, **kw):
return "REMOTE ERROR HANDLER"
class NotFoundController(TGController):pass
-
+
class DefaultWithArgsController(TGController):
@expose()
def default(self, a, b=None, **kw):
@@ -146,7 +150,7 @@ class DefaultWithArgsAndValidatorsController(TGController):
@expose()
def failure(self, *args, **kw):
return "FAILURE"
-
+
@expose()
@validate({'a': validators.Int(),
'b': validators.StringBool()}, error_handler=failure)
@@ -158,19 +162,19 @@ class SubController4:
class SubController5:
default_with_args = DefaultWithArgsAndValidatorsController()
-
+
class BasicTGController(TGController):
mounted_app = WSGIAppController(wsgi_app)
-
+
error_controller = RemoteErrorHandler()
-
+
lookup = LoookupController()
lookup_with_args = LoookupControllerWithArgs()
-
+
@expose(content_type='application/rss+xml')
def ticket2351(self, **kw):
return 'test'
-
+
@expose()
def index(self, **kwargs):
return 'hello world'
@@ -189,6 +193,11 @@ def feed(self, feed=None):
sub5 = SubController5()
@expose()
+ def test_args(self, id, one=None, two=2, three=3):
+ r = dict(id=id, one=str(one), two=str(two), three=str(three))
+ return str(r)
+
+ @expose()
def redirect_me(self, target, **kw):
tg.redirect(target, kw)
@@ -199,13 +208,13 @@ def hello(self, name, silly=None):
@expose()
def optional_and_req_args(self, id, one=None, two=2, three=3):
r = dict(id=id, one=str(one), two=str(two), three=str(three))
-
+
return str(r)
-
+
@expose()
def ticket2412(self, arg1):
return arg1
-
+
@expose()
def redirect_cookie(self, name):
pylons.response.set_cookie('name', name)
@@ -254,7 +263,7 @@ def validated_and_unvalidated(self, a, b):
@expose()
def error_handler(self, **kw):
return 'VALIDATION ERROR HANDLER'
-
+
@expose('json')
@validate(validators={"a":validators.Int()}, error_handler=error_handler)
def validated_with_error_handler(self, a, b):
@@ -300,7 +309,7 @@ class TestNotFoundController(TestWSGIController):
def __init__(self, *args, **kargs):
TestWSGIController.__init__(self, *args, **kargs)
self.app = make_app(NotFoundController)
-
+
def test_not_found(self):
r = self.app.get('/something', status=404)
assert '404 Not Found' in r, r
@@ -336,7 +345,7 @@ class TestTGController(TestWSGIController):
def __init__(self, *args, **kargs):
TestWSGIController.__init__(self, *args, **kargs)
self.app = make_app(BasicTGController)
-
+
def test_lookup(self):
r = self.app.get('/lookup/EYE')
msg = 'EYE'
@@ -347,25 +356,30 @@ def test_lookup_with_args(self):
msg = 'got_here'
assert r.body==msg, r
+ def test_post_with_mixed_args(self):
+ r = self.app.post('/lookup_with_args/post_with_mixed_args/test', params={'arg2': 'time'})
+ msg = 'testtime'
+ assert r.body==msg, r
+
def test_validated_int(self):
r = self.app.get('/validated_int/1')
assert '{"response": 1}' in r, r
def test_validated_with_error_handler(self):
- r = self.app.get('/validated_with_error_handler?a=asdf')
+ r = self.app.get('/validated_with_error_handler?a=asdf&b=123')
msg = 'VALIDATION ERROR HANDLER'
assert msg in r, r
-
+
def test_validated_with_remote_error_handler(self):
- r = self.app.get('/validated_with_remote_error_handler?a=asdf')
+ r = self.app.get('/validated_with_remote_error_handler?a=asdf&b=123')
msg = 'REMOTE ERROR HANDLER'
assert msg in r, r
-
+
def test_unknown_template(self):
r = self.app.get('/sub/unknown_template/')
msg = 'sub unknown template'
assert msg in r, r
-
+
def test_mounted_wsgi_app_at_root(self):
r = self.app.get('/mounted_app/')
self.failUnless('Hello from /mounted_app' in r, r)
@@ -428,7 +442,7 @@ def test_default_with_validator_pass2(self):
def test_default_with_validator_fail2(self):
r =self.app.get('/sub5/default_with_args/True/more')
assert "FAILURE" in r.body, r
-
+
def test_custom_content_type_in_controller(self):
resp = self.app.get('/custom_content_type_in_controller')
assert 'PNG' in resp, resp
@@ -444,11 +458,39 @@ def test_custom_content_type_with_ugliness(self):
resp = self.app.get('/custom_content_type_with_ugliness')
assert 'PNG' in resp, resp
assert resp.headers['Content-Type'] == 'image/png', resp
-
+
def test_optional_and_req_args(self):
resp = self.app.get('/optional_and_req_args/test/one')
assert """{'three': '3', 'id': 'test', 'two': '2', 'one': 'one'}""" in resp, resp
+ def test_optional_and_req_args_at_root(self):
+ resp = self.app.get('/test_args/test/one')
+ assert """{'three': '3', 'id': 'test', 'two': '2', 'one': 'one'}""" in resp, resp
+
+ def test_no_args(self):
+ resp = self.app.get('/test_args/test/')
+ assert """{'three': '3', 'id': 'test', 'two': '2', 'one': 'None'}""" in resp, resp
+
+ def test_one_extra_arg(self):
+ resp = self.app.get('/test_args/test/1')
+ assert """{'three': '3', 'id': 'test', 'two': '2', 'one': '1'}""" in resp, resp
+
+ def test_two_extra_args(self):
+ resp = self.app.get('/test_args/test/1/2')
+ assert """{'three': '3', 'id': 'test', 'two': '2', 'one': '1'}""" in resp, resp
+
+ def test_three_extra_args(self):
+ resp = self.app.get('/test_args/test/1/2/3')
+ assert """{'three': '3', 'id': 'test', 'two': '2', 'one': '1'}""" in resp, resp
+
+ def test_extra_args_forces_default_lookup(self):
+ resp = self.app.get('/test_args/test/1/2/3/4')
+ assert resp.body == """Main Default Page called for url /['test_args', 'test', '1', '2', '3', '4']""", resp
+
+ def test_not_enough_args(self):
+ resp = self.app.get('/test_args/test/1')
+ assert """{'three': '3', 'id': 'test', 'two': '2', 'one': '1'}""" in resp, resp
+
def test_ticket_2412_with_ordered_arg(self):
# this is failing
resp = self.app.get('/ticket2412/Abip%C3%B3n')
@@ -457,7 +499,7 @@ def test_ticket_2412_with_ordered_arg(self):
def test_ticket_2412_with_named_arg(self):
resp = self.app.get('/ticket2412?arg1=Abip%C3%B3n')
assert """Abipón""" in resp, resp
-
+
def test_ticket_2351_bad_content_type(self):
resp = self.app.get('/ticket2351', headers={'Accept':'text/html'})
assert 'test' in resp, resp

0 comments on commit 8b9f71c

Please sign in to comment.