diff --git a/src/milla/__init__.py b/src/milla/__init__.py index f8334b3..31d8afc 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. @@ -21,17 +21,18 @@ from webob.exc import * import webob try: import urllib.parse -except ImportError: #pragma: no cover +except ImportError: # pragma: no cover import urllib import urlparse urllib.parse = urlparse - urllib.parse.urlencode = urllib.urlencode #@UndefinedVariable + urllib.parse.urlencode = urllib.urlencode + def allow(*methods): '''Specify the allowed HTTP verbs for a controller callable - + Example:: - + @milla.allow('GET', 'POST') def controller(request): return 'Hello, world!' @@ -56,12 +57,12 @@ class Request(webob.Request): @classmethod def blank(cls, path, *args, **kwargs): - '''Create a simple request for the specified path - + '''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 @@ -73,7 +74,7 @@ class Request(webob.Request): 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. @@ -88,22 +89,22 @@ class Request(webob.Request): 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.parse.urlencode(keywords) - + return url def static_resource(self, path): diff --git a/src/milla/app.py b/src/milla/app.py index dc6ebc7..92449c9 100644 --- a/src/milla/app.py +++ b/src/milla/app.py @@ -28,6 +28,7 @@ import milla.dispatch.traversal __all__ = ['Application'] + class Application(object): '''Represents a Milla web application @@ -88,7 +89,7 @@ class Application(object): return response func = options_response else: - func = HTTPMethodNotAllowed(headers=allow_header) + func = HTTPMethodNotAllowed(headers=allow_header) return func(environ, start_response) start_response_wrapper = StartResponseWrapper(start_response) @@ -118,7 +119,7 @@ class Application(object): return '' else: return response.app_iter - + def _call_after(self, func): try: return self._find_attr(func, '__after__') @@ -130,7 +131,7 @@ class Application(object): return self._find_attr(func, '__before__') except AttributeError: return lambda r: None - + def _find_attr(self, obj, attr): try: # Object has the specified attribute itself diff --git a/src/milla/auth/__init__.py b/src/milla/auth/__init__.py index daeb535..4861bc5 100644 --- a/src/milla/auth/__init__.py +++ b/src/milla/auth/__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. @@ -21,7 +21,7 @@ class NotAuthorized(Exception): '''Base class for unauthorized exceptions - + This class is both an exception and a controller callable. If the request validator raises an instance of this class, it will be called and the resulting value will become the HTTP response. The @@ -33,17 +33,17 @@ class NotAuthorized(Exception): '''Return a response indicating the request is not authorized :param request: WebOb Request instance for the current request - + All other arguments and keywords are ignored. ''' - + response = request.ResponseClass(str(self)) response.status_int = 403 return response class RequestValidator(object): '''Base class for request validators - + A request validator is a class that exposes a ``validate`` method, which accepts an instance of :py:class:`webob.Request` and an optional ``requirement``. The ``validate`` method should return @@ -51,7 +51,7 @@ class RequestValidator(object): :py:exc:`NotAuthorized` on failure. The base implementation will raise an instance of the exception specified by :py:attr:`exc_class`, which defaults to :py:class`NotAuthorized`. - + To customize the response to unauthorized requests, it is sufficient to subclass :py:class:`NotAuthorized`, override its :py:meth:`~NotAuthorized.__call__` method, and specify the class @@ -63,7 +63,7 @@ class RequestValidator(object): def validate(self, request, requirement=None): '''Validates a request - + :param request: The request to validate. Should be an instance of :py:class:`webob.Request`. :param requirement: (Optional) A requirement to check. Should be @@ -71,10 +71,10 @@ class RequestValidator(object): or :py:class:`~milla.auth.permissions.PermissionRequirement`, or some other class with a ``check`` method that accepts a sequence of permissions. - + The base implementation will perform authorization in the following way: - + 1. Does the ``request`` have a ``user`` attribute? If not, raise :py:exc:`NotAuthorized`. 2. Is the truth value of ``request.user`` true? If not, raise @@ -83,16 +83,16 @@ class RequestValidator(object): attribute? If not, raise :py:exc:`NotAuthorized`. 4. Do the user's permissions meet the requirements? If not, raise :py:exc:`NotAuthorized`. - + If none of the above steps raised an exception, the method will return ``None``, indicating that the validation was successful. - + .. note:: WebOb Request instances do not have a ``user`` attribute by default. You will need to supply this yourself, i.e. in a WSGI middleware or in the ``__before__`` method of your controller class. ''' - + try: user = request.user except AttributeError: diff --git a/src/milla/auth/decorators.py b/src/milla/auth/decorators.py index d40c759..104392f 100644 --- a/src/milla/auth/decorators.py +++ b/src/milla/auth/decorators.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,8 +15,6 @@ :Created: Mar 3, 2011 :Author: dustin -:Updated: $Date$ -:Updater: $Author$ ''' from functools import wraps @@ -24,10 +22,13 @@ from milla.auth import RequestValidator, NotAuthorized, permissions import milla import pkg_resources + __all__ = ['auth_required', 'require_perms'] + VALIDATOR_EP_GROUP = 'milla.request_validator' + def _find_request(*args, **kwargs): try: return kwargs['request'] @@ -36,6 +37,7 @@ def _find_request(*args, **kwargs): if isinstance(arg, milla.Request): return arg + def _validate_request(func, requirement, *args, **kwargs): request = _find_request(*args, **kwargs) ep_name = request.config.get('request_validator', 'default') @@ -55,20 +57,21 @@ def _validate_request(func, requirement, *args, **kwargs): return e(request) return func(*args, **kwargs) + def auth_required(func): '''Simple decorator to enforce authentication for a controller - + Example usage:: - + class SomeController(object): - + def __before__(request): request.user = find_a_user_somehow(request) - + @milla.auth_required def __call__(request): return 'Hello, world!' - + In this example, the ``SomeController`` controller class implements an ``__before__`` method that adds the ``user`` attribute to the ``request`` instance. This could be done by extracting user @@ -76,7 +79,7 @@ def auth_required(func): method is decorated with ``auth_required``, which will ensure that the user is successfully authenticated. This is handled by a *request validator*. - + If the request is not authorized, the decorated method will never be called. Instead, the response is generated by calling the :py:exc:`~milla.auth.NotAuthorized` exception raised inside @@ -88,20 +91,21 @@ def auth_required(func): return _validate_request(func, None, *args, **kwargs) return wrapper + class require_perms(object): - '''Decorator that requires the user have certain permissions - + '''Decorator that requires the user have certain permissions + Example usage:: - + class SomeController(object): - + def __before__(request): request.user = find_a_user_somehow(request) - + @milla.require_perms('some_permission', 'and_this_permission') def __call__(request): return 'Hello, world!' - + In this example, the ``SomeController`` controller class implements an ``__before__`` method that adds the ``user`` attribute to the ``request`` instance. This could be done by extracting user @@ -109,9 +113,9 @@ class require_perms(object): method is decorated with ``require_perms``, which will ensure that the user is successfully authenticated and the the user has the specified permissions. This is handled by a *request validator*. - + There are two ways to specify the required permissions: - + * By passing the string name of all required permissions as positional arguments. A complex permission requirement will be constructed that requires *all* of the given permissions to be diff --git a/src/milla/auth/permissions.py b/src/milla/auth/permissions.py index 553285d..ef2105c 100644 --- a/src/milla/auth/permissions.py +++ b/src/milla/auth/permissions.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. @@ -18,11 +18,11 @@ Examples:: >>> req = Permission('foo') & Permission('bar') >>> req.check(PermissionContainer(['foo', 'baz'], ['bar'])) True - + >>> req = Permission('login') >>> req.check(['login']) True - + >>> req = Permission('login') | Permission('admin') >>> req.check(['none']) False @@ -30,62 +30,62 @@ Examples:: class PermissionContainer(object): '''Container object for user and group permissions - + :param list user_perms: List of permissions held by the user itself :param list group_perms: List of permissions held by the groups to which the user belongs - + Iterating over :py:class:`PermissionContainer` objects results in a flattened representation of all permissions. ''' - + def __init__(self, user_perms=[], group_perms=[]): self._user_perms = user_perms self._group_perms = group_perms - + def __iter__(self): for perm in self._user_perms: yield perm for perm in self._group_perms: yield perm - + def __contains__(self, perm): return perm in self._user_perms or perm in self._group_perms - + class BasePermission(object): '''Base class for permissions and requirements - + Complex permission requirements can be created using the bitwise ``and`` and ``or`` operators:: - + login_and_view = Permission('login') & Permission('view') admin_or_root = Permission('admin') | Permission('root') - + complex = Permission('login') & Permission('view') | Permission('admin') ''' - + def __and__(self, other): assert isinstance(other, BasePermission) return PermissionRequirementAll(self, other) - + def __or__(self, other): assert isinstance(other, BasePermission) return PermissionRequirementAny(self, other) class Permission(BasePermission): '''Simple permission implementation - + :param str name: Name of the permission - + Permissions must implement a ``check`` method that accepts an iterable and returns ``True`` if the permission is present or ``False`` otherwise. ''' - + def __init__(self, name): self.name = name - + def __str__(self): return str(self.name) @@ -94,27 +94,27 @@ class Permission(BasePermission): def check(self, perms): '''Check if the permission is held - + This method can be overridden to provide more robust support, but this implementation is simple:: - + return self in perms ''' - + return self in perms class PermissionRequirement(BasePermission): '''Base class for complex permission requirements''' - + def __init__(self, *requirements): self.requirements = requirements - + def __str__(self): return ', '.join(self.requirements) - + class PermissionRequirementAll(PermissionRequirement): '''Complex permission requirement needing all given permissions''' - + def check(self, perms): for req in self.requirements: if not req.check(perms): @@ -123,7 +123,7 @@ class PermissionRequirementAll(PermissionRequirement): class PermissionRequirementAny(PermissionRequirement): '''Complex permission requirement needing any given permissions''' - + def check(self, perms): for req in self.requirements: if req.check(perms): diff --git a/src/milla/dispatch/__init__.py b/src/milla/dispatch/__init__.py index 522fe78..58278d1 100644 --- a/src/milla/dispatch/__init__.py +++ b/src/milla/dispatch/__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. diff --git a/src/milla/dispatch/routing.py b/src/milla/dispatch/routing.py index 433e798..7618b5a 100644 --- a/src/milla/dispatch/routing.py +++ b/src/milla/dispatch/routing.py @@ -1,11 +1,11 @@ # Copyright 2011, 2012, 2015 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. @@ -28,13 +28,13 @@ import warnings class Router(object): '''A dispatcher that maps arbitrary paths to controller callables - + Typical usage:: - + router = Router() router.add_route('/foo/{bar}/{baz:\d+}', some_func) app = milla.Application(dispatcher=router) - + In many cases, paths with trailing slashes need special handling. The ``Router`` has two ways of dealing with requests that should have a trailing slash but do not. The default is to send the client @@ -44,19 +44,19 @@ class Router(object): return HTTP 404 Not Found for requests with missing trailing slashes. To change the behavior, pass a different value to the constructor's ``trailing_slash`` keyword. - + Redirect the client to the proper path (the default):: - + router = Router(trailing_slash=Router.REDIRECT) router.add_route('/my_collection/', some_func) - + Pretend the request had a trailing slash, even if it didn't:: - + router = Router(trailing_slash=Router.SILENT) router.add_route('/my_collection/', some_func) - + Do nothing, let the client get a 404 error:: - + router = Router(trailing_slash=None) router.add_route('/my_collection/', some_func) ''' @@ -74,12 +74,12 @@ class Router(object): def resolve(self, path_info): '''Find a controller for a given path - + :param path_info: Path for which to locate a controller :returns: A :py:class:`functools.partial` instance that sets the values collected from variable segments as keyword - arguments to the callable - + arguments to the callable + This method walks through the routing table created with calls to :py:meth:`add_route` and finds the first whose template matches the given path. Variable segments are added as keywords @@ -132,9 +132,9 @@ class Router(object): def _compile_template(self, template): '''Compiles a template into a real regular expression - + :param template: A route template string - + Converts the ``{name}`` or ``{name:regexp}`` syntax into a full regular expression for later parsing. ''' @@ -163,40 +163,40 @@ class Router(object): def add_route(self, template, controller, **vars): '''Add a route to the routing table - + :param template: Route template string :param controller: Controller callable or string Python path - + Route template strings are path segments, beginning with ``/``. Paths can also contain variable segments, delimited with curly braces. - + Example:: - + /some/other/{variable}/{path} - + By default, variable segments will match any character except a ``/``. Alternate expressions can be passed by specifying them alongside the name, separated by a ``:``. - + Example:: - + /some/other/{alternate:[a-zA-Z]} - + Variable path segments will be passed as keywords to the controller. In the first example above, assuming ``controller`` is the name of the callable passed, and the request path was ``/some/other/great/place``:: - + controller(request, variable='great', path='place') - + The ``controller`` argument itself can be any callable that accepts a *WebOb* request as its first argument, and any keywords that may be passed from variable segments. It can also be a string Python path to such a callable. For example:: - + `some.module:function` - + This string will resolve to the function ``function`` in the module ``some.module``. ''' @@ -208,17 +208,17 @@ class Router(object): class Generator(object): '''URL generator - + Creates URL references based on a *WebOb* request. - + Typical usage: - + >>> generator = Generator(request) >>> generator.generate('foo', 'bar') '/foo/bar' - + A common pattern is to wrap this in a stub function:: - + url = Generator(request).generate .. deprecated:: 0.2 diff --git a/src/milla/dispatch/traversal.py b/src/milla/dispatch/traversal.py index e33e12e..7a73f03 100644 --- a/src/milla/dispatch/traversal.py +++ b/src/milla/dispatch/traversal.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. @@ -23,35 +23,35 @@ from milla.dispatch import UnresolvedPath class Traverser(object): '''Default URL dispatcher - + :param root: The root object at which lookup will begin - + The default URL dispatcher uses object attribute traversal to locate a handler for a given path. For example, consider the following class:: - + class Root(object): - + def foo(self): return 'Hello, world!' - + The path ``/foo`` would resolve to the ``foo`` method of the ``Root`` class. - + If a path cannot be resolved, :py:exc:`UnresolvedPath` will be raised. ''' - + def __init__(self, root): self.root = root - + def resolve(self, path_info): '''Find a handler given a path - + :param path_info: Path for which to find a handler :returns: A handler callable ''' - + def walk_path(handler, parts): if not parts or not parts[0]: # No more parts, or the last part is blank, we're done @@ -66,9 +66,9 @@ class Traverser(object): except AttributeError: # No default either, can't resolve raise UnresolvedPath - + # Strip the leading slash and split the path split_path = path_info.lstrip('/').split('/') - + handler = walk_path(self.root, split_path) - return handler \ No newline at end of file + return handler diff --git a/test/test_auth.py b/test/test_auth.py index fbfb36f..fc3025d 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -10,21 +10,21 @@ import nose.tools def test_permission_check(): '''Ensure Permission.check returns True for lists of strings ''' - + perm = milla.auth.permissions.Permission('foo') assert perm.check(['foo']) def test_permission_check_false(): '''Ensure Permission.check returns False for lists of strings ''' - + perm = milla.auth.permissions.Permission('foo') assert not perm.check(['bar']) def test_permission_check_perm(): '''Ensure Permission.check returns True for lists of Permissions ''' - + req = milla.auth.permissions.Permission('foo') perm = milla.auth.permissions.Permission('foo') assert req.check([perm]) @@ -32,7 +32,7 @@ def test_permission_check_perm(): def test_permission_check_perm_false(): '''Ensure Permission.check returns True for lists of Permissions ''' - + req = milla.auth.permissions.Permission('foo') perm = milla.auth.permissions.Permission('bar') assert not req.check([perm]) @@ -40,7 +40,7 @@ def test_permission_check_perm_false(): def test_permission_check_container(): '''Ensure Permission.check returns True for PermissionContainers of strings ''' - + perm = milla.auth.permissions.Permission('foo') container = milla.auth.permissions.PermissionContainer(['foo']) assert perm.check(container) @@ -48,7 +48,7 @@ def test_permission_check_container(): def test_permission_check_container_false(): '''Ensure Permission.check returns True for PermissionContainers of strings ''' - + perm = milla.auth.permissions.Permission('foo') container = milla.auth.permissions.PermissionContainer(['bar']) assert not perm.check(container) @@ -56,7 +56,7 @@ def test_permission_check_container_false(): def test_permission_check_container_perm(): '''Ensure Permission.check returns True for PermissionContainers of Permissions ''' - + perm = milla.auth.permissions.Permission('foo') req = milla.auth.permissions.Permission('foo') container = milla.auth.permissions.PermissionContainer([perm]) @@ -65,7 +65,7 @@ def test_permission_check_container_perm(): def test_permission_check_container_perm_false(): '''Ensure Permission.check returns False for PermissionContainers of Permissions ''' - + perm = milla.auth.permissions.Permission('foo') req = milla.auth.permissions.Permission('bar') container = milla.auth.permissions.PermissionContainer([perm]) @@ -74,14 +74,14 @@ def test_permission_check_container_perm_false(): def test_permission_container_iter(): '''Ensure iterating a PermissionContainer yields all permissions ''' - + container = milla.auth.permissions.PermissionContainer(['foo'], ['bar']) assert list(container) == ['foo', 'bar'] def test_permission_and(): '''Ensure AND-ing Permissions returns a PermissionRequirementAll ''' - + perm1 = milla.auth.permissions.Permission('foo') perm2 = milla.auth.permissions.Permission('bar') req = perm1 & perm2 @@ -91,7 +91,7 @@ def test_permission_and(): def test_permission_or(): '''Ensure OR-ing Permissions returns a PermissionRequirementAny ''' - + perm1 = milla.auth.permissions.Permission('foo') perm2 = milla.auth.permissions.Permission('bar') req = perm1 | perm2 @@ -101,7 +101,7 @@ def test_permission_or(): def test_permission_str(): '''Ensure calling str on a Permission returns its name ''' - + perm_name = 'foo' perm = milla.auth.permissions.Permission(perm_name) assert str(perm) == perm_name @@ -109,7 +109,7 @@ def test_permission_str(): def test_permission_eq(): '''Ensure two Permissions with the same name are equal but not identical ''' - + perm_name = 'foo' perm1 = milla.auth.permissions.Permission(perm_name) perm2 = milla.auth.permissions.Permission(perm_name) @@ -119,7 +119,7 @@ def test_permission_eq(): def test_permission_check_container_group(): '''Test group permissions in PermissionContainer objects ''' - + perm = milla.auth.permissions.Permission('foo') req = milla.auth.permissions.Permission('foo') container = milla.auth.permissions.PermissionContainer([], [perm]) @@ -128,7 +128,7 @@ def test_permission_check_container_group(): def test_permissionrequirement_all(): '''Ensure PermissionRequirementAll requires all listed permissions ''' - + perm1 = milla.auth.permissions.Permission('foo') perm2 = milla.auth.permissions.Permission('bar') req = milla.auth.permissions.PermissionRequirementAll(perm1, perm2) @@ -141,7 +141,7 @@ def test_permissionrequirement_all(): def test_permissionrequirement_any(): '''Ensure PermissionRequirementAll requires only one permission ''' - + perm1 = milla.auth.permissions.Permission('foo') perm2 = milla.auth.permissions.Permission('bar') req = milla.auth.permissions.PermissionRequirementAny(perm1, perm2) @@ -154,7 +154,7 @@ def test_permissionrequirement_any(): def test_exception_callable(): '''Ensure that NotAuthorizedException is a valid controller callable ''' - + exc = milla.auth.NotAuthorized() request = milla.Request.blank('/') response = exc(request) @@ -165,7 +165,7 @@ def test_exception_callable(): def test_request_validator_nouser(): '''Ensure ensure requests without a user attribute raise NotAuthorized ''' - + validator = milla.auth.RequestValidator() request = milla.Request.blank('/') validator.validate(request) @@ -174,7 +174,7 @@ def test_request_validator_nouser(): def test_request_validator_emptyuser(): '''Ensure requests with an empty user raise NotAuthorized ''' - + validator = milla.auth.RequestValidator() request = milla.Request.blank('/') request.user = None @@ -182,13 +182,13 @@ def test_request_validator_emptyuser(): def test_request_validator_user_noperms(): '''Ensure user permissions are not checked if no requirement is given - + If no ``requirement`` is given to :py:meth:`milla.auth.RequestValidator.validate`, then the fact that the request's ``user`` attribute doesn't have a ``permissions`` attribute - shouldn't matter. + shouldn't matter. ''' - + class User(object): pass @@ -201,10 +201,10 @@ def test_request_validator_user_noperms(): def test_request_validator_missingperms(): '''Ensure requests whose user has no permissions attribute are invalid ''' - + class User(object): pass - + validator = milla.auth.RequestValidator() request = milla.Request.blank('/') request.user = User() @@ -215,10 +215,10 @@ def test_request_validator_missingperms(): def test_request_validator_emptyperms(): '''Ensure requests whose user has an empty set of permissions are invalid ''' - + class User(object): pass - + validator = milla.auth.RequestValidator() request = milla.Request.blank('/') request.user = User() @@ -230,10 +230,10 @@ def test_request_validator_emptyperms(): def test_request_validator_incorrectperms(): '''Ensure requests whose user has incorrect permissions raise NotAuthorized ''' - + class User(object): pass - + validator = milla.auth.RequestValidator() request = milla.Request.blank('/') request.user = User() @@ -244,10 +244,10 @@ def test_request_validator_incorrectperms(): def test_request_validator_correctperms(): '''Ensure requests from users with appropriate permissions are valid ''' - + class User(object): pass - + validator = milla.auth.RequestValidator() request = milla.Request.blank('/') request.user = User() @@ -258,7 +258,7 @@ def test_request_validator_correctperms(): def test_find_request_kwarg(): '''Ensure _find_request finds a request in keyword arguments ''' - + request = milla.Request.blank('/') found = milla.auth.decorators._find_request('foo', request=request) assert found is request @@ -266,7 +266,7 @@ def test_find_request_kwarg(): def test_find_request_arg1(): '''Ensure _find_request finds a request in position 1 ''' - + request = milla.Request.blank('/') found = milla.auth.decorators._find_request(request) assert found is request @@ -274,7 +274,7 @@ def test_find_request_arg1(): def test_find_request_arg2(): '''Ensure _find_request finds a request in another position ''' - + request = milla.Request.blank('/') found = milla.auth.decorators._find_request('foo', request) assert found is request @@ -282,14 +282,14 @@ def test_find_request_arg2(): def test_auth_required_true(): '''Test the auth_required decorator with a valid user ''' - + class User(object): pass - + @milla.auth.decorators.auth_required def controller(request): return 'success' - + request = milla.Request.blank('/') request.user = User() response = controller(request) @@ -298,7 +298,7 @@ def test_auth_required_true(): def test_auth_required_false(): '''Test the auth_required decorator with no user ''' - + @milla.auth.decorators.auth_required def controller(request): return 'success' @@ -312,10 +312,10 @@ def test_auth_required_false(): def test_require_perms_none(): '''Test the require_perms decorator with no requirement ''' - + class User(object): pass - + @milla.auth.decorators.require_perms() def controller(request): return 'success' @@ -328,14 +328,14 @@ def test_require_perms_none(): def test_require_perms_valid_str(): '''Test the require_perms decorator with valid permissions as strings ''' - + class User(object): pass - + @milla.auth.decorators.require_perms('foo') def controller(request): return 'success' - + request = milla.Request.blank('/') request.user = User() request.user.permissions = ['foo'] @@ -345,15 +345,15 @@ def test_require_perms_valid_str(): def test_require_perms_valid_permission(): '''Test the require_perms decorator with valid permissions as Permissions ''' - + class User(object): pass - + req = milla.auth.permissions.Permission('foo') @milla.auth.decorators.require_perms(req) def controller(request): return 'success' - + request = milla.Request.blank('/') request.user = User() request.user.permissions = ['foo'] @@ -363,10 +363,10 @@ def test_require_perms_valid_permission(): def test_require_perms_multi_valid_string(): '''Test the require_perms decorator with multiple requirements as strings ''' - + class User(object): pass - + @milla.auth.decorators.require_perms('foo', 'bar') def controller(request): return 'success' @@ -380,10 +380,10 @@ def test_require_perms_multi_valid_string(): def test_require_perms_multi_valid_permission(): '''Test the require_perms decorator with multiple requirements as Permissions ''' - + class User(object): pass - + req1 = milla.auth.permissions.Permission('foo') req2 = milla.auth.permissions.Permission('bar') @milla.auth.decorators.require_perms(req1, req2) @@ -399,14 +399,14 @@ def test_require_perms_multi_valid_permission(): def test_require_perms_invalid_none(): '''Test the require_perms decorator with no permissions ''' - + class User(object): pass - + @milla.auth.decorators.require_perms('foo') def controller(request): return 'success' - + request = milla.Request.blank('/') request.user = User() response = controller(request) @@ -416,10 +416,10 @@ def test_require_perms_invalid_none(): def test_require_perms_invalid_empty(): '''Test the require_perms decorator with an empty permissions set ''' - + class User(object): pass - + @milla.auth.decorators.require_perms('foo') def controller(request): return 'success' @@ -434,14 +434,14 @@ def test_require_perms_invalid_empty(): def test_require_perms_invalid_string(): '''Test the require_perms decorator with invalid permissions as strings ''' - + class User(object): pass - + @milla.auth.decorators.require_perms('foo') def controller(request): return 'success' - + request = milla.Request.blank('/') request.user = User() request.user.permissions = ['bar'] @@ -452,15 +452,15 @@ def test_require_perms_invalid_string(): def test_require_perms_invalid_permission(): '''Test the require_perms decorator with invalid permissions as Permissions ''' - + class User(object): pass - + req = milla.auth.permissions.Permission('foo') @milla.auth.decorators.require_perms(req) def controller(request): return 'success' - + request = milla.Request.blank('/') request.user = User() request.user.permissions = ['bar'] @@ -471,14 +471,14 @@ def test_require_perms_invalid_permission(): def test_require_perms_multi_invalid_string(): '''Test the require_perms decorator with multiple invalid permissions as strings ''' - + class User(object): pass - + @milla.auth.decorators.require_perms('foo', 'bar') def controller(request): return 'success' - + request = milla.Request.blank('/') request.user = User() request.user.permissions = ['bar'] @@ -489,16 +489,16 @@ def test_require_perms_multi_invalid_string(): def test_require_perms_multi_invalid_permission(): '''Test the require_perms decorator with multiple invalid permissions as Permissions ''' - + class User(object): pass - + req1 = milla.auth.permissions.Permission('foo') req2 = milla.auth.permissions.Permission('foo') @milla.auth.decorators.require_perms(req1, req2) def controller(request): return 'success' - + request = milla.Request.blank('/') request.user = User() request.user.permissions = ['bar'] diff --git a/test/test_routing.py b/test/test_routing.py index a2734c0..43ea8fc 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -12,7 +12,7 @@ import nose.tools def test_static(): '''Ensure the dispatcher can resolve a static path - + Given the path ``/foo/bar/baz`` and a route for the exact same path, the resolver should return the controller mapped to the route. @@ -28,7 +28,7 @@ def test_static(): def test_urlvars(): '''Ensure the dispatcher can resolve a path with variable segments - + Given the path ``/foo/abc/def`` and a route ``/foo/{bar}/{baz}``, the resolver should return the controller mapped to the route with preset keywords ``bar='abc', baz='def'``. @@ -47,7 +47,7 @@ def test_urlvars(): @nose.tools.raises(milla.dispatch.UnresolvedPath) def test_regexp_urlvar(): '''Ensure the dispatcher can resolve alternate regexps in urlvars - + Given a route ``/test/{arg:[a-z]+}``, the resolver should return the mapped controller for the path ``/test/abcde``, but not the path ``/test/1234``. @@ -67,7 +67,7 @@ def test_regexp_urlvar(): @nose.tools.raises(milla.dispatch.UnresolvedPath) def test_unresolved(): '''Ensure the resolver raises an exception for unresolved paths - + Given a route ``/test``, the resolver should raise :py:exc:`~milla.dispatch.UnresolvedPath` for the path ``/tset``. ''' @@ -81,7 +81,7 @@ def test_unresolved(): def test_unrelated(): '''Ensure the dispatcher is not confused by unrelated paths - + Given routes for ``/testA`` and ``/testB``, the resolver should return the controller mapped to the former for the path ``/testA``, without regard for the latter. @@ -101,7 +101,7 @@ def test_unrelated(): def test_string_controller(): '''Ensure the dispatcher can find a controller given a string - + Given a string path to a controller function, the callable defined therein should be returned by the resolver for the corresponding path. @@ -134,10 +134,10 @@ def test_trailing_slash_redir(): def test_trailing_slash_none(): '''Paths that match except the trailing slash are ignored ''' - + def controller(): pass - + router = milla.dispatch.routing.Router(None) router.add_route('/test/', controller) router.resolve('/test') @@ -145,11 +145,11 @@ def test_trailing_slash_none(): def test_trailing_slash_silent(): '''Paths that match except the trailing slash are treated the same ''' - + def controller(): pass - + router = milla.dispatch.routing.Router(milla.dispatch.routing.Router.SILENT) router.add_route('/test/', controller) func = router.resolve('/test') - assert func.func is controller \ No newline at end of file + assert func.func is controller diff --git a/test/test_traversal.py b/test/test_traversal.py index ab53eb8..80c2bf5 100644 --- a/test/test_traversal.py +++ b/test/test_traversal.py @@ -9,7 +9,7 @@ import milla.dispatch.traversal def test_root(): '''Ensure the root path resolves to the root handler - + Given the path ``/``, the resolver should return the root handler, which was given to it at initialization ''' @@ -24,8 +24,8 @@ def test_root(): def test_unrelated(): '''Ensure unrelated attributes do not confuse the dispatcher - - Given the path ``/`` and a root handler with attributes and + + Given the path ``/`` and a root handler with attributes and methods, the resolver should still return the root handler ''' @@ -41,7 +41,7 @@ def test_unrelated(): def test_unresolved(): '''Ensure that the resolver returns remaining parts - + Given the path ``/foo/bar/baz`` and a root handler with no children, the resolver should raise :py:exc:`~milla.dispatch.UnresolvedPath` @@ -61,7 +61,7 @@ def test_unresolved(): def test_method(): '''Ensure the resolver finds an instance method handler - + Given the path ``/test`` and a root handler with an instance method named ``test``, the resolver should return that method. ''' @@ -77,7 +77,7 @@ def test_method(): def test_nested_class(): '''Ensure the resolver finds a nested class handler - + Given the path ``/test`` and a root handler with an inner class named ``test``, the resolver should return the inner class. ''' @@ -93,7 +93,7 @@ def test_nested_class(): def test_nested_class_method(): '''Ensure the resolver finds an instance method of a nested class - + Given the path ``/test/test`` and a root handler with an inner class named ``test``, which in turn has an instance method named ``test``, the resolver should return the ``test`` method of the @@ -112,7 +112,7 @@ def test_nested_class_method(): def test_attribute(): '''Ensure the resolver finds a handler in an instance attribute - + Given the path ``/test`` and a root handler with an attribute named ``test`` containing another class, the resolver should return that class. @@ -130,10 +130,10 @@ def test_attribute(): def test_default(): '''Ensure the resolver finds the default handler - + Given the path ``/test`` and a root handler with a method named ``default``, but no method named ``test``, the resolver should - return the ``default`` method. + return the ``default`` method. ''' class Root(object): @@ -147,7 +147,7 @@ def test_default(): def test_nested_default(): '''Ensure the resolver finds a nested default handler - + Given the path ``/test/bar`` and a root handler with a ``test`` attribute containing a class instance with a ``default`` method but no ``bar`` method, the resolver should return the ``default``