diff --git a/src/milla/__init__.py b/src/milla/__init__.py index ae15041..4d03d24 100644 --- a/src/milla/__init__.py +++ b/src/milla/__init__.py @@ -18,8 +18,7 @@ from milla.app import * from milla.auth.decorators import * from webob.exc import * -from webob.request import * -from webob.response import * +import webob import urllib try: import urllib.parse @@ -44,42 +43,67 @@ def allow(*methods): return wrapper -class Response(Response): +class Response(webob.Response): ''':py:class:`WebOb Response ` with minor tweaks ''' -class Request(Request): +class Request(webob.Request): ''':py:class:`WebOb Request ` with minor tweaks ''' ResponseClass = Response - + @classmethod - def blank(cls, *args, **kwargs): - req = super(Request, cls).blank(*args, **kwargs) + def blank(cls, path, *args, **kwargs): + '''Create a simple request for the specified path + + See :py:meth:`webob.Request.blank ` + for information on other arguments and keywords + ''' + + req = super(Request, cls).blank(path, *args, **kwargs) req.config = {} return req - def relative_url(self, other_url, to_application=True, path_only=True, - **vars): #@ReservedAssignment - '''Create a new URL relative to the request URL + def create_href(self, path, **keywords): + '''Combine the application's path with a path to form an HREF - :param other_url: relative path to join with the request URL - :param to_application: If true, generated URL will be relative - to the application's root path, otherwise relative to the - server root - :param path_only: If true, scheme and host will be omitted + :param path: relative path to join with the request URL Any other keyword arguments will be encoded and appended to the URL as querystring arguments. + + The HREF returned will will be the absolute path on the same host + and protocol as the request. To get the full URL including scheme + and host information, use :py:meth:`create_href_full` instead. ''' - url = super(Request, self).relative_url(other_url, to_application) - if path_only: - url = urllib.parse.urlsplit(url).path - if vars: - url += '?' + urllib.urlencode(vars) + url = self._merge_url(self.script_name, path) + + if keywords: + url += '?' + urllib.urlencode(keywords) + + return url + + def create_href_full(self, path, **keywords): + '''Combine the application's full URL with a path to form a new URL + + :param path: relative path to join with the request URL + + Any other keyword arguments will be encoded and appended to the + URL as querystring arguments/ + + The HREF returned will be the full URL, including scheme and host + information. To get the path only, use :py:meth:`create_href` + instead. + ''' + + url = self._merge_url(self.application_url, path) + + if keywords: + url += '?' + urllib.urlencode(keywords) + return url def static_resource(self, path): @@ -114,6 +138,9 @@ class Request(Request): except KeyError: return path + return self._merge_url(root, path) + + def _merge_url(self, root, path): if path.startswith('/'): path = path[1:] if not root.endswith('/'): diff --git a/src/milla/dispatch/routing.py b/src/milla/dispatch/routing.py index fcd5f92..8477ffe 100644 --- a/src/milla/dispatch/routing.py +++ b/src/milla/dispatch/routing.py @@ -221,7 +221,7 @@ class Generator(object): url = Generator(request).generate .. deprecated:: 0.2 - Use :py:meth:`milla.Request.relative_url` instead. + Use :py:meth:`milla.Request.create_href` instead. ''' def __init__(self, request, path_only=True): @@ -229,7 +229,7 @@ class Generator(object): self.path_only = path_only warnings.warn( 'Use of Generator is deprecated; ' - 'use milla.Request.relative_url instead', + 'use milla.Request.create_href instead', DeprecationWarning, stacklevel=2 ) @@ -239,10 +239,8 @@ class Generator(object): ''' path = '/'.join(str(s) for s in segments) - while path.startswith('/'): - path = path[1:] - return self.request.relative_url(path, - to_application=True, - path_only=self.path_only, - **vars) + if self.path_only: + return self.request.create_href(path, **vars) + else: + return self.request.create_href_full(path, **vars) diff --git a/src/milla/tests/test_app.py b/src/milla/tests/test_app.py index 3238cff..859c044 100644 --- a/src/milla/tests/test_app.py +++ b/src/milla/tests/test_app.py @@ -44,7 +44,7 @@ class ResponseMaker(object): for data in app_iter: self.body += data -def testing_environ(): +def environ_for_testing(): environ = {} wsgiref.util.setup_testing_defaults(environ) return environ @@ -61,7 +61,7 @@ def test_notfound(): ''' app = milla.app.Application(StubResolverUnresolved()) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app_iter = app(environ, response.start_response) response.finish_response(app_iter) @@ -72,7 +72,7 @@ def test_favicon(): ''' app = milla.app.Application(StubResolverUnresolved()) - environ = testing_environ() + environ = environ_for_testing() environ.update({'PATH_INFO': '/favicon.ico'}) response = ResponseMaker() app_iter = app(environ, response.start_response) @@ -85,7 +85,7 @@ def test_allow_header_disallowed(): ''' app = milla.app.Application(StubResolver()) - environ = testing_environ() + environ = environ_for_testing() environ.update({'REQUEST_METHOD': 'POST'}) response = ResponseMaker() app_iter = app(environ, response.start_response) @@ -99,7 +99,7 @@ def test_allow_header_allowed(): resolver = StubResolver() resolver.controller.allowed_methods = ('POST',) app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() environ.update({'REQUEST_METHOD': 'POST'}) response = ResponseMaker() app_iter = app(environ, response.start_response) @@ -113,7 +113,7 @@ def test_allow_header_options(): resolver = StubResolver() resolver.controller.allowed_methods = ('GET',) app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() environ.update({'REQUEST_METHOD': 'OPTIONS'}) response = ResponseMaker() app_iter = app(environ, response.start_response) @@ -130,7 +130,7 @@ def test_emulated_method(): resolver = StubResolver() resolver.controller.allowed_methods = ('PUT',) app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() environ.update({ 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'application/x-www-form-urlencoded', @@ -156,7 +156,7 @@ def test_function_before(): resolver = StubResolver() resolver.controller.__before__ = before app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -172,7 +172,7 @@ def test_instance_before(): return 'success' app = milla.app.Application(StubResolver(Controller())) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -188,7 +188,7 @@ def test_instancemethod_before(): return 'success' app = milla.app.Application(StubResolver(Controller().foo)) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -206,7 +206,7 @@ def test_partial_function_before(): resolver = StubResolver() resolver.controller = functools.partial(controller, text='success') app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -224,7 +224,7 @@ def test_partial_instance_before(): resolver = StubResolver() resolver.controller = functools.partial(Controller(), text='success') app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -245,7 +245,7 @@ def test_partial_instancemethod_before(): resolver = StubResolver() resolver.controller = functools.partial(Controller().foo, text='success') app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -260,7 +260,7 @@ def test_function_after(): resolver = StubResolver() resolver.controller.__after__ = after app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -276,7 +276,7 @@ def test_instance_after(): return 'success' app = milla.app.Application(StubResolver(Controller())) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -292,7 +292,7 @@ def test_instancemethod_after(): return 'success' app = milla.app.Application(StubResolver(Controller().foo)) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -310,7 +310,7 @@ def test_partial_function_after(): resolver = StubResolver() resolver.controller = functools.partial(controller, text='success') app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -328,7 +328,7 @@ def test_partial_instance_after(): resolver = StubResolver() resolver.controller = functools.partial(Controller(), text='success') app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -349,7 +349,7 @@ def test_partial_instancemethod_after(): resolver = StubResolver() resolver.controller = functools.partial(Controller().foo, text='success') app = milla.app.Application(resolver) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app(environ, response.start_response) @@ -361,7 +361,7 @@ def test_httperror_response(): raise webob.exc.HTTPClientError('NotFound') app = milla.app.Application(StubResolver(controller)) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() app_iter = app(environ, response.start_response) response.finish_response(app_iter) @@ -386,7 +386,7 @@ def test_single_start_response(): return 'test' app = milla.app.Application(StubResolver(controller)) - environ = testing_environ() + environ = environ_for_testing() response = ResponseMaker() start_response = TestStartResponse(response.start_response) app_iter = app(environ, start_response) @@ -394,3 +394,66 @@ def test_single_start_response(): assert start_response.call_count == 1, start_response.call_count assert response.headers.startswith('HTTP/1.1 200 OK'), response.headers assert response.body == b'test', response.body + +def test_allow_decorator(): + '''Ensure allow decorator sets allowed_methods on controllers''' + + @milla.allow('GET', 'HEAD', 'POST') + def controller(request): + return 'success' + + assert controller.allowed_methods == ('GET', 'HEAD', 'POST') + +def test_create_href_simple(): + '''Request.create_href creates a valid URL path from the application root''' + + environ = environ_for_testing() + request = milla.Request(environ) + url = request.create_href('/bar') + assert url == '/bar', url + +def test_create_href_nonroot(): + '''Request.create_href handles applications mounted somewhere besides /''' + + environ = environ_for_testing() + environ.update({ + 'SCRIPT_NAME': '/test' + }) + request = milla.Request(environ) + url = request.create_href('/bar') + assert url == '/test/bar', url + +def test_create_href_full(): + '''Request.create_href_full creates appropriate full URL''' + + environ = environ_for_testing() + request = milla.Request(environ) + url = request.create_href_full('/bar') + assert url == 'http://127.0.0.1/bar', url + +def test_create_href_full_nonroot(): + '''Request.create_href_full creates correct full URL for nonroot applications''' + + environ = environ_for_testing() + environ.update({ + 'SCRIPT_NAME': '/test' + }) + request = milla.Request(environ) + url = request.create_href_full('/bar') + assert url == 'http://127.0.0.1/test/bar', url + +def test_create_href_keywords(): + '''Request.create_href properly appends querystring arguments''' + + environ = environ_for_testing() + request = milla.Request(environ) + url = request.create_href('/bar', foo='baz') + assert url == '/bar?foo=baz' + +def test_create_href_full_keywords(): + '''Request.create_href_full properly appends querystring arguments''' + + environ = environ_for_testing() + request = milla.Request(environ) + url = request.create_href_full('/bar', foo='baz') + assert url == 'http://127.0.0.1/bar?foo=baz'