diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | CHANGES.txt | 12 | ||||
| -rw-r--r-- | LICENSE.txt | 10 | ||||
| -rw-r--r-- | TODO.txt | 6 | ||||
| -rw-r--r-- | docs/api/authentication.rst | 1 | ||||
| -rw-r--r-- | docs/api/config.rst | 2 | ||||
| -rw-r--r-- | docs/api/interfaces.rst | 15 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 6 | ||||
| -rw-r--r-- | docs/tutorials/wiki/authorization.rst | 218 | ||||
| -rw-r--r-- | pyramid/authentication.py | 29 | ||||
| -rw-r--r-- | pyramid/authorization.py | 23 | ||||
| -rw-r--r-- | pyramid/config.py | 62 | ||||
| -rw-r--r-- | pyramid/encode.py | 12 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 25 | ||||
| -rw-r--r-- | pyramid/paster.py | 31 | ||||
| -rw-r--r-- | pyramid/renderers.py | 41 | ||||
| -rw-r--r-- | pyramid/static.py | 11 | ||||
| -rw-r--r-- | pyramid/tests/test_asset.py | 74 | ||||
| -rw-r--r-- | pyramid/tests/test_authorization.py | 23 | ||||
| -rw-r--r-- | pyramid/tests/test_config.py | 216 | ||||
| -rw-r--r-- | pyramid/tests/test_encode.py | 5 | ||||
| -rw-r--r-- | pyramid/tests/test_i18n.py | 43 | ||||
| -rw-r--r-- | pyramid/tests/test_paster.py | 75 | ||||
| -rw-r--r-- | pyramid/tests/test_renderers.py | 275 | ||||
| -rw-r--r-- | pyramid/tests/test_router.py | 8 | ||||
| -rw-r--r-- | pyramid/tests/test_static.py | 48 | ||||
| -rw-r--r-- | pyramid/tests/test_view.py | 21 | ||||
| -rw-r--r-- | pyramid/view.py | 26 |
28 files changed, 1030 insertions, 289 deletions
diff --git a/.gitignore b/.gitignore index e36d9f7cf..ea97734f6 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ dist/ bin/ lib/ include/ +.idea/ distribute-*.tar.gz diff --git a/CHANGES.txt b/CHANGES.txt index 768a08b0a..0bd19572a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,18 @@ Documentation - The ``wiki2`` (SQLA+URL Dispatch) tutorial was updated slightly. +- Make ``pyramid.interfaces.IAuthenticationPolicy`` and + ``pyramid.interfaces.IAuthorizationPolicy`` public interfaces, and refer to + them within the ``pyramid.authentication`` and ``pyramid.authorization`` + API docs. + +- Render the function definitions for each exposed interface in + ``pyramid.interfaces``. + +- Add missing docs reference to + ``pyramid.config.Configurator.set_view_mapper`` and refer to it within + Hooks chapter section named "Using a View Mapper". + Features -------- diff --git a/LICENSE.txt b/LICENSE.txt index c2c18cdb5..28824ee3f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -The majority of the code in repoze.bfg is supplied under this license: +The majority of the code in Pyramid is supplied under this license: A copyright notice accompanies this license document that identifies the copyright holders. @@ -39,7 +39,7 @@ The majority of the code in repoze.bfg is supplied under this license: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Portions of the code in repoze.bfg are supplied under the ZPL (headers +Portions of the code in Pyramid are supplied under the ZPL (headers within individiual files indicate that these portions are licensed under the ZPL): @@ -98,7 +98,7 @@ under the ZPL): SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Portions of the code in repoze.bfg are supplied under the Python Software +Portions of the code in Pyramid are supplied under the Python Software Foundation License version 2 (headers within individiual files indicate that these portions are so licensed): @@ -151,13 +151,13 @@ these portions are so licensed): agrees to be bound by the terms and conditions of this License Agreement. -The documentation portion of repoze.bfg (the rendered contents of the +The documentation portion of Pyramid (the rendered contents of the "docs" directory of a software distribution or checkout) is supplied under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License as described by http://creativecommons.org/licenses/by-nc-sa/3.0/us/ -Internationalization Code in ``repoze.bfg.i18n`` is supplied under the +Internationalization Code in ``pyramid.i18n`` is supplied under the following license: Copyright (C) 2007 Edgewall Software @@ -28,10 +28,14 @@ Should-Have - translationdir ZCML directive use of ``path_spec`` should maybe die. - Provide a response_set_cookie method on the request for rendered responses - that can be used as input to response.set_cookie? + that can be used as input to response.set_cookie? Or maybe accessing + ``request.response`` creates a Response, and you do + ``request.response.set_cookie(...)``. - Fix message catalog extraction / compilation documentation. +- Review http://alexmarandon.com/articles/zodb_bfg_pyramid_notes . + Nice-to-Have ------------ diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst index a6d4c1e18..bf7f8f8d5 100644 --- a/docs/api/authentication.rst +++ b/docs/api/authentication.rst @@ -18,6 +18,7 @@ Helper Classes ~~~~~~~~~~~~~~ .. autoclass:: AuthTktCookieHelper + :members: diff --git a/docs/api/config.rst b/docs/api/config.rst index 38f809c7e..2b9d7bcef 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -74,6 +74,8 @@ .. automethod:: set_renderer_globals_factory + .. automethod:: set_view_mapper + .. automethod:: testing_securitypolicy .. automethod:: testing_resources diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 3ce926230..54afdc830 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -21,21 +21,36 @@ Event-Related Interfaces Other Interfaces ++++++++++++++++ + .. autointerface:: IAuthenticationPolicy + :members: + + .. autointerface:: IAuthorizationPolicy + :members: + .. autointerface:: IExceptionResponse + :members: .. autointerface:: IRoute + :members: .. autointerface:: IRoutePregenerator + :members: .. autointerface:: ISession + :members: .. autointerface:: ISessionFactory + :members: .. autointerface:: IRendererInfo + :members: .. autointerface:: ITemplateRenderer + :members: .. autointerface:: IViewMapperFactory + :members: .. autointerface:: IViewMapper + :members: diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 227db2f0f..7e3fe0a5c 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -606,9 +606,9 @@ A user might make use of these framework components like so: config.add_handler('two', '/{action}/{id}', MyController) serve(config.make_wsgi_app()) -The :meth:`pyramid.config.Configurator.set_default_mapper` method can be used -to set a *default* view mapper (overriding the superdefault view mapper used -by Pyramid itself). +The :meth:`pyramid.config.Configurator.set_view_mapper` method can be used to +set a *default* view mapper (overriding the superdefault view mapper used by +Pyramid itself). A *single* view registration can use a view mapper by passing the mapper as the ``mapper`` argument to :meth:`~pyramid.config.Configuration.add_view`. diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index ee86eb543..e4480d6d9 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -7,21 +7,25 @@ edit, and add pages to our wiki. For purposes of demonstration we'll change our application to allow people whom are members of a *group* named ``group:editors`` to add and edit wiki pages but we'll continue allowing anyone with access to the server to view pages. :app:`Pyramid` provides -facilities for *authorization* and *authentication*. We'll make use of both -features to provide security to our application. +facilities for :term:`authorization` and :term:`authentication`. We'll make +use of both features to provide security to our application. -The source code for this tutorial stage can be browsed via -`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/ -<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/>`_. +We will add an :term:`authentication policy` and an +:term:`authorization policy` to our :term:`application +registry`, add a ``security.py`` module and give our :term:`root` +resource an :term:`ACL`. +Then we will add ``login`` and ``logout`` views, and modify the +existing views to make them return a ``logged_in`` flag to the +renderer and add :term:`permission` declarations to their ``view_config`` +decorators. -Configuring a ``pyramid`` Authentication Policy --------------------------------------------------- +Finally, we will add a ``login.pt`` template and change the existing +``view.pt`` and ``edit.pt`` to show a "Logout" link when not logged in. -For any :app:`Pyramid` application to perform authorization, we need to add a -``security.py`` module and we'll need to change our :term:`application -registry` to add an :term:`authentication policy` and a :term:`authorization -policy`. +The source code for this tutorial stage can be browsed via +`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/ +<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/>`_. Adding Authentication and Authorization Policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -64,6 +68,43 @@ user and groups sources. Note that the ``editor`` user is a member of the ``group:editors`` group in our dummy group data (the ``GROUPS`` data structure). +Giving Our Root Resource an ACL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We need to give our root resource object an :term:`ACL`. This ACL will be +sufficient to provide enough information to the :app:`Pyramid` security +machinery to challenge a user who doesn't have appropriate credentials when +he attempts to invoke the ``add_page`` or ``edit_page`` views. + +We need to perform some imports at module scope in our ``models.py`` file: + +.. code-block:: python + :linenos: + + from pyramid.security import Allow + from pyramid.security import Everyone + +Our root resource object is a ``Wiki`` instance. We'll add the following +line at class scope to our ``Wiki`` class: + +.. code-block:: python + :linenos: + + __acl__ = [ (Allow, Everyone, 'view'), + (Allow, 'group:editors', 'edit') ] + +It's only happenstance that we're assigning this ACL at class scope. An ACL +can be attached to an object *instance* too; this is how "row level security" +can be achieved in :app:`Pyramid` applications. We actually only need *one* +ACL for the entire system, however, because our security requirements are +simple, so this feature is not demonstrated. + +Our resulting ``models.py`` file will now look like so: + +.. literalinclude:: src/authorization/tutorial/models.py + :linenos: + :language: python + Adding Login and Logout Views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -129,6 +170,38 @@ template. For example: logged_in = logged_in, edit_url = edit_url) +Adding ``permission`` Declarations to our ``view_config`` Decorators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To protect each of our views with a particular permission, we need to pass a +``permission`` argument to each of our :class:`pyramid.view.view_config` +decorators. To do so, within ``views.py``: + +- We add ``permission='view'`` to the decorator attached to the + ``view_wiki`` and ``view_page`` view functions. This makes the + assertion that only users who possess the ``view`` permission + against the context resource at the time of the request may + invoke these views. We've granted + :data:`pyramid.security.Everyone` the view permission at the + root model via its ACL, so everyone will be able to invoke the + ``view_wiki`` and ``view_page`` views. + +- We add ``permission='edit'`` to the decorator attached to the + ``add_page`` and ``edit_page`` view functions. This makes the + assertion that only users who possess the effective ``edit`` + permission against the context resource at the time of the + request may invoke these views. We've granted the + ``group:editors`` principal the ``edit`` permission at the + root model via its ACL, so only a user whom is a member of + the group named ``group:editors`` will able to invoke the + ``add_page`` or ``edit_page`` views. We've likewise given + the ``editor`` user membership to this group via the + ``security.py`` file by mapping him to the ``group:editors`` + group in the ``GROUPS`` data structure (``GROUPS + = {'editor':['group:editors']}``); the ``groupfinder`` + function consults the ``GROUPS`` data structure. This means + that the ``editor`` user can add and edit pages. + Adding the ``login.pt`` Template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -154,92 +227,29 @@ class="app-welcome align-right">`` div: <a href="${request.application_url}/logout">Logout</a> </span> -Giving Our Root Resource an ACL -------------------------------- - -We need to give our root resource object an :term:`ACL`. This ACL will be -sufficient to provide enough information to the :app:`Pyramid` security -machinery to challenge a user who doesn't have appropriate credentials when -he attempts to invoke the ``add_page`` or ``edit_page`` views. +Seeing Our Changes To ``views.py`` and our Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We need to perform some imports at module scope in our ``models.py`` file: +Our ``views.py`` module will look something like this when we're done: -.. code-block:: python +.. literalinclude:: src/authorization/tutorial/views.py :linenos: + :language: python - from pyramid.security import Allow - from pyramid.security import Everyone - -Our root resource object is a ``Wiki`` instance. We'll add the following -line at class scope to our ``Wiki`` class: +Our ``edit.pt`` template will look something like this when we're done: -.. code-block:: python +.. literalinclude:: src/authorization/tutorial/templates/edit.pt :linenos: + :language: xml - __acl__ = [ (Allow, Everyone, 'view'), - (Allow, 'group:editors', 'edit') ] - -It's only happenstance that we're assigning this ACL at class scope. An ACL -can be attached to an object *instance* too; this is how "row level security" -can be achieved in :app:`Pyramid` applications. We actually only need *one* -ACL for the entire system, however, because our security requirements are -simple, so this feature is not demonstrated. - -Our resulting ``models.py`` file will now look like so: +Our ``view.pt`` template will look something like this when we're done: -.. literalinclude:: src/authorization/tutorial/models.py +.. literalinclude:: src/authorization/tutorial/templates/view.pt :linenos: - :language: python - -Adding ``permission`` Declarations to our ``view_config`` Decorators --------------------------------------------------------------------- - -To protect each of our views with a particular permission, we need to pass a -``permission`` argument to each of our :class:`pyramid.view.view_config` -decorators. To do so, within ``views.py``: - -- We add ``permission='view'`` to the decorator attached to the ``view_wiki`` - view function. This makes the assertion that only users who possess the - ``view`` permission against the context resource at the time of the request - may invoke this view. We've granted :data:`pyramid.security.Everyone` the - view permission at the root model via its ACL, so everyone will be able to - invoke the ``view_wiki`` view. - -- We add ``permission='view'`` to the decorator attached to the ``view_page`` - view function. This makes the assertion that only users who possess the - effective ``view`` permission against the context resource at the time of - the request may invoke this view. We've granted - :data:`pyramid.security.Everyone` the view permission at the root model via - its ACL, so everyone will be able to invoke the ``view_page`` view. - -- We add ``permission='edit'`` to the decorator attached to the ``add_page`` - view function. This makes the assertion that only users who possess the - effective ``edit`` permission against the context resource at the time of - the request may invoke this view. We've granted the ``group:editors`` - principal the ``edit`` permission at the root model via its ACL, so only - the a user whom is a member of the group named ``group:editors`` will able - to invoke the ``add_page`` view. We've likewise given the ``editor`` user - membership to this group via thes ``security.py`` file by mapping him to - the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function consults the - ``GROUPS`` data structure. This means that the ``editor`` user can add - pages. - -- We add ``permission='edit'`` to the decorator attached to the ``edit_page`` - view function. This makes the assertion that only users who possess the - effective ``edit`` permission against the context resource at the time of - the request may invoke this view. We've granted the ``group:editors`` - principal the ``edit`` permission at the root model via its ACL, so only - the a user whom is a member of the group named ``group:editors`` will able - to invoke the ``edit_page`` view. We've likewise given the ``editor`` user - membership to this group via thes ``security.py`` file by mapping him to - the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function consults the - ``GROUPS`` data structure. This means that the ``editor`` user can edit - pages. + :language: xml Viewing the Application in a Browser ------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can finally examine our application in a browser. The views we'll try are as follows: @@ -267,35 +277,7 @@ as follows: credentials with the username ``editor``, password ``editor`` will show the edit page form being displayed. -Seeing Our Changes To ``views.py`` and our Templates ----------------------------------------------------- - -Our ``views.py`` module will look something like this when we're done: - -.. literalinclude:: src/authorization/tutorial/views.py - :linenos: - :language: python - -Our ``edit.pt`` template will look something like this when we're done: - -.. literalinclude:: src/authorization/tutorial/templates/edit.pt - :linenos: - :language: xml - -Our ``view.pt`` template will look something like this when we're done: - -.. literalinclude:: src/authorization/tutorial/templates/view.pt - :linenos: - :language: xml - -Revisiting the Application ---------------------------- - -When we revisit the application in a browser, and log in (as a result -of hitting an edit or add page and submitting the login form with the -``editor`` credentials), we'll see a Logout link in the upper right -hand corner. When we click it, we're logged out, and redirected back -to the front page. - - - +- After logging in (as a result of hitting an edit or add page and + submitting the login form with the ``editor`` credentials), we'll see + a Logout link in the upper right hand corner. When we click it, + we're logged out, and redirected back to the front page. diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 46593c8a4..a6c74e549 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -61,12 +61,15 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): ``callback`` - Default: ``None``. A callback passed the :mod:`repoze.who` - identity and the :term:`request`, expected to return ``None`` - if the user represented by the identity doesn't exist or a - sequence of principal identifiers (possibly empty) if the user - does exist. If ``callback`` is None, the userid will be - assumed to exist with no principals. + Default: ``None``. A callback passed the :mod:`repoze.who` identity + and the :term:`request`, expected to return ``None`` if the user + represented by the identity doesn't exist or a sequence of principal + identifiers (possibly empty) representing groups if the user does + exist. If ``callback`` is None, the userid will be assumed to exist + with no group principals. + + Objects of this class implement the interface described by + :class:`pyramid.interfaces.IAuthenticationPolicy`. """ implements(IAuthenticationPolicy) @@ -146,10 +149,13 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): ``callback`` Default: ``None``. A callback passed the userid and the request, - expected to return None if the userid doesn't exist or a sequence - of principal identifiers (possibly empty) if the user does exist. - If ``callback`` is None, the userid will be assumed to exist with no - principals. + expected to return None if the userid doesn't exist or a sequence of + principal identifiers (possibly empty) representing groups if the + user does exist. If ``callback`` is None, the userid will be assumed + to exist with no group principals. + + Objects of this class implement the interface described by + :class:`pyramid.interfaces.IAuthenticationPolicy`. """ implements(IAuthenticationPolicy) @@ -257,6 +263,9 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): Default: ``True``. An auth_tkt cookie will be generated for the wildcard domain. Optional. + + Objects of this class implement the interface described by + :class:`pyramid.interfaces.IAuthenticationPolicy`. """ implements(IAuthenticationPolicy) def __init__(self, diff --git a/pyramid/authorization.py b/pyramid/authorization.py index f27369172..ac8f195f2 100644 --- a/pyramid/authorization.py +++ b/pyramid/authorization.py @@ -55,6 +55,9 @@ class ACLAuthorizationPolicy(object): is cleared for all principals encountered in previous ACLs. The walking process ends after we've processed the any ACL directly attached to ``context``; a set of principals is returned. + + Objects of this class implement the + :class:`pyramid.interfaces.IAuthorizationPolicy` interface. """ implements(IAuthorizationPolicy) @@ -115,18 +118,18 @@ class ACLAuthorizationPolicy(object): for ace_action, ace_principal, ace_permissions in acl: if not hasattr(ace_permissions, '__iter__'): ace_permissions = [ace_permissions] - if ace_action == Allow and permission in ace_permissions: + if (ace_action == Allow) and (permission in ace_permissions): if not ace_principal in denied_here: allowed_here.add(ace_principal) - if ace_action == Deny and permission in ace_permissions: - denied_here.add(ace_principal) - if ace_principal == Everyone: - # clear the entire allowed set, as we've hit a - # deny of Everyone ala (Deny, Everyone, ALL) - allowed = set() - break - elif ace_principal in allowed: - allowed.remove(ace_principal) + if (ace_action == Deny) and (permission in ace_permissions): + denied_here.add(ace_principal) + if ace_principal == Everyone: + # clear the entire allowed set, as we've hit a + # deny of Everyone ala (Deny, Everyone, ALL) + allowed = set() + break + elif ace_principal in allowed: + allowed.remove(ace_principal) allowed.update(allowed_here) diff --git a/pyramid/config.py b/pyramid/config.py index 11a639286..9fda75daa 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -381,18 +381,6 @@ class Configurator(object): return deriver(view) - def _override(self, package, path, override_package, override_prefix, - PackageOverrides=PackageOverrides): - pkg_name = package.__name__ - override_pkg_name = override_package.__name__ - override = self.registry.queryUtility( - IPackageOverrides, name=pkg_name) - if override is None: - override = PackageOverrides(package) - self.registry.registerUtility(override, IPackageOverrides, - name=pkg_name) - override.insert(path, override_pkg_name, override_prefix) - @action_method def _set_security_policies(self, authentication, authorization=None): if (authorization is not None) and (not authentication): @@ -1698,7 +1686,10 @@ class Configurator(object): request_iface = self.registry.queryUtility(IRouteRequest, name=name) if request_iface is None: - bases = use_global_views and (IRequest,) or () + if use_global_views: + bases = (IRequest,) + else: + bases = () request_iface = route_request_iface(name, bases) self.registry.registerUtility( request_iface, IRouteRequest, name=name) @@ -1832,6 +1823,17 @@ class Configurator(object): self.registry.registerUtility(factory, IRendererFactory, name=name) self.action((IRendererFactory, name), None) + def _override(self, package, path, override_package, override_prefix, + PackageOverrides=PackageOverrides): + pkg_name = package.__name__ + override_pkg_name = override_package.__name__ + override = self.registry.queryUtility(IPackageOverrides, name=pkg_name) + if override is None: + override = PackageOverrides(package) + self.registry.registerUtility(override, IPackageOverrides, + name=pkg_name) + override.insert(path, override_pkg_name, override_prefix) + @action_method def override_asset(self, to_override, override_with, _override=None): """ Add a :app:`Pyramid` asset override to the current @@ -1846,8 +1848,7 @@ class Configurator(object): See :ref:`assets_chapter` for more information about asset overrides.""" if to_override == override_with: - raise ConfigurationError('You cannot override an asset with ' - 'itself') + raise ConfigurationError('You cannot override an asset with itself') package = to_override path = '' @@ -1859,19 +1860,22 @@ class Configurator(object): if ':' in override_with: override_package, override_prefix = override_with.split(':', 1) - if path and path.endswith('/'): - if override_prefix and (not override_prefix.endswith('/')): - raise ConfigurationError( - 'A directory cannot be overridden with a file (put a ' - 'slash at the end of override_with if necessary)') + # *_isdir = override is package or directory + overridden_isdir = path=='' or path.endswith('/') + override_isdir = override_prefix=='' or override_prefix.endswith('/') - if override_prefix and override_prefix.endswith('/'): - if path and (not path.endswith('/')): - raise ConfigurationError( - 'A file cannot be overridden with a directory (put a ' - 'slash at the end of to_override if necessary)') + if overridden_isdir and (not override_isdir): + raise ConfigurationError( + 'A directory cannot be overridden with a file (put a ' + 'slash at the end of override_with if necessary)') + + if (not overridden_isdir) and override_isdir: + raise ConfigurationError( + 'A file cannot be overridden with a directory (put a ' + 'slash at the end of to_override if necessary)') override = _override or self._override # test jig + def register(): __import__(package) __import__(override_package) @@ -2085,7 +2089,7 @@ class Configurator(object): if you had passed a ``default_view_mapper`` argument to the :class:`pyramid.config.Configurator` constructor. - See also :ref:`using_an_alternate_view_mapper`. + See also :ref:`using_a_view_mapper`. """ mapper = self.maybe_dotted(mapper) self.registry.registerUtility(mapper, IViewMapperFactory) @@ -2666,10 +2670,8 @@ class ViewDeriver(object): def __init__(self, **kw): self.kw = kw self.registry = kw['registry'] - self.authn_policy = self.registry.queryUtility( - IAuthenticationPolicy) - self.authz_policy = self.registry.queryUtility( - IAuthorizationPolicy) + self.authn_policy = self.registry.queryUtility(IAuthenticationPolicy) + self.authz_policy = self.registry.queryUtility(IAuthorizationPolicy) self.logger = self.registry.queryUtility(IDebugLogger) def __call__(self, view): diff --git a/pyramid/encode.py b/pyramid/encode.py index 127c405ed..9c3a8f7c7 100644 --- a/pyramid/encode.py +++ b/pyramid/encode.py @@ -9,9 +9,6 @@ _must_quote = {} def url_quote(s, safe=''): """quote('abc def') -> 'abc%20def' - Faster version of Python stdlib urllib.quote which also quotes - the '/' character. - Each part of a URL, e.g. the path info, the query, etc., has a different set of reserved characters that must be quoted. @@ -25,10 +22,10 @@ def url_quote(s, safe=''): but not necessarily in all of them. Unlike the default version of this function in the Python stdlib, - by default, the quote function is intended for quoting individual + by default, the url_quote function is intended for quoting individual path segments instead of an already composed path that might have '/' characters in it. Thus, it *will* encode any '/' character it - finds in a string. + finds in a string. It is also slightly faster than the stdlib version. """ cachekey = (safe, always_safe) try: @@ -41,7 +38,10 @@ def url_quote(s, safe=''): safe_map = {} for i in range(256): c = chr(i) - safe_map[c] = (c in safe) and c or ('%%%02X' % i) + if c in safe: + safe_map[c] = c + else: + safe_map[c] = '%%%02X' % i _safemaps[cachekey] = safe_map res = map(safe_map.__getitem__, s) return ''.join(res) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 0f098f0f6..2364757ab 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -141,17 +141,6 @@ class IViewMapperFactory(Interface): invocation signatures and response values. """ -# internal interfaces - -class IRequest(Interface): - """ Request type interface attached to all request objects """ - -IRequest.combined = IRequest # for exception view lookups - -class IRouteRequest(Interface): - """ *internal only* interface used as in a utility lookup to find - route-specific interfaces. Not an API.""" - class IAuthenticationPolicy(Interface): """ An object representing a Pyramid authentication policy. """ def authenticated_userid(request): @@ -179,7 +168,7 @@ class IAuthenticationPolicy(Interface): """ Return a set of headers suitable for 'remembering' the principal named ``principal`` when set in a response. An individual authentication policy and its consumers can decide - on the composition and meaning of **kw. """ + on the composition and meaning of ``**kw.`` """ def forget(request): """ Return a set of headers suitable for 'forgetting' the @@ -201,6 +190,18 @@ class IAuthorizationPolicy(Interface): ``pyramid.security.principals_allowed_by_permission`` API is used.""" + +# internal interfaces + +class IRequest(Interface): + """ Request type interface attached to all request objects """ + +IRequest.combined = IRequest # for exception view lookups + +class IRouteRequest(Interface): + """ *internal only* interface used as in a utility lookup to find + route-specific interfaces. Not an API.""" + class IStaticURLInfo(Interface): """ A policy for generating URLs to static assets """ def add(name, spec, **extra): diff --git a/pyramid/paster.py b/pyramid/paster.py index bc1573fb8..f9f8925d7 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -10,7 +10,7 @@ from paste.util.template import paste_script_template_renderer from pyramid.scripting import get_root class PyramidTemplate(Template): - def pre(self, command, output_dir, vars): # pragma: no cover + def pre(self, command, output_dir, vars): vars['random_string'] = os.urandom(20).encode('hex') package_logger = vars['package'] if package_logger == 'root': @@ -19,9 +19,12 @@ class PyramidTemplate(Template): vars['package_logger'] = package_logger return Template.pre(self, command, output_dir, vars) - def post(self, *arg, **kw): # pragma: no cover - print 'Welcome to Pyramid. Sorry for the convenience.' - return Template.post(self, *arg, **kw) + def post(self, command, output_dir, vars): + self.out('Welcome to Pyramid. Sorry for the convenience.') + return Template.post(self, command, output_dir, vars) + + def out(self, msg): # pragma: no cover (replaceable testing hook) + print msg class StarterProjectTemplate(PyramidTemplate): _template_dir = 'paster_templates/starter' @@ -88,7 +91,7 @@ class PShellCommand(PCommand): command will almost certainly fail. """ - summary = "Open an interactive shell with a pyramid app loaded" + summary = "Open an interactive shell with a Pyramid application loaded" min_args = 2 max_args = 2 @@ -100,10 +103,11 @@ class PShellCommand(PCommand): help="Don't use IPython even if it is available") def command(self, IPShell=_marker): - if IPShell is _marker: - try: #pragma no cover + # IPShell passed to command method is for testing purposes + if IPShell is _marker: # pragma: no cover + try: from IPython.Shell import IPShell - except ImportError: #pragma no cover + except ImportError: IPShell = None cprt =('Type "help" for more information. "root" is the Pyramid app ' 'root object, "registry" is the Pyramid registry object.') @@ -113,16 +117,17 @@ class PShellCommand(PCommand): app = self.get_app(config_file, section_name, loadapp=self.loadapp[0]) root, closer = self.get_root(app) shell_globals = {'root':root, 'registry':app.registry} - if IPShell is not None and not self.options.disable_ipython: + + if (IPShell is None) or self.options.disable_ipython: try: - shell = IPShell(argv=[], user_ns=shell_globals) - shell.IP.BANNER = shell.IP.BANNER + '\n\n' + banner - shell.mainloop() + self.interact[0](banner, local=shell_globals) finally: closer() else: try: - self.interact[0](banner, local=shell_globals) + shell = IPShell(argv=[], user_ns=shell_globals) + shell.IP.BANNER = shell.IP.BANNER + '\n\n' + banner + shell.mainloop() finally: closer() diff --git a/pyramid/renderers.py b/pyramid/renderers.py index f58595a2c..c8771709a 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -160,15 +160,30 @@ class ChameleonRendererLookup(object): self.lock = threading.Lock() def get_spec(self, name, package): + if not package: + # if there's no package, we can't do any conversion + return name + spec = name - isabs = os.path.isabs(name) + isabspath = os.path.isabs(name) + colon_in_name = ':' in name + isabsspec = colon_in_name and (not isabspath) + isrelspec = (not isabsspec) and (not isabspath) + + # if it's already an absolute spec, we don't need to do anything, + # but if it's a relative spec or an absolute path, we need to try + # to convert it to an absolute spec + + if isrelspec: + # convert relative asset spec to absolute asset spec + pp = package_path(package) + spec = os.path.join(pp, spec) + spec = asset_spec_from_abspath(spec, package) - if (not isabs) and (not ':' in name) and package: - # relative asset spec - if not isabs: - pp = package_path(package) - spec = os.path.join(pp, spec) + elif isabspath: + # convert absolute path to absolute asset spec spec = asset_spec_from_abspath(spec, package) + return spec @property # wait until completely necessary to look up translator @@ -177,12 +192,16 @@ class ChameleonRendererLookup(object): @property # wait until completely necessary to look up debug_templates def debug(self): - settings = self.registry.settings or {} + settings = self.registry.settings + if settings is None: + return False return settings.get('debug_templates', False) @property # wait until completely necessary to look up reload_templates def auto_reload(self): - settings = self.registry.settings or {} + settings = self.registry.settings + if settings is None: + return False return settings.get('reload_templates', False) def __call__(self, info): @@ -220,7 +239,7 @@ class ChameleonRendererLookup(object): raise ValueError( 'Missing template asset: %s (%s)' % (spec, abspath)) renderer = self.impl(abspath, self) - settings = info.settings or {} + settings = info.settings if not settings.get('reload_assets'): # cache the template self.lock.acquire() @@ -268,7 +287,9 @@ class RendererHelper(object): @reify def settings(self): - settings = self.registry.settings or {} + settings = self.registry.settings + if settings is None: + settings = {} return settings @reify diff --git a/pyramid/static.py b/pyramid/static.py index 3866126ac..80981f0b8 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -37,21 +37,18 @@ class PackageURLParser(StaticURLParser): filename = request.path_info_pop(environ) resource = os.path.normcase(os.path.normpath( self.resource_name + '/' + filename)) - if ( (self.root_resource is not None) and - (not resource.startswith(self.root_resource)) ): + if not resource.startswith(self.root_resource): # Out of bounds return self.not_found(environ, start_response) if not pkg_resources.resource_exists(self.package_name, resource): return self.not_found(environ, start_response) if pkg_resources.resource_isdir(self.package_name, resource): # @@: Cache? - child_root = (self.root_resource is not None and - self.root_resource or self.resource_name) return self.__class__( - self.package_name, resource, root_resource=child_root, + self.package_name, resource, root_resource=self.resource_name, cache_max_age=self.cache_max_age)(environ, start_response) - if (environ.get('PATH_INFO') - and environ.get('PATH_INFO') != '/'): # pragma: no cover + pi = environ.get('PATH_INFO') + if pi and pi != '/': return self.error_extra_path(environ, start_response) full = pkg_resources.resource_filename(self.package_name, resource) if_none_match = environ.get('HTTP_IF_NONE_MATCH') diff --git a/pyramid/tests/test_asset.py b/pyramid/tests/test_asset.py index 260e4994e..4a93b14a0 100644 --- a/pyramid/tests/test_asset.py +++ b/pyramid/tests/test_asset.py @@ -73,7 +73,7 @@ class TestOverrideProvider(unittest.TestCase): resource_name = 'fixtures' import pyramid.tests provider = self._makeOne(pyramid.tests) - result = provider.resource_isdir(resource_name) + result = provider.resource_listdir(resource_name) self.failUnless(result) def test_get_resource_filename_override_returns_None(self): @@ -96,9 +96,9 @@ class TestOverrideProvider(unittest.TestCase): import pyramid.tests provider = self._makeOne(pyramid.tests) here = os.path.dirname(os.path.abspath(__file__)) - expected = os.path.join(here, resource_name) - result = provider.get_resource_filename(None, resource_name) - self.assertEqual(result, expected) + expected = open(os.path.join(here, resource_name)).read() + result = provider.get_resource_stream(None, resource_name) + self.assertEqual(result.read(), expected) def test_get_resource_string_override_returns_None(self): overrides = DummyOverrides(None) @@ -108,8 +108,8 @@ class TestOverrideProvider(unittest.TestCase): import pyramid.tests provider = self._makeOne(pyramid.tests) here = os.path.dirname(os.path.abspath(__file__)) - expected = os.path.join(here, resource_name) - result = provider.get_resource_filename(None, resource_name) + expected = open(os.path.join(here, resource_name)).read() + result = provider.get_resource_string(None, resource_name) self.assertEqual(result, expected) def test_has_resource_override_returns_None(self): @@ -248,6 +248,17 @@ class TestPackageOverrides(unittest.TestCase): override = po.overrides[0] self.assertEqual(override.__class__, FileOverride) + def test_insert_emptystring(self): + # XXX is this a valid case for a directory? + from pyramid.resource import DirectoryOverride + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides= [None] + po.insert('', 'package', 'bar/') + self.assertEqual(len(po.overrides), 2) + override = po.overrides[0] + self.assertEqual(override.__class__, DirectoryOverride) + def test_search_path(self): overrides = [ DummyOverride(None), DummyOverride(('package', 'name'))] package = DummyPackage('package') @@ -266,6 +277,14 @@ class TestPackageOverrides(unittest.TestCase): here = os.path.dirname(os.path.abspath(__file__)) expected = os.path.join(here, 'test_asset.py') self.assertEqual(po.get_filename('whatever'), expected) + + def test_get_filename_file_doesnt_exist(self): + overrides = [ DummyOverride(None), DummyOverride( + ('pyramid.tests', 'wont_exist'))] + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides= overrides + self.assertEqual(po.get_filename('whatever'), None) def test_get_stream(self): import os @@ -276,8 +295,17 @@ class TestPackageOverrides(unittest.TestCase): po.overrides= overrides here = os.path.dirname(os.path.abspath(__file__)) expected = open(os.path.join(here, 'test_asset.py')).read() - self.assertEqual(po.get_stream('whatever').read().replace('\r', ''), expected) + self.assertEqual(po.get_stream('whatever').read().replace('\r', ''), + expected) + def test_get_stream_file_doesnt_exist(self): + overrides = [ DummyOverride(None), DummyOverride( + ('pyramid.tests', 'wont_exist'))] + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides= overrides + self.assertEqual(po.get_stream('whatever'), None) + def test_get_string(self): import os overrides = [ DummyOverride(None), DummyOverride( @@ -289,6 +317,14 @@ class TestPackageOverrides(unittest.TestCase): expected = open(os.path.join(here, 'test_asset.py')).read() self.assertEqual(po.get_string('whatever').replace('\r', ''), expected) + def test_get_string_file_doesnt_exist(self): + overrides = [ DummyOverride(None), DummyOverride( + ('pyramid.tests', 'wont_exist'))] + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides= overrides + self.assertEqual(po.get_string('whatever'), None) + def test_has_resource(self): overrides = [ DummyOverride(None), DummyOverride( ('pyramid.tests', 'test_asset.py'))] @@ -297,6 +333,14 @@ class TestPackageOverrides(unittest.TestCase): po.overrides= overrides self.assertEqual(po.has_resource('whatever'), True) + def test_has_resource_file_doesnt_exist(self): + overrides = [ DummyOverride(None), DummyOverride( + ('pyramid.tests', 'wont_exist'))] + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides= overrides + self.assertEqual(po.has_resource('whatever'), None) + def test_isdir_false(self): overrides = [ DummyOverride( ('pyramid.tests', 'test_asset.py'))] @@ -313,6 +357,14 @@ class TestPackageOverrides(unittest.TestCase): po.overrides= overrides self.assertEqual(po.isdir('whatever'), True) + def test_isdir_doesnt_exist(self): + overrides = [ DummyOverride(None), DummyOverride( + ('pyramid.tests', 'wont_exist'))] + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides= overrides + self.assertEqual(po.isdir('whatever'), None) + def test_listdir(self): overrides = [ DummyOverride( ('pyramid.tests', 'fixtures'))] @@ -321,6 +373,14 @@ class TestPackageOverrides(unittest.TestCase): po.overrides= overrides self.failUnless(po.listdir('whatever')) + def test_listdir_doesnt_exist(self): + overrides = [ DummyOverride(None), DummyOverride( + ('pyramid.tests', 'wont_exist'))] + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides= overrides + self.assertEqual(po.listdir('whatever'), None) + class TestDirectoryOverride(unittest.TestCase): def _getTargetClass(self): from pyramid.asset import DirectoryOverride diff --git a/pyramid/tests/test_authorization.py b/pyramid/tests/test_authorization.py index c4b2fb142..ed461e2ba 100644 --- a/pyramid/tests/test_authorization.py +++ b/pyramid/tests/test_authorization.py @@ -169,6 +169,29 @@ class TestACLAuthorizationPolicy(unittest.TestCase): result = sorted(policy.principals_allowed_by_permission(context,'read')) self.assertEqual(result, []) + def test_principals_allowed_by_permission_deny_not_permission_in_acl(self): + from pyramid.security import Deny + from pyramid.security import Everyone + context = DummyContext() + acl = [ (Deny, Everyone, 'write') ] + context.__acl__ = acl + policy = self._makeOne() + result = sorted( + policy.principals_allowed_by_permission(context, 'read')) + self.assertEqual(result, []) + + def test_principals_allowed_by_permission_deny_permission_in_acl(self): + from pyramid.security import Deny + from pyramid.security import Everyone + context = DummyContext() + acl = [ (Deny, Everyone, 'read') ] + context.__acl__ = acl + policy = self._makeOne() + result = sorted( + policy.principals_allowed_by_permission(context, 'read')) + self.assertEqual(result, []) + + class DummyContext: def __init__(self, *arg, **kw): self.__dict__.update(kw) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 202dce7bf..d2ff65878 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -1491,6 +1491,16 @@ class ConfiguratorTests(unittest.TestCase): self.failIfEqual(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): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) @@ -2489,6 +2499,16 @@ class ConfiguratorTests(unittest.TestCase): config.add_translation_dirs, '/wont/exist/on/my/system') + def test_add_translation_dirs_no_specs(self): + from pyramid.interfaces import ITranslationDirectories + from pyramid.interfaces import IChameleonTranslate + config = self._makeOne() + config.add_translation_dirs() + self.assertEqual(config.registry.queryUtility(ITranslationDirectories), + None) + self.assertEqual(config.registry.queryUtility(IChameleonTranslate), + None) + def test_add_translation_dirs_asset_spec(self): import os from pyramid.interfaces import ITranslationDirectories @@ -2499,6 +2519,18 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.registry.getUtility(ITranslationDirectories), [locale]) + def test_add_translation_dirs_asset_spec_existing_translation_dirs(self): + import os + from pyramid.interfaces import ITranslationDirectories + config = self._makeOne(autocommit=True) + directories = ['abc'] + config.registry.registerUtility(directories, ITranslationDirectories) + config.add_translation_dirs('pyramid.tests.localeapp:locale') + here = os.path.dirname(__file__) + locale = os.path.join(here, 'localeapp', 'locale') + result = config.registry.getUtility(ITranslationDirectories) + self.assertEqual(result, [locale, 'abc']) + def test_add_translation_dirs_registers_chameleon_translate(self): from pyramid.interfaces import IChameleonTranslate from pyramid.threadlocal import manager @@ -2539,7 +2571,13 @@ class ConfiguratorTests(unittest.TestCase): self.assertRaises(ConfigurationError, config.override_asset, 'a:foo.pt', 'a:foo/') - def test_override_asset_success(self): + def test_override_asset_file_with_package(self): + from pyramid.exceptions import ConfigurationError + config = self._makeOne() + self.assertRaises(ConfigurationError, config.override_asset, + 'a:foo.pt', 'a') + + def test_override_asset_file_with_file(self): config = self._makeOne(autocommit=True) override = DummyUnderOverride() config.override_asset( @@ -2553,6 +2591,62 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(override.override_package, subpackage) self.assertEqual(override.override_prefix, 'templates/bar.pt') + def test_override_asset_package_with_package(self): + config = self._makeOne(autocommit=True) + override = DummyUnderOverride() + config.override_asset( + 'pyramid.tests.fixtureapp', + 'pyramid.tests.fixtureapp.subpackage', + _override=override) + from pyramid.tests import fixtureapp + from pyramid.tests.fixtureapp import subpackage + self.assertEqual(override.package, fixtureapp) + self.assertEqual(override.path, '') + self.assertEqual(override.override_package, subpackage) + self.assertEqual(override.override_prefix, '') + + def test_override_asset_directory_with_directory(self): + config = self._makeOne(autocommit=True) + override = DummyUnderOverride() + config.override_asset( + 'pyramid.tests.fixtureapp:templates/', + 'pyramid.tests.fixtureapp.subpackage:templates/', + _override=override) + from pyramid.tests import fixtureapp + from pyramid.tests.fixtureapp import subpackage + self.assertEqual(override.package, fixtureapp) + self.assertEqual(override.path, 'templates/') + self.assertEqual(override.override_package, subpackage) + self.assertEqual(override.override_prefix, 'templates/') + + def test_override_asset_directory_with_package(self): + config = self._makeOne(autocommit=True) + override = DummyUnderOverride() + config.override_asset( + 'pyramid.tests.fixtureapp:templates/', + 'pyramid.tests.fixtureapp.subpackage', + _override=override) + from pyramid.tests import fixtureapp + from pyramid.tests.fixtureapp import subpackage + self.assertEqual(override.package, fixtureapp) + self.assertEqual(override.path, 'templates/') + self.assertEqual(override.override_package, subpackage) + self.assertEqual(override.override_prefix, '') + + def test_override_asset_package_with_directory(self): + config = self._makeOne(autocommit=True) + override = DummyUnderOverride() + config.override_asset( + 'pyramid.tests.fixtureapp', + 'pyramid.tests.fixtureapp.subpackage:templates/', + _override=override) + from pyramid.tests import fixtureapp + from pyramid.tests.fixtureapp import subpackage + self.assertEqual(override.package, fixtureapp) + self.assertEqual(override.path, '') + self.assertEqual(override.override_package, subpackage) + self.assertEqual(override.override_prefix, 'templates/') + def test_add_renderer(self): from pyramid.interfaces import IRendererFactory config = self._makeOne(autocommit=True) @@ -2816,6 +2910,28 @@ class ConfiguratorTests(unittest.TestCase): renderer.assert_(bar=2) renderer.assert_(request=request) + def test_testing_add_renderer_twice(self): + config = self._makeOne(autocommit=True) + renderer1 = config.testing_add_renderer('templates/foo.pt') + renderer2 = config.testing_add_renderer('templates/bar.pt') + from pyramid.testing import DummyTemplateRenderer + self.failUnless(isinstance(renderer1, DummyTemplateRenderer)) + self.failUnless(isinstance(renderer2, DummyTemplateRenderer)) + from pyramid.renderers import render_to_response + # must provide request to pass in registry (this is a functest) + request = DummyRequest() + request.registry = config.registry + render_to_response( + 'templates/foo.pt', {'foo':1, 'bar':2}, request=request) + renderer1.assert_(foo=1) + renderer1.assert_(bar=2) + renderer1.assert_(request=request) + render_to_response( + 'templates/bar.pt', {'foo':1, 'bar':2}, request=request) + renderer2.assert_(foo=1) + renderer2.assert_(bar=2) + renderer2.assert_(request=request) + def test_testing_add_renderer_explicitrenderer(self): config = self._makeOne(autocommit=True) class E(Exception): pass @@ -3351,6 +3467,52 @@ class TestViewDeriver(unittest.TestCase): "'view_name' against context None): Allowed " "(no authorization policy in use)") + def test_with_debug_authorization_authn_policy_no_authz_policy(self): + view = lambda *arg: 'OK' + self.config.registry.settings = dict(debug_authorization=True) + from pyramid.interfaces import IAuthenticationPolicy + policy = DummySecurityPolicy(False) + self.config.registry.registerUtility(policy, IAuthenticationPolicy) + logger = self._registerLogger() + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): Allowed " + "(no authorization policy in use)") + + def test_with_debug_authorization_authz_policy_no_authn_policy(self): + view = lambda *arg: 'OK' + self.config.registry.settings = dict(debug_authorization=True) + from pyramid.interfaces import IAuthorizationPolicy + policy = DummySecurityPolicy(False) + self.config.registry.registerUtility(policy, IAuthorizationPolicy) + logger = self._registerLogger() + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): Allowed " + "(no authorization policy in use)") + def test_with_debug_authorization_no_permission(self): view = lambda *arg: 'OK' self.config.registry.settings = dict( @@ -3394,6 +3556,24 @@ class TestViewDeriver(unittest.TestCase): "debug_authorization of url url (view name " "'view_name' against context None): True") + def test_debug_auth_permission_authpol_permitted_no_request(self): + view = lambda *arg: 'OK' + self.config.registry.settings = dict( + debug_authorization=True, reload_templates=True) + logger = self._registerLogger() + self._registerSecurityPolicy(True) + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result.__call_permissive__, view) + self.assertEqual(result(None, None), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url None (view name " + "None against context None): True") + def test_debug_auth_permission_authpol_denied(self): from pyramid.exceptions import Forbidden view = lambda *arg: 'OK' @@ -3454,6 +3634,40 @@ class TestViewDeriver(unittest.TestCase): "debug_authorization of url url (view name " "'view_name' against context None): False") + def test_secured_view_authn_policy_no_authz_policy(self): + view = lambda *arg: 'OK' + self.config.registry.settings = {} + from pyramid.interfaces import IAuthenticationPolicy + policy = DummySecurityPolicy(False) + self.config.registry.registerUtility(policy, IAuthenticationPolicy) + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + + def test_secured_view_authz_policy_no_authn_policy(self): + view = lambda *arg: 'OK' + self.config.registry.settings = {} + from pyramid.interfaces import IAuthorizationPolicy + policy = DummySecurityPolicy(False) + self.config.registry.registerUtility(policy, IAuthorizationPolicy) + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + def test_with_predicates_all(self): view = lambda *arg: 'OK' predicates = [] diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py index 8a5530f2d..741a24393 100644 --- a/pyramid/tests/test_encode.py +++ b/pyramid/tests/test_encode.py @@ -24,6 +24,11 @@ class UrlEncodeTests(unittest.TestCase): result = self._callFUT([('a', la), ('b',2)], doseq=True) self.assertEqual(result, 'a=LaPe%C3%B1a&a=LaPe%C3%B1a&b=2') + def test_int_val_multiple(self): + s = [1, 2] + result = self._callFUT([('a', s)], doseq=True) + self.assertEqual(result, 'a=1&a=2') + def test_dict(self): result = self._callFUT({'a':1}) self.assertEqual(result, 'a=1') diff --git a/pyramid/tests/test_i18n.py b/pyramid/tests/test_i18n.py index ce36c57c8..3155f0ba1 100644 --- a/pyramid/tests/test_i18n.py +++ b/pyramid/tests/test_i18n.py @@ -51,6 +51,20 @@ class TestLocalizer(unittest.TestCase): 'singular') self.failUnless(localizer.pluralizer) + def test_pluralize_pluralizer_already_added(self): + translations = DummyTranslations() + localizer = self._makeOne(None, translations) + def pluralizer(*arg, **kw): + return arg, kw + localizer.pluralizer = pluralizer + result = localizer.pluralize('singular', 'plural', 1, domain='1', + mapping={}) + self.assertEqual( + result, + (('singular', 'plural', 1), {'domain': '1', 'mapping': {}}) + ) + self.failUnless(localizer.pluralizer is pluralizer) + class Test_negotiate_locale_name(unittest.TestCase): def setUp(self): cleanUp() @@ -174,6 +188,18 @@ class Test_make_localizer(unittest.TestCase): self.assertEqual(result.translate('Approve', 'deformsite'), 'Approve') + def test_locale_from_mo_mo_isdir(self): + import os + from pyramid.i18n import Localizer + here = os.path.dirname(__file__) + localedir = os.path.join(here, 'localeapp', 'locale') + localedirs = [localedir] + locale_name = 'gb' + result = self._callFUT(locale_name, localedirs) + self.assertEqual(result.__class__, Localizer) + self.assertEqual(result.translate('Approve', 'deformsite'), + 'Approve') + class Test_get_localizer(unittest.TestCase): def setUp(self): cleanUp() @@ -313,6 +339,15 @@ class TestTranslations(unittest.TestCase): translations = translations1.add(translations2, merge=False) return translations + def test_load_locales_None(self): + import gettext + import os + here = os.path.dirname(__file__) + localedir = os.path.join(here, 'localeapp', 'locale') + klass = self._getTargetClass() + result = klass.load(localedir, None, domain=None) + self.assertEqual(result.__class__, gettext.NullTranslations) + def test_load_domain_None(self): import gettext import os @@ -357,6 +392,14 @@ class TestTranslations(unittest.TestCase): inst.merge(inst2) self.assertEqual(inst._catalog['a'], 'b') + def test_merge_gnutranslations_not_translations(self): + import gettext + t = gettext.GNUTranslations() + t._catalog = {'a':'b'} + inst = self._makeOne() + inst.merge(t) + self.assertEqual(inst._catalog['a'], 'b') + def test_add_different_domain_merge_true_notexisting(self): inst = self._makeOne() inst2 = self._makeOne() diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 35349b7c7..07ec4f7b7 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -1,5 +1,39 @@ import unittest +class TestPyramidTemplate(unittest.TestCase): + def _getTargetClass(self): + from pyramid.paster import PyramidTemplate + return PyramidTemplate + + def _makeOne(self, name): + cls = self._getTargetClass() + return cls(name) + + def test_pre_logger_eq_root(self): + tmpl = self._makeOne('name') + vars = {'package':'root'} + result = tmpl.pre(None, None, vars) + self.assertEqual(result, None) + self.assertEqual(vars['package_logger'], 'app') + self.failUnless(len(vars['random_string']) == 40) + + def test_pre_logger_noteq_root(self): + tmpl = self._makeOne('name') + vars = {'package':'notroot'} + result = tmpl.pre(None, None, vars) + self.assertEqual(result, None) + self.assertEqual(vars['package_logger'], 'notroot') + self.failUnless(len(vars['random_string']) == 40) + + def test_post(self): + tmpl = self._makeOne('name') + vars = {'package':'root'} + L = [] + tmpl.out = lambda msg: L.append(msg) + result = tmpl.post(None, None, vars) + self.assertEqual(result, None) + self.assertEqual(L, ['Welcome to Pyramid. Sorry for the convenience.']) + class TestPShellCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.paster import PShellCommand @@ -8,7 +42,7 @@ class TestPShellCommand(unittest.TestCase): def _makeOne(self): return self._getTargetClass()('pshell') - def test_command_ipython_disabled(self): + def test_command_ipshell_is_None_ipython_enabled(self): command = self._makeOne() interact = DummyInteractor() app = DummyApp() @@ -18,7 +52,7 @@ class TestPShellCommand(unittest.TestCase): command.args = ('/foo/bar/myapp.ini', 'myapp') class Options(object): pass command.options = Options() - command.options.disable_ipython =True + command.options.disable_ipython = False command.command(IPShell=None) self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(loadapp.section_name, 'myapp') @@ -32,6 +66,30 @@ class TestPShellCommand(unittest.TestCase): self.failUnless(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) + def test_command_ipshell_is_not_None_ipython_disabled(self): + command = self._makeOne() + interact = DummyInteractor() + app = DummyApp() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + command.args = ('/foo/bar/myapp.ini', 'myapp') + class Options(object): pass + command.options = Options() + command.options.disable_ipython = True + command.command(IPShell='notnone') + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.failUnless(loadapp.relative_to) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['registry'], dummy_registry) + self.assertEqual(pushed['request'].registry, dummy_registry) + self.assertEqual(interact.local, {'root':dummy_root, + 'registry':dummy_registry}) + self.failUnless(interact.banner) + self.assertEqual(len(app.threadlocal_manager.popped), 1) + def test_command_ipython_enabled(self): command = self._makeOne() app = DummyApp() @@ -133,6 +191,19 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(result, None) self.assertEqual(L, []) + def test_no_mapper(self): + command = self._makeOne() + command._get_mapper = lambda *arg:None + L = [] + command.out = L.append + app = DummyApp() + loadapp = DummyLoadApp(app) + command.loadapp = (loadapp,) + command.args = ('/foo/bar/myapp.ini', 'myapp') + result = command.command() + self.assertEqual(result, None) + self.assertEqual(L, []) + def test_single_route_no_route_registered(self): command = self._makeOne() route = DummyRoute('a', '/a') diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 3edeb0f4c..70c2c620e 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -10,15 +10,34 @@ class TestTemplateRendererFactory(unittest.TestCase): def tearDown(self): cleanUp() - def _callFUT(self, path, factory): + def _callFUT(self, info, impl): from pyramid.renderers import template_renderer_factory - return template_renderer_factory(path, factory) + return template_renderer_factory(info, impl) + + def test_lookup_found(self): + from pyramid.interfaces import IChameleonLookup + L = [] + def dummy(info): + L.append(info) + return True + self.config.registry.registerUtility(dummy, IChameleonLookup, + name='abc') + class DummyInfo(object): + pass + info = DummyInfo() + info.registry = self.config.registry + info.type = 'abc' + result = self._callFUT(info, None) + self.assertEqual(result, True) + self.assertEqual(L, [info]) - def test_abspath_notfound(self): + def test_lookup_miss(self): from pyramid.interfaces import ITemplateRenderer - abspath = '/wont/exist' + import os + abspath = os.path.abspath(__file__) + renderer = {} self.config.registry.registerUtility( - {}, ITemplateRenderer, name=abspath) + renderer, ITemplateRenderer, name=abspath) info = DummyRendererInfo({ 'name':abspath, 'package':None, @@ -26,15 +45,142 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':{}, 'type':'type', }) - self.assertRaises(ValueError, self._callFUT, info, None) + result = self._callFUT(info, None) + self.failUnless(result is renderer) + +class TestChameleonRendererLookup(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _makeOne(self, impl): + from pyramid.renderers import ChameleonRendererLookup + return ChameleonRendererLookup(impl, self.config.registry) - def test_abspath_alreadyregistered(self): + def _registerTemplateRenderer(self, renderer, name): from pyramid.interfaces import ITemplateRenderer + self.config.registry.registerUtility( + renderer, ITemplateRenderer, name=name) + + def test_get_spec_not_abspath_no_colon_no_package(self): + lookup = self._makeOne(None) + result = lookup.get_spec('foo', None) + self.assertEqual(result, 'foo') + + def test_get_spec_not_abspath_no_colon_with_package(self): + from pyramid import tests + lookup = self._makeOne(None) + result = lookup.get_spec('foo', tests) + self.assertEqual(result, 'pyramid.tests:foo') + + def test_get_spec_not_abspath_with_colon_no_package(self): + lookup = self._makeOne(None) + result = lookup.get_spec('fudge:foo', None) + self.assertEqual(result, 'fudge:foo') + + def test_get_spec_not_abspath_with_colon_with_package(self): + from pyramid import tests + lookup = self._makeOne(None) + result = lookup.get_spec('fudge:foo', tests) + self.assertEqual(result, 'fudge:foo') + + def test_get_spec_is_abspath_no_colon_no_package(self): + import os + lookup = self._makeOne(None) + spec = os.path.abspath(__file__) + result = lookup.get_spec(spec, None) + self.assertEqual(result, spec) + + def test_get_spec_is_abspath_no_colon_with_path_in_package(self): + from pyramid import tests + import os + lookup = self._makeOne(None) + f = __file__ + spec = os.path.abspath(f) + result = lookup.get_spec(spec, tests) + self.assertEqual(result, 'pyramid.tests:%s' % os.path.split(f)[-1]) + + def test_get_spec_is_abspath_no_colon_with_path_outside_package(self): + import venusian # used only because it's outside of pyramid.tests + import os + lookup = self._makeOne(None) + f = __file__ + spec = os.path.abspath(f) + result = lookup.get_spec(spec, venusian) + self.assertEqual(result, spec) + + def test_get_spec_is_abspath_with_colon_no_package(self): + import os + lookup = self._makeOne(None) + spec = os.path.join(os.path.abspath(__file__), ':foo') + result = lookup.get_spec(spec, None) + self.assertEqual(result, spec) + + def test_get_spec_is_abspath_with_colon_with_path_in_package(self): + from pyramid import tests + import os + lookup = self._makeOne(None) + f = os.path.abspath(__file__) + spec = os.path.join(f, ':foo') + result = lookup.get_spec(spec, tests) + tail = spec.split(os.sep)[-2:] + self.assertEqual(result, 'pyramid.tests:%s/%s' % tuple(tail)) + + def test_get_spec_is_abspath_with_colon_with_path_outside_package(self): + import venusian # used only because it's outside of pyramid.tests + import os + lookup = self._makeOne(None) + spec = os.path.join(os.path.abspath(__file__), ':foo') + result = lookup.get_spec(spec, venusian) + self.assertEqual(result, spec) + + def test_translate(self): + from pyramid.interfaces import IChameleonTranslate + def t(): pass + self.config.registry.registerUtility(t, IChameleonTranslate) + lookup = self._makeOne(None) + self.assertEqual(lookup.translate, t) + + def test_debug_settings_None(self): + self.config.registry.settings = None + lookup = self._makeOne(None) + self.assertEqual(lookup.debug, False) + + def test_debug_settings_not_None(self): + self.config.registry.settings = {'debug_templates':True} + lookup = self._makeOne(None) + self.assertEqual(lookup.debug, True) + + def test_auto_reload_settings_None(self): + self.config.registry.settings = None + lookup = self._makeOne(None) + self.assertEqual(lookup.auto_reload, False) + + def test_auto_reload_settings_not_None(self): + self.config.registry.settings = {'reload_templates':True} + lookup = self._makeOne(None) + self.assertEqual(lookup.auto_reload, True) + + def test___call__abspath_path_notexists(self): + abspath = '/wont/exist' + self._registerTemplateRenderer({}, abspath) + info = DummyRendererInfo({ + 'name':abspath, + 'package':None, + 'registry':self.config.registry, + 'settings':{}, + 'type':'type', + }) + lookup = self._makeOne(None) + self.assertRaises(ValueError, lookup.__call__, info) + + def test___call__abspath_alreadyregistered(self): import os abspath = os.path.abspath(__file__) renderer = {} - self.config.registry.registerUtility( - renderer, ITemplateRenderer, name=abspath) + self._registerTemplateRenderer(renderer, abspath) info = DummyRendererInfo({ 'name':abspath, 'package':None, @@ -42,16 +188,15 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':{}, 'type':'type', }) - result = self._callFUT(info, None) + lookup = self._makeOne(None) + result = lookup(info) self.failUnless(result is renderer) - def test_abspath_notyetregistered(self): - from pyramid.interfaces import ITemplateRenderer + def test___call__abspath_notyetregistered(self): import os abspath = os.path.abspath(__file__) renderer = {} - self.config.registry.registerUtility( - renderer, ITemplateRenderer, name=abspath) + factory = DummyFactory(renderer) info = DummyRendererInfo({ 'name':abspath, 'package':None, @@ -59,15 +204,14 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':{}, 'type':'type', }) - result = self._callFUT(info, None) - self.failUnless(result is renderer) + lookup = self._makeOne(factory) + result = lookup(info) + self.assertEqual(result, renderer) - def test_relpath_path_registered(self): + def test___call__relpath_path_registered(self): renderer = {} - from pyramid.interfaces import ITemplateRenderer - self.config.registry.registerUtility( - renderer, ITemplateRenderer, name='foo/bar') spec = 'foo/bar' + self._registerTemplateRenderer(renderer, spec) info = DummyRendererInfo({ 'name':spec, 'package':None, @@ -75,17 +219,15 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':{}, 'type':'type', }) - result = self._callFUT(info, None) + lookup = self._makeOne(None) + result = lookup(info) self.failUnless(renderer is result) - def test_relpath_has_package_registered(self): + def test___call__relpath_has_package_registered(self): renderer = {} - from pyramid.interfaces import ITemplateRenderer import pyramid.tests spec = 'bar/baz' - self.config.registry.registerUtility( - renderer, ITemplateRenderer, - name='pyramid.tests:bar/baz') + self._registerTemplateRenderer(renderer, 'pyramid.tests:bar/baz') info = DummyRendererInfo({ 'name':spec, 'package':pyramid.tests, @@ -93,10 +235,11 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':{}, 'type':'type', }) - result = self._callFUT(info, None) + lookup = self._makeOne(None) + result = lookup(info) self.failUnless(renderer is result) - def test_spec_notfound(self): + def test___call__spec_notfound(self): spec = 'pyramid.tests:wont/exist' info = DummyRendererInfo({ 'name':spec, @@ -105,10 +248,10 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':{}, 'type':'type', }) - self.assertRaises(ValueError, self._callFUT, info, None) + lookup = self._makeOne(None) + self.assertRaises(ValueError, lookup.__call__, info) - def test_spec_alreadyregistered(self): - from pyramid.interfaces import ITemplateRenderer + def test___call__spec_alreadyregistered(self): from pyramid import tests module_name = tests.__name__ relpath = 'test_renderers.py' @@ -121,12 +264,12 @@ class TestTemplateRendererFactory(unittest.TestCase): 'type':'type', }) renderer = {} - self.config.registry.registerUtility( - renderer, ITemplateRenderer, name=spec) - result = self._callFUT(info, None) + self._registerTemplateRenderer(renderer, spec) + lookup = self._makeOne(None) + result = lookup(info) self.failUnless(result is renderer) - def test_spec_notyetregistered(self): + def test___call__spec_notyetregistered(self): import os from pyramid import tests module_name = tests.__name__ @@ -141,7 +284,8 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':{}, 'type':'type', }) - result = self._callFUT(info, factory) + lookup = self._makeOne(factory) + result = lookup(info) self.failUnless(result is renderer) path = os.path.abspath(__file__).split('$')[0] # jython if path.endswith('.pyc'): # pragma: no cover @@ -149,7 +293,7 @@ class TestTemplateRendererFactory(unittest.TestCase): self.failUnless(factory.path.startswith(path)) self.assertEqual(factory.kw, {}) - def test_reload_assets_true(self): + def test___call__reload_assets_true(self): import pyramid.tests from pyramid.interfaces import ISettings from pyramid.interfaces import ITemplateRenderer @@ -166,13 +310,14 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':settings, 'type':'type', }) - result = self._callFUT(info, factory) + lookup = self._makeOne(factory) + result = lookup(info) self.failUnless(result is renderer) spec = '%s:%s' % ('pyramid.tests', 'test_renderers.py') self.assertEqual(reg.queryUtility(ITemplateRenderer, name=spec), None) - def test_reload_assets_false(self): + def test___call__reload_assets_false(self): import pyramid.tests from pyramid.interfaces import ITemplateRenderer settings = {'reload_assets':False} @@ -187,7 +332,8 @@ class TestTemplateRendererFactory(unittest.TestCase): 'settings':settings, 'type':'type', }) - result = self._callFUT(info, factory) + lookup = self._makeOne(factory) + result = lookup(info) self.failUnless(result is renderer) spec = '%s:%s' % ('pyramid.tests', 'test_renderers.py') self.assertNotEqual(reg.queryUtility(ITemplateRenderer, name=spec), @@ -324,6 +470,18 @@ class TestRendererHelper(unittest.TestCase): helper = self._makeOne() verifyObject(IRendererInfo, helper) + def test_settings_registry_settings_is_None(self): + class Dummy(object): + settings = None + helper = self._makeOne(registry=Dummy) + self.assertEqual(helper.settings, {}) + + def test_settings_registry_settings_is_not_None(self): + class Dummy(object): + settings = {'a':1} + helper = self._makeOne(registry=Dummy) + self.assertEqual(helper.settings, {'a':1}) + def _registerRendererFactory(self): from pyramid.interfaces import IRendererFactory def renderer(*arg): @@ -504,6 +662,18 @@ class Test_render(unittest.TestCase): renderer.assert_(a=1) renderer.assert_(request=request) + def test_it_with_package(self): + import pyramid.tests + renderer = self.config.testing_add_renderer( + 'pyramid.tests:abc/def.pt') + renderer.string_response = 'abc' + request = testing.DummyRequest() + result = self._callFUT('abc/def.pt', dict(a=1), request=request, + package=pyramid.tests) + self.assertEqual(result, 'abc') + renderer.assert_(a=1) + renderer.assert_(request=request) + class Test_render_to_response(unittest.TestCase): def setUp(self): self.config = testing.setUp() @@ -535,7 +705,19 @@ class Test_render_to_response(unittest.TestCase): self.assertEqual(response.body, 'abc') renderer.assert_(a=1) renderer.assert_(request=request) - + + def test_it_with_package(self): + import pyramid.tests + renderer = self.config.testing_add_renderer( + 'pyramid.tests:abc/def.pt') + renderer.string_response = 'abc' + request = testing.DummyRequest() + response = self._callFUT('abc/def.pt', dict(a=1), request=request, + package=pyramid.tests) + self.assertEqual(response.body, 'abc') + renderer.assert_(a=1) + renderer.assert_(request=request) + class Test_get_renderer(unittest.TestCase): def setUp(self): self.config = testing.setUp() @@ -545,14 +727,21 @@ class Test_get_renderer(unittest.TestCase): def _callFUT(self, renderer_name, **kw): from pyramid.renderers import get_renderer - return get_renderer(renderer_name) + return get_renderer(renderer_name, **kw) - def test_it(self): + def test_it_no_package(self): renderer = self.config.testing_add_renderer( 'pyramid.tests:abc/def.pt') result = self._callFUT('abc/def.pt') self.assertEqual(result, renderer) + def test_it_with_package(self): + import pyramid.tests + renderer = self.config.testing_add_renderer( + 'pyramid.tests:abc/def.pt') + result = self._callFUT('abc/def.pt', package=pyramid.tests) + self.assertEqual(result, renderer) + class Dummy: pass diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index c559f58d0..ffda14738 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -112,6 +112,14 @@ class TestRouter(unittest.TestCase): environ.update(extras) return environ + def test_ctor_registry_has_no_settings(self): + self.registry.settings = None + router = self._makeOne() + self.assertEqual(router.debug_notfound, False) + self.assertEqual(router.debug_routematch, False) + self.failIf('debug_notfound' in router.__dict__) + self.failIf('debug_routematch' in router.__dict__) + def test_root_policy(self): context = DummyContext() self._registerTraverserFactory(context) diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index acf5a754b..cd3006689 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -92,6 +92,14 @@ class TestPackageURLParser(unittest.TestCase): body = response[0] self.failUnless('<html>static</html>' in body) + def test_resource_has_extra_path_info(self): + environ = self._makeEnviron(PATH_INFO='/static/index.html/more') + inst = self._makeOne('pyramid.tests', 'fixtures') + sr = DummyStartResponse() + response = inst(environ, sr) + body = response[0] + self.failUnless("The trailing path '/more' is not allowed" in body) + def test_resource_is_file_with_cache_max_age(self): environ = self._makeEnviron(PATH_INFO='/index.html') inst = self._makeOne('pyramid.tests', 'fixtures/static', @@ -122,6 +130,15 @@ class TestPackageURLParser(unittest.TestCase): ['Accept-Ranges', 'Content-Length', 'Content-Range', 'Content-Type', 'ETag', 'Last-Modified']) + def test_with_root_resource(self): + environ = self._makeEnviron(PATH_INFO='/static/index.html') + inst = self._makeOne('pyramid.tests', 'fixtures', + root_resource='fixtures/static') + sr = DummyStartResponse() + response = inst(environ, sr) + body = response[0] + self.failUnless('<html>static</html>' in body) + def test_if_none_match(self): class DummyEq(object): def __eq__(self, other): @@ -136,6 +153,18 @@ class TestPackageURLParser(unittest.TestCase): self.assertEqual(sr.headerlist[0][0], 'ETag') self.assertEqual(response[0], '') + def test_if_none_match_miss(self): + class DummyEq(object): + def __eq__(self, other): + return False + dummy_eq = DummyEq() + environ = self._makeEnviron(HTTP_IF_NONE_MATCH=dummy_eq) + inst = self._makeOne('pyramid.tests', 'fixtures/static') + sr = DummyStartResponse() + inst(environ, sr) + self.assertEqual(len(sr.headerlist), 6) + self.assertEqual(sr.status, '200 OK') + def test_repr(self): import os.path inst = self._makeOne('pyramid.tests', 'fixtures/static') @@ -258,6 +287,14 @@ class TestStaticURLInfo(unittest.TestCase): request = DummyRequest() self.assertRaises(ValueError, inst.generate, 'path', request) + def test_generate_registration_miss(self): + inst = self._makeOne(None) + inst.registrations = [('name', 'spec', False), + ('http://example.com/foo/', 'package:path/',True)] + request = DummyRequest() + result = inst.generate('package:path/abc', request) + self.assertEqual(result, 'http://example.com/foo/abc') + def test_generate_slash_in_name1(self): inst = self._makeOne(None) inst.registrations = [('http://example.com/foo/', 'package:path/',True)] @@ -333,6 +370,17 @@ class TestStaticURLInfo(unittest.TestCase): permission='abc') self.assertEqual(config.kw['view_permission'], 'abc') + def test_add_viewname_with_view_permission(self): + class Config: + def add_route(self, *arg, **kw): + self.arg = arg + self.kw = kw + config = Config() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_permission='abc') + self.assertEqual(config.kw['view_permission'], 'abc') + class DummyStartResponse: def __call__(self, status, headerlist, exc_info=None): self.status = status diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 5d6028b4f..826fc4290 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -499,6 +499,27 @@ class Test_default_exceptionresponse_view(unittest.TestCase): result = self._callFUT(context, request) self.assertEqual(result, 'abc') +class Test_patch_mimetypes(unittest.TestCase): + def _callFUT(self, module): + from pyramid.view import init_mimetypes + return init_mimetypes(module) + + def test_has_init(self): + class DummyMimetypes(object): + def init(self): + self.initted = True + module = DummyMimetypes() + result = self._callFUT(module) + self.assertEqual(result, True) + self.assertEqual(module.initted, True) + + def test_missing_init(self): + class DummyMimetypes(object): + pass + module = DummyMimetypes() + result = self._callFUT(module) + self.assertEqual(result, False) + class ExceptionResponse(Exception): status = '404 Not Found' app_iter = ['Not Found'] diff --git a/pyramid/view.py b/pyramid/view.py index 659a63e1e..b2aaba964 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -1,15 +1,4 @@ import mimetypes - -# See http://bugs.python.org/issue5853 which is a recursion bug -# that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix -# has been applied on the Python 2 trunk). This workaround should -# really be in Paste if anywhere, but it's easiest to just do it -# here and get it over with to avoid needing to deal with any -# fallout. - -if hasattr(mimetypes, 'init'): - mimetypes.init() - import venusian from zope.interface import providedBy @@ -24,6 +13,21 @@ from pyramid.renderers import RendererHelper from pyramid.static import static_view from pyramid.threadlocal import get_current_registry +def init_mimetypes(mimetypes): + # this is a function so it can be unittested + if hasattr(mimetypes, 'init'): + mimetypes.init() + return True + return False + +# See http://bugs.python.org/issue5853 which is a recursion bug +# that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix +# has been applied on the Python 2 trunk). This workaround should +# really be in Paste if anywhere, but it's easiest to just do it +# here and get it over with to avoid needing to deal with any +# fallout. +init_mimetypes(mimetypes) + # Nast BW compat hack: dont yet deprecate this (ever?) class static(static_view): # only subclass for purposes of autodoc __doc__ = static_view.__doc__ |
