From 91f2d954e7602826ff23ba51a46e5ea4a4f0d5ab Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 14:58:26 -0600 Subject: [PATCH 1/4] test_app: Rename environ maker so nose doesn't think it is a test --- src/milla/tests/test_app.py | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/milla/tests/test_app.py b/src/milla/tests/test_app.py index 15a72d0..1376e20 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() 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) From 47103b76a475a32b86ab4a1946d466059c1c9928 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 21:17:16 -0600 Subject: [PATCH 2/4] Add test for the milla.allow decorator --- src/milla/tests/test_app.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/milla/tests/test_app.py b/src/milla/tests/test_app.py index 1376e20..7afefa0 100644 --- a/src/milla/tests/test_app.py +++ b/src/milla/tests/test_app.py @@ -394,3 +394,12 @@ 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 == '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') From 11271ebc314c86f6fa720616ce870b4d51c371f5 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 22:01:25 -0600 Subject: [PATCH 3/4] app: Cleaned up imports and fixed Request.blank docstring --- src/milla/__init__.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/milla/__init__.py b/src/milla/__init__.py index 7442e2a..29eb958 100644 --- a/src/milla/__init__.py +++ b/src/milla/__init__.py @@ -1,11 +1,11 @@ # Copyright 2011 Dustin C. Hatch -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,12 +15,12 @@ ''' -from app import * +from milla.app import * +from milla.auth.decorators import * from webob.exc import * -from webob.request import * -from webob.response import * -from auth.decorators import * +import webob import urllib +import urlparse def allow(*methods): '''Specify the allowed HTTP verbs for a controller callable @@ -38,20 +38,26 @@ 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 From c4e9397e8ca2f430017de5035593831af41d73b9 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 22:02:28 -0600 Subject: [PATCH 4/4] Replace Request.relative_url with Request.create_url and friends --- src/milla/__init__.py | 48 ++++++++++++++++++++++--------- src/milla/dispatch/routing.py | 14 ++++----- src/milla/tests/test_app.py | 54 +++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 21 deletions(-) diff --git a/src/milla/__init__.py b/src/milla/__init__.py index 29eb958..bef31c3 100644 --- a/src/milla/__init__.py +++ b/src/milla/__init__.py @@ -61,25 +61,44 @@ class Request(webob.Request): 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 = urlparse.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 +133,9 @@ class Request(webob.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 34ddfde..d16d1fa 100644 --- a/src/milla/dispatch/routing.py +++ b/src/milla/dispatch/routing.py @@ -223,7 +223,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): @@ -231,7 +231,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 ) @@ -241,10 +241,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 7afefa0..3e7dfda 100644 --- a/src/milla/tests/test_app.py +++ b/src/milla/tests/test_app.py @@ -403,3 +403,57 @@ def test_allow_decorator(): 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'