Changeset 2030
- Timestamp:
- 08/04/08 11:31:30
- Files:
-
- trunk/cherrypy/_cpdispatch.py (modified) (1 diff)
- trunk/cherrypy/test/test_core.py (modified) (3 diffs)
- trunk/cherrypy/test/test_objectmapping.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cpdispatch.py
r1893 r2030 22 22 23 23 def __call__(self): 24 return self.callable(*self.args, **self.kwargs) 24 try: 25 return self.callable(*self.args, **self.kwargs) 26 except TypeError, x: 27 test_callable_spec(self.callable, self.args, self.kwargs) 28 raise 29 30 def test_callable_spec(callable, callable_args, callable_kwargs): 31 """ 32 Inspect callable and test to see if the given args are suitable for it. 33 34 When an error occurs during the handler's invoking stage there are 2 35 erroneous cases: 36 1. Too many parameters passed to a function which doesn't define 37 one of *args or **kwargs. 38 2. Too little parameters are passed to the function. 39 40 There are 3 sources of parameters to a cherrypy handler. 41 1. query string parameters are passed as keyword parameters to the handler. 42 2. body parameters are also passed as keyword parameters. 43 3. when partial matching occurs, the final path atoms are passed as 44 positional args. 45 Both the query string and path atoms are part of the URI. If they are 46 incorrect, then a 404 Not Found should be raised. Conversely the body 47 parameters are part of the request; if they are invalid a 400 Bad Request. 48 """ 49 (args, varargs, varkw, defaults) = inspect.getargspec(callable) 50 51 if args and args[0] == 'self': 52 args = args[1:] 53 54 arg_usage = dict([(arg, 0,) for arg in args]) 55 vararg_usage = 0 56 varkw_usage = 0 57 extra_kwargs = set() 58 59 for i, value in enumerate(callable_args): 60 try: 61 arg_usage[args[i]] += 1 62 except IndexError: 63 vararg_usage += 1 64 65 for key in callable_kwargs.keys(): 66 try: 67 arg_usage[key] += 1 68 except KeyError: 69 varkw_usage += 1 70 extra_kwargs.add(key) 71 72 for i, val in enumerate(defaults or []): 73 # Defaults take effect only when the arg hasn't been used yet. 74 if arg_usage[args[i]] == 0: 75 arg_usage[args[i]] += 1 76 77 missing_args = [] 78 multiple_args = [] 79 for key, usage in arg_usage.iteritems(): 80 if usage == 0: 81 missing_args.append(key) 82 elif usage > 1: 83 multiple_args.append(key) 84 85 if missing_args: 86 # In the case where the method allows body arguments 87 # there are 3 potential errors: 88 # 1. not enough query string parameters -> 404 89 # 2. not enough body parameters -> 400 90 # 3. not enough path parts (partial matches) -> 404 91 # 92 # We can't actually tell which case it is, 93 # so I'm raising a 404 because that covers 2/3 of the 94 # possibilities 95 # 96 # In the case where the method does not allow body 97 # arguments it's definitely a 404. 98 raise cherrypy.HTTPError(404, 99 message="Missing parameters: %s" % ",".join(missing_args)) 100 101 # the extra positional arguments come from the path - 404 Not Found 102 if not varargs and vararg_usage > 0: 103 raise cherrypy.HTTPError(404) 104 105 body_params = cherrypy.request.body_params or {} 106 body_params = set(body_params.keys()) 107 qs_params = set(callable_kwargs.keys()) - body_params 108 109 if multiple_args: 110 111 if qs_params.intersection(set(multiple_args)): 112 # If any of the multiple parameters came from the query string then 113 # it's a 404 Not Found 114 error = 404 115 else: 116 # Otherwise it's a 400 Bad Request 117 error = 400 118 119 raise cherrypy.HTTPError(error, 120 message="Multiple values for parameters: "\ 121 "%s" % ",".join(multiple_args)) 122 123 if not varkw and varkw_usage > 0: 124 125 # If there were extra query string parameters, it's a 404 Not Found 126 extra_qs_params = set(qs_params).intersection(extra_kwargs) 127 if extra_qs_params: 128 raise cherrypy.HTTPError(404, 129 message="Unexpected query string "\ 130 "parameters: %s" % ", ".join(extra_qs_params)) 131 132 # If there were any extra body parameters, it's a 400 Not Found 133 extra_body_params = set(body_params).intersection(extra_kwargs) 134 if extra_body_params: 135 raise cherrypy.HTTPError(400, 136 message="Unexpected body parameters: "\ 137 "%s" % ", ".join(extra_body_params)) 138 139 140 try: 141 import inspect 142 except ImportError: 143 test_callable_spec = lambda callable, args, kwargs: None 144 25 145 26 146 trunk/cherrypy/test/test_core.py
r1992 r2030 99 99 return "args: %s kwargs: %s" % (args, kwargs) 100 100 101 class ParamErrors(Test): 102 103 def one_positional(self, param1): 104 return "data" 105 one_positional.exposed = True 106 107 def one_positional_args(self, param1, *args): 108 return "data" 109 one_positional_args.exposed = True 110 111 def one_positional_args_kwargs(self, param1, *args, **kwargs): 112 return "data" 113 one_positional_args_kwargs.exposed = True 114 115 def one_positional_kwargs(self, param1, **kwargs): 116 return "data" 117 one_positional_kwargs.exposed = True 118 119 def no_positional(self): 120 return "data" 121 no_positional.exposed = True 122 123 def no_positional_args(self, *args): 124 return "data" 125 no_positional_args.exposed = True 126 127 def no_positional_args_kwargs(self, *args, **kwargs): 128 return "data" 129 no_positional_args_kwargs.exposed = True 130 131 def no_positional_kwargs(self, **kwargs): 132 return "data" 133 no_positional_kwargs.exposed = True 134 101 135 102 136 class Status(Test): … … 444 478 self.getPage("/params/?thing=a&thing=b&thing=c") 445 479 self.assertBody("['a', 'b', 'c']") 446 480 447 481 # Test friendly error message when given params are not accepted. 448 ignore = helper.webtest.ignored_exceptions 449 ignore.append(TypeError) 450 try: 451 self.getPage("/params/?notathing=meeting") 452 self.assertInBody("index() got an unexpected keyword argument 'notathing'") 453 finally: 454 ignore.pop() 482 self.getPage("/params/?notathing=meeting") 483 self.assertInBody("Missing parameters: thing") 484 self.getPage("/params/?thing=meeting¬athing=meeting") 485 self.assertInBody("Unexpected query string parameters: notathing") 455 486 456 487 # Test "% HEX HEX"-encoded URL, param keys, and values … … 467 498 self.getPage("/params/ismap?223,114") 468 499 self.assertBody("Coordinates: 223, 114") 469 500 501 def testParamErrors(self): 502 503 # test that all of the handlers work when given 504 # the correct parameters in order to ensure that the 505 # errors below aren't coming from some other source. 506 for uri in ( 507 '/paramerrors/one_positional?param1=foo', 508 '/paramerrors/one_positional_args?param1=foo', 509 '/paramerrors/one_positional_args/foo', 510 '/paramerrors/one_positional_args/foo/bar/baz', 511 '/paramerrors/one_positional_args_kwargs?param1=foo¶m2=bar', 512 '/paramerrors/one_positional_args_kwargs/foo?param2=bar¶m3=baz', 513 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz', 514 '/paramerrors/one_positional_kwargs?param1=foo¶m2=bar¶m3=baz', 515 '/paramerrors/one_positional_kwargs/foo?param4=foo¶m2=bar¶m3=baz', 516 '/paramerrors/no_positional', 517 '/paramerrors/no_positional_args/foo', 518 '/paramerrors/no_positional_args/foo/bar/baz', 519 '/paramerrors/no_positional_args_kwargs?param1=foo¶m2=bar', 520 '/paramerrors/no_positional_args_kwargs/foo?param2=bar', 521 '/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz', 522 '/paramerrors/no_positional_kwargs?param1=foo¶m2=bar', 523 ): 524 self.getPage(uri) 525 self.assertStatus(200) 526 527 # query string parameters are part of the URI, so if they are wrong 528 # for a particular handler, the status MUST be a 404. 529 for uri in ( 530 '/paramerrors/one_positional', 531 '/paramerrors/one_positional?foo=foo', 532 '/paramerrors/one_positional/foo/bar/baz', 533 '/paramerrors/one_positional/foo?param1=foo', 534 '/paramerrors/one_positional/foo?param1=foo¶m2=foo', 535 '/paramerrors/one_positional_args/foo?param1=foo¶m2=foo', 536 '/paramerrors/one_positional_args/foo/bar/baz?param2=foo', 537 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param1=bar¶m3=baz', 538 '/paramerrors/one_positional_kwargs/foo?param1=foo¶m2=bar¶m3=baz', 539 '/paramerrors/no_positional/boo', 540 '/paramerrors/no_positional?param1=foo', 541 '/paramerrors/no_positional_args/boo?param1=foo', 542 '/paramerrors/no_positional_kwargs/boo?param1=foo', 543 ): 544 self.getPage(uri) 545 self.assertStatus(404) 546 547 # if body parameters are wrong, a 400 must be returned. 548 for uri, body in ( 549 ('/paramerrors/one_positional/foo', 'param1=foo',), 550 ('/paramerrors/one_positional/foo', 'param1=foo¶m2=foo',), 551 ('/paramerrors/one_positional_args/foo', 'param1=foo¶m2=foo',), 552 ('/paramerrors/one_positional_args/foo/bar/baz', 'param2=foo',), 553 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 'param1=bar¶m3=baz',), 554 ('/paramerrors/one_positional_kwargs/foo', 'param1=foo¶m2=bar¶m3=baz',), 555 ('/paramerrors/no_positional', 'param1=foo',), 556 ('/paramerrors/no_positional_args/boo', 'param1=foo',), 557 ): 558 self.getPage(uri, method='POST', body=body) 559 self.assertStatus(400) 560 561 562 # even if body parameters are wrong, if we get the uri wrong, then 563 # it's a 404 564 for uri, body in ( 565 ('/paramerrors/one_positional?param2=foo', 'param1=foo',), 566 ('/paramerrors/one_positional/foo/bar', 'param2=foo',), 567 ('/paramerrors/one_positional_args/foo/bar?param2=foo', 'param3=foo',), 568 ('/paramerrors/one_positional_kwargs/foo/bar', 'param2=bar¶m3=baz',), 569 ('/paramerrors/no_positional?param1=foo', 'param2=foo',), 570 ('/paramerrors/no_positional_args/boo?param2=foo', 'param1=foo',), 571 ): 572 self.getPage(uri, method='POST', body=body) 573 self.assertStatus(404) 574 575 470 576 def testStatus(self): 471 577 self.getPage("/status/") trunk/cherrypy/test/test_objectmapping.py
r1767 r2030 269 269 self.assertBody("default for dir1, param is:('dir2', '5', '3', 'sir')") 270 270 271 # test that extra positional args raises an error. 272 # 500 for now, maybe 404 in the future. 271 # test that extra positional args raises an 404 Not Found 273 272 # See http://www.cherrypy.org/ticket/733. 274 273 self.getPage("/dir1/dir2/script_name/extra/stuff") 275 self.assertStatus( 500)274 self.assertStatus(404) 276 275 277 276 def testExpose(self):

