diff options
| -rw-r--r-- | CHANGES.txt | 35 | ||||
| -rw-r--r-- | README.rst | 2 | ||||
| -rw-r--r-- | TODO.txt | 6 | ||||
| -rw-r--r-- | docs/conf.py | 2 | ||||
| -rw-r--r-- | docs/whatsnew-1.2.rst | 40 | ||||
| -rw-r--r-- | pyramid/config/__init__.py | 32 | ||||
| -rw-r--r-- | pyramid/config/adapters.py | 5 | ||||
| -rw-r--r-- | pyramid/config/assets.py | 1 | ||||
| -rw-r--r-- | pyramid/config/factories.py | 7 | ||||
| -rw-r--r-- | pyramid/config/rendering.py | 14 | ||||
| -rw-r--r-- | pyramid/config/routes.py | 72 | ||||
| -rw-r--r-- | pyramid/config/security.py | 40 | ||||
| -rw-r--r-- | pyramid/config/tweens.py | 14 | ||||
| -rw-r--r-- | pyramid/config/views.py | 54 | ||||
| -rw-r--r-- | pyramid/httpexceptions.py | 2 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 10 | ||||
| -rw-r--r-- | pyramid/tests/ccbugapp/__init__.py | 16 | ||||
| -rw-r--r-- | pyramid/tests/ccbugapp/views.py | 10 | ||||
| -rw-r--r-- | pyramid/tests/conflictapp/__init__.py | 23 | ||||
| -rw-r--r-- | pyramid/tests/conflictapp/included.py | 6 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_init.py | 179 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_security.py | 84 | ||||
| -rw-r--r-- | pyramid/tests/test_integration.py | 65 | ||||
| -rw-r--r-- | setup.py | 2 |
24 files changed, 451 insertions, 270 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index c752f8005..e5340de4c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,14 @@ Next release ============ +- When a ``renderers=`` argument is not specified to the Configurator + constructor, eagerly register and commit the default renderer set. This + permits the overriding of the default renderers, which was broken in 1.2a1 + without a commit directly after Configurator construction. + +1.2a1 (2011-08-24) +================== + Features -------- @@ -171,6 +179,33 @@ Backwards Incompatibilities instead make a separate call to the method for each callable. This change was introduced to support the ``route_prefix`` feature of include. +- It may be necessary to more strictly order configuration route and view + statements when using an "autocommitting" Configurator. In the past, it + was possible to add a view which named a route name before adding a route + with that name when you used an autocommitting configurator. For example:: + + config = Configurator(autocommit=True) + config.add_view('my.pkg.someview', route_name='foo') + config.add_route('foo', '/foo') + + The above will raise an exception when the view attempts to add itself. + Now you must add the route before adding the view:: + + config = Configurator(autocommit=True) + config.add_route('foo', '/foo') + config.add_view('my.pkg.someview', route_name='foo') + + This won't effect "normal" users, only people who have legacy BFG codebases + that used an autommitting configurator and possibly tests that use the + configurator API (the configurator returned by ``pyramid.testing.setUp`` is + an autocommitting configurator). The right way to get around this is to + use a non-autocommitting configurator (the default), which does not have + these directive ordering requirements. + +- The ``pyramid.config.Configurator.add_route`` directive no longer returns a + route object. This change was required to make route vs. view + configuration processing work properly. + Documentation ------------- diff --git a/README.rst b/README.rst index 5534efc4d..895c5bb07 100644 --- a/README.rst +++ b/README.rst @@ -8,8 +8,6 @@ deployment more fun, more predictable, and more productive. Pyramid is the newest web framework produced by the `Pylons Project <http://pylonsproject.org/>`_. -Pyramid was previously known as `repoze.bfg <http://bfg.repoze.org>`_. - Support and Documentation ------------------------- @@ -8,12 +8,12 @@ Should-Have deploying via proxy: https://docs.pylonsproject.org/projects/pyramid_cookbook/dev/deployment/nginx.html#step-2-starting-paster -- Fix conflict behavior for routes and auth policies (define phases and use - order=). - Nice-to-Have ------------ +- Add a default-view-config-params decorator that can be applied to a class + which names defaults for method-based view_config decorator options. + - Flesh out "paste" chapter. - Move config-related stuff from "renderers" to config/rendering, and diff --git a/docs/conf.py b/docs/conf.py index f021f01a2..23b88b949 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.2dev' +version = '1.2a1' # The full version, including alpha/beta/rc tags. release = version diff --git a/docs/whatsnew-1.2.rst b/docs/whatsnew-1.2.rst index 9718a547c..c5a71e1ce 100644 --- a/docs/whatsnew-1.2.rst +++ b/docs/whatsnew-1.2.rst @@ -176,6 +176,46 @@ Backwards Incompatibilities :meth:`pyramid.config.Configurator.include`, it will break. You now must now instead make a separate call to the method for each callable. +- It may be necessary to more strictly order configuration route and view + statements when using an "autocommitting" :term:`Configurator`. In the + past, it was possible to add a view which named a route name before adding + a route with that name when you used an autocommitting configurator. For + example: + + .. code-block:: python + + config = Configurator(autocommit=True) + config.add_view('my.pkg.someview', route_name='foo') + config.add_route('foo', '/foo') + + The above will raise an exception when the view attempts to add itself. + Now you must add the route before adding the view: + + .. code-block:: python + + config = Configurator(autocommit=True) + config.add_route('foo', '/foo') + config.add_view('my.pkg.someview', route_name='foo') + + This won't effect "normal" users, only people who have legacy BFG codebases + that used an autommitting configurator and possibly tests that use the + configurator API (the configurator returned by + :func:`pyramid.testing.setUp` is an autocommitting configurator). The + right way to get around this is to use a default non-autocommitting + configurator, which does not have these directive ordering requirements: + + .. code-block:: python + + config = Configurator() + config.add_view('my.pkg.someview', route_name='foo') + config.add_route('foo', '/foo') + + The above will work fine. + +- The :meth:`pyramid.config.Configurator.add_route` directive no longer + returns a route object. This change was required to make route vs. view + configuration processing work properly. + Documentation Enhancements -------------------------- diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index c7ee8c47f..dc592977b 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -303,29 +303,33 @@ class Configurator( self.add_view(exceptionresponse_view, context=IExceptionResponse) self.add_view(exceptionresponse_view,context=WebobWSGIHTTPException) - # commit before adding default_view_mapper, as the - # exceptionresponse_view above requires the superdefault view - # mapper + # commit below because: + # + # - the default exceptionresponse_view requires the superdefault view + # mapper, so we need to configure it before adding default_view_mapper + # + # - superdefault renderers should be overrideable without requiring + # the user to commit before calling config.add_renderer self.commit() - if default_view_mapper is not None: - self.set_view_mapper(default_view_mapper) - self.commit() - - # The following registrations should be treated as if the methods had - # been called after configurator construction (commit should not be - # called after this). Rationale: user-supplied implementations - # should be preferred rather than add-on author implementations (as - # per automatic conflict resolution). + # self.commit() should not be called after this point because the + # following registrations should be treated as analogues of methods + # called by the user after configurator construction. Rationale: + # user-supplied implementations should be preferred rather than + # add-on author implementations with the help of automatic conflict + # resolution. if authentication_policy and not authorization_policy: authorization_policy = ACLAuthorizationPolicy() # default - if authentication_policy: - self.set_authentication_policy(authentication_policy) if authorization_policy: self.set_authorization_policy(authorization_policy) + if authentication_policy: + self.set_authentication_policy(authentication_policy) + + if default_view_mapper is not None: + self.set_view_mapper(default_view_mapper) for name, renderer in renderers: self.add_renderer(name, renderer) diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py index f022e7f08..6c6ca92b7 100644 --- a/pyramid/config/adapters.py +++ b/pyramid/config/adapters.py @@ -1,6 +1,7 @@ from zope.interface import Interface from pyramid.interfaces import IResponse +from pyramid.interfaces import PHASE3_CONFIG from pyramid.config.util import action_method @@ -27,7 +28,7 @@ class AdaptersConfiguratorMixin(object): iface = (iface,) def register(): self.registry.registerHandler(subscriber, iface) - self.action(None, register) + self.action(None, register, order=PHASE3_CONFIG) return subscriber @action_method @@ -52,7 +53,7 @@ class AdaptersConfiguratorMixin(object): reg.registerSelfAdapter((type_or_iface,), IResponse) else: reg.registerAdapter(adapter, (type_or_iface,), IResponse) - self.action((IResponse, type_or_iface), register) + self.action((IResponse, type_or_iface), register, order=PHASE3_CONFIG) def _register_response_adapters(self): # cope with WebOb response objects that aren't decorated with IResponse diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py index 931ffb74c..1b5254072 100644 --- a/pyramid/config/assets.py +++ b/pyramid/config/assets.py @@ -235,6 +235,7 @@ class AssetsConfiguratorMixin(object): from_package = sys.modules[package] to_package = sys.modules[override_package] override(from_package, path, to_package, override_prefix) + self.action(None, register) override_resource = override_asset # bw compat diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 1421082cd..53db93c64 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -4,6 +4,7 @@ from pyramid.interfaces import IDefaultRootFactory from pyramid.interfaces import IRequestFactory from pyramid.interfaces import IRootFactory from pyramid.interfaces import ISessionFactory +from pyramid.interfaces import PHASE3_CONFIG from pyramid.traversal import DefaultRootFactory @@ -24,7 +25,7 @@ class FactoriesConfiguratorMixin(object): def register(): self.registry.registerUtility(factory, IRootFactory) self.registry.registerUtility(factory, IDefaultRootFactory) # b/c - self.action(IRootFactory, register) + self.action(IRootFactory, register, order=PHASE3_CONFIG) _set_root_factory = set_root_factory # bw compat @@ -41,7 +42,7 @@ class FactoriesConfiguratorMixin(object): """ def register(): self.registry.registerUtility(session_factory, ISessionFactory) - self.action(ISessionFactory, register) + self.action(ISessionFactory, register, order=PHASE3_CONFIG) @action_method def set_request_factory(self, factory): @@ -60,5 +61,5 @@ class FactoriesConfiguratorMixin(object): factory = self.maybe_dotted(factory) def register(): self.registry.registerUtility(factory, IRequestFactory) - self.action(IRequestFactory, register) + self.action(IRequestFactory, register, order=PHASE3_CONFIG) diff --git a/pyramid/config/rendering.py b/pyramid/config/rendering.py index 3096b3d8e..deb1404e7 100644 --- a/pyramid/config/rendering.py +++ b/pyramid/config/rendering.py @@ -2,6 +2,8 @@ import warnings from pyramid.interfaces import IRendererFactory from pyramid.interfaces import IRendererGlobalsFactory +from pyramid.interfaces import PHASE1_CONFIG +from pyramid.interfaces import PHASE3_CONFIG from pyramid.config.util import action_method @@ -49,10 +51,11 @@ class RenderingConfiguratorMixin(object): # as a name if not name: name = '' - # we need to register renderers eagerly because they are used during - # view configuration - self.registry.registerUtility(factory, IRendererFactory, name=name) - self.action((IRendererFactory, name), None) + def register(): + self.registry.registerUtility(factory, IRendererFactory, name=name) + # we need to register renderers early (in phase 1) because they are + # used during view configuration (which happens in phase 3) + self.action((IRendererFactory, name), register, order=PHASE1_CONFIG) @action_method def set_renderer_globals_factory(self, factory, warn=True): @@ -86,5 +89,4 @@ class RenderingConfiguratorMixin(object): factory = self.maybe_dotted(factory) def register(): self.registry.registerUtility(factory, IRendererGlobalsFactory) - self.action(IRendererGlobalsFactory, register) - + self.action(IRendererGlobalsFactory, register, order=PHASE3_CONFIG) diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 625ef436d..6de9b6e59 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -3,6 +3,7 @@ import warnings from pyramid.interfaces import IRequest from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IRoutesMapper +from pyramid.interfaces import PHASE2_CONFIG from pyramid.exceptions import ConfigurationError from pyramid.request import route_request_iface @@ -344,34 +345,6 @@ class RoutesConfiguratorMixin(object): custom=custom_predicates ) - request_iface = self.registry.queryUtility(IRouteRequest, name=name) - if request_iface is None: - if use_global_views: - bases = (IRequest,) - else: - bases = () - request_iface = route_request_iface(name, bases) - self.registry.registerUtility( - request_iface, IRouteRequest, name=name) - deferred_views = getattr(self.registry, 'deferred_route_views', {}) - view_info = deferred_views.pop(name, ()) - for info in view_info: - self.add_view(**info) - - # deprecated adding views from add_route - if any([view, view_context, view_permission, view_renderer, - view_for, for_, permission, renderer, view_attr]): - self._add_view_from_route( - route_name=name, - view=view, - permission=view_permission or permission, - context=view_context or view_for or for_, - renderer=view_renderer or renderer, - attr=view_attr, - ) - - mapper = self.get_routes_mapper() - factory = self.maybe_dotted(factory) if pattern is None: pattern = path @@ -381,11 +354,46 @@ class RoutesConfiguratorMixin(object): if self.route_prefix: pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') - discriminator = ('route', name) - self.action(discriminator, None) + mapper = self.get_routes_mapper() - return mapper.connect(name, pattern, factory, predicates=predicates, - pregenerator=pregenerator, static=static) + def register_route_request_iface(): + request_iface = self.registry.queryUtility(IRouteRequest, name=name) + if request_iface is None: + if use_global_views: + bases = (IRequest,) + else: + bases = () + request_iface = route_request_iface(name, bases) + self.registry.registerUtility( + request_iface, IRouteRequest, name=name) + + def register_connect(): + return mapper.connect(name, pattern, factory, predicates=predicates, + pregenerator=pregenerator, static=static) + + + # We have to connect routes in the order they were provided; + # we can't use a phase to do that, because when the actions are + # sorted, actions in the same phase lose relative ordering + self.action(('route-connect', name), register_connect) + + # But IRouteRequest interfaces must be registered before we begin to + # process view registrations (in phase 3) + self.action(('route', name), register_route_request_iface, + order=PHASE2_CONFIG) + + # deprecated adding views from add_route; must come after + # route registration for purposes of autocommit ordering + if any([view, view_context, view_permission, view_renderer, + view_for, for_, permission, renderer, view_attr]): + self._add_view_from_route( + route_name=name, + view=view, + permission=view_permission or permission, + context=view_context or view_for or for_, + renderer=view_renderer or renderer, + attr=view_attr, + ) def get_routes_mapper(self): """ Return the :term:`routes mapper` object associated with diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 897d4bbec..bdf9be5e6 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -1,6 +1,9 @@ from pyramid.interfaces import IAuthorizationPolicy from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IDefaultPermission +from pyramid.interfaces import PHASE1_CONFIG +from pyramid.interfaces import PHASE2_CONFIG +from pyramid.interfaces import PHASE3_CONFIG from pyramid.exceptions import ConfigurationError from pyramid.config.util import action_method @@ -18,18 +21,16 @@ class SecurityConfiguratorMixin(object): can be used to achieve the same purpose. """ - self._set_authentication_policy(policy) - def ensure(): - if self.autocommit: - return + def register(): + self._set_authentication_policy(policy) if self.registry.queryUtility(IAuthorizationPolicy) is None: raise ConfigurationError( 'Cannot configure an authentication policy without ' 'also configuring an authorization policy ' - '(see the set_authorization_policy method)') - self.action(IAuthenticationPolicy, callable=ensure) + '(use the set_authorization_policy method)') + # authentication policy used by view config (phase 3) + self.action(IAuthenticationPolicy, register, order=PHASE2_CONFIG) - @action_method def _set_authentication_policy(self, policy): policy = self.maybe_dotted(policy) self.registry.registerUtility(policy, IAuthenticationPolicy) @@ -45,16 +46,22 @@ class SecurityConfiguratorMixin(object): :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ - self._set_authorization_policy(policy) + def register(): + self._set_authorization_policy(policy) def ensure(): + if self.autocommit: + return if self.registry.queryUtility(IAuthenticationPolicy) is None: raise ConfigurationError( - 'Cannot configure an authorization policy without also ' - 'configuring an authentication policy ' - '(see the set_authentication_policy method)') - self.action(IAuthorizationPolicy, callable=ensure) + 'Cannot configure an authorization policy without ' + 'also configuring an authentication policy ' + '(use the set_authorization_policy method)') + + # authorization policy used by view config (phase 3) and + # authentication policy (phase 2) + self.action(IAuthorizationPolicy, register, order=PHASE1_CONFIG) + self.action(None, ensure, order=PHASE3_CONFIG) - @action_method def _set_authorization_policy(self, policy): policy = self.maybe_dotted(policy) self.registry.registerUtility(policy, IAuthorizationPolicy) @@ -96,8 +103,9 @@ class SecurityConfiguratorMixin(object): :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ - # default permission used during view registration - self.registry.registerUtility(permission, IDefaultPermission) - self.action(IDefaultPermission, None) + # default permission used during view registration (phase 3) + def register(): + self.registry.registerUtility(permission, IDefaultPermission) + self.action(IDefaultPermission, register, order=PHASE1_CONFIG) diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py index be7f3d478..bf127b5df 100644 --- a/pyramid/config/tweens.py +++ b/pyramid/config/tweens.py @@ -1,6 +1,7 @@ from zope.interface import implements from pyramid.interfaces import ITweens +from pyramid.interfaces import PHASE3_CONFIG from pyramid.exceptions import ConfigurationError from pyramid.tweens import excview_tween_factory @@ -128,17 +129,20 @@ class TweensConfiguratorMixin(object): raise ConfigurationError('%s cannot be under MAIN' % name) registry = self.registry + tweens = registry.queryUtility(ITweens) if tweens is None: tweens = Tweens() registry.registerUtility(tweens, ITweens) tweens.add_implicit(EXCVIEW, excview_tween_factory, over=MAIN) - if explicit: - tweens.add_explicit(name, tween_factory) - else: - tweens.add_implicit(name, tween_factory, under=under, over=over) - self.action(('tween', name, explicit)) + def register(): + if explicit: + tweens.add_explicit(name, tween_factory) + else: + tweens.add_implicit(name, tween_factory, under=under, over=over) + + self.action(('tween', name, explicit), register, order=PHASE3_CONFIG) class CyclicDependencyError(Exception): def __init__(self, cycles): diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 8c7106736..06d6e901e 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -25,6 +25,8 @@ from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IRequest from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IRendererFactory +from pyramid.interfaces import PHASE1_CONFIG +from pyramid.interfaces import PHASE3_CONFIG from pyramid.exceptions import ConfigurationError from pyramid.exceptions import PredicateMismatch @@ -885,31 +887,6 @@ class ViewsConfiguratorMixin(object): raise ConfigurationError( 'request_type must be an interface, not %s' % request_type) - request_iface = IRequest - - if route_name is not None: - request_iface = self.registry.queryUtility(IRouteRequest, - name=route_name) - if request_iface is None: - deferred_views = getattr(self.registry, - 'deferred_route_views', None) - if deferred_views is None: - deferred_views = self.registry.deferred_route_views = {} - info = dict( - view=view, name=name, for_=for_, permission=permission, - request_type=request_type, route_name=route_name, - request_method=request_method, request_param=request_param, - containment=containment, attr=attr, - renderer=renderer, wrapper=wrapper, xhr=xhr, accept=accept, - header=header, path_info=path_info, - match_param=match_param, - custom_predicates=custom_predicates, context=context, - mapper = mapper, http_cache = http_cache, - ) - view_info = deferred_views.setdefault(route_name, []) - view_info.append(info) - return - order, predicates, phash = make_predicates(xhr=xhr, request_method=request_method, path_info=path_info, request_param=request_param, header=header, accept=accept, @@ -931,8 +908,19 @@ class ViewsConfiguratorMixin(object): registry = self.registry) def register(permission=permission, renderer=renderer): + request_iface = IRequest + if route_name is not None: + request_iface = self.registry.queryUtility(IRouteRequest, + name=route_name) + if request_iface is None: + # route configuration should have already happened in + # phase 2 + raise ConfigurationError( + 'No route named %s found for view registration' % + route_name) + if renderer is None: - # use default renderer if one exists + # use default renderer if one exists (reg'd in phase 1) if self.registry.queryUtility(IRendererFactory) is not None: renderer = renderers.RendererHelper( name=None, @@ -941,6 +929,7 @@ class ViewsConfiguratorMixin(object): if permission is None: # intent: will be None if no default permission is registered + # (reg'd in phase 1) permission = self.registry.queryUtility(IDefaultPermission) # __no_permission_required__ handled by _secure_view @@ -1058,11 +1047,11 @@ class ViewsConfiguratorMixin(object): discriminator = [ 'view', context, name, request_type, IView, containment, - request_param, request_method, match_param, route_name, attr, - xhr, accept, header, path_info] + request_param, request_method, route_name, attr, + xhr, accept, header, path_info, match_param] discriminator.extend(sorted(custom_predicates)) discriminator = tuple(discriminator) - self.action(discriminator, register) + self.action(discriminator, register, order=PHASE3_CONFIG) def derive_view(self, view, attr=None, renderer=None): """ @@ -1281,8 +1270,11 @@ class ViewsConfiguratorMixin(object): can be used to achieve the same purpose. """ mapper = self.maybe_dotted(mapper) - self.registry.registerUtility(mapper, IViewMapperFactory) - self.action(IViewMapperFactory, None) + def register(): + self.registry.registerUtility(mapper, IViewMapperFactory) + # IViewMapperFactory is looked up as the result of view config + # in phase 3 + self.action(IViewMapperFactory, register, order=PHASE1_CONFIG) @action_method def add_static_view(self, name, path, **kw): diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index 8c49889be..fef4fe47e 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -987,7 +987,7 @@ class HTTPInsufficientStorage(HTTPServerError): def exception_response(status_code, **kw): """Creates an HTTP exception based on a status code. Example:: - raise responsecode(404) # raises an HTTPNotFound exception. + raise exception_response(404) # raises an HTTPNotFound exception. The values passed as ``kw`` are provided to the exception's constructor. """ diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 6864e5dfb..408135711 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -847,4 +847,12 @@ class IRendererInfo(Interface): 'renderer was created') settings = Attribute('The deployment settings dictionary related ' 'to the current application') - + + +# configuration phases: a lower phase number means the actions associated +# with this phase will be executed earlier than those with later phase +# numbers + +PHASE1_CONFIG = -20 +PHASE2_CONFIG = -10 +PHASE3_CONFIG = 0 diff --git a/pyramid/tests/ccbugapp/__init__.py b/pyramid/tests/ccbugapp/__init__.py index ad6387a75..afe21d4e0 100644 --- a/pyramid/tests/ccbugapp/__init__.py +++ b/pyramid/tests/ccbugapp/__init__.py @@ -1,8 +1,16 @@ +from webob import Response + +def rdf_view(request): + """ """ + return Response('rdf') + +def juri_view(request): + """ """ + return Response('juri') + def includeme(config): config.add_route('rdf', 'licenses/:license_code/:license_version/rdf') config.add_route('juri', 'licenses/:license_code/:license_version/:jurisdiction') - config.add_view('.views.rdf_view', route_name='rdf') - config.add_view('.views.juri_view', route_name='juri') - - + config.add_view(rdf_view, route_name='rdf') + config.add_view(juri_view, route_name='juri') diff --git a/pyramid/tests/ccbugapp/views.py b/pyramid/tests/ccbugapp/views.py deleted file mode 100644 index 4a6939ac2..000000000 --- a/pyramid/tests/ccbugapp/views.py +++ /dev/null @@ -1,10 +0,0 @@ -from webob import Response - -def rdf_view(request): - """ """ - return Response('rdf') - -def juri_view(request): - """ """ - return Response('juri') - diff --git a/pyramid/tests/conflictapp/__init__.py b/pyramid/tests/conflictapp/__init__.py new file mode 100644 index 000000000..429237973 --- /dev/null +++ b/pyramid/tests/conflictapp/__init__.py @@ -0,0 +1,23 @@ +from pyramid.response import Response +from pyramid.authentication import AuthTktAuthenticationPolicy +from pyramid.authorization import ACLAuthorizationPolicy + +def aview(request): + return Response('a view') + +def routeview(request): + return Response('route view') + +def protectedview(request): + return Response('protected view') + +def includeme(config): + # purposely sorta-randomly ordered (route comes after view naming it, + # authz comes after views) + config.add_view(aview) + config.add_view(protectedview, name='protected', permission='view') + config.add_view(routeview, route_name='aroute') + config.add_route('aroute', '/route') + config.set_authentication_policy(AuthTktAuthenticationPolicy('seekri1t')) + config.set_authorization_policy(ACLAuthorizationPolicy()) + config.include('pyramid.tests.conflictapp.included') diff --git a/pyramid/tests/conflictapp/included.py b/pyramid/tests/conflictapp/included.py new file mode 100644 index 000000000..0b76fb2bc --- /dev/null +++ b/pyramid/tests/conflictapp/included.py @@ -0,0 +1,6 @@ +from webob import Response + +def bview(request): return Response('b view') + +def includeme(config): + config.add_view(bview) diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 0f7c7677e..0a787275d 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -103,6 +103,7 @@ class ConfiguratorTests(unittest.TestCase): this_pkg = sys.modules['pyramid.tests.test_config'] self.assertTrue(config.registry.getUtility(ISettings)) self.assertEqual(config.package, this_pkg) + config.commit() self.assertTrue(config.registry.getUtility(IRendererFactory, 'json')) self.assertTrue(config.registry.getUtility(IRendererFactory, 'string')) if not __pypy__: @@ -185,6 +186,7 @@ class ConfiguratorTests(unittest.TestCase): from pyramid.interfaces import IAuthenticationPolicy policy = object() config = self._makeOne(authentication_policy=policy) + config.commit() result = config.registry.getUtility(IAuthenticationPolicy) self.assertEqual(policy, result) @@ -205,12 +207,21 @@ class ConfiguratorTests(unittest.TestCase): from pyramid.interfaces import IRendererFactory renderer = object() config = self._makeOne(renderers=[('yeah', renderer)]) + config.commit() self.assertEqual(config.registry.getUtility(IRendererFactory, 'yeah'), renderer) + def test_ctor_default_renderers(self): + from pyramid.interfaces import IRendererFactory + from pyramid.renderers import json_renderer_factory + config = self._makeOne() + self.assertEqual(config.registry.getUtility(IRendererFactory, 'json'), + json_renderer_factory) + def test_ctor_default_permission(self): from pyramid.interfaces import IDefaultPermission config = self._makeOne(default_permission='view') + config.commit() self.assertEqual(config.registry.getUtility(IDefaultPermission), 'view') def test_ctor_session_factory(self): @@ -224,6 +235,7 @@ class ConfiguratorTests(unittest.TestCase): from pyramid.interfaces import IViewMapperFactory mapper = object() config = self._makeOne(default_view_mapper=mapper) + config.commit() self.assertEqual(config.registry.getUtility(IViewMapperFactory), mapper) @@ -463,6 +475,7 @@ class ConfiguratorTests(unittest.TestCase): reg = Registry() config = self._makeOne(reg) config.setup_registry(authentication_policy=policy) + config.commit() result = reg.getUtility(IAuthenticationPolicy) self.assertEqual(policy, result) @@ -472,6 +485,7 @@ class ConfiguratorTests(unittest.TestCase): reg = Registry() config = self._makeOne(reg) config.setup_registry(authentication_policy='pyramid.tests') + config.commit() result = reg.getUtility(IAuthenticationPolicy) import pyramid.tests self.assertEqual(result, pyramid.tests) @@ -484,6 +498,7 @@ class ConfiguratorTests(unittest.TestCase): dummy = object() config.setup_registry(authentication_policy=dummy, authorization_policy='pyramid.tests') + config.commit() result = reg.getUtility(IAuthorizationPolicy) import pyramid.tests self.assertEqual(result, pyramid.tests) @@ -607,6 +622,7 @@ class ConfiguratorTests(unittest.TestCase): reg = Registry() config = self._makeOne(reg) config.setup_registry(renderers=[('yeah', renderer)]) + config.commit() self.assertEqual(reg.getUtility(IRendererFactory, 'yeah'), renderer) @@ -616,6 +632,7 @@ class ConfiguratorTests(unittest.TestCase): reg = Registry() config = self._makeOne(reg) config.setup_registry(default_permission='view') + config.commit() self.assertEqual(reg.getUtility(IDefaultPermission), 'view') def test_setup_registry_includes(self): @@ -1913,71 +1930,33 @@ pyramid.tests.test_config.dummy_include2""", def test_add_view_with_route_name(self): from pyramid.renderers import null_renderer - from zope.component import ComponentLookupError view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, route_name='foo', renderer=null_renderer) - self.assertEqual(len(config.registry.deferred_route_views), 1) - infos = config.registry.deferred_route_views['foo'] - self.assertEqual(len(infos), 1) - info = infos[0] - self.assertEqual(info['route_name'], 'foo') - self.assertEqual(info['view'], view) - self.assertRaises(ComponentLookupError, - self._getRouteRequestIface, config, 'foo') - wrapper = self._getViewCallable(config, None) - self.assertEqual(wrapper, None) config.add_route('foo', '/a/b') + config.add_view(view=view, route_name='foo', renderer=null_renderer) request_iface = self._getRouteRequestIface(config, 'foo') self.assertNotEqual(request_iface, None) wrapper = self._getViewCallable(config, request_iface=request_iface) self.assertNotEqual(wrapper, None) self.assertEqual(wrapper(None, None), 'OK') - def test_add_view_with_route_name_deferred_views_already_exist(self): - view = lambda *arg: 'OK' - config = self._makeOne(autocommit=True) - config.registry.deferred_route_views = {'bar':[]} - config.add_view(view=view, route_name='foo') - self.assertEqual(len(config.registry.deferred_route_views), 2) - self.assertEqual(config.registry.deferred_route_views['bar'], []) - infos = config.registry.deferred_route_views['foo'] - self.assertEqual(len(infos), 1) - - def test_deferred_route_views_retains_custom_predicates(self): + def test_add_view_with_nonexistant_route_name(self): + from pyramid.renderers import null_renderer + from zope.configuration.config import ConfigurationExecutionError view = lambda *arg: 'OK' - config = self._makeOne(autocommit=True) - config.add_view(view=view, route_name='foo', custom_predicates=('123',)) - self.assertEqual(len(config.registry.deferred_route_views), 1) - infos = config.registry.deferred_route_views['foo'] - self.assertEqual(len(infos), 1) - info = infos[0] - self.assertEqual(info['route_name'], 'foo') - self.assertEqual(info['custom_predicates'], ('123',)) + config = self._makeOne() + config.add_view(view=view, route_name='foo', renderer=null_renderer) + self.assertRaises(ConfigurationExecutionError, config.commit) def test_add_view_with_route_name_exception(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy - from zope.component import ComponentLookupError view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) + config.add_route('foo', '/a/b') config.add_view(view=view, route_name='foo', context=RuntimeError, renderer=null_renderer) - self.assertEqual(len(config.registry.deferred_route_views), 1) - infos = config.registry.deferred_route_views['foo'] - self.assertEqual(len(infos), 1) - info = infos[0] - self.assertEqual(info['route_name'], 'foo') - self.assertEqual(info['view'], view) - self.assertRaises(ComponentLookupError, - self._getRouteRequestIface, config, 'foo') - wrapper_exc_view = self._getViewCallable( - config, ctx_iface=implementedBy(RuntimeError), - exception_view=True) - self.assertEqual(wrapper_exc_view, None) - config.add_route('foo', '/a/b') request_iface = self._getRouteRequestIface(config, 'foo') - self.assertNotEqual(request_iface, None) wrapper_exc_view = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), request_iface=request_iface, exception_view=True) @@ -2351,45 +2330,40 @@ pyramid.tests.test_config.dummy_include2""", def test_add_route_defaults(self): config = self._makeOne(autocommit=True) - route = config.add_route('name', 'path') + config.add_route('name', 'path') self._assertRoute(config, 'name', 'path') - self.assertEqual(route.name, 'name') def test_add_route_with_route_prefix(self): config = self._makeOne(autocommit=True) config.route_prefix = 'root' - route = config.add_route('name', 'path') - self.assertEqual(route.name, 'name') - self.assertEqual(route.pattern, 'root/path') - + config.add_route('name', 'path') self._assertRoute(config, 'name', 'root/path') def test_add_route_discriminator(self): config = self._makeOne() - route = config.add_route('name', 'path') - self._assertRoute(config, 'name', 'path') - self.assertEqual(route.name, 'name') + config.add_route('name', 'path') self.assertEqual(config._ctx.actions[-1][0], ('route', 'name')) def test_add_route_with_factory(self): config = self._makeOne(autocommit=True) factory = object() - route = config.add_route('name', 'path', factory=factory) + config.add_route('name', 'path', factory=factory) + route = self._assertRoute(config, 'name', 'path') self.assertEqual(route.factory, factory) def test_add_route_with_static(self): config = self._makeOne(autocommit=True) - route = config.add_route('name', 'path/{foo}', static=True) - self.assertEqual(route.name, 'name') + config.add_route('name', 'path/{foo}', static=True) mapper = config.get_routes_mapper() self.assertEqual(len(mapper.get_routes()), 0) self.assertEqual(mapper.generate('name', {"foo":"a"}), '/path/a') def test_add_route_with_factory_dottedname(self): config = self._makeOne(autocommit=True) - route = config.add_route( + config.add_route( 'name', 'path', factory='pyramid.tests.test_config.dummyfactory') + route = self._assertRoute(config, 'name', 'path') self.assertEqual(route.factory, dummyfactory) def test_add_route_with_xhr(self): @@ -2474,9 +2448,8 @@ pyramid.tests.test_config.dummy_include2""", def test_add_route_no_pattern_with_path(self): config = self._makeOne(autocommit=True) - route = config.add_route('name', path='path') + config.add_route('name', path='path') self._assertRoute(config, 'name', 'path') - self.assertEqual(route.name, 'name') def test_add_route_no_path_no_pattern(self): from pyramid.exceptions import ConfigurationError @@ -2485,7 +2458,8 @@ pyramid.tests.test_config.dummy_include2""", def test_add_route_with_pregenerator(self): config = self._makeOne(autocommit=True) - route = config.add_route('name', 'pattern', pregenerator='123') + config.add_route('name', 'pattern', pregenerator='123') + route = self._assertRoute(config, 'name', 'pattern') self.assertEqual(route.pregenerator, '123') def test_add_route_no_view_with_view_attr(self): @@ -2554,6 +2528,7 @@ pyramid.tests.test_config.dummy_include2""", def __call__(self, *arg, **kw): return 'moo' config.add_renderer(None, moo) + config.commit() def view(request): return 'OK' result = config.derive_view(view) @@ -2572,6 +2547,7 @@ pyramid.tests.test_config.dummy_include2""", config = self._makeOne() config.add_renderer(None, moo) config.add_renderer('foo', foo) + config.commit() result = config.derive_view(view, renderer='foo') self.assertFalse(result is view) request = self._makeRequest(config) @@ -2787,81 +2763,6 @@ pyramid.tests.test_config.dummy_include2""", config.end() self.assertTrue('div' in result.body) - def test_set_authentication_policy_no_authz_policy(self): - from zope.configuration.config import ConfigurationExecutionError - config = self._makeOne() - policy = object() - config.set_authentication_policy(policy) - self.assertRaises(ConfigurationExecutionError, config.commit) - - def test_set_authentication_policy_no_authz_policy_autocommit(self): - from pyramid.interfaces import IAuthenticationPolicy - config = self._makeOne(autocommit=True) - policy = object() - config.set_authentication_policy(policy) - self.assertEqual( - config.registry.getUtility(IAuthenticationPolicy), policy) - - def test_set_authentication_policy_with_authz_policy(self): - from pyramid.interfaces import IAuthenticationPolicy - from pyramid.interfaces import IAuthorizationPolicy - config = self._makeOne() - authn_policy = object() - authz_policy = object() - config.registry.registerUtility(authz_policy, IAuthorizationPolicy) - config.set_authentication_policy(authn_policy) - config.commit() - self.assertEqual( - config.registry.getUtility(IAuthenticationPolicy), authn_policy) - - def test_set_authentication_policy_with_authz_policy_autocommit(self): - from pyramid.interfaces import IAuthenticationPolicy - from pyramid.interfaces import IAuthorizationPolicy - config = self._makeOne(autocommit=True) - authn_policy = object() - authz_policy = object() - config.registry.registerUtility(authz_policy, IAuthorizationPolicy) - config.set_authentication_policy(authn_policy) - config.commit() - self.assertEqual( - config.registry.getUtility(IAuthenticationPolicy), authn_policy) - - def test_set_authorization_policy_no_authn_policy(self): - from zope.configuration.config import ConfigurationExecutionError - config = self._makeOne() - policy = object() - config.set_authorization_policy(policy) - self.assertRaises(ConfigurationExecutionError, config.commit) - - def test_set_authorization_policy_no_authn_policy_autocommit(self): - from pyramid.exceptions import ConfigurationError - config = self._makeOne(autocommit=True) - policy = object() - self.assertRaises(ConfigurationError, - config.set_authorization_policy, policy) - - def test_set_authorization_policy_with_authn_policy(self): - from pyramid.interfaces import IAuthorizationPolicy - from pyramid.interfaces import IAuthenticationPolicy - config = self._makeOne() - authn_policy = object() - authz_policy = object() - config.registry.registerUtility(authn_policy, IAuthenticationPolicy) - config.set_authorization_policy(authz_policy) - self.assertEqual( - config.registry.getUtility(IAuthorizationPolicy), authz_policy) - - def test_set_authorization_policy_with_authn_policy_autocommit(self): - from pyramid.interfaces import IAuthorizationPolicy - from pyramid.interfaces import IAuthenticationPolicy - config = self._makeOne(autocommit=True) - authn_policy = object() - authz_policy = object() - config.registry.registerUtility(authn_policy, IAuthenticationPolicy) - config.set_authorization_policy(authz_policy) - self.assertEqual( - config.registry.getUtility(IAuthorizationPolicy), authz_policy) - def test_set_locale_negotiator(self): from pyramid.interfaces import ILocaleNegotiator config = self._makeOne(autocommit=True) @@ -3830,11 +3731,13 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): try: config.commit() except ConfigurationConflictError, why: - c1, c2, c3, c4 = self._conflictFunctions(why) + c1, c2, c3, c4, c5, c6 = self._conflictFunctions(why) self.assertEqual(c1, 'test_conflict_route_with_view') self.assertEqual(c2, 'test_conflict_route_with_view') self.assertEqual(c3, 'test_conflict_route_with_view') self.assertEqual(c4, 'test_conflict_route_with_view') + self.assertEqual(c5, 'test_conflict_route_with_view') + self.assertEqual(c6, 'test_conflict_route_with_view') else: # pragma: no cover raise AssertionError diff --git a/pyramid/tests/test_config/test_security.py b/pyramid/tests/test_config/test_security.py new file mode 100644 index 000000000..64ba895c6 --- /dev/null +++ b/pyramid/tests/test_config/test_security.py @@ -0,0 +1,84 @@ +import unittest + +class ConfiguratorSecurityMethodsTests(unittest.TestCase): + def _makeOne(self, *arg, **kw): + from pyramid.config import Configurator + config = Configurator(*arg, **kw) + return config + + def test_set_authentication_policy_no_authz_policy(self): + from zope.configuration.config import ConfigurationExecutionError + config = self._makeOne() + policy = object() + config.set_authentication_policy(policy) + self.assertRaises(ConfigurationExecutionError, config.commit) + + def test_set_authentication_policy_no_authz_policy_autocommit(self): + from pyramid.exceptions import ConfigurationError + config = self._makeOne(autocommit=True) + policy = object() + self.assertRaises(ConfigurationError, + config.set_authentication_policy, policy) + + def test_set_authentication_policy_with_authz_policy(self): + from pyramid.interfaces import IAuthenticationPolicy + from pyramid.interfaces import IAuthorizationPolicy + config = self._makeOne() + authn_policy = object() + authz_policy = object() + config.registry.registerUtility(authz_policy, IAuthorizationPolicy) + config.set_authentication_policy(authn_policy) + config.commit() + self.assertEqual( + config.registry.getUtility(IAuthenticationPolicy), authn_policy) + + def test_set_authentication_policy_with_authz_policy_autocommit(self): + from pyramid.interfaces import IAuthenticationPolicy + from pyramid.interfaces import IAuthorizationPolicy + config = self._makeOne(autocommit=True) + authn_policy = object() + authz_policy = object() + config.registry.registerUtility(authz_policy, IAuthorizationPolicy) + config.set_authentication_policy(authn_policy) + config.commit() + self.assertEqual( + config.registry.getUtility(IAuthenticationPolicy), authn_policy) + + def test_set_authorization_policy_no_authn_policy(self): + from zope.configuration.config import ConfigurationExecutionError + config = self._makeOne() + policy = object() + config.set_authorization_policy(policy) + self.assertRaises(ConfigurationExecutionError, config.commit) + + def test_set_authorization_policy_no_authn_policy_autocommit(self): + from pyramid.interfaces import IAuthorizationPolicy + config = self._makeOne(autocommit=True) + policy = object() + config.set_authorization_policy(policy) + self.assertEqual( + config.registry.getUtility(IAuthorizationPolicy), policy) + + def test_set_authorization_policy_with_authn_policy(self): + from pyramid.interfaces import IAuthorizationPolicy + from pyramid.interfaces import IAuthenticationPolicy + config = self._makeOne() + authn_policy = object() + authz_policy = object() + config.registry.registerUtility(authn_policy, IAuthenticationPolicy) + config.set_authorization_policy(authz_policy) + config.commit() + self.assertEqual( + config.registry.getUtility(IAuthorizationPolicy), authz_policy) + + def test_set_authorization_policy_with_authn_policy_autocommit(self): + from pyramid.interfaces import IAuthorizationPolicy + from pyramid.interfaces import IAuthenticationPolicy + config = self._makeOne(autocommit=True) + authn_policy = object() + authz_policy = object() + config.registry.registerUtility(authn_policy, IAuthenticationPolicy) + config.set_authorization_policy(authz_policy) + self.assertEqual( + config.registry.getUtility(IAuthorizationPolicy), authz_policy) + diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 1ebf83062..f391e65cb 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -344,6 +344,71 @@ class TestExceptionViewsApp(IntegrationBase): res = self.testapp.get('/route_raise_exception4', status=200) self.assertTrue('whoa' in res.body) +class TestConflictApp(unittest.TestCase): + package = 'pyramid.tests.conflictapp' + def _makeConfig(self): + from pyramid.config import Configurator + config = Configurator() + return config + + def test_autoresolved_view(self): + config = self._makeConfig() + config.include(self.package) + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + res = self.testapp.get('/') + self.assertTrue('a view' in res.body) + res = self.testapp.get('/route') + self.assertTrue('route view' in res.body) + + def test_overridden_autoresolved_view(self): + from pyramid.response import Response + config = self._makeConfig() + config.include(self.package) + def thisview(request): + return Response('this view') + config.add_view(thisview) + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + res = self.testapp.get('/') + self.assertTrue('this view' in res.body) + + def test_overridden_route_view(self): + from pyramid.response import Response + config = self._makeConfig() + config.include(self.package) + def thisview(request): + return Response('this view') + config.add_view(thisview, route_name='aroute') + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + res = self.testapp.get('/route') + self.assertTrue('this view' in res.body) + + def test_nonoverridden_authorization_policy(self): + config = self._makeConfig() + config.include(self.package) + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + res = self.testapp.get('/protected', status=403) + self.assertTrue('403 Forbidden' in res) + + def test_overridden_authorization_policy(self): + config = self._makeConfig() + config.include(self.package) + from pyramid.testing import DummySecurityPolicy + config.set_authorization_policy(DummySecurityPolicy('fred')) + config.set_authentication_policy(DummySecurityPolicy(permissive=True)) + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + res = self.testapp.get('/protected', status=200) + self.assertTrue('protected view' in res) + class ImperativeIncludeConfigurationTest(unittest.TestCase): def setUp(self): from pyramid.config import Configurator @@ -53,7 +53,7 @@ if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') setup(name='pyramid', - version='1.2dev', + version='1.2a1', description=('The Pyramid web application development framework, a ' 'Pylons project'), long_description=README + '\n\n' + CHANGES, |
