summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml6
-rw-r--r--CHANGES.txt416
-rw-r--r--CONTRIBUTORS.txt14
-rw-r--r--HACKING.txt25
-rw-r--r--RELEASING.txt6
-rw-r--r--TODO.txt108
-rw-r--r--docs/Makefile8
-rw-r--r--docs/api.rst3
-rw-r--r--docs/api/authentication.rst16
-rw-r--r--docs/api/chameleon_text.rst31
-rw-r--r--docs/api/chameleon_zpt.rst28
-rw-r--r--docs/api/config.rst6
-rw-r--r--docs/api/decorator.rst9
-rw-r--r--docs/api/registry.rst14
-rw-r--r--docs/api/request.rst56
-rw-r--r--docs/api/session.rst2
-rw-r--r--docs/api/settings.rst2
-rw-r--r--docs/api/testing.rst2
-rw-r--r--docs/changes.rst2
-rw-r--r--docs/conf.py4
-rw-r--r--docs/designdefense.rst14
-rw-r--r--docs/glossary.rst58
-rw-r--r--docs/index.rst15
-rw-r--r--docs/latexindex.rst3
-rw-r--r--docs/narr/MyProject/myproject/static/pylons.css435
-rw-r--r--docs/narr/advconfig.rst8
-rw-r--r--docs/narr/commandline.rst4
-rw-r--r--docs/narr/firstapp.rst13
-rw-r--r--docs/narr/helloworld.py15
-rw-r--r--docs/narr/hooks.rst200
-rw-r--r--docs/narr/install.rst213
-rw-r--r--docs/narr/introduction.rst6
-rw-r--r--docs/narr/introspector.rst4
-rw-r--r--docs/narr/project.rst41
-rw-r--r--docs/narr/renderers.rst6
-rw-r--r--docs/narr/security.rst8
-rw-r--r--docs/narr/sessions.rst13
-rw-r--r--docs/narr/subrequest.rst271
-rw-r--r--docs/narr/templates.rst75
-rw-r--r--docs/narr/testing.rst26
-rw-r--r--docs/narr/upgrading.rst234
-rw-r--r--docs/narr/viewconfig.rst68
-rw-r--r--docs/narr/views.rst12
-rwxr-xr-xdocs/remake2
-rw-r--r--docs/tutorials/modwsgi/index.rst31
-rw-r--r--docs/tutorials/wiki/authorization.rst18
-rw-r--r--docs/tutorials/wiki/definingviews.rst2
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py4
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py10
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py4
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views.py10
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views.py10
-rw-r--r--docs/tutorials/wiki2/authorization.rst26
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst32
-rw-r--r--docs/tutorials/wiki2/definingviews.rst4
-rw-r--r--docs/tutorials/wiki2/design.rst5
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py8
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views.py16
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/__init__.py8
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views.py16
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/static/pylons.css435
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views.py16
-rw-r--r--docs/whatsnew-1.4.rst310
-rw-r--r--pyramid/__init__.py6
-rw-r--r--pyramid/authentication.py389
-rw-r--r--pyramid/chameleon_text.py114
-rw-r--r--pyramid/chameleon_zpt.py128
-rw-r--r--pyramid/config/__init__.py195
-rw-r--r--pyramid/config/adapters.py115
-rw-r--r--pyramid/config/factories.py132
-rw-r--r--pyramid/config/predicates.py289
-rw-r--r--pyramid/config/routes.py101
-rw-r--r--pyramid/config/security.py20
-rw-r--r--pyramid/config/testing.py26
-rw-r--r--pyramid/config/tweens.py114
-rw-r--r--pyramid/config/util.py320
-rw-r--r--pyramid/config/views.py422
-rw-r--r--pyramid/configuration.py60
-rw-r--r--pyramid/decorator.py28
-rw-r--r--pyramid/events.py17
-rw-r--r--pyramid/exceptions.py18
-rw-r--r--pyramid/interfaces.py13
-rw-r--r--pyramid/mako_templating.py38
-rw-r--r--pyramid/paster.py10
-rw-r--r--pyramid/registry.py33
-rw-r--r--pyramid/renderers.py54
-rw-r--r--pyramid/request.py11
-rw-r--r--pyramid/router.py86
-rw-r--r--pyramid/scaffolds/alchemy/+package+/__init__.py6
-rw-r--r--pyramid/scaffolds/alchemy/+package+/static/pylons.css435
-rw-r--r--pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl16
-rw-r--r--pyramid/scaffolds/alchemy/development.ini_tmpl20
-rw-r--r--pyramid/scaffolds/alchemy/production.ini_tmpl12
-rw-r--r--pyramid/scaffolds/starter/+package+/static/pylons.css435
-rw-r--r--pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl16
-rw-r--r--pyramid/scaffolds/starter/development.ini_tmpl20
-rw-r--r--pyramid/scaffolds/starter/production.ini_tmpl16
-rw-r--r--pyramid/scaffolds/zodb/+package+/static/pylons.css435
-rw-r--r--pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt16
-rw-r--r--pyramid/scaffolds/zodb/development.ini_tmpl20
-rw-r--r--pyramid/scaffolds/zodb/production.ini_tmpl16
-rw-r--r--pyramid/scripting.py11
-rw-r--r--pyramid/scripts/proutes.py6
-rw-r--r--pyramid/scripts/pserve.py8
-rw-r--r--pyramid/scripts/pviews.py8
-rw-r--r--pyramid/security.py14
-rw-r--r--pyramid/session.py130
-rw-r--r--pyramid/settings.py26
-rw-r--r--pyramid/testing.py429
-rw-r--r--pyramid/tests/fixtures/withmacro.pt7
-rw-r--r--pyramid/tests/pkgs/conflictapp/__init__.py3
-rw-r--r--pyramid/tests/pkgs/defpermbugapp/__init__.py2
-rw-r--r--pyramid/tests/pkgs/forbiddenapp/__init__.py2
-rw-r--r--pyramid/tests/pkgs/forbiddenview/__init__.py2
-rw-r--r--pyramid/tests/pkgs/permbugapp/__init__.py2
-rw-r--r--pyramid/tests/pkgs/subrequestapp/__init__.py50
-rw-r--r--pyramid/tests/test_authentication.py260
-rw-r--r--pyramid/tests/test_chameleon_text.py122
-rw-r--r--pyramid/tests/test_chameleon_zpt.py140
-rw-r--r--pyramid/tests/test_config/__init__.py8
-rw-r--r--pyramid/tests/test_config/test_adapters.py127
-rw-r--r--pyramid/tests/test_config/test_factories.py95
-rw-r--r--pyramid/tests/test_config/test_init.py96
-rw-r--r--pyramid/tests/test_config/test_predicates.py502
-rw-r--r--pyramid/tests/test_config/test_routes.py2
-rw-r--r--pyramid/tests/test_config/test_security.py9
-rw-r--r--pyramid/tests/test_config/test_settings.py9
-rw-r--r--pyramid/tests/test_config/test_testing.py24
-rw-r--r--pyramid/tests/test_config/test_tweens.py46
-rw-r--r--pyramid/tests/test_config/test_util.py108
-rw-r--r--pyramid/tests/test_config/test_views.py169
-rw-r--r--pyramid/tests/test_configuration.py31
-rw-r--r--pyramid/tests/test_events.py119
-rw-r--r--pyramid/tests/test_exceptions.py12
-rw-r--r--pyramid/tests/test_integration.py63
-rw-r--r--pyramid/tests/test_mako_templating.py66
-rw-r--r--pyramid/tests/test_path.py26
-rw-r--r--pyramid/tests/test_renderers.py56
-rw-r--r--pyramid/tests/test_request.py71
-rw-r--r--pyramid/tests/test_router.py36
-rw-r--r--pyramid/tests/test_scaffolds/test_copydir.py8
-rw-r--r--pyramid/tests/test_scripting.py94
-rw-r--r--pyramid/tests/test_scripts/test_pviews.py4
-rw-r--r--pyramid/tests/test_security.py18
-rw-r--r--pyramid/tests/test_session.py67
-rw-r--r--pyramid/tests/test_settings.py26
-rw-r--r--pyramid/tests/test_testing.py341
-rw-r--r--pyramid/tests/test_traversal.py172
-rw-r--r--pyramid/tests/test_url.py10
-rw-r--r--pyramid/tests/test_urldispatch.py8
-rw-r--r--pyramid/tests/test_util.py271
-rw-r--r--pyramid/tests/test_view.py30
-rw-r--r--pyramid/traversal.py32
-rw-r--r--pyramid/tweens.py5
-rw-r--r--pyramid/urldispatch.py4
-rw-r--r--pyramid/util.py173
-rw-r--r--pyramid/view.py79
-rw-r--r--setup.py5
-rw-r--r--tox.ini33
172 files changed, 11987 insertions, 3894 deletions
diff --git a/.gitignore b/.gitignore
index 8e2f83e7d..5fa2a2ee4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
*.pt.py
*.txt.py
*~
+.*.swp
.coverage
.tox/
nosetests.xml
@@ -21,3 +22,4 @@ bookenv/
jyenv/
pypyenv/
env*/
+venv/
diff --git a/.travis.yml b/.travis.yml
index 490fd2df7..ab9c3ff30 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,12 +6,6 @@ python:
- pypy
- 3.2
-# Why does travis-ci's PyPy blow up? Pyramid's tests
-# run fine under tox.
-matrix:
- allow_failures:
- - python: pypy
-
script: python setup.py test
notifications:
diff --git a/CHANGES.txt b/CHANGES.txt
index 3cb2f2848..5175baa5a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,177 @@
Next release
============
+Features
+--------
+
+- ``pyramid.authentication.AuthTktAuthenticationPolicy`` has been updated to
+ support newer hashing algorithms such as ``sha512``. Existing applications
+ should consider updating if possible.
+
+- Added an ``effective_principals`` route and view predicate.
+
+- Do not allow the userid returned from the ``authenticated_userid`` or the
+ userid that is one of the list of principals returned by
+ ``effective_principals`` to be either of the strings ``system.Everyone`` or
+ ``system.Authenticated`` when any of the built-in authorization policies that
+ live in ``pyramid.authentication`` are in use. These two strings are
+ reserved for internal usage by Pyramid and they will not be accepted as valid
+ userids.
+
+- Slightly better debug logging from
+ ``pyramid.authentication.RepozeWho1AuthenticationPolicy``.
+
+- ``pyramid.security.view_execution_permitted`` used to return `True` if no
+ view could be found. It now raises a ``TypeError`` exception in that case, as
+ it doesn't make sense to assert that a nonexistent view is
+ execution-permitted. See https://github.com/Pylons/pyramid/issues/299.
+
+- Get rid of shady monkeypatching of ``pyramid.request.Request`` and
+ ``pyramid.response.Response`` done within the ``__init__.py`` of Pyramid.
+ Webob no longer relies on this being done. Instead, the ResponseClass
+ attribute of the Pyramid Request class is assigned to the Pyramid response
+ class; that's enough to satisfy WebOb and behave as it did before with the
+ monkeypatching.
+
+- Allow a ``_depth`` argument to ``pyramid.view.view_config``, which will
+ permit limited composition reuse of the decorator by other software that
+ wants to provide custom decorators that are much like view_config.
+
+Bug Fixes
+---------
+
+- In the past if a renderer returned ``None``, the body of the resulting
+ response would be set explicitly to the empty string. Instead, now, the body
+ is left unchanged, which allows the renderer to set a body itself by using
+ e.g. ``request.response.body = b'foo'``. The body set by the renderer will
+ be unmolested on the way out. See
+ https://github.com/Pylons/pyramid/issues/709
+
+- In uncommon cases, the ``pyramid_excview_tween_factory`` might have
+ inadvertently raised a ``KeyError`` looking for ``request_iface`` as an
+ attribute of the request. It no longer fails in this case. See
+ https://github.com/Pylons/pyramid/issues/700
+
+Deprecations
+------------
+
+- ``pyramid.authentication.AuthTktAuthenticationPolicy`` will emit a warning
+ if an application is using the policy without explicitly setting the
+ ``hashalg``. This is because the default is "md5" which is considered
+ insecure. If you really want "md5" then you must specify it explicitly to
+ get rid of the warning.
+
+Internals
+---------
+
+- Move ``TopologicalSorter`` from ``pyramid.config.util`` to ``pyramid.util``,
+ move ``CyclicDependencyError`` from ``pyramid.config.util`` to
+ ``pyramid.exceptions``, rename ``Singleton`` to ``Sentinel`` and move from
+ ``pyramid.config.util`` to ``pyramid.util``; this is in an effort to
+ move that stuff that may be an API one day out of ``pyramid.config.util``,
+ because that package should never be imported from non-Pyramid code.
+ TopologicalSorter is still not an API, but may become one.
+
+1.4a3 (2012-10-26)
+==================
+
+Bug Fixes
+---------
+
+- The match_param predicate's text method was fixed to sort its values.
+ Part of https://github.com/Pylons/pyramid/pull/705
+
+- 1.4a ``pyramid.scripting.prepare`` behaved differently than 1.3 series
+ function of same name. In particular, if passed a request, it would not
+ set the ``registry`` attribute of the request like 1.3 did. A symptom
+ would be that passing a request to ``pyramid.paster.bootstrap`` (which uses
+ the function) that did not have a ``registry`` attribute could assume that
+ the registry would be attached to the request by Pyramid. This assumption
+ could be made in 1.3, but not in 1.4. The assumption can now be made in
+ 1.4 too (a registry is attached to a request passed to bootstrap or
+ prepare).
+
+- When registering a view configuration that named a Chameleon ZPT renderer
+ with a macro name in it (e.g. ``renderer='some/template#somemacro.pt``) as
+ well as a view configuration without a macro name it it that pointed to the
+ same template (e.g. ``renderer='some/template.pt'``), internal caching could
+ confuse the two, and your code might have rendered one instead of the
+ other.
+
+Features
+--------
+
+- Allow multiple values to be specified to the ``request_param`` view/route
+ predicate as a sequence. Previously only a single string value was allowed.
+ See https://github.com/Pylons/pyramid/pull/705
+
+- Comments with references to documentation sections placed in scaffold
+ ``.ini`` files.
+
+- Added an HTTP Basic authentication policy
+ at ``pyramid.authentication.BasicAuthAuthenticationPolicy``.
+
+- The Configurator ``testing_securitypolicy`` method now returns the policy
+ object it creates.
+
+- The Configurator ``testing_securitypolicy`` method accepts two new
+ arguments: ``remember_result`` and ``forget_result``. If supplied, these
+ values influence the result of the policy's ``remember`` and ``forget``
+ methods, respectively.
+
+- The DummySecurityPolicy created by ``testing_securitypolicy`` now sets a
+ ``forgotten`` value on the policy (the value ``True``) when its ``forget``
+ method is called.
+
+- The DummySecurityPolicy created by ``testing_securitypolicy`` now sets a
+ ``remembered`` value on the policy, which is the value of the ``principal``
+ argument it's called with when its ``remember`` method is called.
+
+- New ``physical_path`` view predicate. If specified, this value should be a
+ string or a tuple representing the physical traversal path of the context
+ found via traversal for this predicate to match as true. For example:
+ ``physical_path='/'`` or ``physical_path='/a/b/c'`` or ``physical_path=('',
+ 'a', 'b', 'c')``. This is not a path prefix match or a regex, it's a
+ whole-path match. It's useful when you want to always potentially show a
+ view when some object is traversed to, but you can't be sure about what kind
+ of object it will be, so you can't use the ``context`` predicate. The
+ individual path elements inbetween slash characters or in tuple elements
+ should be the Unicode representation of the name of the resource and should
+ not be encoded in any way.
+
+1.4a2 (2012-09-27)
+==================
+
+Bug Fixes
+---------
+
+- When trying to determine Mako defnames and Chameleon macro names in asset
+ specifications, take into account that the filename may have a hyphen in
+ it. See https://github.com/Pylons/pyramid/pull/692
+
+Features
+--------
+
+- A new ``pyramid.session.check_csrf_token`` convenience function was added.
+
+- A ``check_csrf`` view predicate was added. For example, you can now do
+ ``config.add_view(someview, check_csrf=True)``. When the predicate is
+ checked, if the ``csrf_token`` value in ``request.params`` matches the CSRF
+ token in the request's session, the view will be permitted to execute.
+ Otherwise, it will not be permitted to execute.
+
+- Add ``Base.metadata.bind = engine`` to alchemy template, so that tables
+ defined imperatively will work.
+
+Documentation
+-------------
+
+- update wiki2 SQLA tutorial with the changes required after inserting
+ ``Base.metadata.bind = engine`` into the alchemy scaffold.
+
+1.4a1 (2012-09-16)
+==================
+
Bug Fixes
---------
@@ -9,9 +180,66 @@ Bug Fixes
return the empty list. This was incorrect, it should have unconditionally
returned ``[Everyone]``, and now does.
+- Explicit url dispatch regexes can now contain colons.
+ https://github.com/Pylons/pyramid/issues/629
+
+- On at least one 64-bit Ubuntu system under Python 3.2, using the
+ ``view_config`` decorator caused a ``RuntimeError: dictionary changed size
+ during iteration`` exception. It no longer does. See
+ https://github.com/Pylons/pyramid/issues/635 for more information.
+
+- In Mako Templates lookup, check if the uri is already adjusted and bring
+ it back to an asset spec. Normally occurs with inherited templates or
+ included components.
+ https://github.com/Pylons/pyramid/issues/606
+ https://github.com/Pylons/pyramid/issues/607
+
+- In Mako Templates lookup, check for absolute uri (using mako directories)
+ when mixing up inheritance with asset specs.
+ https://github.com/Pylons/pyramid/issues/662
+
+- HTTP Accept headers were not being normalized causing potentially
+ conflicting view registrations to go unnoticed. Two views that only
+ differ in the case ('text/html' vs. 'text/HTML') will now raise an error.
+ https://github.com/Pylons/pyramid/pull/620
+
+- Forward-port from 1.3 branch: when registering multiple views with an
+ ``accept`` predicate in a Pyramid application runing under Python 3, you
+ might have received a ``TypeError: unorderable types: function() <
+ function()`` exception.
+
Features
--------
+- Configurator.add_directive now accepts arbitrary callables like partials or
+ objects implementing ``__call__`` which dont have ``__name__`` and
+ ``__doc__`` attributes. See https://github.com/Pylons/pyramid/issues/621
+ and https://github.com/Pylons/pyramid/pull/647.
+
+- Third-party custom view, route, and subscriber predicates can now be added
+ for use by view authors via
+ ``pyramid.config.Configurator.add_view_predicate``,
+ ``pyramid.config.Configurator.add_route_predicate`` and
+ ``pyramid.config.Configurator.add_subscriber_predicate``. So, for example,
+ doing this::
+
+ config.add_view_predicate('abc', my.package.ABCPredicate)
+
+ Might allow a view author to do this in an application that configured that
+ predicate::
+
+ @view_config(abc=1)
+
+ Similar features exist for ``add_route``, and ``add_subscriber``. See
+ "Adding A Third Party View, Route, or Subscriber Predicate" in the Hooks
+ chapter for more information.
+
+ Note that changes made to support the above feature now means that only
+ actions registered using the same "order" can conflict with one another.
+ It used to be the case that actions registered at different orders could
+ potentially conflict, but to my knowledge nothing ever depended on this
+ behavior (it was a bit silly).
+
- Custom objects can be made easily JSON-serializable in Pyramid by defining
a ``__json__`` method on the object's class. This method should return
values natively serializable by ``json.dumps`` (such as ints, lists,
@@ -29,20 +257,192 @@ Features
HEAD is a variant of GET that omits the body, and WebOb has special support
to return an empty body when a HEAD is used.
-- ``config.set_request_property`` now causes less code to be executed at
- request construction time.
+- ``config.add_request_method`` has been introduced to support extending
+ request objects with arbitrary callables. This method expands on the
+ previous ``config.set_request_property`` by supporting methods as well as
+ properties. This method now causes less code to be executed at
+ request construction time than ``config.set_request_property`` in
+ version 1.3.
-- Don't add a ``?`` to URLs generated by request.resource_url if the
+- Don't add a ``?`` to URLs generated by ``request.resource_url`` if the
``query`` argument is provided but empty.
-- Don't add a ``?`` to URLs generated by request.route_url if the
+- Don't add a ``?`` to URLs generated by ``request.route_url`` if the
``_query`` argument is provided but empty.
- The static view machinery now raises (rather than returns) ``HTTPNotFound``
and ``HTTPMovedPermanently`` exceptions, so these can be caught by the
NotFound view (and other exception views).
-- The mako renderer now accepts a def name and returns the template def
- result for the view being called. The uri format using an asset spec is
- package:path/to/template#defname.mako. The old way of returning a tuple
- from the view is supported for backward compatibility, ('defname', {}).
+- The Mako renderer now supports a def name in an asset spec. When the def
+ name is present in the asset spec, the system will render the template def
+ within the template and will return the result. An example asset spec is
+ ``package:path/to/template#defname.mako``. This will render the def named
+ ``defname`` inside the ``template.mako`` template instead of rendering the
+ entire template. The old way of returning a tuple in the form
+ ``('defname', {})`` from the view is supported for backward compatibility,
+
+- The Chameleon ZPT renderer now accepts a macro name in an asset spec. When
+ the macro name is present in the asset spec, the system will render the
+ macro listed as a ``define-macro`` and return the result instead of
+ rendering the entire template. An example asset spec:
+ ``package:path/to/template#macroname.pt``. This will render the macro
+ defined as ``macroname`` within the ``template.pt`` template instead of the
+ entire templae.
+
+- When there is a predicate mismatch exception (seen when no view matches for
+ a given request due to predicates not working), the exception now contains
+ a textual description of the predicate which didn't match.
+
+- An ``add_permission`` directive method was added to the Configurator. This
+ directive registers a free-standing permission introspectable into the
+ Pyramid introspection system. Frameworks built atop Pyramid can thus use
+ the the ``permissions`` introspectable category data to build a
+ comprehensive list of permissions supported by a running system. Before
+ this method was added, permissions were already registered in this
+ introspectable category as a side effect of naming them in an ``add_view``
+ call, this method just makes it possible to arrange for a permission to be
+ put into the ``permissions`` introspectable category without naming it
+ along with an associated view. Here's an example of usage of
+ ``add_permission``::
+
+ config = Configurator()
+ config.add_permission('view')
+
+- The ``UnencryptedCookieSessionFactoryConfig`` now accepts
+ ``signed_serialize`` and ``signed_deserialize`` hooks which may be used
+ to influence how the sessions are marshalled (by default this is done
+ with HMAC+pickle).
+
+- ``pyramid.testing.DummyRequest`` now supports methods supplied by the
+ ``pyramid.util.InstancePropertyMixin`` class such as ``set_property``.
+
+- Request properties and methods added via ``config.set_request_property`` or
+ ``config.add_request_method`` are now available to tweens.
+
+- Request properties and methods added via ``config.set_request_property`` or
+ ``config.add_request_method`` are now available in the request object
+ returned from ``pyramid.paster.bootstrap``.
+
+- ``request.context`` of environment request during ``bootstrap`` is now the
+ root object if a context isn't already set on a provided request.
+
+- The ``pyramid.decorator.reify`` function is now an API, and was added to
+ the API documentation.
+
+- Added the ``pyramid.testing.testConfig`` context manager, which can be used
+ to generate a configurator in a test, e.g. ``with testing.testConfig(...):``.
+
+- Users can now invoke a subrequest from within view code using a new
+ ``request.invoke_subrequest`` API.
+
+Deprecations
+------------
+
+- The ``pyramid.config.Configurator.set_request_property`` has been
+ documentation-deprecated. The method remains usable but the more
+ featureful ``pyramid.config.Configurator.add_request_method`` should be
+ used in its place (it has all of the same capabilities but can also extend
+ the request object with methods).
+
+Backwards Incompatibilities
+---------------------------
+
+- The Pyramid router no longer adds the values ``bfg.routes.route`` or
+ ``bfg.routes.matchdict`` to the request's WSGI environment dictionary.
+ These values were docs-deprecated in ``repoze.bfg`` 1.0 (effectively seven
+ minor releases ago). If your code depended on these values, use
+ request.matched_route and request.matchdict instead.
+
+- It is no longer possible to pass an environ dictionary directly to
+ ``pyramid.traversal.ResourceTreeTraverser.__call__`` (aka
+ ``ModelGraphTraverser.__call__``). Instead, you must pass a request
+ object. Passing an environment instead of a request has generated a
+ deprecation warning since Pyramid 1.1.
+
+- Pyramid will no longer work properly if you use the
+ ``webob.request.LegacyRequest`` as a request factory. Instances of the
+ LegacyRequest class have a ``request.path_info`` which return a string.
+ This Pyramid release assumes that ``request.path_info`` will
+ unconditionally be Unicode.
+
+- The functions from ``pyramid.chameleon_zpt`` and ``pyramid.chameleon_text``
+ named ``get_renderer``, ``get_template``, ``render_template``, and
+ ``render_template_to_response`` have been removed. These have issued a
+ deprecation warning upon import since Pyramid 1.0. Use
+ ``pyramid.renderers.get_renderer()``,
+ ``pyramid.renderers.get_renderer().implementation()``,
+ ``pyramid.renderers.render()`` or ``pyramid.renderers.render_to_response``
+ respectively instead of these functions.
+
+- The ``pyramid.configuration`` module was removed. It had been deprecated
+ since Pyramid 1.0 and printed a deprecation warning upon its use. Use
+ ``pyramid.config`` instead.
+
+- The ``pyramid.paster.PyramidTemplate`` API was removed. It had been
+ deprecated since Pyramid 1.1 and issued a warning on import. If your code
+ depended on this, adjust your code to import
+ ``pyramid.scaffolds.PyramidTemplate`` instead.
+
+- The ``pyramid.settings.get_settings()`` API was removed. It had been
+ printing a deprecation warning since Pyramid 1.0. If your code depended on
+ this API, use ``pyramid.threadlocal.get_current_registry().settings``
+ instead or use the ``settings`` attribute of the registry available from
+ the request (``request.registry.settings``).
+
+- These APIs from the ``pyramid.testing`` module were removed. They have
+ been printing deprecation warnings since Pyramid 1.0:
+
+ * ``registerDummySecurityPolicy``, use
+ ``pyramid.config.Configurator.testing_securitypolicy`` instead.
+
+ * ``registerResources`` (aka ``registerModels``, use
+ ``pyramid.config.Configurator.testing_resources`` instead.
+
+ * ``registerEventListener``, use
+ ``pyramid.config.Configurator.testing_add_subscriber`` instead.
+
+ * ``registerTemplateRenderer`` (aka `registerDummyRenderer``), use
+ ``pyramid.config.Configurator.testing_add_template`` instead.
+
+ * ``registerView``, use ``pyramid.config.Configurator.add_view`` instead.
+
+ * ``registerUtility``, use
+ ``pyramid.config.Configurator.registry.registerUtility`` instead.
+
+ * ``registerAdapter``, use
+ ``pyramid.config.Configurator.registry.registerAdapter`` instead.
+
+ * ``registerSubscriber``, use
+ ``pyramid.config.Configurator.add_subscriber`` instead.
+
+ * ``registerRoute``, use
+ ``pyramid.config.Configurator.add_route`` instead.
+
+ * ``registerSettings``, use
+ ``pyramid.config.Configurator.add_settings`` instead.
+
+- In Pyramid 1.3 and previous, the ``__call__`` method of a Response object
+ was invoked before any finished callbacks were executed. As of this
+ release, the ``__call__`` method of a Response object is invoked *after*
+ finished callbacks are executed. This is in support of the
+ ``request.invoke_subrequest`` feature.
+
+Documentation
+-------------
+
+- Added an "Upgrading Pyramid" chapter to the narrative documentation. It
+ describes how to cope with deprecations and removals of Pyramid APIs and
+ how to show Pyramid-generated deprecation warnings while running tests and
+ while running a server.
+
+- Added a "Invoking a Subrequest" chapter to the documentation. It describes
+ how to use the new ``request.invoke_subrequest`` API.
+
+Dependencies
+------------
+
+- Pyramid now requires WebOb 1.2b3+ (the prior Pyramid release only relied on
+ 1.2dev+). This is to ensure that we obtain a version of WebOb that returns
+ ``request.path_info`` as text.
+
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index c3995aaba..d03da3e62 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -1,4 +1,4 @@
- Pylons Project Contributor Agreement
+Pylons Project Contributor Agreement
====================================
The submitter agrees by adding his or her name within the section below named
@@ -173,6 +173,18 @@ Contributors
- Marin Rukavina, 2012/05/03
+- Lorenzo M. Catucci, 2012/06/08
+
- Marc Abramowitz, 2012/06/13
+- Brian Sutherland, 2012/06/16
+
- Jeff Cook, 2012/06/16
+
+- Ian Wilson, 2012/06/17
+
+- Roman Kozlovskyi, 2012/08/11
+
+- Domen Kozar, 2012/09/11
+
+- David Gay, 2012/09/16
diff --git a/HACKING.txt b/HACKING.txt
index dd735bf22..87d1422dc 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -30,11 +30,13 @@ checkout.
$ env/bin/easy_install setuptools-git
- Install Pyramid from the checkout into the virtualenv using ``setup.py
- develop`` (running ``setup.py develop`` *must* be done while the current
- working directory is the ``pyramid`` checkout directory)::
+ dev``. ``setup.py dev`` is an alias for "setup.py develop" which also
+ installs testing requirements such as nose and coverage. Running
+ ``setup.py dev`` *must* be done while the current working directory is the
+ ``pyramid`` checkout directory::
$ cd pyramid
- $ ../env/bin/python setup.py develop
+ $ ../env/bin/python setup.py dev
- At that point, you should be able to create new Pyramid projects by using
``pcreate``::
@@ -85,6 +87,9 @@ Coding Style
2 newlines between classes. But 80-column lines, in particular, are
mandatory.
+- Please do not remove trailing whitespace. Configure your editor to reduce
+ diff noise.
+
Running Tests
--------------
@@ -110,8 +115,8 @@ Test Coverage
- The codebase *must* have 100% test statement coverage after each commit.
You can test coverage via ``tox -e coverage``, or alternately by installing
- ``nose`` and ``coverage`` into your virtualenv, and running ``setup.py
- nosetests --with-coverage``.
+ ``nose`` and ``coverage`` into your virtualenv (easiest via ``setup.py
+ dev``) , and running ``setup.py nosetests --with-coverage``.
Documentation Coverage and Building HTML Documentation
------------------------------------------------------
@@ -127,13 +132,19 @@ using to develop Pyramid):
1. Run ``$yourvenv/bin/python setup.py dev docs``. This will cause Sphinx
and all development requirements to be installed in your virtualenv.
-2. cd to the ``docs`` directory within your Pyramid checkout and execute
+2. Update all git submodules from the top-level of your Pyramid checkout, like
+ so:
+ git submodule update --init --recursive
+ This will checkout theme subrepositories and prevent error conditions when
+ HTML docs are generated.
+
+3. cd to the ``docs`` directory within your Pyramid checkout and execute
``make clean html SPHINXBUILD=$yourvenv/bin/sphinx-build``. The
``SPHINXBUILD=...`` hair is there in order to tell it to use the
virtualenv Python, which will have both Sphinx and Pyramid (for API
documentation generation) installed.
-3. Open the ``docs/_build/html/index.html`` file to see the resulting HTML
+4. Open the ``docs/_build/html/index.html`` file to see the resulting HTML
rendering.
Change Log
diff --git a/RELEASING.txt b/RELEASING.txt
index c97c8ef60..379965c53 100644
--- a/RELEASING.txt
+++ b/RELEASING.txt
@@ -13,10 +13,10 @@ Releasing Pyramid
Make sure statement coverage is at 100%::
-- Run Windows tests for Python 2.6, 2.7, and 3.2 if feasible.
+- Run Windows tests for Python 2.6, 2.7, 3.2, and 3.3 if feasible.
-- Make sure all scaffold tests pass (Py 2.6, 2.7, 3.2 and pypy on UNIX; this
- doesn't work on Windows):
+- Make sure all scaffold tests pass (Py 2.6, 2.7, 3.2, 3.3 and pypy on UNIX;
+ this doesn't work on Windows):
$ python pyramid/scaffolds/tests.py
diff --git a/TODO.txt b/TODO.txt
index 4b4f48499..46edd8c6b 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -4,18 +4,11 @@ Pyramid TODOs
Nice-to-Have
------------
-- config.set_registry_attr (with conflict detection).
-
-- _fix_registry should dictify the registry being fixed.
-
- Provide the presumed renderer name to the called view as an attribute of
the request.
- Have action methods return their discriminators.
-- Add docs about upgrading between Pyramid versions (e.g. how to see
- deprecation warnings).
-
- Fix renderers chapter to better document system values passed to template
renderers.
@@ -24,17 +17,6 @@ Nice-to-Have
- Modify the urldispatch chapter examples to assume a scan rather than
``add_view``.
-- Context manager for creating a new configurator (replacing
- ``with_package``). E.g.::
-
- with config.partial(package='bar') as c:
- c.add_view(...)
-
- or::
-
- with config.partial(introspection=False) as c:
- c.add_view(..)
-
- Introspection:
* ``default root factory`` category (prevent folks from needing to searh
@@ -93,46 +75,73 @@ Nice-to-Have
- Deprecate pyramid.security.view_execution_permitted (it only works for
traversal).
-- Create a function which performs a recursive request.
-
-- Create a ``render_view`` that works by using config.derive_view against an
- existing view callable instead of querying the registry (some sort of API
- for rendering a view callable object to a response from within another view
- callable). Possible idea: have config.add_view mark up the
- function/method/class like @view_config does, then use the attached info to
- derive a view callable whenever called via some API.
-
- Provide a ``has_view`` function.
- Update App engine chapter with less creaky directions.
+- Idea from Zart:
+
+ diff --git a/pyramid/paster.py b/pyramid/paster.py
+ index b0e4d79..b3bd82a 100644
+ --- a/pyramid/paster.py
+ +++ b/pyramid/paster.py
+ @@ -8,6 +8,7 @@ from paste.deploy import (
+ from pyramid.compat import configparser
+ from logging.config import fileConfig
+ from pyramid.scripting import prepare
+ +from pyramid.config import Configurator
+
+ def get_app(config_uri, name=None, loadapp=loadapp):
+ """ Return the WSGI application named ``name`` in the PasteDeploy
+ @@ -111,3 +112,10 @@ def bootstrap(config_uri, request=None):
+ env['app'] = app
+ return env
+
+ +def make_pyramid_app(global_conf, app=None, **settings):
+ + """Return pyramid application configured with provided settings"""
+ + config = Configurator(package='pyramid', settings=settings)
+ + if app:
+ + config.include(app)
+ + app = config.make_wsgi_app()
+ + return app
+ diff --git a/setup.py b/setup.py
+ index 03ebb42..91e0e21 100644
+ --- a/setup.py
+ +++ b/setup.py
+ @@ -118,6 +118,8 @@ setup(name='pyramid',
+ [paste.server_runner]
+ wsgiref = pyramid.scripts.pserve:wsgiref_server_runner
+ cherrypy = pyramid.scripts.pserve:cherrypy_server_runner
+ + [paster.app_factory]
+ + main = pyramid.paster:make_pyramid_app
+ """
+ )
+
+
Future
------
-- 1.4: Kill off ``bfg.routes`` envvars in router.
-
-- 1.4: Remove ``chameleon_text`` / ``chameleon_zpt`` deprecated functions
- (render_*)
-
-- 1.4: Remove ``pyramid.configuration.ConfigurationError`` (deprecated).
-
-- 1.4: Remove ``pyramid.paster.PyramidTemplate`` (deprecated).
-
-- 1.4: Remove ``pyramid.settings.get_settings`` (deprecated).
-
-- 1.5: Remove all deprecated ``pyramid.testing`` functions.
+- 1.5: remove ``pyramid.view.static`` and ``pyramid.view.is_response``.
- 1.5: turn ``pyramid.settings.Settings`` into a function that returns the
original dict (after ``__getattr__`` deprecation period, it was deprecated
in 1.2).
-- 1.5: Remove ``pyramid.requests.DeprecatedRequestMethodsMixin``.
+- 1.5: Remove ``pyramid.requests.DeprecatedRequestMethodsMixin`` and code in
+ renderers module that looks for _response_content_type, et. al.
- 1.5: Maybe? deprecate set_request_property in favor of pointing people at
- set_request_method.
+ add_request_method, schedule removal for 1.8?
+
+- 1.5: Remove pyramid.config.rendering set_renderer_globals_factory maybe.
+
+- 1.5: remove pyramid.config.route _add_view_from_route function.
- 1.6: Remove IContextURL and TraversalContextURL.
+- 1.7: Change ``pyramid.authentication.AuthTktAuthenticationPolicy`` default
+ ``hashalg`` to ``sha512``.
+
Probably Bad Ideas
------------------
@@ -152,3 +161,20 @@ Probably Bad Ideas
- http://pythonguy.wordpress.com/2011/06/22/dynamic-variables-revisited/
instead of thread locals
+
+- Context manager for creating a new configurator (replacing
+ ``with_package``). E.g.::
+
+ with config.partial(package='bar') as c:
+ c.add_view(...)
+
+ or::
+
+ with config.partial(introspection=False) as c:
+ c.add_view(..)
+
+- _fix_registry should dictify the registry being fixed.
+
+- config.set_registry_attr (with conflict detection)... bad idea because it
+ won't take effect until after a commit and folks will be confused by that.
+
diff --git a/docs/Makefile b/docs/Makefile
index e4a325022..c98fdc65e 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -25,7 +25,7 @@ help:
clean:
-rm -rf _build/*
-html: _themes
+html: themes
mkdir -p _build/html _build/doctrees
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
@echo
@@ -47,7 +47,7 @@ pickle:
web: pickle
-htmlhelp: _themes
+htmlhelp: themes
mkdir -p _build/htmlhelp _build/doctrees
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
@echo
@@ -84,5 +84,5 @@ epub:
@echo
@echo "Build finished. The epub file is in _build/epub."
-_themes:
- git submodule update --init
+themes:
+ cd ..; git submodule update --init --recursive; cd docs;
diff --git a/docs/api.rst b/docs/api.rst
index d510c0d27..9e540b49b 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -10,10 +10,9 @@ documentation is organized alphabetically by module name.
api/authorization
api/authentication
- api/chameleon_text
- api/chameleon_zpt
api/compat
api/config
+ api/decorator
api/events
api/exceptions
api/httpexceptions
diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst
index 5d4dbd9e3..19d08618b 100644
--- a/docs/api/authentication.rst
+++ b/docs/api/authentication.rst
@@ -9,12 +9,24 @@ Authentication Policies
.. automodule:: pyramid.authentication
.. autoclass:: AuthTktAuthenticationPolicy
-
- .. autoclass:: RepozeWho1AuthenticationPolicy
+ :members:
+ :inherited-members:
.. autoclass:: RemoteUserAuthenticationPolicy
+ :members:
+ :inherited-members:
.. autoclass:: SessionAuthenticationPolicy
+ :members:
+ :inherited-members:
+
+ .. autoclass:: BasicAuthAuthenticationPolicy
+ :members:
+ :inherited-members:
+
+ .. autoclass:: RepozeWho1AuthenticationPolicy
+ :members:
+ :inherited-members:
Helper Classes
~~~~~~~~~~~~~~
diff --git a/docs/api/chameleon_text.rst b/docs/api/chameleon_text.rst
deleted file mode 100644
index 494f5b464..000000000
--- a/docs/api/chameleon_text.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-.. _chameleon_text_module:
-
-:mod:`pyramid.chameleon_text`
-----------------------------------
-
-.. automodule:: pyramid.chameleon_text
-
- .. autofunction:: get_template
-
- .. autofunction:: render_template
-
- .. autofunction:: render_template_to_response
-
-These APIs will will work against template files which contain simple
-``${Genshi}`` - style replacement markers.
-
-The API of :mod:`pyramid.chameleon_text` is identical to that of
-:mod:`pyramid.chameleon_zpt`; only its import location is
-different. If you need to import an API functions from this module as
-well as the :mod:`pyramid.chameleon_zpt` module within the same
-view file, use the ``as`` feature of the Python import statement,
-e.g.:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.chameleon_zpt import render_template as zpt_render
- from pyramid.chameleon_text import render_template as text_render
-
-
-
diff --git a/docs/api/chameleon_zpt.rst b/docs/api/chameleon_zpt.rst
deleted file mode 100644
index df9a36a56..000000000
--- a/docs/api/chameleon_zpt.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-.. _chameleon_zpt_module:
-
-:mod:`pyramid.chameleon_zpt`
--------------------------------
-
-.. automodule:: pyramid.chameleon_zpt
-
- .. autofunction:: get_template
-
- .. autofunction:: render_template
-
- .. autofunction:: render_template_to_response
-
-These APIs will work against files which supply template text which
-matches the :term:`ZPT` specification.
-
-The API of :mod:`pyramid.chameleon_zpt` is identical to that of
-:mod:`pyramid.chameleon_text`; only its import location is
-different. If you need to import an API functions from this module as
-well as the :mod:`pyramid.chameleon_text` module within the same
-view file, use the ``as`` feature of the Python import statement,
-e.g.:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.chameleon_zpt import render_template as zpt_render
- from pyramid.chameleon_text import render_template as text_render
diff --git a/docs/api/config.rst b/docs/api/config.rst
index cd58e74d3..5d2bce23e 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -36,9 +36,11 @@
.. automethod:: set_authentication_policy
.. automethod:: set_authorization_policy
.. automethod:: set_default_permission
+ .. automethod:: add_permission
- :methodcategory:`Setting Request Properties`
+ :methodcategory:`Extending the Request Object`
+ .. automethod:: add_request_method
.. automethod:: set_request_property
:methodcategory:`Using I18N`
@@ -66,6 +68,8 @@
.. automethod:: add_response_adapter
.. automethod:: add_traverser
.. automethod:: add_tween
+ .. automethod:: add_route_predicate
+ .. automethod:: add_view_predicate
.. automethod:: set_request_factory
.. automethod:: set_root_factory
.. automethod:: set_session_factory
diff --git a/docs/api/decorator.rst b/docs/api/decorator.rst
new file mode 100644
index 000000000..35d9131df
--- /dev/null
+++ b/docs/api/decorator.rst
@@ -0,0 +1,9 @@
+.. _decorator_module:
+
+:mod:`pyramid.decorator`
+--------------------------
+
+.. automodule:: pyramid.decorator
+
+.. autofunction:: reify
+
diff --git a/docs/api/registry.rst b/docs/api/registry.rst
index e62e2ba6f..1d5d52248 100644
--- a/docs/api/registry.rst
+++ b/docs/api/registry.rst
@@ -38,3 +38,17 @@
This class is new as of :app:`Pyramid` 1.3.
+.. autoclass:: Deferred
+
+ This class is new as of :app:`Pyramid` 1.4.
+
+.. autofunction:: undefer
+
+ This function is new as of :app:`Pyramid` 1.4.
+
+.. autoclass:: predvalseq
+
+ This class is new as of :app:`Pyramid` 1.4.
+
+
+
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 1112ea069..3a1439874 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -161,6 +161,62 @@
request, the value of this attribute will be ``None``. See
:ref:`matched_route`.
+ .. method:: invoke_subrequest(request, use_tweens=False)
+
+ .. warning::
+
+ This API was added in Pyramid 1.4a1.
+
+ Obtain a response object from the Pyramid application based on
+ information in the ``request`` object provided. The ``request`` object
+ must be an object that implements the Pyramid request interface (such
+ as a :class:`pyramid.request.Request` instance). If ``use_tweens`` is
+ ``True``, the request will be sent to the :term:`tween` in the tween
+ stack closest to the request ingress. If ``use_tweens`` is ``False``,
+ the request will be sent to the main router handler, and no tweens will
+ be invoked.
+
+ This function also:
+
+ - manages the threadlocal stack (so that
+ :func:`~pyramid.threadlocal.get_current_request` and
+ :func:`~pyramid.threadlocal.get_current_registry` work during a
+ request)
+
+ - Adds a ``registry`` attribute (the current Pyramid registry) and a
+ ``invoke_subrequest`` attribute (a callable) to the request object it's
+ handed.
+
+ - sets request extensions (such as those added via
+ :meth:`~pyramid.config.Configurator.add_request_method` or
+ :meth:`~pyramid.config.Configurator.set_request_property`) on the
+ request it's passed.
+
+ - causes a :class:`~pyramid.event.NewRequest` event to be sent at the
+ beginning of request processing.
+
+ - causes a :class:`~pyramid.event.ContextFound` event to be sent
+ when a context resource is found.
+
+ - Ensures that the user implied by the request passed has the necessary
+ authorization to invoke view callable before calling it.
+
+ - causes a :class:`~pyramid.event.NewResponse` event to be sent when
+ the Pyramid application returns a response.
+
+ - Calls any :term:`response callback` functions defined within the
+ request's lifetime if a response is obtained from the Pyramid
+ application.
+
+ - Calls any :term:`finished callback` functions defined within the
+ request's lifetime.
+
+ ``invoke_subrequest`` isn't *actually* a method of the Request object;
+ it's a callable added when the Pyramid router is invoked, or when a
+ subrequest is invoked. This means that it's not available for use on a
+ request provided by e.g. the ``pshell`` environment. For more
+ information, see :ref:`subrequest_chapter`.
+
.. automethod:: add_response_callback
.. automethod:: add_finished_callback
diff --git a/docs/api/session.rst b/docs/api/session.rst
index 44b4bd860..31bc196ad 100644
--- a/docs/api/session.rst
+++ b/docs/api/session.rst
@@ -11,4 +11,6 @@
.. autofunction:: signed_deserialize
+ .. autofunction:: check_csrf_token
+
diff --git a/docs/api/settings.rst b/docs/api/settings.rst
index 6b12c038c..cd802e138 100644
--- a/docs/api/settings.rst
+++ b/docs/api/settings.rst
@@ -5,8 +5,6 @@
.. automodule:: pyramid.settings
- .. autofunction:: get_settings
-
.. autofunction:: asbool
.. autofunction:: aslist
diff --git a/docs/api/testing.rst b/docs/api/testing.rst
index f388dc263..1366a1795 100644
--- a/docs/api/testing.rst
+++ b/docs/api/testing.rst
@@ -9,6 +9,8 @@
.. autofunction:: tearDown
+ .. autofunction:: testConfig(registry=None, request=None, hook_zca=True, autocommit=True, settings=None)
+
.. autofunction:: cleanUp
.. autoclass:: DummyResource
diff --git a/docs/changes.rst b/docs/changes.rst
index 6294123ed..fdeaf1e99 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -1,3 +1,5 @@
+.. _changelog:
+
:app:`Pyramid` Change History
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/conf.py b/docs/conf.py
index 80ee0d2e5..9bda4c798 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -81,7 +81,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year
# other places throughout the built documents.
#
# The short X.Y version.
-version = '1.4dev'
+version = '1.4a3'
# The full version, including alpha/beta/rc tags.
release = version
@@ -160,7 +160,7 @@ html_theme_path = ['_themes']
html_theme = 'pyramid'
html_theme_options = dict(
github_url='https://github.com/Pylons/pyramid',
- in_progress='true',
+# in_progress='true',
)
# The style sheet to use for HTML and HTML Help pages. A file of that name
# must exist either in Sphinx' static/ path, or in one of the custom paths
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index d896022e6..b468ddac6 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -390,20 +390,6 @@ completely optional. No ZCML is required at all to use :app:`Pyramid`, nor
any other sort of frameworky declarative frontend to application
configuration.
-.. _model_traversal_confusion:
-
-Pyramid Uses "Model" To Represent A Node In The Graph of Objects Traversed
---------------------------------------------------------------------------
-
-The ``repoze.bfg`` documentation used to refer to the graph being traversed
-when :term:`traversal` is used as a "model graph". A terminology overlap
-confused people who wrote applications that always use ORM packages such as
-SQLAlchemy, which has a different notion of the definition of a "model". As
-a result, in Pyramid 1.0a7, the tree of objects traversed is now renamed to
-:term:`resource tree` and its components are now named :term:`resource`
-objects. Associated APIs have been changed. This hopefully alleviates the
-terminology confusion caused by overriding the term "model".
-
Pyramid Does Traversal, And I Don't Like Traversal
--------------------------------------------------
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 45a79326f..adcf36f7c 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -41,14 +41,20 @@ Glossary
setuptools
`Setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
builds on Python's ``distutils`` to provide easier building,
- distribution, and installation of libraries and applications.
+ distribution, and installation of libraries and applications. As of
+ this writing, setuptools runs under Python 2, but not under Python 3.
+ You can use :term:`distribute` under Python 3 instead.
+
+ distribute
+ `Distribute <http://packages.python.org/distribute/>`_ is a fork of
+ :term:`setuptools` which runs on both Python 2 and Python 3.
pkg_resources
- A module which ships with :term:`setuptools` that provides an API for
- addressing "asset files" within a Python :term:`package`. Asset files
- are static files, template files, etc; basically anything
- non-Python-source that lives in a Python package can be considered a
- asset file. See also `PkgResources
+ A module which ships with :term:`setuptools` and :term:`distribute` that
+ provides an API for addressing "asset files" within a Python
+ :term:`package`. Asset files are static files, template files, etc;
+ basically anything non-Python-source that lives in a Python package can
+ be considered a asset file. See also `PkgResources
<http://peak.telecommunity.com/DevCenter/PkgResources>`_
asset
@@ -84,7 +90,7 @@ Glossary
(Setuptools/distutils terminology). A file representing an
installable library or application. Distributions are usually
files that have the suffix of ``.egg``, ``.tar.gz``, or ``.zip``.
- Distributions are the target of Setuptools commands such as
+ Distributions are the target of Setuptools-related commands such as
``easy_install``.
entry point
@@ -475,10 +481,24 @@ Glossary
:app:`Pyramid` to form a workflow system.
virtual root
- A resource object representing the "virtual" root of a request; this
- is typically the physical root object (the object returned by the
- application root factory) unless :ref:`vhosting_chapter` is in
- use.
+ A resource object representing the "virtual" root of a request; this is
+ typically the :term:`physical root` object unless :ref:`vhosting_chapter`
+ is in use.
+
+ physical root
+ The object returned by the application :term:`root factory`. Unlike the
+ the :term:`virtual root` of a request, it is not impacted by
+ :ref:`vhosting_chapter`: it will always be the actual object returned by
+ the root factory, never a subobject.
+
+ physical path
+ The path required by a traversal which resolve a :term:`resource` starting
+ from the :term:`physical root`. For example, the physical path of the
+ ``abc`` subobject of the physical root object is ``/abc``. Physical paths
+ can also be specified as tuples where the first element is the empty
+ string (representing the root), and every other element is a Unicode
+ object, e.g. ``('', 'abc')``. Physical paths are also sometimes called
+ "traversal paths".
lineage
An ordered sequence of objects based on a ":term:`location` -aware"
@@ -812,9 +832,12 @@ Glossary
application.
session factory
- A callable, which, when called with a single argument named
- ``request`` (a :term:`request` object), returns a
- :term:`session` object.
+ A callable, which, when called with a single argument named ``request``
+ (a :term:`request` object), returns a :term:`session` object. See
+ :ref:`using_the_default_session_factory`,
+ :ref:`using_alternate_session_factories` and
+ :meth:`pyramid.config.Configurator.set_session_factory` for more
+ information.
Mako
`Mako <http://www.makotemplates.org/>`_ is a template language language
@@ -994,3 +1017,10 @@ Glossary
Aka ``gunicorn``, a fast :term:`WSGI` server that runs on UNIX under
Python 2.5+ (although at the time of this writing does not support
Python 3). See http://gunicorn.org/ for detailed information.
+
+ predicate factory
+ A callable which is used by a third party during the registration of a
+ route, view, or subscriber predicates to extend the configuration
+ system. See :ref:`registering_thirdparty_predicates` for more
+ information.
+
diff --git a/docs/index.rst b/docs/index.rst
index 31c2fde6b..faf8258c1 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -13,15 +13,9 @@ Here is one of the simplest :app:`Pyramid` applications you can make:
.. literalinclude:: narr/helloworld.py
-When saved to ``helloworld.py``, the above application can be run via:
-
-.. code-block:: text
-
- $ easy_install pyramid
- $ python helloworld.py
-
-When you visit ``http://localhost:8080/hello/world`` in a browser, you will
-see the text ``Hello, world!``.
+After you install :app:`Pyramid` and run this application, when you visit
+``http://localhost:8080/hello/world`` in a browser, you will see the text
+``Hello, world!``
See :ref:`firstapp_chapter` for a full explanation of how this application
works. Read the :ref:`html_narrative_documentation` to understand how
@@ -43,6 +37,7 @@ What's New
.. toctree::
:maxdepth: 1
+ whatsnew-1.4
whatsnew-1.3
whatsnew-1.2
whatsnew-1.1
@@ -88,12 +83,14 @@ Narrative documentation in chapter form explaining how to use
narr/traversal
narr/security
narr/hybrid
+ narr/subrequest
narr/hooks
narr/introspector
narr/extending
narr/advconfig
narr/extconfig
narr/scaffolding
+ narr/upgrading
narr/threadlocals
narr/zca
diff --git a/docs/latexindex.rst b/docs/latexindex.rst
index 99ec2f54d..6bb875f73 100644
--- a/docs/latexindex.rst
+++ b/docs/latexindex.rst
@@ -54,6 +54,7 @@ Narrative Documentation
narr/traversal
narr/security
narr/hybrid
+ narr/subrequest
narr/hooks
narr/introspector
narr/extending
@@ -85,8 +86,6 @@ API Reference
api/authorization
api/authentication
- api/chameleon_text
- api/chameleon_zpt
api/config
api/events
api/exceptions
diff --git a/docs/narr/MyProject/myproject/static/pylons.css b/docs/narr/MyProject/myproject/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/narr/MyProject/myproject/static/pylons.css
+++ b/docs/narr/MyProject/myproject/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst
index 2949dc808..ad22a82c9 100644
--- a/docs/narr/advconfig.rst
+++ b/docs/narr/advconfig.rst
@@ -294,6 +294,7 @@ These are the methods of the configurator which provide conflict detection:
:meth:`~pyramid.config.Configurator.add_view`,
:meth:`~pyramid.config.Configurator.add_route`,
:meth:`~pyramid.config.Configurator.add_renderer`,
+:meth:`~pyramid.config.Configurator.add_request_method`,
:meth:`~pyramid.config.Configurator.set_request_factory`,
:meth:`~pyramid.config.Configurator.set_session_factory`,
:meth:`~pyramid.config.Configurator.set_request_property`,
@@ -413,3 +414,10 @@ constraints: the routes they imply require relative ordering. Such ordering
constraints are not absolved by two-phase configuration. Routes are still
added in configuration execution order.
+More Information
+----------------
+
+For more information, see the article entitled `"A Whirlwind Rour of Advanced
+Configuration Tactics"
+<http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/configuration/whirlwind_tour.html>`_
+in the Pyramid Cookbook.
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index af53c1f78..3bdf8c5cd 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -349,7 +349,7 @@ setting) orderings using the ``ptweens`` command. Tween factories
will show up represented by their standard Python dotted name in the
``ptweens`` output.
-For example, here's the ``pwteens`` command run against a system
+For example, here's the ``ptweens`` command run against a system
configured without any explicit tweens:
.. code-block:: text
@@ -367,7 +367,7 @@ configured without any explicit tweens:
1 pyramid.tweens.excview_tween_factory excview
- - MAIN
-Here's the ``pwteens`` command run against a system configured *with*
+Here's the ``ptweens`` command run against a system configured *with*
explicit tweens defined in its ``development.ini`` file:
.. code-block:: text
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
index 1ca188d7e..ccaa6e9e2 100644
--- a/docs/narr/firstapp.rst
+++ b/docs/narr/firstapp.rst
@@ -8,7 +8,8 @@ Creating Your First :app:`Pyramid` Application
In this chapter, we will walk through the creation of a tiny :app:`Pyramid`
application. After we're finished creating the application, we'll explain in
-more detail how it works.
+more detail how it works. It assumes you already have :app:`Pyramid` installed.
+If you do not, head over to the :ref:`installing_chapter` section.
.. _helloworld_imperative:
@@ -126,7 +127,7 @@ defined imports and function definitions, placed within the confines of an
.. literalinclude:: helloworld.py
:linenos:
- :lines: 8-13
+ :lines: 9-15
Let's break this down piece-by-piece.
@@ -135,7 +136,7 @@ Configurator Construction
.. literalinclude:: helloworld.py
:linenos:
- :lines: 8-9
+ :lines: 9-10
The ``if __name__ == '__main__':`` line in the code sample above represents a
Python idiom: the code inside this if clause is not invoked unless the script
@@ -168,7 +169,7 @@ Adding Configuration
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 10-11
+ :lines: 11-12
First line above calls the :meth:`pyramid.config.Configurator.add_route`
method, which registers a :term:`route` to match any URL path that begins
@@ -188,7 +189,7 @@ WSGI Application Creation
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 12
+ :lines: 13
After configuring views and ending configuration, the script creates a WSGI
*application* via the :meth:`pyramid.config.Configurator.make_wsgi_app`
@@ -217,7 +218,7 @@ WSGI Application Serving
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 13
+ :lines: 14-15
Finally, we actually serve the application to requestors by starting up a
WSGI server. We happen to use the :mod:`wsgiref` ``make_server`` server
diff --git a/docs/narr/helloworld.py b/docs/narr/helloworld.py
index 7c26c8cdc..c01329af9 100644
--- a/docs/narr/helloworld.py
+++ b/docs/narr/helloworld.py
@@ -2,14 +2,15 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
+
def hello_world(request):
- return Response('Hello %(name)s!' % request.matchdict)
+ return Response('Hello %(name)s!' % request.matchdict)
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/hello/{name}')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
- server = make_server('0.0.0.0', 8080, app)
- server.serve_forever()
+ config = Configurator()
+ config.add_route('hello', '/hello/{name}')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 332805152..96fa77a07 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -1232,3 +1232,203 @@ Displaying Tween Ordering
The ``ptweens`` command-line utility can be used to report the current
implict and explicit tween chains used by an application. See
:ref:`displaying_tweens`.
+
+.. _registering_thirdparty_predicates:
+
+Adding A Third Party View, Route, or Subscriber Predicate
+---------------------------------------------------------
+
+.. note::
+
+ Third-party view, route, and subscriber predicates are a feature new as of
+ Pyramid 1.4.
+
+.. _view_and_route_predicates:
+
+View and Route Predicates
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+View and route predicates used during configuration allow you to narrow the
+set of circumstances under which a view or route will match. For example,
+the ``request_method`` view predicate can be used to ensure a view callable
+is only invoked when the request's method is ``POST``:
+
+.. code-block:: python
+
+ @view_config(request_method='POST')
+ def someview(request):
+ ...
+
+Likewise, a similar predicate can be used as a *route* predicate:
+
+.. code-block:: python
+
+ config.add_route('name', '/foo', request_method='POST')
+
+Many other built-in predicates exists (``request_param``, and others). You
+can add third-party predicates to the list of available predicates by using
+one of :meth:`pyramid.config.Configurator.add_view_predicate` or
+:meth:`pyramid.config.Configurator.add_route_predicate`. The former adds a
+view predicate, the latter a route predicate.
+
+When using one of those APIs, you pass a *name* and a *factory* to add a
+predicate during Pyramid's configuration stage. For example:
+
+.. code-block:: python
+
+ config.add_view_predicate('content_type', ContentTypePredicate)
+
+The above example adds a new predicate named ``content_type`` to the list of
+available predicates for views. This will allow the following view
+configuration statement to work:
+
+.. code-block:: python
+ :linenos:
+
+ @view_config(content_type='File')
+ def aview(request): ...
+
+The first argument to :meth:`pyramid.config.Configurator.add_view_predicate`,
+the name, is a string representing the name that is expected to be passed to
+``view_config`` (or its imperative analogue ``add_view``).
+
+The second argument is a view or route predicate factory. A view or route
+predicate factory is most often a class with a constructor (``__init__``), a
+``text`` method, a ``phash`` method and a ``__call__`` method. For example:
+
+.. code-block:: python
+ :linenos:
+
+ class ContentTypePredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'content_type = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return getattr(context, 'content_type', None) == self.val
+
+The constructor of a predicate factory takes two arguments: ``val`` and
+``config``. The ``val`` argument will be the argument passed to
+``view_config`` (or ``add_view``). In the example above, it will be the
+string ``File``. The second arg, ``config`` will be the Configurator
+instance at the time of configuration.
+
+The ``text`` method must return a string. It should be useful to describe
+the behavior of the predicate in error messages.
+
+The ``phash`` method must return a string or a sequence of strings. It's
+most often the same as ``text``, as long as ``text`` uniquely describes the
+predicate's name and the value passed to the constructor. If ``text`` is
+more general, or doesn't describe things that way, ``phash`` should return a
+string with the name and the value serialized. The result of ``phash`` is
+not seen in output anywhere, it just informs the uniqueness constraints for
+view configuration.
+
+The ``__call__`` method of a predicate factory must accept a resource
+(``context``) and a request, and must return ``True`` or ``False``. It is
+the "meat" of the predicate.
+
+You can use the same predicate factory as both a view predicate and as a
+route predicate, but you'll need to call ``add_view_predicate`` and
+``add_route_predicate`` separately with the same factory.
+
+.. _subscriber_predicates:
+
+Subscriber Predicates
+~~~~~~~~~~~~~~~~~~~~~
+
+Subscriber predicates work almost exactly like view and route predicates.
+They narrow the set of circumstances in which a subscriber will be called.
+There are several minor differences between a subscriber predicate and a
+view/route predicate:
+
+- There are no default subscriber predicates. You must register one to use
+ one.
+
+- The ``__call__`` method of a subscriber predicate accepts a single
+ ``event`` object instead of a ``context`` and a ``request``.
+
+- Not every subscriber predicate can be used with every event type. Some
+ subscriber predicates will assume a certain event type.
+
+Here's an example of a subscriber predicate that can be used in conjunction
+with a subscriber that subscribes to the :class:`pyramid.events.NewReqest`
+event type.
+
+.. code-block:: python
+ :linenos:
+
+ class RequestPathStartsWith(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'path_startswith = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, event):
+ return event.request.path.startswith(self.val)
+
+Once you've created a subscriber predicate, it may registered via
+:meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example:
+
+.. code-block:: python
+
+ config.add_subscriber_predicate(
+ 'request_path_startswith', RequestPathStartsWith)
+
+Once a subscriber predicate is registered, you can use it in a call to
+:meth:`pyramid.config.Configurator.add_subscriber` or to
+:class:`pyramid.events.subscriber`. Here's an example of using the
+previously registered ``request_path_startswith`` predicate in a call to
+:meth:`~pyramid.config.Configurator.add_subscriber`:
+
+.. code-block:: python
+ :linenos:
+
+ # define a subscriber in your code
+
+ def yosubscriber(event):
+ event.request.yo = 'YO!'
+
+ # and at configuration time
+
+ config.add_subscriber(yosubscriber, NewRequest,
+ request_path_startswith='/add_yo')
+
+Here's the same subscriber/predicate/event-type combination used via
+:class:`~pyramid.events.subscriber`.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.events import subscriber
+
+ @subscriber(NewRequest, request_path_startswith='/add_yo')
+ def yosubscriber(event):
+ event.request.yo = 'YO!'
+
+In either of the above configurations, the ``yosubscriber`` callable will
+only be called if the request path starts with ``/add_yo``. Otherwise the
+event subscriber will not be called.
+
+Note that the ``request_path_startswith`` subscriber you defined can be used
+with events that have a ``request`` attribute, but not ones that do not. So,
+for example, the predicate can be used with subscribers registered for
+:class:`pyramid.events.NewRequest` and :class:`pyramid.events.ContextFound`
+events, but it cannot be used with subscribers registered for
+:class:`pyramid.events.ApplicationCreated` because the latter type of event
+has no ``request`` attribute. The point being: unlike route and view
+predicates, not every type of subscriber predicate will necessarily be
+applicable for use in every subscriber registration. It is not the
+responsibility of the predicate author to make every predicate make sense for
+every event type; it is the responsibility of the predicate consumer to use
+predicates that make sense for a particular event type registration.
+
+
+
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index 172cfd6d3..e8482a289 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -14,13 +14,13 @@ run :app:`Pyramid`.
.. sidebar:: Python Versions
- As of this writing, :app:`Pyramid` has been tested under Python 2.6.6,
- Python 2.7.2, and Python 3.2. :app:`Pyramid` does not run under any
- version of Python before 2.6.
+ As of this writing, :app:`Pyramid` has been tested under Python 2.6.8,
+ Python 2.7.3, Python 3.2.3, and Python 3.3b1. :app:`Pyramid` does not
+ run under any version of Python before 2.6.
:app:`Pyramid` is known to run on all popular UNIX-like systems such as
Linux, MacOS X, and FreeBSD as well as on Windows platforms. It is also
-known to run on :term:`PyPy` (1.6+).
+known to run on :term:`PyPy` (1.9+).
:app:`Pyramid` installation does not require the compilation of any
C code, so you need only a Python interpreter that meets the
@@ -45,15 +45,15 @@ system's package manager is slightly different, but the "flavor" of
them is usually the same.
For example, on an Ubuntu Linux system, to use the system package
-manager to install a Python 2.6 interpreter, use the following
+manager to install a Python 2.7 interpreter, use the following
command:
.. code-block:: text
- $ sudo apt-get install python2.6-dev
+ $ sudo apt-get install python2.7-dev
Once these steps are performed, the Python interpreter will usually be
-invokable via ``python2.6`` from a shell prompt.
+invokable via ``python2.7`` from a shell prompt.
.. index::
pair: install; Python (from source, UNIX)
@@ -80,7 +80,7 @@ On Mac OS X, installing `XCode
<http://developer.apple.com/tools/xcode/>`_ has much the same effect.
Once you've got development tools installed on your system, you can
-install a Python 2.6 interpreter from *source*, on the same system,
+install a Python 2.7 interpreter from *source*, on the same system,
using the following commands:
.. code-block:: text
@@ -90,15 +90,15 @@ using the following commands:
[chrism@vitaminf ~]$ mkdir opt
[chrism@vitaminf ~]$ cd tmp
[chrism@vitaminf tmp]$ wget \
- http://www.python.org/ftp/python/2.6.4/Python-2.6.4.tgz
- [chrism@vitaminf tmp]$ tar xvzf Python-2.6.4.tgz
- [chrism@vitaminf tmp]$ cd Python-2.6.4
- [chrism@vitaminf Python-2.6.4]$ ./configure \
- --prefix=$HOME/opt/Python-2.6.4
- [chrism@vitaminf Python-2.6.4]$ make; make install
+ http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz
+ [chrism@vitaminf tmp]$ tar xvzf Python-2.7.3.tgz
+ [chrism@vitaminf tmp]$ cd Python-2.7.3
+ [chrism@vitaminf Python-2.7.3]$ ./configure \
+ --prefix=$HOME/opt/Python-2.7.3
+ [chrism@vitaminf Python-2.7.3]$ make; make install
Once these steps are performed, the Python interpreter will be
-invokable via ``$HOME/opt/Python-2.6.4/bin/python`` from a shell
+invokable via ``$HOME/opt/Python-2.7.3/bin/python`` from a shell
prompt.
.. index::
@@ -108,7 +108,7 @@ If You Don't Yet Have A Python Interpreter (Windows)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If your Windows system doesn't have a Python interpreter, you'll need
-to install it by downloading a Python 2.6-series interpreter
+to install it by downloading a Python 2.7-series interpreter
executable from `python.org's download section
<http://python.org/download/>`_ (the files labeled "Windows
Installer"). Once you've downloaded it, double click on the
@@ -119,7 +119,7 @@ extensions <http://sourceforge.net/projects/pywin32/files/>`_.
.. warning::
After you install Python on Windows, you may need to add the
- ``C:\Python26`` directory to your environment's ``Path`` in order
+ ``C:\Python27`` directory to your environment's ``Path`` in order
to make it possible to invoke Python from a command prompt by
typing ``python``. To do so, right click ``My Computer``, select
``Properties`` --> ``Advanced Tab`` --> ``Environment Variables``
@@ -141,39 +141,62 @@ done by using the :term:`virtualenv` package. Using a virtualenv will
also prevent :app:`Pyramid` from globally installing versions of
packages that are not compatible with your system Python.
-To set up a virtualenv in which to install :app:`Pyramid`, first
-ensure that :term:`setuptools` is installed. Invoke ``import
-setuptools`` within the Python interpreter you'd like to run
-:app:`Pyramid` under:
+To set up a virtualenv in which to install :app:`Pyramid`, first ensure that
+:term:`setuptools` or :term:`distribute` is installed. To do so, invoke
+``import setuptools`` within the Python interpreter you'd like to run
+:app:`Pyramid` under.
+
+Here's the output you'll expect if setuptools or distribute is already
+installed:
.. code-block:: text
- [chrism@vitaminf pyramid]$ python
- Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32)
- [GCC 4.4.3] on linux2
+ [chrism@thinko docs]$ python2.7
+ Python 2.7.3 (default, Aug 1 2012, 05:14:39)
+ [GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import setuptools
+ >>>
-If running ``import setuptools`` does not raise an ``ImportError``, it
-means that setuptools is already installed into your Python
-interpreter. If ``import setuptools`` fails, you will need to install
-setuptools manually. Note that above we're using a Python 2.6-series
-interpreter on Mac OS X; your output may differ if you're using a
-later Python version or a different platform.
-
-If you are using a "system" Python (one installed by your OS
-distributor or a 3rd-party packager such as Fink or MacPorts), you can
-usually install the setuptools package by using your system's package
-manager. If you cannot do this, or if you're using a self-installed
-version of Python, you will need to install setuptools "by hand".
-Installing setuptools "by hand" is always a reasonable thing to do,
-even if your package manager already has a pre-chewed version of
-setuptools for installation.
-
-To install setuptools by hand, first download `ez_setup.py
-<http://peak.telecommunity.com/dist/ez_setup.py>`_ then invoke it
-using the Python interpreter into which you want to install
-setuptools.
+Here's the output you can expect if setuptools or distribute is not already
+installed:
+
+.. code-block:: text
+
+ [chrism@thinko docs]$ python2.7
+ Python 2.7.3 (default, Aug 1 2012, 05:14:39)
+ [GCC 4.6.3] on linux2
+ Type "help", "copyright", "credits" or "license" for more information.
+ >>> import setutptools
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ ImportError: No module named setutptools
+ >>>
+
+If ``import setuptools`` raises an :exc:`ImportError` as it does above, you
+will need to install setuptools or distribute manually. Note that above
+we're using a Python 2.7-series interpreter on Mac OS X; your output may
+differ if you're using a later Python version or a different platform.
+
+If you are using a "system" Python (one installed by your OS distributor or a
+3rd-party packager such as Fink or MacPorts), you can usually install the
+setuptools or distribute package by using your system's package manager. If
+you cannot do this, or if you're using a self-installed version of Python,
+you will need to install setuptools or distribute "by hand". Installing
+setuptools or distribute "by hand" is always a reasonable thing to do, even
+if your package manager already has a pre-chewed version of setuptools for
+installation.
+
+If you're using Python 2, you'll want to install ``setuptools``. If you're
+using Python 3, you'll want to install ``distribute``. Below we tell you how
+to do both.
+
+Installing Setuptools On Python 2
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To install setuptools by hand under Python 2, first download `ez_setup.py
+<http://peak.telecommunity.com/dist/ez_setup.py>`_ then invoke it using the
+Python interpreter into which you want to install setuptools.
.. code-block:: text
@@ -188,16 +211,37 @@ the script. To remediate this, you may need to do:
$ sudo python ez_setup.py
+Installing Distribute On Python 3
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``setuptools`` doesn't work under Python 3. Instead, you can use
+``distribute``, which is a fork of setuptools that does work on Python 3. To
+install it, first download `distribute_setup.py
+<http://python-distribute.org/distribute_setup.py>`_ then invoke it using the
+Python interpreter into which you want to install setuptools.
+
+.. code-block:: text
+
+ $ python3 distribute_setup.py
+
+Once this command is invoked, distribute should be installed on your system.
+If the command fails due to permission errors, you may need to be the
+administrative user on your system to successfully invoke the script. To
+remediate this, you may need to do:
+
+.. code-block:: text
+
+ $ sudo python3 distribute_setup.py
+
.. index::
pair: install; virtualenv
Installing the ``virtualenv`` Package
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Once you've got setuptools installed, you should install the
-:term:`virtualenv` package. To install the :term:`virtualenv` package
-into your setuptools-enabled Python interpreter, use the
-``easy_install`` command.
+Once you've got setuptools or distribute installed, you should install the
+:term:`virtualenv` package. To install the :term:`virtualenv` package into
+your setuptools-enabled Python interpreter, use the ``easy_install`` command.
.. code-block:: text
@@ -269,35 +313,90 @@ complete, as it downloads and installs a number of dependencies.
Installing :app:`Pyramid` on a Windows System
-------------------------------------------------
-#. Install, or find `Python 2.6
- <http://python.org/download/releases/2.6.4/>`_ for your system.
+You can use Pyramid on Windows under Python 2 or under Python 3. Directions
+for both versions are included below.
+
+Windows Using Python 2
+~~~~~~~~~~~~~~~~~~~~~~
+
+#. Install, or find `Python 2.7
+ <http://www.python.org/download/releases/2.7.3/>`_ for your system.
#. Install the `Python for Windows extensions
<http://sourceforge.net/projects/pywin32/files/>`_. Make sure to
- pick the right download for Python 2.6 and install it using the
+ pick the right download for Python 2.7 and install it using the
same Python installation from the previous step.
#. Install latest :term:`setuptools` distribution into the Python you
obtained/installed/found in the step above: download `ez_setup.py
<http://peak.telecommunity.com/dist/ez_setup.py>`_ and run it using
- the ``python`` interpreter of your Python 2.6 installation using a
+ the ``python`` interpreter of your Python 2.7 installation using a
command prompt:
.. code-block:: text
- c:\> c:\Python26\python ez_setup.py
+ c:\> c:\Python27\python ez_setup.py
+
+#. Use that Python's `bin/easy_install` to install `virtualenv`:
+
+ .. code-block:: text
+
+ c:\> c:\Python27\Scripts\easy_install virtualenv
+
+#. Use that Python's virtualenv to make a workspace:
+
+ .. code-block:: text
+
+ c:\> c:\Python27\Scripts\virtualenv --no-site-packages env
+
+#. Switch to the ``env`` directory:
+
+ .. code-block:: text
+
+ c:\> cd env
+
+#. (Optional) Consider using ``Scripts\activate.bat`` to make your shell
+ environment wired to use the virtualenv.
+
+#. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies
+ installed:
+
+ .. code-block:: text
+
+ c:\env> Scripts\easy_install pyramid
+
+Windows Using Python 3
+~~~~~~~~~~~~~~~~~~~~~~
+
+#. Install, or find `Python 3.2
+ <http://www.python.org/download/releases/3.2.3/>`_ for your system.
+
+#. Install the `Python for Windows extensions
+ <http://sourceforge.net/projects/pywin32/files/>`_. Make sure to
+ pick the right download for Python 3.2 and install it using the
+ same Python installation from the previous step.
+
+#. Install latest :term:`distribute` distribution into the Python you
+ obtained/installed/found in the step above: download `distribute_setup.py
+ <http://python-distribute.org/distribute_setup.py>`_ and run it using the
+ ``python`` interpreter of your Python 3.2 installation using a command
+ prompt:
+
+ .. code-block:: text
+
+ c:\> c:\Python32\python distribute_setup.py
#. Use that Python's `bin/easy_install` to install `virtualenv`:
.. code-block:: text
- c:\> c:\Python26\Scripts\easy_install virtualenv
+ c:\> c:\Python32\Scripts\easy_install virtualenv
#. Use that Python's virtualenv to make a workspace:
.. code-block:: text
- c:\> c:\Python26\Scripts\virtualenv --no-site-packages env
+ c:\> c:\Python32\Scripts\virtualenv --no-site-packages env
#. Switch to the ``env`` directory:
@@ -308,8 +407,8 @@ Installing :app:`Pyramid` on a Windows System
#. (Optional) Consider using ``Scripts\activate.bat`` to make your shell
environment wired to use the virtualenv.
-#. Use ``easy_install`` pointed at the "current" index to get
- :app:`Pyramid` and its direct dependencies installed:
+#. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies
+ installed:
.. code-block:: text
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index b5fa6a9f7..b35c61720 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -315,7 +315,7 @@ Rendered views can return dictionaries
If you use a :term:`renderer`, you don't have to return a special kind of
"webby" ``Response`` object from a view. Instead, you can return a
-dictionary instead, and Pyramid will take care of converting that dictionary
+dictionary, and Pyramid will take care of converting that dictionary
to a Response using a template on your behalf. This makes the view easier to
test, because you don't have to parse HTML in your tests; just make an
assertion instead that the view returns "the right stuff" in the dictionary
@@ -803,7 +803,7 @@ within a function called when another user uses the
See also :ref:`add_directive`.
Programmatic Introspection
---------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~
If you're building a large system that other users may plug code into, it's
useful to be able to get an enumeration of what code they plugged in *at
@@ -831,7 +831,7 @@ callable:
See also :ref:`using_introspection`.
Python 3 Compatibility
-----------------------
+~~~~~~~~~~~~~~~~~~~~~~
Pyramid and most of its add-ons are Python 3 compatible. If you develop a
Pyramid application today, you won't need to worry that five years from now
diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst
index 6bfaf11c0..b88f3f0c8 100644
--- a/docs/narr/introspector.rst
+++ b/docs/narr/introspector.rst
@@ -393,6 +393,10 @@ introspectables in categories not described here.
The ``match_param`` argument passed to ``add_view``.
+ ``csrf_token``
+
+ The ``csrf_token`` argument passed to ``add_view``.
+
``callable``
The (resolved) ``view`` argument passed to ``add_view``. Represents the
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 1e2c225d2..b08948e43 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -286,11 +286,39 @@ Here's sample output from a run of ``pserve`` on UNIX:
$ ../bin/pserve development.ini
Starting server in PID 16601.
- serving on 0.0.0.0:6543 view at http://127.0.0.1:6543
+ serving on http://0.0.0.0:6543
-By default, :app:`Pyramid` applications generated from a scaffold
-will listen on TCP port 6543. You can shut down a server started this way by
-pressing ``Ctrl-C``.
+When you use ``pserve`` to start the application implied by the default
+rendering of a scaffold, it will respond to requests on *all* IP addresses
+possessed by your system, not just requests to ``localhost``. This is what
+the ``0.0.0.0`` in ``serving on http://0.0.0.0:6543`` means. The server will
+respond to requests made to ``127.0.0.1`` and on any external IP address.
+For example, your system might be configured to have an external IP address
+``192.168.1.50``. If that's the case, if you use a browser running on the
+same system as Pyramid, it will be able to access the application via
+``http://127.0.0.1:6543/`` as well as via
+``http://129.168.1.50:6543/``. However, *other people* on other computers on
+the same network will also be able to visit your Pyramid application in their
+browser by visiting ``http://192.168.1.50:6543/``.
+
+If you want to restrict access such that only a browser running on the same
+machine as Pyramid will be able to access your Pyramid application, edit the
+``development.ini`` file, and replace the ``host`` value in the
+``[server:main]`` section. Change it from ``0.0.0.0`` to ``127.0.0.1``. For
+example::
+
+ [server:main]
+ use = egg:waitress#main
+ host = 127.0.0.1
+ port = 6543
+
+You can change the port on which the server runs on by changing the same
+portion of the ``development.ini`` file. For example, you can change the
+``port = 6543`` line in the ``development.ini`` file's ``[server:main]``
+section to ``port = 8080`` to run the server on port 8080 instead of
+port 6543.
+
+You can shut down a server started this way by pressing ``Ctrl-C``.
The default server used to run your Pyramid application when a project is
created from a scaffold is named :term:`Waitress`. This server is what
@@ -309,11 +337,6 @@ under the default server, it will almost certainly work under any other
server in production if you eventually choose to use a different one. Don't
worry about it right now.
-You can change the port on which the server runs on by changing the
-``development.ini`` file. For example, you can change the ``port = 6543``
-line in the ``development.ini`` file's ``[server:main]`` section to ``port =
-8080`` to run the server on port 8080 instead of port 6543.
-
For more detailed information about the startup process, see
:ref:`startup_chapter`. For more information about environment variables and
configuration file settings that influence startup and runtime behavior, see
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index 57b5bc65b..1158d2225 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -306,7 +306,9 @@ See :class:`pyramid.renderers.JSON` and
JSONP Renderer
~~~~~~~~~~~~~~
-.. note:: This feature is new in Pyramid 1.1.
+.. note::
+
+ This feature is new in Pyramid 1.1.
:class:`pyramid.renderers.JSONP` is a `JSONP
<http://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper which
@@ -327,7 +329,7 @@ time "by hand". Configure a JSONP renderer using the
Once this renderer is registered via
:meth:`~pyramid.config.Configurator.add_renderer` as above, you can use
``jsonp`` as the ``renderer=`` parameter to ``@view_config`` or
-:meth:`pyramid.config.Configurator.add_view``:
+:meth:`pyramid.config.Configurator.add_view`:
.. code-block:: python
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index 07ec0f21e..3a94b4f7d 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -92,11 +92,11 @@ For example:
from pyramid.config import Configurator
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
- authentication_policy = AuthTktAuthenticationPolicy('seekrit')
- authorization_policy = ACLAuthorizationPolicy()
+ authn_policy = AuthTktAuthenticationPolicy('seekrit', hashalg='sha512')
+ authz_policy = ACLAuthorizationPolicy()
config = Configurator()
- config.set_authentication_policy(authentication_policy)
- config.set_authorization_policy(authorization_policy)
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
.. note:: the ``authentication_policy`` and ``authorization_policy``
arguments may also be passed to their respective methods mentioned above
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index 1aa1b6341..f7da7838e 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -63,10 +63,15 @@ application by using the ``session_factory`` argument to the
this implementation is, by default, *unencrypted*. You should not use it
when you keep sensitive information in the session object, as the
information can be easily read by both users of your application and third
- parties who have access to your users' network traffic. Use a different
- session factory implementation (preferably one which keeps session data on
- the server) for anything but the most basic of applications where "session
- security doesn't matter".
+ parties who have access to your users' network traffic. And if you use this
+ sessioning implementation, and you inadvertently create a cross-site
+ scripting vulnerability in your application, because the session data is
+ stored unencrypted in a cookie, it will also be easier for evildoers to
+ obtain the current user's cross-site scripting token. In short, use a
+ different session factory implementation (preferably one which keeps session
+ data on the server) for anything but the most basic of applications where
+ "session security doesn't matter", and you are sure your application has no
+ cross-site scripting vulnerabilities.
.. index::
single: session object
diff --git a/docs/narr/subrequest.rst b/docs/narr/subrequest.rst
new file mode 100644
index 000000000..89551ab35
--- /dev/null
+++ b/docs/narr/subrequest.rst
@@ -0,0 +1,271 @@
+.. index::
+ single: subrequest
+
+.. _subrequest_chapter:
+
+Invoking a Subrequest
+=====================
+
+.. warning::
+
+ This feature was added in Pyramid 1.4a1.
+
+:app:`Pyramid` allows you to invoke a subrequest at any point during the
+processing of a request. Invoking a subrequest allows you to obtain a
+:term:`response` object from a view callable within your :app:`Pyramid`
+application while you're executing a different view callable within the same
+application.
+
+Here's an example application which uses a subrequest:
+
+.. code-block:: python
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.request import Request
+
+ def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq)
+ return response
+
+ def view_two(request):
+ request.response.body = 'This came from view_two'
+ return request.response
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+When ``/view_one`` is visted in a browser, the text printed in the browser
+pane will be ``This came from view_two``. The ``view_one`` view used the
+:meth:`pyramid.request.Request.invoke_subrequest` API to obtain a response
+from another view (``view_two``) within the same application when it
+executed. It did so by constructing a new request that had a URL that it
+knew would match the ``view_two`` view registration, and passed that new
+request along to :meth:`pyramid.request.Request.invoke_subrequest`. The
+``view_two`` view callable was invoked, and it returned a response. The
+``view_one`` view callable then simply returned the response it obtained from
+the ``view_two`` view callable.
+
+Note that it doesn't matter if the view callable invoked via a subrequest
+actually returns a *literal* Response object. Any view callable that uses a
+renderer or which returns an object that can be interpreted by a response
+adapter when found and invoked via
+:meth:`pyramid.request.Request.invoke_subrequest` will return a Response
+object:
+
+.. code-block:: python
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.request import Request
+
+ def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq)
+ return response
+
+ def view_two(request):
+ return 'This came from view_two'
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two', renderer='string')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+Even though the ``view_two`` view callable returned a string, it was invoked
+in such a way that the ``string`` renderer associated with the view
+registration that was found turned it into a "real" response object for
+consumption by ``view_one``.
+
+Being able to unconditionally obtain a response object by invoking a view
+callable indirectly is the main advantage to using
+:meth:`pyramid.request.Request.invoke_subrequest` instead of simply importing
+a view callable and executing it directly. Note that there's not much
+advantage to invoking a view using a subrequest if you *can* invoke a view
+callable directly. Subrequests are slower and are less convenient if you
+actually do want just the literal information returned by a function that
+happens to be a view callable.
+
+Note that, by default, if a view callable invoked by a subrequest raises an
+exception, the exception will be raised to the caller of
+:meth:`~pyramid.request.Request.invoke_subrequest` even if you have a
+:term:`exception view` configured:
+
+.. code-block:: python
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.request import Request
+
+ def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq)
+ return response
+
+ def view_two(request):
+ raise ValueError('foo')
+
+ def excview(request):
+ request.response.body = b'An exception was raised'
+ request.response.status_int = 500
+ return request.response
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two', renderer='string')
+ config.add_view(excview, context=Exception)
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+When we run the above code and visit ``/view_one`` in a browser, the
+``excview`` :term:`exception view` will *not* be executed. Instead, the call
+to :meth:`~pyramid.request.Request.invoke_subrequest` will cause a
+:exc:`ValueError` exception to be raised and a response will never be
+generated. We can change this behavior; how to do so is described below in
+our discussion of the ``use_tweens`` argument.
+
+The :meth:`pyramid.request.Request.invoke_subrequest` API accepts two
+arguments: a positional argument ``request`` that must be provided, and and
+``use_tweens`` keyword argument that is optional; it defaults to ``False``.
+
+The ``request`` object passed to the API must be an object that implements
+the Pyramid request interface (such as a :class:`pyramid.request.Request`
+instance). If ``use_tweens`` is ``True``, the request will be sent to the
+:term:`tween` in the tween stack closest to the request ingress. If
+``use_tweens`` is ``False``, the request will be sent to the main router
+handler, and no tweens will be invoked.
+
+In the example above, the call to
+:meth:`~pyramid.request.Request.invoke_subrequest` will always raise an
+exception. This is because it's using the default value for ``use_tweens``,
+which is ``False``. You can pass ``use_tweens=True`` instead to ensure that
+it will convert an exception to a Response if an :term:`exception view` is
+configured instead of raising the exception. This because exception views
+are called by the exception view :term:`tween` as described in
+:ref:`exception_views` when any view raises an exception.
+
+We can cause the subrequest to be run through the tween stack by passing
+``use_tweens=True`` to the call to
+:meth:`~pyramid.request.Request.invoke_subrequest`, like this:
+
+.. code-block:: python
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.request import Request
+
+ def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq, use_tweens=True)
+ return response
+
+ def view_two(request):
+ raise ValueError('foo')
+
+ def excview(request):
+ request.response.body = b'An exception was raised'
+ request.response.status_int = 500
+ return request.response
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two', renderer='string')
+ config.add_view(excview, context=Exception)
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+In the above case, the call to ``request.invoke_subrequest(subreq)`` will not
+raise an exception. Instead, it will retrieve a "500" response from the
+attempted invocation of ``view_two``, because the tween which invokes an
+exception view to generate a response is run, and therefore ``excview`` is
+executed.
+
+This is one of the major differences between specifying the
+``use_tweens=True`` and ``use_tweens=False`` arguments to
+:meth:`~pyramid.request.Request.invoke_subrequest`. ``use_tweens=True`` may
+also imply invoking transaction commit/abort for the logic executed in the
+subrequest if you've got ``pyramid_tm`` in the tween list, injecting debug
+HTML if you've got ``pyramid_debugtoolbar`` in the tween list, and other
+tween-related side effects as defined by your particular tween list.
+
+The :meth:`~pyramid.request.Request.invoke_subrequest` function also
+unconditionally:
+
+- manages the threadlocal stack so that
+ :func:`~pyramid.threadlocal.get_current_request` and
+ :func:`~pyramid.threadlocal.get_current_registry` work during a request
+ (they will return the subrequest instead of the original request)
+
+- Adds a ``registry`` attribute and a ``invoke_subrequest`` attribute (a
+ callable) to the request object it's handed.
+
+- sets request extensions (such as those added via
+ :meth:`~pyramid.config.Configurator.add_request_method` or
+ :meth:`~pyramid.config.Configurator.set_request_property`) on the subrequest
+ object passed as ``request``
+
+- causes a :class:`~pyramid.event.NewRequest` event to be sent at the
+ beginning of request processing.
+
+- causes a :class:`~pyramid.event.ContextFound` event to be sent when a
+ context resource is found.
+
+- Ensures that the user implied by the request passed has the necessary
+ authorization to invoke view callable before calling it.
+
+- causes a :class:`~pyramid.event.NewResponse` event to be sent when the
+ Pyramid application returns a response.
+
+- Calls any :term:`response callback` functions defined within the subrequest's
+ lifetime if a response is obtained from the Pyramid application.
+
+- Calls any :term:`finished callback` functions defined within the subrequest's
+ lifetime.
+
+The invocation of a subrequest has more or less exactly the same effect as
+the invocation of a request received by the Pyramid router from a web client
+when ``use_tweens=True``. When ``use_tweens=False``, the tweens are skipped
+but all the other steps take place.
+
+It's a poor idea to use the original ``request`` object as an argument to
+:meth:`~pyramid.request.Request.invoke_subrequest`. You should construct a
+new request instead as demonstrated in the above example, using
+:meth:`pyramid.request.Request.blank`. Once you've constructed a request
+object, you'll need to massage the it to match the view callable you'd like
+to be executed during the subrequest. This can be done by adjusting the
+subrequest's URL, its headers, its request method, and other attributes. The
+documentation for :class:`pyramid.request.Request` exposes the methods you
+should call and attributes you should set on the request you create to
+massage it into something that will actually match the view you'd like to
+call via a subrequest.
+
+We've demonstrated use of a subrequest from within a view callable, but you
+can use the :meth:`~pyramid.request.Request.invoke_subrequest` API from
+within a tween or an event handler as well. It's usually a poor idea to
+invoke :meth:`~pyramid.request.Request.invoke_subrequest` from within a
+tween, because tweens already by definition have access to a function that
+will cause a subrequest (they are passed a ``handle`` function), but you can
+do it. It's fine to invoke
+:meth:`~pyramid.request.Request.invoke_subrequest` from within an event
+handler, however.
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 860010a1a..ba72ebfbf 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -46,20 +46,6 @@ within the body of a view callable like so:
{'foo':1, 'bar':2},
request=request)
-.. warning::
-
- Earlier iterations of this documentation
- (pre-version-1.3) encouraged the application developer to use
- ZPT-specific APIs such as
- :func:`pyramid.chameleon_zpt.render_template_to_response` and
- :func:`pyramid.chameleon_zpt.render_template` to render templates
- directly. This style of rendering still works, but at least for
- purposes of this documentation, those functions are deprecated.
- Application developers are encouraged instead to use the functions
- available in the :mod:`pyramid.renderers` module to perform
- rendering tasks. This set of functions works to render templates
- for all renderer extensions registered with :app:`Pyramid`.
-
The ``sample_view`` :term:`view callable` function above returns a
:term:`response` object which contains the body of the
``templates/foo.pt`` template. In this case, the ``templates``
@@ -79,12 +65,12 @@ prefix on Windows.
.. warning::
Only :term:`Chameleon` templates support defining a renderer for a
- template relative to the location of the module where the view
- callable is defined. Mako templates, and other templating system
- bindings work differently. In particular, Mako templates use a
- "lookup path" as defined by the ``mako.directories`` configuration
- file instead of treating relative paths as relative to the current
- view module. See :ref:`mako_templates`.
+ template relative to the location of the module where the view callable is
+ defined. Mako templates, and other templating system bindings work
+ differently. In particular, Mako templates use a "lookup path" as defined
+ by the ``mako.directories`` configuration file instead of treating
+ relative paths as relative to the current view module. See
+ :ref:`mako_templates`.
The path can alternately be a :term:`asset specification` in the form
``some.dotted.package_name:relative/path``. This makes it possible to
@@ -534,6 +520,33 @@ And ``templates/mytemplate.pt`` might look like so:
</span>
</html>
+
+Using A Chameleon Macro Name Within a Renderer Name
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sommetime you'd like to render a macro inside of a Chameleon ZPT template
+instead of the full Chameleon ZPT template. To render the content of a
+``define-macro`` field inside a Chameleon ZPT template, given a Chameleon
+template file named ``foo.pt`` and a macro named ``bar`` defined within it
+(e.g. ``<div metal:define-macro="bar">...</div>``), you can configure the
+template as a :term:`renderer` like so:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+
+ @view_config(renderer='foo#bar.pt')
+ def my_view(request):
+ return {'project':'my project'}
+
+The above will render only the ``bar`` macro defined within the ``foo.pt``
+template instead of the entire template.
+
+.. note::
+
+ This feature is new in Pyramid 1.4.
+
.. index::
single: Chameleon text templates
@@ -573,10 +586,6 @@ When the template is rendered, it will show:
Hello, world!
-If you'd rather use templates directly within a view callable (without
-the indirection of using a renderer), see :ref:`chameleon_text_module`
-for the API description.
-
See also :ref:`built_in_renderers` for more general information about
renderers, including Chameleon text renderers.
@@ -714,12 +723,13 @@ This template doesn't use any advanced features of Mako, only the
:term:`renderer globals`. See the `the Mako documentation
<http://www.makotemplates.org/>`_ to use more advanced features.
-Using def inside Mako Templates
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Using A Mako def name Within a Renderer Name
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To use a def inside a Mako template, given a :term:`Mako` template file named
-``foo.mak`` and a def named ``bar``, you can configure the template as a
-:term:`renderer` like so:
+Sommetime you'd like to render a ``def`` inside of a Mako template instead of
+the full Mako template. To render a def inside a Mako template, given a
+:term:`Mako` template file named ``foo.mak`` and a def named ``bar``, you can
+configure the template as a :term:`renderer` like so:
.. code-block:: python
:linenos:
@@ -730,6 +740,13 @@ To use a def inside a Mako template, given a :term:`Mako` template file named
def my_view(request):
return {'project':'my project'}
+The above will render the ``bar`` def from within the ``foo.mak`` template
+instead of the entire template.
+
+.. note::
+
+ This feature is new in Pyramid 1.4.
+
.. index::
single: automatic reloading of templates
single: template automatic reload
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index 5ce2c8a66..20017064b 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -52,7 +52,7 @@ The suggested mechanism for unit and integration testing of a :app:`Pyramid`
application is the Python :mod:`unittest` module. Although this module is
named :mod:`unittest`, it is actually capable of driving both unit and
integration tests. A good :mod:`unittest` tutorial is available within `Dive
-Into Python <http://diveintopython.nfshost.com/unit_testing/index.html>`_ by Mark
+Into Python <http://www.diveintopython.net/unit_testing/index.html>`_ by Mark
Pilgrim.
:app:`Pyramid` provides a number of facilities that make unit, integration,
@@ -157,6 +157,30 @@ We use a "dummy" request implementation supplied by
:class:`pyramid.testing.DummyRequest` because it's easier to construct
than a "real" :app:`Pyramid` request object.
+Test setup using a context manager
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An alternative style of setting up a test configuration is to use the
+`with` statement and :func:`pyramid.testing.testConfig` to create a
+context manager. The context manager will call
+:func:`pyramid.testing.setUp` before the code under test and
+:func:`pyramid.testing.tearDown` afterwards.
+
+This style is useful for small self-contained tests. For example:
+
+.. code-block:: python
+ :linenos:
+
+ import unittest
+
+ class MyTest(unittest.TestCase):
+
+ def test_my_function(self):
+ from pyramid import testing
+ with testing.testConfig() as config:
+ config.add_route('bar', '/bar/{id}')
+ my_function_which_needs_route_bar()
+
What?
~~~~~
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
new file mode 100644
index 000000000..839f59c35
--- /dev/null
+++ b/docs/narr/upgrading.rst
@@ -0,0 +1,234 @@
+.. _upgrading_chapter:
+
+Upgrading Pyramid
+=================
+
+When a new version of Pyramid is released, it will sometimes deprecate a
+feature or remove a feature that was deprecated in an older release. When
+features are removed from Pyramid, applications that depend on those features
+will begin to break. This chapter explains how to ensure your Pyramid
+applications keep working when you upgrade the Pyramid version you're using.
+
+.. sidebar:: About Release Numbering
+
+ Conventionally, application version numbering in Python is described as
+ ``major.minor.micro``. If your Pyramid version is "1.2.3", it means
+ you're running a version of Pyramid with the major version "1", the minor
+ version "2" and the micro version "3". A "major" release is one that
+ increments the first-dot number; 2.X.X might follow 1.X.X. A "minor"
+ release is one that increments the second-dot number; 1.3.X might follow
+ 1.2.X. A "micro" release is one that increments the third-dot number;
+ 1.2.3 might follow 1.2.2. In general, micro releases are "bugfix-only",
+ and contain no new features, minor releases contain new features but are
+ largely backwards compatible with older versions, and a major release
+ indicates a large set of backwards incompatibilities.
+
+The Pyramid core team is conservative when it comes to removing features. We
+don't remove features unnecessarily, but we're human, and we make mistakes
+which cause some features to be evolutionary dead ends. Though we are
+willing to support dead-end features for some amount of time, some eventually
+have to be removed when the cost of supporting them outweighs the benefit of
+keeping them around, because each feature in Pyramid represents a certain
+documentation and maintenance burden.
+
+Deprecation and Removal Policy
+------------------------------
+
+When a feature is scheduled for removal from Pyramid or any of its official
+add-ons, the core development team takes these steps:
+
+- Using the feature will begin to generate a `DeprecationWarning`, indicating
+ the version in which the feature became deprecated.
+
+- A note is added to the documentation indicating that the feature is
+ deprecated.
+
+- A note is added to the :ref:`changelog` about the deprecation.
+
+When a deprecated feature is eventually removed:
+
+- The feature is removed.
+
+- A note is added to the :ref:`changelog` about the removal.
+
+Features are never removed in *micro* releases. They are only removed in
+minor and major releases. Deprecated features are kept around for at least
+*three* minor releases from the time the feature became deprecated.
+Therefore, if a feature is added in Pyramid 1.0, but it's deprecated in
+Pyramid 1.1, it will be kept around through all 1.1.X releases, all 1.2.X
+releases and all 1.3.X releases. It will finally be removed in the first
+1.4.X release.
+
+Sometimes features are "docs-deprecated" instead of formally deprecated.
+This means that the feature will be kept around indefinitely, but it will be
+removed from the documentation or a note will be added to the documentation
+telling folks to use some other newer feature. This happens when the cost of
+keeping an old feature around is very minimal and the support and
+documentation burden is very low. For example, we might rename a function
+that is an API without changing the arguments it accepts. In this case,
+we'll often rename the function, and change the docs to point at the new
+function name, but leave around a backwards compatibility alias to the old
+function name so older code doesn't break.
+
+"Docs deprecated" features tend to work "forever", meaning that they won't be
+removed, and they'll never generate a deprecation warning. However, such
+changes are noted in the :ref:`changelog`, so it's possible to know that you
+should change older spellings to newer ones to ensure that people reading
+your code can find the APIs you're using in the Pyramid docs.
+
+Consulting the Change History
+-----------------------------
+
+Your first line of defense against application failures caused by upgrading
+to a newer Pyramid release is always to read the :ref:`changelog`. to find
+the deprecations and removals for each release between the release you're
+currently running and the one you wish to upgrade to. The change history
+notes every deprecation within a ``Deprecation`` section and every removal
+within a ``Backwards Incompatibilies`` section for each release.
+
+The change history often contains instructions for changing your code to
+avoid deprecation warnings and how to change docs-deprecated spellings to
+newer ones. You can follow along with each deprecation explanation in the
+change history, simply doing a grep or other code search to your application,
+using the change log examples to remediate each potential problem.
+
+.. _testing_under_new_release:
+
+Testing Your Application Under a New Pyramid Release
+----------------------------------------------------
+
+Once you've upgraded your application to a new Pyramid release and you've
+remediated as much as possible by using the change history notes, you'll want
+to run your application's tests (see :ref:`running_tests`) in such a way that
+you can see DeprecationWarnings printed to the console when the tests run.
+
+.. code-block:: bash
+
+ $ python -Wd setup.py test -q
+
+The ``-Wd`` argument is an argument that tells Python to print deprecation
+warnings to the console. Note that the ``-Wd`` flag is only required for
+Python 2.7 and better: Python versions 2.6 and older print deprecation
+warnings to the console by default. See `the Python -W flag documentation
+<http://docs.python.org/using/cmdline.html#cmdoption-W>`_ for more
+information.
+
+As your tests run, deprecation warnings will be printed to the console
+explaining the deprecation and providing instructions about how to prevent
+the deprecation warning from being issued. For example:
+
+.. code-block:: text
+
+ $ python -Wd setup.py test -q
+ # .. elided ...
+ running build_ext
+ /home/chrism/projects/pyramid/env27/myproj/myproj/views.py:3:
+ DeprecationWarning: static: The "pyramid.view.static" class is deprecated
+ as of Pyramid 1.1; use the "pyramid.static.static_view" class instead with
+ the "use_subpath" argument set to True.
+ from pyramid.view import static
+ .
+ ----------------------------------------------------------------------
+ Ran 1 test in 0.014s
+
+ OK
+
+In the above case, it's line #3 in the ``myproj.views`` module (``from
+pyramid.view import static``) that is causing the problem:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+
+ from pyramid.view import static
+ myview = static('static', 'static')
+
+The deprecation warning tells me how to fix it, so I can change the code to
+do things the newer way:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view. import view_config
+
+ from pyramid.static import static_view
+ myview = static_view('static', 'static', use_subpath=True)
+
+When I run the tests again, the deprecation warning is no longer printed to
+my console:
+
+.. code-block:: text
+
+ $ python -Wd setup.py test -q
+ # .. elided ...
+ running build_ext
+ .
+ ----------------------------------------------------------------------
+ Ran 1 test in 0.014s
+
+ OK
+
+
+My Application Doesn't Have Any Tests or Has Few Tests
+------------------------------------------------------
+
+If your application has no tests, or has only moderate test coverage, running
+tests won't tell you very much, because the Pyramid codepaths that generate
+deprecation warnings won't be executed.
+
+In this circumstance, you can start your application interactively under a
+server run with the ``PYTHONWARNINGS`` environment variable set to
+``default``. On UNIX, you can do that via:
+
+.. code-block:: bash
+
+ $ PYTHONWARNINGS=default bin/pserve development.ini
+
+On Windows, you need to issue two commands:
+
+.. code-block:: bash
+
+ C:\> set PYTHONWARNINGS=default
+ C:\> Scripts/pserve.exe development.ini
+
+At this point, it's ensured that deprecation warnings will be printed to the
+console whenever a codepath is hit that generates one. You can then click
+around in your application interactively to try to generate them, and
+remediate as explained in :ref:`testing_under_new_release`.
+
+See `the PYTHONWARNINGS environment variable documentation
+<http://docs.python.org/using/cmdline.html#envvar-PYTHONWARNINGS>`_ or `the
+Python -W flag documentation
+<http://docs.python.org/using/cmdline.html#cmdoption-W>`_ for more
+information.
+
+Upgrading to the Very Latest Pyramid Release
+--------------------------------------------
+
+When you upgrade your application to the very most recent Pyramid release,
+it's advisable to upgrade step-wise through each most recent minor release,
+beginning with the one that you know your application currently runs under,
+and ending on the most recent release. For example, if your application is
+running in production on Pyramid 1.2.1, and the most recent Pyramid 1.3
+release is Pyramid 1.3.3, and the most recent Pyramid release is 1.4.4, it's
+advisable to do this:
+
+- Upgrade your environment to the most recent 1.2 release. For example, the
+ most recent 1.2 release might be 1.2.3, so upgrade to it. Then run your
+ application's tests under 1.2.3 as described in
+ :ref:`testing_under_new_release`. Note any deprecation warnings and
+ remediate.
+
+- Upgrade to the most recent 1.3 release, 1.3.3. Run your application's
+ tests, note any deprecation warnings and remediate.
+
+- Upgrade to 1.4.4. Run your application's tests, note any deprecation
+ warnings and remediate.
+
+If you skip testing your application under each minor release (for example if
+you upgrade directly from 1.2.1 to 1.4.4), you might miss a deprecation
+warning and waste more time trying to figure out an error caused by a feature
+removal than it would take to upgrade stepwise through each minor release.
+
+
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 23b4fde68..6373a8d26 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -290,12 +290,13 @@ configured view.
of the ``REQUEST_METHOD`` of the :term:`WSGI` environment.
``request_param``
- This value can be any string. A view declaration with this argument
- ensures that the view will only be called when the :term:`request` has a
- key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST``
- variable) that has a name which matches the supplied value.
+ This value can be any string or a sequence of strings. A view declaration
+ with this argument ensures that the view will only be called when the
+ :term:`request` has a key in the ``request.params`` dictionary (an HTTP
+ ``GET`` or ``POST`` variable) that has a name which matches the a
+ supplied value.
- If the value supplied has a ``=`` sign in it,
+ If any value supplied has a ``=`` sign in it,
e.g. ``request_param="foo=123"``, then the key (``foo``) must both exist
in the ``request.params`` dictionary, *and* the value must match the right
hand side of the expression (``123``) for the view to "match" the current
@@ -394,6 +395,54 @@ configured view.
consideration when deciding whether or not to invoke the associated view
callable.
+``check_csrf``
+ If specified, this value should be one of ``None``, ``True``, ``False``, or
+ a string representing the 'check name'. If the value is ``True`` or a
+ string, CSRF checking will be performed. If the value is ``False`` or
+ ``None``, CSRF checking will not be performed.
+
+ If the value provided is a string, that string will be used as the 'check
+ name'. If the value provided is ``True``, ``csrf_token`` will be used as
+ the check name.
+
+ If CSRF checking is performed, the checked value will be the value of
+ ``request.params[check_name]``. This value will be compared against the
+ value of ``request.session.get_csrf_token()``, and the check will pass if
+ these two values are the same. If the check passes, the associated view
+ will be permitted to execute. If the check fails, the associated view
+ will not be permitted to execute.
+
+ Note that using this feature requires a :term:`session factory` to have
+ been configured.
+
+ .. versionadded:: 1.4a2
+
+``physical_path``
+ If specified, this value should be a string or a tuple representing the
+ :term:`physical path` of the context found via traversal for this predicate
+ to match as true. For example: ``physical_path='/'`` or
+ ``physical_path='/a/b/c'`` or ``physical_path=('', 'a', 'b', 'c')``. This is
+ not a path prefix match or a regex, it's a whole-path match. It's useful
+ when you want to always potentially show a view when some object is traversed
+ to, but you can't be sure about what kind of object it will be, so you can't
+ use the ``context`` predicate. The individual path elements inbetween slash
+ characters or in tuple elements should be the Unicode representation of the
+ name of the resource and should not be encoded in any way.
+
+ .. versionadded:: 1.4a3
+
+``effective_principals``
+
+ If specified, this value should be a :term:`principal` identifier or a
+ sequence of principal identifiers. If the
+ :func:`pyramid.security.effective_principals` method indicates that every
+ principal named in the argument list is present in the current request, this
+ predicate will return True; otherwise it will return False. For example:
+ ``effective_principals=pyramid.security.Authenticated`` or
+ ``effective_principals=('fred', 'group:admins')``.
+
+ .. versionadded:: 1.4a4
+
``custom_predicates``
If ``custom_predicates`` is specified, it must be a sequence of references
to custom predicate callables. Use custom predicates when no set of
@@ -407,6 +456,15 @@ configured view.
If ``custom_predicates`` is not specified, no custom predicates are
used.
+``predicates``
+ Pass a key/value pair here to use a third-party predicate registered via
+ :meth:`pyramid.config.Configurator.add_view_predicate`. More than one
+ key/value pair can be used at the same time. See
+ :ref:`view_and_route_predicates` for more information about third-party
+ predicates.
+
+ .. versionadded:: 1.4a1
+
.. index::
single: view_config decorator
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index f6ee9a8d5..07d018127 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -177,7 +177,7 @@ HTTP Exceptions
~~~~~~~~~~~~~~~
All classes documented in the :mod:`pyramid.httpexceptions` module documented
-as inheriting from the :class:`pryamid.httpexceptions.HTTPException` are
+as inheriting from the :class:`pyramid.httpexceptions.HTTPException` are
:term:`http exception` objects. Instances of an HTTP exception object may
either be *returned* or *raised* from within view code. In either case
(return or raise) the instance will be used as as the view's response.
@@ -338,6 +338,16 @@ exception views which have a name will be ignored.
Exception views can be configured with any view registration mechanism:
``@view_config`` decorator or imperative ``add_view`` styles.
+.. note::
+
+ Pyramid's :term:`exception view` handling logic is implemented as a tween
+ factory function: :func:`pyramid.tweens.excview_tween_factory`. If
+ Pyramid exception view handling is desired, and tween factories are
+ specified via the ``pyramid.tweens`` configuration setting, the
+ :func:`pyramid.tweens.excview_tween_factory` function must be added to the
+ ``pyramid.tweens`` configuration setting list explicitly. If it is not
+ present, Pyramid will not perform exception view handling.
+
.. index::
single: view http redirect
single: http redirect (from a view)
diff --git a/docs/remake b/docs/remake
index b236f2976..eb818289f 100755
--- a/docs/remake
+++ b/docs/remake
@@ -1 +1 @@
-make clean html SPHINXBUILD=../env26/bin/sphinx-build
+make clean html SPHINXBUILD=../env27/bin/sphinx-build
diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst
index e070f8eda..a22f12610 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -7,12 +7,11 @@ Running a :app:`Pyramid` Application under ``mod_wsgi``
It allows :term:`WSGI` programs to be served using the Apache web
server.
-This guide will outline broad steps that can be used to get a
-:app:`Pyramid` application running under Apache via ``mod_wsgi``.
-This particular tutorial was developed under Apple's Mac OS X platform
-(Snow Leopard, on a 32-bit Mac), but the instructions should be
-largely the same for all systems, delta specific path information for
-commands and files.
+This guide will outline broad steps that can be used to get a :app:`Pyramid`
+application running under Apache via ``mod_wsgi``. This particular tutorial
+was developed under Apple's Mac OS X platform (Snow Leopard, on a 32-bit
+Mac), but the instructions should be largely the same for all systems, delta
+specific path information for commands and files.
.. note:: Unfortunately these instructions almost certainly won't work for
deploying a :app:`Pyramid` application on a Windows system using
@@ -73,9 +72,10 @@ commands and files.
.. code-block:: python
- from pyramid.paster import get_app
- application = get_app(
- '/Users/chrism/modwsgi/env/myapp/production.ini', 'main')
+ from pyramid.paster import get_app, setup_logging
+ ini_path = '/Users/chrism/modwsgi/env/myapp/production.ini'
+ setup_logging(ini_path)
+ application = get_app(ini_path, 'main')
The first argument to ``get_app`` is the project configuration file
name. It's best to use the ``production.ini`` file provided by your
@@ -85,12 +85,15 @@ commands and files.
``application`` is important: mod_wsgi requires finding such an
assignment when it opens the file.
-#. Make the ``pyramid.wsgi`` script executable.
+ The call to ``setup_logging`` initializes the standard library's
+ `logging` module to allow logging within your application.
+ See :ref:`logging_config`.
- .. code-block:: text
-
- $ cd ~/modwsgi/env
- $ chmod 755 pyramid.wsgi
+ There is no need to make the ``pyramid.wsgi`` script executable.
+ However, you'll need to make sure that *two* users have access to change
+ into the ``~/modwsgi/env`` directory: your current user (mine is
+ ``chrism`` and the user that Apache will run as often named ``apache`` or
+ ``httpd``). Make sure both of these users can "cd" into that directory.
#. Edit your Apache configuration and add some stuff. I happened to
create a file named ``/etc/apache2/other/modwsgi.conf`` on my own
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 9e0bf0f09..24249945a 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -134,15 +134,15 @@ Now add those policies to the configuration:
(Only the highlighted lines need to be added.)
-We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an auth
-ticket that may be included in the request, and an ``ACLAuthorizationPolicy``
-that uses an ACL to determine the allow or deny outcome for a view.
-
-Note that the
-:class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor
-accepts two arguments: ``secret`` and ``callback``. ``secret`` is a string
-representing an encryption key used by the "authentication ticket" machinery
-represented by this policy: it is required. The ``callback`` is the
+We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an
+auth ticket that may be included in the request, and an
+``ACLAuthorizationPolicy`` that uses an ACL to determine the allow or deny
+outcome for a view.
+
+Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
+constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is
+a string representing an encryption key used by the "authentication ticket"
+machinery represented by this policy: it is required. The ``callback`` is the
``groupfinder()`` function that we created before.
Add permission declarations
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index 28cecb787..529603546 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -251,7 +251,7 @@ wiki page. It includes:
- A ``div`` element that is replaced with the ``content``
value provided by the view (rows 45-47). ``content``
contains HTML, so the ``structure`` keyword is used
- to prevent escaping it (i.e. changing ">" to &gt;, etc.)
+ to prevent escaping it (i.e. changing ">" to "&gt;", etc.)
- A link that points
at the "edit" URL which invokes the ``edit_page`` view for
the page being viewed (rows 49-51).
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 6989145d8..b42e01d03 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -14,8 +14,8 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a WSGI application.
"""
- authn_policy = AuthTktAuthenticationPolicy(secret='sosecret',
- callback=groupfinder)
+ authn_policy = AuthTktAuthenticationPolicy(
+ 'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(root_factory=root_factory, settings=settings)
config.set_authentication_policy(authn_policy)
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css b/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py
index 21f12b31d..50485d279 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -51,17 +51,17 @@ def view_page(context, request):
renderer='templates/edit.pt',
permission='edit')
def add_page(context, request):
- name = request.subpath[0]
+ pagename = request.subpath[0]
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(body)
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- context[name] = page
+ context[pagename] = page
return HTTPFound(location = request.resource_url(page))
- save_url = request.resource_url(context, 'add_page', name)
+ save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
return dict(page = page, save_url = save_url,
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pylons.css b/docs/tutorials/wiki/src/models/tutorial/static/pylons.css
index 7e6ec739d..4b1c017cd 100644
--- a/docs/tutorials/wiki/src/models/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki/src/models/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-snall,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
index 6989145d8..b42e01d03 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -14,8 +14,8 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a WSGI application.
"""
- authn_policy = AuthTktAuthenticationPolicy(secret='sosecret',
- callback=groupfinder)
+ authn_policy = AuthTktAuthenticationPolicy(
+ 'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(root_factory=root_factory, settings=settings)
config.set_authentication_policy(authn_policy)
diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/pylons.css b/docs/tutorials/wiki/src/tests/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki/src/tests/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py
index 21f12b31d..50485d279 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/views.py
@@ -51,17 +51,17 @@ def view_page(context, request):
renderer='templates/edit.pt',
permission='edit')
def add_page(context, request):
- name = request.subpath[0]
+ pagename = request.subpath[0]
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(body)
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- context[name] = page
+ context[pagename] = page
return HTTPFound(location = request.resource_url(page))
- save_url = request.resource_url(context, 'add_page', name)
+ save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
return dict(page = page, save_url = save_url,
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pylons.css b/docs/tutorials/wiki/src/views/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki/src/views/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki/src/views/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py
index 016f5b6bb..b0c15297f 100644
--- a/docs/tutorials/wiki/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki/src/views/tutorial/views.py
@@ -35,17 +35,17 @@ def view_page(context, request):
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt')
def add_page(context, request):
- name = request.subpath[0]
+ pagename = request.subpath[0]
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(body)
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- context[name] = page
+ context[pagename] = page
return HTTPFound(location = request.resource_url(page))
- save_url = request.resource_url(context, 'add_page', name)
+ save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
return dict(page = page, save_url = save_url)
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index 2ef55d15b..1ddf8c82d 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -112,7 +112,7 @@ parameter to our :term:`Configurator` constructor, that points to
the class we created above:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 19-20
+ :lines: 23-24
:linenos:
:emphasize-lines: 2
:language: python
@@ -144,22 +144,22 @@ add these import statements:
Now add those policies to the configuration:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 16-22
+ :lines: 20-26
:linenos:
:emphasize-lines: 1-3,6-7
:language: python
(Only the highlighted lines need to be added.)
-We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an auth
-ticket that may be included in the request, and an ``ACLAuthorizationPolicy``
-that uses an ACL to determine the allow or deny outcome for a view.
+We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an
+auth ticket that may be included in the request, and an
+``ACLAuthorizationPolicy`` that uses an ACL to determine the allow or deny
+outcome for a view.
-Note that the
-:class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor
-accepts two arguments: ``secret`` and ``callback``. ``secret`` is a string
-representing an encryption key used by the "authentication ticket" machinery
-represented by this policy: it is required. The ``callback`` is the
+Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
+constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is
+a string representing an encryption key used by the "authentication ticket"
+machinery represented by this policy: it is required. The ``callback`` is the
``groupfinder()`` function that we created before.
Add permission declarations
@@ -206,7 +206,7 @@ Go back to ``tutorial/tutorial/__init__.py`` and add these two
routes:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 25-26
+ :lines: 29-30
:linenos:
:language: python
@@ -333,7 +333,7 @@ when we're done:
.. literalinclude:: src/authorization/tutorial/__init__.py
:linenos:
- :emphasize-lines: 2-3,7,16-18,20-22,25-26
+ :emphasize-lines: 2-3,7,23-24,20-26,29-30
:language: python
(Only the highlighted lines need to be added.)
@@ -353,7 +353,7 @@ when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
- :emphasize-lines: 11,14-18,31,37,58,61,73,76,88,91-117,119-123
+ :emphasize-lines: 11,14-18,25,31,37,58,61,73,76,88,91-117,119-123
:language: python
(Only the highlighted lines need to be added.)
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 5f4ea671c..dbd130c36 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -51,21 +51,33 @@ The main function first creates a SQLAlchemy database engine using
(something like ``sqlite://``):
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 9
+ :lines: 12
:linenos:
:language: py
-``main`` then initializes our SQL database using SQLAlchemy, passing it the
+``main`` then initializes our SQLAlchemy session object, passing it the
engine:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 10
+ :lines: 13
+ :language: py
+
+``main`` subsequently initializes our SQLAlchemy declarative Base object,
+assigning the engine we created to the ``bind`` attribute of it's
+``metadata`` object. This allows table definitions done imperatively
+(instead of declaratively, via a class statement) to work. We won't use any
+such tables in our application, but if you add one later, long after you've
+forgotten about this tutorial, you won't be left scratching your head when it
+doesn't work.
+
+ .. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 14
:language: py
The next step of ``main`` is to construct a :term:`Configurator` object:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 11
+ :lines: 15
:language: py
``settings`` is passed to the Configurator as a keyword argument with the
@@ -78,7 +90,7 @@ deployment-related values such as ``pyramid.reload_templates``,
two arguments: ``static`` (the name), and ``static`` (the path):
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 12
+ :lines: 16
:language: py
This registers a static resource view which will match any URL that starts
@@ -96,11 +108,11 @@ via the :meth:`pyramid.config.Configurator.add_route` method that will be
used when the URL is ``/``:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 13
+ :lines: 17
:language: py
Since this route has a ``pattern`` equalling ``/`` it is the route that will
-be matched when the URL ``/`` is visted, e.g. ``http://localhost:6543/``.
+be matched when the URL ``/`` is visited, e.g. ``http://localhost:6543/``.
``main`` next calls the ``scan`` method of the configurator, which will
recursively scan our ``tutorial`` package, looking for ``@view_config`` (and
@@ -109,7 +121,7 @@ view configuration will be registered, which will allow one of our
application URLs to be mapped to some code.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 14
+ :lines: 18
:language: py
Finally, ``main`` is finished configuring things, so it uses the
@@ -117,7 +129,7 @@ Finally, ``main`` is finished configuring things, so it uses the
:term:`WSGI` application:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 15
+ :lines: 19
:language: py
View Declarations via ``views.py``
@@ -190,7 +202,7 @@ Next we set up a SQLAlchemy "DBSession" object:
``scoped_session`` allows us to access our database connection globally.
``sessionmaker`` creates a database session object. We pass to
``sessionmaker`` the ``extension=ZopeTransactionExtension()`` extension
-option in order to allow the system to automatically manage datbase
+option in order to allow the system to automatically manage database
transactions. With ``ZopeTransactionExtension`` activated, our application
will automatically issue a transaction commit after every request unless an
exception is raised, in which case the transaction will be aborted.
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index efb72230e..a54996e3c 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -248,7 +248,7 @@ wiki page. It includes:
- A ``div`` element that is replaced with the ``content``
value provided by the view (rows 45-47). ``content``
contains HTML, so the ``structure`` keyword is used
- to prevent escaping it (i.e. changing ">" to &gt;, etc.)
+ to prevent escaping it (i.e. changing ">" to "&gt;", etc.)
- A link that points
at the "edit" URL which invokes the ``edit_page`` view for
the page being viewed (rows 49-51).
@@ -342,7 +342,7 @@ something like:
.. literalinclude:: src/views/tutorial/__init__.py
:linenos:
:language: python
- :emphasize-lines: 13-16
+ :emphasize-lines: 17-20
(The highlighted lines are the ones that need to be added or edited.)
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index 2e6fc0e77..6e41e00aa 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -20,7 +20,7 @@ Models
We'll be using a SQLite database to hold our wiki data, and we'll be using
:term:`SQLAlchemy` to access the data in this database.
-Within the database, we define a single table named `tables`, whose elements
+Within the database, we define a single table named `pages`, whose elements
will store the wiki pages. There are two columns: `name` and `data`.
URLs like ``/PageName`` will try to find an element in
@@ -115,7 +115,8 @@ listed in the following table:
| | redirect to | | | |
| | /PageName | | | |
+----------------------+-----------------------+-------------+------------+------------+
-| /login | Display login form. | login | login.pt | |
+| /login | Display login form, | login | login.pt | |
+| | Forbidden [3]_ | | | |
| | | | | |
| | If the form was | | | |
| | submitted, | | | |
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
index 7e290a1e1..76071173a 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -6,15 +6,19 @@ from sqlalchemy import engine_from_config
from tutorial.security import groupfinder
-from .models import DBSession
+from .models import (
+ DBSession,
+ Base,
+ )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
authn_policy = AuthTktAuthenticationPolicy(
- 'sosecret', callback=groupfinder)
+ 'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
root_factory='tutorial.models.RootFactory')
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
index c7670b049..0d085b0e2 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -60,14 +60,14 @@ def view_page(request):
@view_config(route_name='add_page', renderer='templates/edit.pt',
permission='edit')
def add_page(request):
- name = request.matchdict['pagename']
+ pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
- page = Page(name, body)
+ page = Page(pagename, body)
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
- save_url = request.route_url('add_page', pagename=name)
+ pagename=pagename))
+ save_url = request.route_url('add_page', pagename=pagename)
page = Page('', '')
return dict(page=page, save_url=save_url,
logged_in=authenticated_userid(request))
@@ -75,16 +75,16 @@ def add_page(request):
@view_config(route_name='edit_page', renderer='templates/edit.pt',
permission='edit')
def edit_page(request):
- name = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=name).one()
+ pagename = request.matchdict['pagename']
+ page = DBSession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
+ pagename=pagename))
return dict(
page=page,
- save_url = request.route_url('edit_page', pagename=name),
+ save_url = request.route_url('edit_page', pagename=pagename),
logged_in=authenticated_userid(request),
)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
index 253341563..e39f619ed 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
@@ -1,13 +1,17 @@
from pyramid.config import Configurator
from sqlalchemy import engine_from_config
-from .models import DBSession
+from .models import (
+ DBSession,
+ Base,
+ )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
config = Configurator(settings=settings)
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
index 253341563..e39f619ed 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
@@ -1,13 +1,17 @@
from pyramid.config import Configurator
from sqlalchemy import engine_from_config
-from .models import DBSession
+from .models import (
+ DBSession,
+ Base,
+ )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
config = Configurator(settings=settings)
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
index 7e290a1e1..76071173a 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
@@ -6,15 +6,19 @@ from sqlalchemy import engine_from_config
from tutorial.security import groupfinder
-from .models import DBSession
+from .models import (
+ DBSession,
+ Base,
+ )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
authn_policy = AuthTktAuthenticationPolicy(
- 'sosecret', callback=groupfinder)
+ 'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
root_factory='tutorial.models.RootFactory')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py
index f2a33af1e..42ac0eb7f 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views.py
@@ -61,15 +61,15 @@ def view_page(request):
@view_config(route_name='add_page', renderer='templates/edit.pt',
permission='edit')
def add_page(request):
- name = request.matchdict['pagename']
+ pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
session = DBSession()
body = request.params['body']
- page = Page(name, body)
+ page = Page(pagename, body)
session.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
- save_url = request.route_url('add_page', pagename=name)
+ pagename=pagename))
+ save_url = request.route_url('add_page', pagename=pagename)
page = Page('', '')
return dict(page=page, save_url=save_url,
logged_in=authenticated_userid(request))
@@ -77,17 +77,17 @@ def add_page(request):
@view_config(route_name='edit_page', renderer='templates/edit.pt',
permission='edit')
def edit_page(request):
- name = request.matchdict['pagename']
+ pagename = request.matchdict['pagename']
session = DBSession()
- page = session.query(Page).filter_by(name=name).one()
+ page = session.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
session.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
+ pagename=pagename))
return dict(
page=page,
- save_url = request.route_url('edit_page', pagename=name),
+ save_url = request.route_url('edit_page', pagename=pagename),
logged_in=authenticated_userid(request),
)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
index b30d593cf..810e92f75 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
@@ -1,13 +1,17 @@
from pyramid.config import Configurator
from sqlalchemy import engine_from_config
-from .models import DBSession
+from .models import (
+ DBSession,
+ Base,
+ )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
config = Configurator(settings=settings)
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css
+++ b/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py
index c2a94a96b..5a9c75a61 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views.py
@@ -44,27 +44,27 @@ def view_page(request):
@view_config(route_name='add_page', renderer='templates/edit.pt')
def add_page(request):
- name = request.matchdict['pagename']
+ pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
- page = Page(name, body)
+ page = Page(pagename, body)
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
- save_url = request.route_url('add_page', pagename=name)
+ pagename=pagename))
+ save_url = request.route_url('add_page', pagename=pagename)
page = Page('', '')
return dict(page=page, save_url=save_url)
@view_config(route_name='edit_page', renderer='templates/edit.pt')
def edit_page(request):
- name = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=name).one()
+ pagename = request.matchdict['pagename']
+ page = DBSession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
+ pagename=pagename))
return dict(
page=page,
- save_url = request.route_url('edit_page', pagename=name),
+ save_url = request.route_url('edit_page', pagename=pagename),
)
diff --git a/docs/whatsnew-1.4.rst b/docs/whatsnew-1.4.rst
new file mode 100644
index 000000000..59e1f7a96
--- /dev/null
+++ b/docs/whatsnew-1.4.rst
@@ -0,0 +1,310 @@
+What's New In Pyramid 1.4
+=========================
+
+This article explains the new features in :app:`Pyramid` version 1.4 as
+compared to its predecessor, :app:`Pyramid` 1.3. It also documents backwards
+incompatibilities between the two versions and deprecations added to
+:app:`Pyramid` 1.4, as well as software dependency changes and notable
+documentation additions.
+
+Major Feature Additions
+-----------------------
+
+The major feature additions in Pyramid 1.4 follow.
+
+Third-Party Predicates
+~~~~~~~~~~~~~~~~~~~~~~~
+
+- Third-party custom view, route, and subscriber predicates can now be added
+ for use by view authors via
+ :meth:`pyramid.config.Configurator.add_view_predicate`,
+ :meth:`pyramid.config.Configurator.add_route_predicate` and
+ :meth:`pyramid.config.Configurator.add_subscriber_predicate`. So, for
+ example, doing this::
+
+ config.add_view_predicate('abc', my.package.ABCPredicate)
+
+ Might allow a view author to do this in an application that configured that
+ predicate::
+
+ @view_config(abc=1)
+
+ Similar features exist for :meth:`pyramid.config.Configurator.add_route`,
+ and :meth:`pyramid.config.Configurator.add_subscriber`. See
+ :ref:`registering_thirdparty_predicates` for more information.
+
+Easy Custom JSON Serialization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Views can now return custom objects which will be serialized to JSON by a
+ JSON renderer by defining a ``__json__`` method on the object's class. This
+ method should return values natively serializable by ``json.dumps`` (such
+ as ints, lists, dictionaries, strings, and so forth). See
+ :ref:`json_serializing_custom_objects` for more information. The JSON
+ renderer now also allows for the definition of custom type adapters to
+ convert unknown objects to JSON serializations, in case you can't add a
+ ``__json__`` method to returned objects.
+
+Partial Mako and Chameleon Template Renderings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- The Mako renderer now supports using a def name in an asset spec. When the
+ def name is present in the asset spec, the system will render the template
+ named def within the template instead of rendering the entire template. An
+ example asset spec which names a def is
+ ``package:path/to/template#defname.mako``. This will render the def named
+ ``defname`` inside the ``template.mako`` template instead of rendering the
+ entire template. The old way of returning a tuple in the form
+ ``('defname', {})`` from the view is supported for backward compatibility.
+
+- The Chameleon ZPT renderer now supports using a macro name in an asset
+ spec. When the macro name is present in the asset spec, the system will
+ render the macro listed as a ``define-macro`` and return the result instead
+ of rendering the entire template. An example asset spec:
+ ``package:path/to/template#macroname.pt``. This will render the macro
+ defined as ``macroname`` within the ``template.pt`` template instead of the
+ entire template.
+
+Subrequest Support
+~~~~~~~~~~~~~~~~~~
+
+- Developers may invoke a subrequest by using the
+ :meth:`pyramid.request.Request.invoke_subrequest` API. This allows a
+ developer to obtain a response from one view callable by issuing a subrequest
+ from within a different view callable. See :ref:`subrequest_chapter` for
+ more information.
+
+Minor Feature Additions
+-----------------------
+
+- :meth:`pyramid.config.Configurator.add_directive` now accepts arbitrary
+ callables like partials or objects implementing ``__call__`` which don't
+ have ``__name__`` and ``__doc__`` attributes. See
+ https://github.com/Pylons/pyramid/issues/621 and
+ https://github.com/Pylons/pyramid/pull/647.
+
+- As of this release, the ``request_method`` view/route predicate, when used,
+ will also imply that ``HEAD`` is implied when you use ``GET``. For
+ example, using ``@view_config(request_method='GET')`` is equivalent to
+ using ``@view_config(request_method=('GET', 'HEAD'))``. Using
+ ``@view_config(request_method=('GET', 'POST')`` is equivalent to using
+ ``@view_config(request_method=('GET', 'HEAD', 'POST')``. This is because
+ HEAD is a variant of GET that omits the body, and WebOb has special support
+ to return an empty body when a HEAD is used.
+
+- :meth:`pyramid.config.Configurator.add_request_method` has been introduced
+ to support extending request objects with arbitrary callables. This method
+ expands on the now documentation-deprecated
+ :meth:`pyramid.config.Configurator.set_request_property` by supporting
+ methods as well as properties. This method also causes less code to be
+ executed at request construction time than
+ :meth:`~pyramid.config.Configurator.set_request_property`.
+
+- The static view machinery now raises rather than returns
+ :class:`pyramid.httpexceptions.HTTPNotFound` and
+ :class:`pyramid.httpexceptions.HTTPMovedPermanently` exceptions, so these can
+ be caught by the notfound view (and other exception views).
+
+- When there is a predicate mismatch exception (seen when no view matches for
+ a given request due to predicates not working), the exception now contains
+ a textual description of the predicate which didn't match.
+
+- An :meth:`pyramid.config.Configurator.add_permission` directive method was
+ added to the Configurator. This directive registers a free-standing
+ permission introspectable into the Pyramid introspection system.
+ Frameworks built atop Pyramid can thus use the ``permissions``
+ introspectable category data to build a comprehensive list of permissions
+ supported by a running system. Before this method was added, permissions
+ were already registered in this introspectable category as a side effect of
+ naming them in an :meth:`pyramid.config.Configurator.add_view` call, this
+ method just makes it possible to arrange for a permission to be put into
+ the ``permissions`` introspectable category without naming it along with an
+ associated view. Here's an example of usage of ``add_permission``::
+
+ config = Configurator()
+ config.add_permission('view')
+
+- The :func:`pyramid.session.UnencryptedCookieSessionFactoryConfig` function
+ now accepts ``signed_serialize`` and ``signed_deserialize`` hooks which may
+ be used to influence how the sessions are marshalled (by default this is
+ done with HMAC+pickle).
+
+- :class:`pyramid.testing.DummyRequest` now supports methods supplied by the
+ ``pyramid.util.InstancePropertyMixin`` class such as ``set_property``.
+
+- Request properties and methods added via
+ :meth:`pyramid.config.Configurator.add_request_method` or
+ :meth:`pyramid.config.Configurator.set_request_property` are now available to
+ tweens.
+
+- Request properties and methods added via
+ :meth:`pyramid.config.Configurator.add_request_method` or
+ :meth:`pyramid.config.Configurator.set_request_property` are now available
+ in the request object returned from :func:`pyramid.paster.bootstrap`.
+
+- ``request.context`` of environment request during
+ :func:`pyramid.paster.bootstrap` is now the root object if a context isn't
+ already set on a provided request.
+
+- :class:`pyramid.decorator.reify` is now an API, and was added to
+ the API documentation.
+
+- Added the :func:`pyramid.testing.testConfig` context manager, which can be
+ used to generate a configurator in a test, e.g. ``with
+ testing.testConfig(...):``.
+
+- A new :func:`pyramid.session.check_csrf_token` convenience API function was
+ added.
+
+- A ``check_csrf`` view predicate was added. For example, you can now do
+ ``config.add_view(someview, check_csrf=True)``. When the predicate is
+ checked, if the ``csrf_token`` value in ``request.params`` matches the csrf
+ token in the request's session, the view will be permitted to execute.
+ Otherwise, it will not be permitted to execute.
+
+- Add ``Base.metadata.bind = engine`` to ``alchemy`` scaffold, so that tables
+ defined imperatively will work.
+
+- Comments with references to documentation sections placed in scaffold
+ ``.ini`` files.
+
+- Allow multiple values to be specified to the ``request_param`` view/route
+ predicate as a sequence. Previously only a single string value was allowed.
+ See https://github.com/Pylons/pyramid/pull/705
+
+- Added an HTTP Basic authentication policy
+ at :class:`pyramid.authentication.BasicAuthAuthenticationPolicy`.
+
+- The :meth:`pyramid.config.Configurator.testing_securitypolicy` method now
+ returns the policy object it creates.
+
+- The DummySecurityPolicy created by
+ :meth:`pyramid.config.testing_securitypolicy` now sets a ``forgotten`` value
+ on the policy (the value ``True``) when its ``forget`` method is called.
+
+
+- The DummySecurityPolicy created by
+ :meth:`pyramid.config.testing_securitypolicy` now sets a
+ ``remembered`` value on the policy, which is the value of the ``principal``
+ argument it's called with when its ``remember`` method is called.
+
+- New ``physical_path`` view predicate. If specified, this value should be a
+ string or a tuple representing the physical traversal path of the context
+ found via traversal for this predicate to match as true. For example:
+ ``physical_path='/'`` or ``physical_path='/a/b/c'`` or ``physical_path=('',
+ 'a', 'b', 'c')``. It's useful when you want to always potentially show a
+ view when some object is traversed to, but you can't be sure about what kind
+ of object it will be, so you can't use the ``context`` predicate.
+
+Backwards Incompatibilities
+---------------------------
+
+- The Pyramid router no longer adds the values ``bfg.routes.route`` or
+ ``bfg.routes.matchdict`` to the request's WSGI environment dictionary.
+ These values were docs-deprecated in ``repoze.bfg`` 1.0 (effectively seven
+ minor releases ago). If your code depended on these values, use
+ ``request.matched_route`` and ``request.matchdict`` instead.
+
+- It is no longer possible to pass an environ dictionary directly to
+ ``pyramid.traversal.ResourceTreeTraverser.__call__`` (aka
+ ``ModelGraphTraverser.__call__``). Instead, you must pass a request
+ object. Passing an environment instead of a request has generated a
+ deprecation warning since Pyramid 1.1.
+
+- Pyramid will no longer work properly if you use the
+ ``webob.request.LegacyRequest`` as a request factory. Instances of the
+ LegacyRequest class have a ``request.path_info`` which return a string.
+ This Pyramid release assumes that ``request.path_info`` will
+ unconditionally be Unicode.
+
+- The functions from ``pyramid.chameleon_zpt`` and ``pyramid.chameleon_text``
+ named ``get_renderer``, ``get_template``, ``render_template``, and
+ ``render_template_to_response`` have been removed. These have issued a
+ deprecation warning upon import since Pyramid 1.0. Use
+ :func:`pyramid.renderers.get_renderer`,
+ ``pyramid.renderers.get_renderer().implementation()``,
+ :func:`pyramid.renderers.render` or
+ :func:`pyramid.renderers.render_to_response` respectively instead of these
+ functions.
+
+- The ``pyramid.configuration`` module was removed. It had been deprecated
+ since Pyramid 1.0 and printed a deprecation warning upon its use. Use
+ :mod:`pyramid.config` instead.
+
+- The ``pyramid.paster.PyramidTemplate`` API was removed. It had been
+ deprecated since Pyramid 1.1 and issued a warning on import. If your code
+ depended on this, adjust your code to import
+ :class:`pyramid.scaffolds.PyramidTemplate` instead.
+
+- The ``pyramid.settings.get_settings()`` API was removed. It had been
+ printing a deprecation warning since Pyramid 1.0. If your code depended on
+ this API, use ``pyramid.threadlocal.get_current_registry().settings``
+ instead or use the ``settings`` attribute of the registry available from
+ the request (``request.registry.settings``).
+
+- These APIs from the ``pyramid.testing`` module were removed. They have
+ been printing deprecation warnings since Pyramid 1.0:
+
+ * ``registerDummySecurityPolicy``, use
+ :meth:`pyramid.config.Configurator.testing_securitypolicy` instead.
+
+ * ``registerResources`` (aka ``registerModels``), use
+ :meth:`pyramid.config.Configurator.testing_resources` instead.
+
+ * ``registerEventListener``, use
+ :meth:`pyramid.config.Configurator.testing_add_subscriber` instead.
+
+ * ``registerTemplateRenderer`` (aka ``registerDummyRenderer``), use
+ :meth:`pyramid.config.Configurator.testing_add_template` instead.
+
+ * ``registerView``, use :meth:`pyramid.config.Configurator.add_view` instead.
+
+ * ``registerUtility``, use
+ :meth:`pyramid.config.Configurator.registry.registerUtility` instead.
+
+ * ``registerAdapter``, use
+ :meth:`pyramid.config.Configurator.registry.registerAdapter` instead.
+
+ * ``registerSubscriber``, use
+ :meth:`pyramid.config.Configurator.add_subscriber` instead.
+
+ * ``registerRoute``, use
+ :meth:`pyramid.config.Configurator.add_route` instead.
+
+ * ``registerSettings``, use
+ :meth:`pyramid.config.Configurator.add_settings` instead.
+
+- In Pyramid 1.3 and previous, the ``__call__`` method of a Response object
+ returned by a view was invoked before any finished callbacks were executed.
+ As of this release, the ``__call__`` method of a Response object is invoked
+ *after* finished callbacks are executed. This is in support of the
+ :meth:`pyramid.request.Request.invoke_subrequest` feature.
+
+Deprecations
+------------
+
+- The :meth:`pyramid.config.Configurator.set_request_property` directive has
+ been documentation-deprecated. The method remains usable but the more
+ featureful :meth:`pyramid.config.Configurator.add_request_method` should be
+ used in its place (it has all of the same capabilities but can also extend
+ the request object with methods).
+
+Documentation Enhancements
+--------------------------
+
+- Added an :ref:`upgrading_chapter` chapter to the narrative documentation.
+ It describes how to cope with deprecations and removals of Pyramid APIs and
+ how to show Pyramid-generated deprecation warnings while running tests and
+ while running a server.
+
+- Added a :ref:`subrequest_chapter` chapter to the narrative documentation.
+
+- Many cleanups and improvements to narrative and API docs.
+
+Dependency Changes
+------------------
+
+- Pyramid now requires WebOb 1.2b3+ (the prior Pyramid release only relied on
+ 1.2dev+). This is to ensure that we obtain a version of WebOb that returns
+ ``request.path_info`` as text.
+
diff --git a/pyramid/__init__.py b/pyramid/__init__.py
index 473d5e1c6..5bb534f79 100644
--- a/pyramid/__init__.py
+++ b/pyramid/__init__.py
@@ -1,5 +1 @@
-from pyramid.request import Request
-from pyramid.response import Response
-Response.RequestClass = Request
-Request.ResponseClass = Response
-del Request, Response
+# package
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index 83bdb13d1..08d283acc 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -1,10 +1,12 @@
+import binascii
from codecs import utf_8_decode
from codecs import utf_8_encode
-from hashlib import md5
+import hashlib
import base64
import datetime
import re
import time as time_mod
+import warnings
from zope.interface import implementer
@@ -46,7 +48,21 @@ class CallbackAuthenticationPolicy(object):
methodname = classname + '.' + methodname
logger.debug(methodname + ': ' + msg)
+ def _clean_principal(self, princid):
+ if princid in (Authenticated, Everyone):
+ princid = None
+ return princid
+
def authenticated_userid(self, request):
+ """ Return the authenticated userid or ``None``.
+
+ If no callback is registered, this will be the same as
+ ``unauthenticated_userid``.
+
+ If a ``callback`` is registered, this will return the userid if
+ and only if the callback returns a value that is not ``None``.
+
+ """
debug = self.debug
userid = self.unauthenticated_userid(request)
if userid is None:
@@ -55,6 +71,14 @@ class CallbackAuthenticationPolicy(object):
'authenticated_userid',
request)
return None
+ if self._clean_principal(userid) is None:
+ debug and self._log(
+ ('use of userid %r is disallowed by any built-in Pyramid '
+ 'security policy, returning None' % userid),
+ 'authenticated_userid' ,
+ request)
+ return None
+
if self.callback is None:
debug and self._log(
'there was no groupfinder callback; returning %r' % (userid,),
@@ -77,9 +101,32 @@ class CallbackAuthenticationPolicy(object):
)
def effective_principals(self, request):
+ """ A list of effective principals derived from request.
+
+ This will return a list of principals including, at least,
+ :data:`pyramid.security.Everyone`. If there is no authenticated
+ userid, or the ``callback`` returns ``None``, this will be the
+ only principal:
+
+ .. code-block:: python
+
+ return [Everyone]
+
+ If the ``callback`` does not return ``None`` and an authenticated
+ userid is found, then the principals will include
+ :data:`pyramid.security.Authenticated`, the ``authenticated_userid``
+ and the list of principals returned by the ``callback``:
+
+ .. code-block:: python
+
+ extra_principals = callback(userid, request)
+ return [Everyone, Authenticated, userid] + extra_principals
+
+ """
debug = self.debug
effective_principals = [Everyone]
userid = self.unauthenticated_userid(request)
+
if userid is None:
debug and self._log(
'unauthenticated_userid returned %r; returning %r' % (
@@ -88,6 +135,16 @@ class CallbackAuthenticationPolicy(object):
request
)
return effective_principals
+
+ if self._clean_principal(userid) is None:
+ debug and self._log(
+ ('unauthenticated_userid returned disallowed %r; returning %r '
+ 'as if it was None' % (userid, effective_principals)),
+ 'effective_principals',
+ request
+ )
+ return effective_principals
+
if self.callback is None:
debug and self._log(
'groupfinder callback is None, so groups is []',
@@ -100,6 +157,7 @@ class CallbackAuthenticationPolicy(object):
'groupfinder callback returned %r as groups' % (groups,),
'effective_principals',
request)
+
if groups is None: # is None!
debug and self._log(
'returning effective principals: %r' % (
@@ -162,39 +220,120 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy):
return identifier
def authenticated_userid(self, request):
+ """ Return the authenticated userid or ``None``.
+
+ If no callback is registered, this will be the same as
+ ``unauthenticated_userid``.
+
+ If a ``callback`` is registered, this will return the userid if
+ and only if the callback returns a value that is not ``None``.
+
+ """
identity = self._get_identity(request)
+
if identity is None:
+ self.debug and self._log(
+ 'repoze.who identity is None, returning None',
+ 'authenticated_userid',
+ request)
+ return None
+
+ userid = identity['repoze.who.userid']
+
+ if userid is None:
+ self.debug and self._log(
+ 'repoze.who.userid is None, returning None' % userid,
+ 'authenticated_userid',
+ request)
return None
+
+ if self._clean_principal(userid) is None:
+ self.debug and self._log(
+ ('use of userid %r is disallowed by any built-in Pyramid '
+ 'security policy, returning None' % userid),
+ 'authenticated_userid',
+ request)
+ return None
+
if self.callback is None:
- return identity['repoze.who.userid']
+ return userid
+
if self.callback(identity, request) is not None: # is not None!
- return identity['repoze.who.userid']
+ return userid
def unauthenticated_userid(self, request):
+ """ Return the ``repoze.who.userid`` key from the detected identity."""
identity = self._get_identity(request)
if identity is None:
return None
return identity['repoze.who.userid']
def effective_principals(self, request):
+ """ A list of effective principals derived from the identity.
+
+ This will return a list of principals including, at least,
+ :data:`pyramid.security.Everyone`. If there is no identity, or
+ the ``callback`` returns ``None``, this will be the only principal.
+
+ If the ``callback`` does not return ``None`` and an identity is
+ found, then the principals will include
+ :data:`pyramid.security.Authenticated`, the ``authenticated_userid``
+ and the list of principals returned by the ``callback``.
+
+ """
effective_principals = [Everyone]
identity = self._get_identity(request)
+
if identity is None:
+ self.debug and self._log(
+ ('repoze.who identity was None; returning %r' %
+ effective_principals),
+ 'effective_principals',
+ request
+ )
return effective_principals
+
if self.callback is None:
groups = []
else:
groups = self.callback(identity, request)
+
if groups is None: # is None!
+ self.debug and self._log(
+ ('security policy groups callback returned None; returning %r' %
+ effective_principals),
+ 'effective_principals',
+ request
+ )
return effective_principals
+
userid = identity['repoze.who.userid']
+
+ if userid is None:
+ self.debug and self._log(
+ ('repoze.who.userid was None; returning %r' %
+ effective_principals),
+ 'effective_principals',
+ request
+ )
+ return effective_principals
+
+ if self._clean_principal(userid) is None:
+ self.debug and self._log(
+ ('unauthenticated_userid returned disallowed %r; returning %r '
+ 'as if it was None' % (userid, effective_principals)),
+ 'effective_principals',
+ request
+ )
+ return effective_principals
+
effective_principals.append(Authenticated)
effective_principals.append(userid)
effective_principals.extend(groups)
-
return effective_principals
def remember(self, request, principal, **kw):
+ """ Store the ``principal`` as ``repoze.who.userid``."""
identifier = self._get_identifier(request)
if identifier is None:
return []
@@ -203,6 +342,12 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy):
return identifier.remember(environ, identity)
def forget(self, request):
+ """ Forget the current authenticated user.
+
+ Return headers that, if included in a response, will delete the
+ cookie responsible for tracking the current user.
+
+ """
identifier = self._get_identifier(request)
if identifier is None:
return []
@@ -246,19 +391,35 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy):
self.debug = debug
def unauthenticated_userid(self, request):
+ """ The ``REMOTE_USER`` value found within the ``environ``."""
return request.environ.get(self.environ_key)
def remember(self, request, principal, **kw):
+ """ A no-op. The ``REMOTE_USER`` does not provide a protocol for
+ remembering the user. This will be application-specific and can
+ be done somewhere else or in a subclass."""
return []
def forget(self, request):
+ """ A no-op. The ``REMOTE_USER`` does not provide a protocol for
+ forgetting the user. This will be application-specific and can
+ be done somewhere else or in a subclass."""
return []
+_marker = object()
+
@implementer(IAuthenticationPolicy)
class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
- """ A :app:`Pyramid` :term:`authentication policy` which
+ """A :app:`Pyramid` :term:`authentication policy` which
obtains data from a Pyramid "auth ticket" cookie.
+ .. warning::
+
+ The default hash algorithm used in this policy is MD5 and has known
+ hash collision vulnerabilities. The risk of an exploit is low.
+ However, for improved authentication security, use
+ ``hashalg='sha512'``.
+
Constructor Arguments
``secret``
@@ -330,13 +491,13 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
Optional.
``path``
-
+
Default: ``/``. The path for which the auth_tkt cookie is valid.
May be desirable if the application only serves part of a domain.
Optional.
-
+
``http_only``
-
+
Default: ``False``. Hide cookie from JavaScript by setting the
HttpOnly flag. Not honored by all browsers.
Optional.
@@ -347,6 +508,33 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
wildcard domain.
Optional.
+ ``hashalg``
+
+ Default: ``md5`` (the literal string).
+
+ Any hash algorithm supported by Python's ``hashlib.new()`` function
+ can be used as the ``hashalg``.
+
+ Cookies generated by different instances of AuthTktAuthenticationPolicy
+ using different ``hashalg`` options are not compatible. Switching the
+ ``hashalg`` will imply that all existing users with a valid cookie will
+ be required to re-login.
+
+ A warning is emitted at startup if an explicit ``hashalg`` is not
+ passed. This is for backwards compatibility reasons.
+
+ This option is available as of :app:`Pyramid` 1.4.
+
+ Optional.
+
+ .. note::
+
+ ``md5`` is the default for backwards compatibility reasons. However,
+ if you don't specify ``md5`` as the hashalg explicitly, a warning is
+ issued at application startup time. An explicit value of ``sha512``
+ is recommended for improved security, and ``sha512`` will become the
+ default in a future Pyramid version.
+
``debug``
Default: ``False``. If ``debug`` is ``True``, log messages to the
@@ -357,6 +545,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
Objects of this class implement the interface described by
:class:`pyramid.interfaces.IAuthenticationPolicy`.
"""
+
def __init__(self,
secret,
callback=None,
@@ -370,7 +559,32 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
http_only=False,
wild_domain=True,
debug=False,
+ hashalg=_marker
):
+ if hashalg is _marker:
+ hashalg = 'md5'
+ warnings.warn(
+ 'The MD5 hash function used by default by the '
+ 'AuthTktAuthenticationPolicy is known to be '
+ 'susceptible to collision attacks. It is the current default '
+ 'for backwards compatibility reasons, but we recommend that '
+ 'you use the SHA512 algorithm instead for improved security. '
+ 'Pass ``hashalg=\'sha512\'`` to the '
+ 'AuthTktAuthenticationPolicy constructor to do so.\n\nNote '
+ 'that a change to the hash algorithms will invalidate existing '
+ 'auth tkt cookies set by your application. If backwards '
+ 'compatibility of existing auth tkt cookies is of greater '
+ 'concern than the risk posed by the potential for a hash '
+ 'collision, you\'ll want to continue using MD5 explicitly. '
+ 'To do so, pass ``hashalg=\'md5\'`` in your application to '
+ 'the AuthTktAuthenticationPolicy constructor. When you do so '
+ 'this warning will not be emitted again. The default '
+ 'algorithm used in this policy will change in the future, so '
+ 'setting an explicit hashalg will futureproof your '
+ 'application.',
+ DeprecationWarning,
+ stacklevel=2
+ )
self.cookie = AuthTktCookieHelper(
secret,
cookie_name=cookie_name,
@@ -382,21 +596,29 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
http_only=http_only,
path=path,
wild_domain=wild_domain,
+ hashalg=hashalg,
)
self.callback = callback
self.debug = debug
def unauthenticated_userid(self, request):
+ """ The userid key within the auth_tkt cookie."""
result = self.cookie.identify(request)
if result:
return result['userid']
def remember(self, request, principal, **kw):
""" Accepts the following kw args: ``max_age=<int-seconds>,
- ``tokens=<sequence-of-ascii-strings>``"""
+ ``tokens=<sequence-of-ascii-strings>``.
+
+ Return a list of headers which will set appropriate cookies on
+ the response.
+
+ """
return self.cookie.remember(request, principal, **kw)
def forget(self, request):
+ """ A list of headers which will delete appropriate cookies."""
return self.cookie.forget(request)
def b64encode(v):
@@ -427,7 +649,8 @@ class AuthTicket(object):
"""
def __init__(self, secret, userid, ip, tokens=(), user_data='',
- time=None, cookie_name='auth_tkt', secure=False):
+ time=None, cookie_name='auth_tkt', secure=False,
+ hashalg='md5'):
self.secret = secret
self.userid = userid
self.ip = ip
@@ -439,11 +662,12 @@ class AuthTicket(object):
self.time = time
self.cookie_name = cookie_name
self.secure = secure
+ self.hashalg = hashalg
def digest(self):
return calculate_digest(
self.ip, self.time, self.secret, self.userid, self.tokens,
- self.user_data)
+ self.user_data, self.hashalg)
def cookie_value(self):
v = '%s%08x%s!' % (self.digest(), int(self.time),
@@ -465,7 +689,7 @@ class BadTicket(Exception):
Exception.__init__(self, msg)
# this function licensed under the MIT license (stolen from Paste)
-def parse_ticket(secret, ticket, ip):
+def parse_ticket(secret, ticket, ip, hashalg='md5'):
"""
Parse the ticket, returning (timestamp, userid, tokens, user_data).
@@ -473,13 +697,14 @@ def parse_ticket(secret, ticket, ip):
with an explanation.
"""
ticket = ticket.strip('"')
- digest = ticket[:32]
+ digest_size = hashlib.new(hashalg).digest_size * 2
+ digest = ticket[:digest_size]
try:
- timestamp = int(ticket[32:40], 16)
+ timestamp = int(ticket[digest_size:digest_size + 8], 16)
except ValueError as e:
raise BadTicket('Timestamp is not a hex integer: %s' % e)
try:
- userid, data = ticket[40:].split('!', 1)
+ userid, data = ticket[digest_size + 8:].split('!', 1)
except ValueError:
raise BadTicket('userid is not followed by !')
userid = url_unquote(userid)
@@ -491,7 +716,7 @@ def parse_ticket(secret, ticket, ip):
user_data = data
expected = calculate_digest(ip, timestamp, secret,
- userid, tokens, user_data)
+ userid, tokens, user_data, hashalg)
# Avoid timing attacks (see
# http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf)
@@ -504,16 +729,20 @@ def parse_ticket(secret, ticket, ip):
return (timestamp, userid, tokens, user_data)
# this function licensed under the MIT license (stolen from Paste)
-def calculate_digest(ip, timestamp, secret, userid, tokens, user_data):
+def calculate_digest(ip, timestamp, secret, userid, tokens, user_data,
+ hashalg='md5'):
secret = bytes_(secret, 'utf-8')
userid = bytes_(userid, 'utf-8')
tokens = bytes_(tokens, 'utf-8')
user_data = bytes_(user_data, 'utf-8')
- digest0 = md5(
+ hash_obj = hashlib.new(hashalg)
+ hash_obj.update(
encode_ip_timestamp(ip, timestamp) + secret + userid + b'\0'
- + tokens + b'\0' + user_data).hexdigest()
- digest = md5(bytes_(digest0) + secret).hexdigest()
- return digest
+ + tokens + b'\0' + user_data)
+ digest = hash_obj.hexdigest()
+ hash_obj2 = hashlib.new(hashalg)
+ hash_obj2.update(bytes_(digest) + secret)
+ return hash_obj2.hexdigest()
# this function licensed under the MIT license (stolen from Paste)
def encode_ip_timestamp(ip, timestamp):
@@ -553,10 +782,11 @@ class AuthTktCookieHelper(object):
text_type: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])),
binary_type: ('b64str', lambda x: b64encode(x)),
}
-
+
def __init__(self, secret, cookie_name='auth_tkt', secure=False,
include_ip=False, timeout=None, reissue_time=None,
- max_age=None, http_only=False, path="/", wild_domain=True):
+ max_age=None, http_only=False, path="/", wild_domain=True,
+ hashalg='md5'):
self.secret = secret
self.cookie_name = cookie_name
self.include_ip = include_ip
@@ -567,6 +797,7 @@ class AuthTktCookieHelper(object):
self.http_only = http_only
self.path = path
self.wild_domain = wild_domain
+ self.hashalg = hashalg
static_flags = []
if self.secure:
@@ -632,16 +863,16 @@ class AuthTktCookieHelper(object):
remote_addr = environ['REMOTE_ADDR']
else:
remote_addr = '0.0.0.0'
-
+
try:
timestamp, userid, tokens, user_data = self.parse_ticket(
- self.secret, cookie, remote_addr)
+ self.secret, cookie, remote_addr, self.hashalg)
except self.BadTicket:
return None
now = self.now # service tests
- if now is None:
+ if now is None:
now = time_mod.time()
if self.timeout and ( (timestamp + self.timeout) < now ):
@@ -689,7 +920,7 @@ class AuthTktCookieHelper(object):
environ = request.environ
request._authtkt_reissue_revoked = True
return self._get_cookies(environ, '', max_age=EXPIRE)
-
+
def remember(self, request, userid, max_age=None, tokens=()):
""" Return a set of Set-Cookie headers; when set into a response,
these headers will represent a valid authentication ticket.
@@ -750,7 +981,9 @@ class AuthTktCookieHelper(object):
tokens=tokens,
user_data=user_data,
cookie_name=self.cookie_name,
- secure=self.secure)
+ secure=self.secure,
+ hashalg=self.hashalg
+ )
cookie_value = ticket.cookie_value()
return self._get_cookies(environ, cookie_value, max_age)
@@ -783,7 +1016,7 @@ class SessionAuthenticationPolicy(CallbackAuthenticationPolicy):
Pyramid debug logger about the results of various authentication
steps. The output from debugging is useful for reporting to maillist
or IRC channels when asking for support.
-
+
"""
def __init__(self, prefix='auth.', callback=None, debug=False):
@@ -806,3 +1039,101 @@ class SessionAuthenticationPolicy(CallbackAuthenticationPolicy):
def unauthenticated_userid(self, request):
return request.session.get(self.userid_key)
+
+@implementer(IAuthenticationPolicy)
+class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy):
+ """ A :app:`Pyramid` authentication policy which uses HTTP standard basic
+ authentication protocol to authenticate users. To use this policy you will
+ need to provide a callback which checks the supplied user credentials
+ against your source of login data.
+
+ Constructor Arguments
+
+ ``check``
+
+ A callback function passed a username, password and request, in that
+ order as positional arguments. Expected to return ``None`` if the
+ userid doesn't exist or a sequence of principal identifiers (possibly
+ empty) if the user does exist.
+
+ ``realm``
+
+ Default: ``"Realm"``. The Basic Auth Realm string. Usually displayed to
+ the user by the browser in the login dialog.
+
+ ``debug``
+
+ Default: ``False``. If ``debug`` is ``True``, log messages to the
+ Pyramid debug logger about the results of various authentication
+ steps. The output from debugging is useful for reporting to maillist
+ or IRC channels when asking for support.
+
+ **Issuing a challenge**
+
+ Regular browsers will not send username/password credentials unless they
+ first receive a challenge from the server. The following recipe will
+ register a view that will send a Basic Auth challenge to the user whenever
+ there is an attempt to call a view which results in a Forbidden response::
+
+ from pyramid.httpexceptions import HTTPForbidden
+ from pyramid.httpexceptions import HTTPUnauthorized
+ from pyramid.security import forget
+ from pyramid.view import view_config
+
+ @view_config(context=HTTPForbidden)
+ def basic_challenge(request):
+ response = HTTPUnauthorized()
+ response.headers.update(forget(request))
+ return response
+ """
+ def __init__(self, check, realm='Realm', debug=False):
+ self.check = check
+ self.realm = realm
+ self.debug = debug
+
+ def unauthenticated_userid(self, request):
+ """ The userid parsed from the ``Authorization`` request header."""
+ credentials = self._get_credentials(request)
+ if credentials:
+ return credentials[0]
+
+ def remember(self, request, principal, **kw):
+ """ A no-op. Basic authentication does not provide a protocol for
+ remembering the user. Credentials are sent on every request.
+
+ """
+ return []
+
+ def forget(self, request):
+ """ Returns challenge headers. This should be attached to a response
+ to indicate that credentials are required."""
+ return [('WWW-Authenticate', 'Basic realm="%s"' % self.realm)]
+
+ def callback(self, username, request):
+ # Username arg is ignored. Unfortunately _get_credentials winds up
+ # getting called twice when authenticated_userid is called. Avoiding
+ # that, however, winds up duplicating logic from the superclass.
+ credentials = self._get_credentials(request)
+ if credentials:
+ username, password = credentials
+ return self.check(username, password, request)
+
+ def _get_credentials(self, request):
+ authorization = request.headers.get('Authorization')
+ if not authorization:
+ return None
+ try:
+ authmeth, auth = authorization.split(' ', 1)
+ except ValueError: # not enough values to unpack
+ return None
+ if authmeth.lower() != 'basic':
+ return None
+ try:
+ auth = b64decode(auth.strip()).decode('ascii')
+ except (TypeError, binascii.Error): # can't decode
+ return None
+ try:
+ username, password = auth.split(':', 1)
+ except ValueError: # not enough values to unpack
+ return None
+ return username, password
diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py
index 872d3b920..8cf04bf79 100644
--- a/pyramid/chameleon_text.py
+++ b/pyramid/chameleon_text.py
@@ -1,41 +1,25 @@
-import sys
-
-from zope.deprecation import deprecated
from zope.interface import implementer
-from pyramid.compat import reraise
-
-try:
- from chameleon.zpt.template import PageTextTemplateFile
- # prevent pyflakes complaining about a redefinition below
- PageTextTemplateFile
-except ImportError: # pragma: no cover
- exc_class, exc, tb = sys.exc_info()
- # Chameleon doesn't work on non-CPython platforms
- class PageTextTemplateFile(object):
- def __init__(self, *arg, **kw):
- reraise(ImportError, exc, tb)
+from chameleon.zpt.template import PageTextTemplateFile
from pyramid.interfaces import ITemplateRenderer
from pyramid.decorator import reify
from pyramid import renderers
-from pyramid.path import caller_package
def renderer_factory(info):
return renderers.template_renderer_factory(info, TextTemplateRenderer)
@implementer(ITemplateRenderer)
class TextTemplateRenderer(object):
- def __init__(self, path, lookup):
+ def __init__(self, path, lookup, macro=None):
self.path = path
self.lookup = lookup
+ # text template renderers have no macros, so we ignore the
+ # macro arg
@reify # avoid looking up reload_templates before manager pushed
def template(self):
- if sys.platform.startswith('java'): # pragma: no cover
- raise RuntimeError(
- 'Chameleon templates are not compatible with Jython')
return PageTextTemplateFile(self.path,
auto_reload=self.lookup.auto_reload,
debug=self.lookup.debug,
@@ -52,93 +36,3 @@ class TextTemplateRenderer(object):
result = self.template(**system)
return result
-def get_renderer(path):
- """ Return a callable object which can be used to render a
- :term:`Chameleon` text template using the template implied by the
- ``path`` argument. The ``path`` argument may be a
- package-relative path, an absolute path, or a :term:`asset
- specification`.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.get_renderer` instead.
- """
- package = caller_package()
- factory = renderers.RendererHelper(path, package=package)
- return factory.get_renderer()
-
-deprecated(
- 'get_renderer',
- '(pyramid.chameleon_text.get_renderer is deprecated '
- 'as of Pyramid 1.0; instead use pyramid.renderers.get_renderer)')
-
-def get_template(path):
- """ Return the underyling object representing a :term:`Chameleon`
- text template using the template implied by the ``path`` argument.
- The ``path`` argument may be a package-relative path, an absolute
- path, or a :term:`asset specification`.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- the ``implementation()`` method of a template renderer retrieved via
- :func:`pyramid.renderers.get_renderer` instead.
- """
- package = caller_package()
- factory = renderers.RendererHelper(path, package=package)
- return factory.get_renderer().implementation()
-
-deprecated(
- 'get_template',
- '(pyramid.chameleon_text.get_template is deprecated '
- 'as of Pyramid 1.0; instead use '
- 'pyramid.renderers.get_renderer().implementation())')
-
-def render_template(path, **kw):
- """ Render a :term:`Chameleon` text template using the template
- implied by the ``path`` argument. The ``path`` argument may be a
- package-relative path, an absolute path, or a :term:`asset
- specification`. The arguments in ``*kw`` are passed as top-level
- names to the template, and so may be used within the template
- itself. Returns a string.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.render` instead.
- """
- package = caller_package()
- request = kw.pop('request', None)
- renderer = renderers.RendererHelper(path, package=package)
- return renderer.render(kw, None, request=request)
-
-deprecated(
- 'render_template',
- '(pyramid.chameleon_text.render_template is deprecated '
- 'as of Pyramid 1.0; instead use pyramid.renderers.render)')
-
-def render_template_to_response(path, **kw):
- """ Render a :term:`Chameleon` text template using the template
- implied by the ``path`` argument. The ``path`` argument may be a
- package-relative path, an absolute path, or a :term:`asset
- specification`. The arguments in ``*kw`` are passed as top-level
- names to the template, and so may be used within the template
- itself. Returns a :term:`Response` object with the body as the
- template result.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.render_to_response` instead.
- """
- package = caller_package()
- request = kw.pop('request', None)
- renderer = renderers.RendererHelper(path, package=package)
- return renderer.render_to_response(kw, None, request=request)
-
-deprecated(
- 'render_template_to_response',
- '(pyramid.chameleon_text.render_template_to_response is deprecated '
- 'as of Pyramid 1.0; instead use pyramid.renderers.render_to_response)')
-
diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py
index aa6f89e07..d8a8ee1be 100644
--- a/pyramid/chameleon_zpt.py
+++ b/pyramid/chameleon_zpt.py
@@ -1,24 +1,9 @@
-import sys
-
-from zope.deprecation import deprecated
from zope.interface import implementer
-from pyramid.compat import reraise
-
-try:
- from chameleon.zpt.template import PageTemplateFile
- PageTemplateFile # prevent pyflakes complaining about a redefinition below
-except ImportError: # pragma: no cover
- exc_class, exc, tb = sys.exc_info()
- # Chameleon doesn't work on non-CPython platforms
- class PageTemplateFile(object):
- def __init__(self, *arg, **kw):
- reraise(ImportError, exc, tb)
+from chameleon.zpt.template import PageTemplateFile
from pyramid.interfaces import ITemplateRenderer
-
from pyramid.decorator import reify
-from pyramid.path import caller_package
from pyramid import renderers
def renderer_factory(info):
@@ -26,19 +11,25 @@ def renderer_factory(info):
@implementer(ITemplateRenderer)
class ZPTTemplateRenderer(object):
- def __init__(self, path, lookup):
+ def __init__(self, path, lookup, macro=None):
self.path = path
self.lookup = lookup
+ self.macro = macro
@reify # avoid looking up reload_templates before manager pushed
def template(self):
- if sys.platform.startswith('java'): # pragma: no cover
- raise RuntimeError(
- 'Chameleon templates are not compatible with Jython')
- return PageTemplateFile(self.path,
- auto_reload=self.lookup.auto_reload,
- debug=self.lookup.debug,
- translate=self.lookup.translate)
+ tf = PageTemplateFile(
+ self.path,
+ auto_reload=self.lookup.auto_reload,
+ debug=self.lookup.debug,
+ translate=self.lookup.translate
+ )
+ if self.macro:
+ # render only the portion of the template included in a
+ # define-macro named the value of self.macro
+ macro_renderer = tf.macros[self.macro].include
+ tf._render = macro_renderer
+ return tf
def implementation(self):
return self.template
@@ -51,92 +42,3 @@ class ZPTTemplateRenderer(object):
result = self.template(**system)
return result
-def get_renderer(path):
- """ Return a callable object which can be used to render a
- :term:`Chameleon` ZPT template using the template implied by the
- ``path`` argument. The ``path`` argument may be a
- package-relative path, an absolute path, or a :term:`asset
- specification`.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.get_renderer` instead.
- """
- package = caller_package()
- factory = renderers.RendererHelper(name=path, package=package)
- return factory.get_renderer()
-
-deprecated(
- 'get_renderer',
- '(pyramid.chameleon_zpt.get_renderer is deprecated '
- 'as of Pyramid 1.0; instead use pyramid.renderers.get_renderer)')
-
-def get_template(path):
- """ Return the underyling object representing a :term:`Chameleon`
- ZPT template using the template implied by the ``path`` argument.
- The ``path`` argument may be a package-relative path, an absolute
- path, or a :term:`asset specification`.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- the ``implementation()`` method of a template renderer retrieved via
- :func:`pyramid.renderers.get_renderer` instead.
- """
- package = caller_package()
- factory = renderers.RendererHelper(name=path, package=package)
- return factory.get_renderer().implementation()
-
-deprecated(
- 'get_template',
- '(pyramid.chameleon_zpt.get_template is deprecated '
- 'as of Pyramid 1.0; instead use '
- 'pyramid.renderers.get_renderer().implementation())')
-
-def render_template(path, **kw):
- """ Render a :term:`Chameleon` ZPT template using the template
- implied by the ``path`` argument. The ``path`` argument may be a
- package-relative path, an absolute path, or a :term:`asset
- specification`. The arguments in ``*kw`` are passed as top-level
- names to the template, and so may be used within the template
- itself. Returns a string.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.render` instead.
- """
- package = caller_package()
- request = kw.pop('request', None)
- renderer = renderers.RendererHelper(name=path, package=package)
- return renderer.render(kw, None, request=request)
-
-deprecated(
- 'render_template',
- '(pyramid.chameleon_zpt.render_template is deprecated as of Pyramid 1.0; '
- 'instead use pyramid.renderers.render)')
-
-def render_template_to_response(path, **kw):
- """ Render a :term:`Chameleon` ZPT template using the template
- implied by the ``path`` argument. The ``path`` argument may be a
- package-relative path, an absolute path, or a :term:`asset
- specification`. The arguments in ``*kw`` are passed as top-level
- names to the template, and so may be used within the template
- itself. Returns a :term:`Response` object with the body as the
- template result.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.render_to_response` instead.
- """
- package = caller_package()
- request = kw.pop('request', None)
- renderer = renderers.RendererHelper(name=path, package=package)
- return renderer.render_to_response(kw, None, request=request)
-
-deprecated(
- 'render_template_to_response',
- '(pyramid.chameleon_zpt.render_template_to_response is deprecated; as of '
- 'Pyramid 1.0, instead use pyramid.renderers.render_to_response)')
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 52d7aca83..1dc438597 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -1,9 +1,9 @@
import inspect
+import itertools
import logging
import operator
import os
import sys
-import types
import warnings
import venusian
@@ -12,6 +12,8 @@ from webob.exc import WSGIHTTPException as WebobWSGIHTTPException
from pyramid.interfaces import (
IDebugLogger,
IExceptionResponse,
+ IPredicateList,
+ PHASE1_CONFIG,
)
from pyramid.asset import resolve_asset_spec
@@ -22,7 +24,6 @@ from pyramid.compat import (
text_,
reraise,
string_types,
- PY3,
)
from pyramid.events import ApplicationCreated
@@ -44,6 +45,7 @@ from pyramid.registry import (
Introspectable,
Introspector,
Registry,
+ undefer,
)
from pyramid.router import Router
@@ -71,6 +73,7 @@ from pyramid.config.tweens import TweensConfiguratorMixin
from pyramid.config.util import (
action_method,
ActionInfo,
+ PredicateList,
)
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
@@ -353,6 +356,9 @@ class Configurator(
for name, renderer in DEFAULT_RENDERERS:
self.add_renderer(name, renderer)
+ self.add_default_view_predicates()
+ self.add_default_route_predicates()
+
if exceptionresponse_view is not None:
exceptionresponse_view = self.maybe_dotted(exceptionresponse_view)
self.add_view(exceptionresponse_view, context=IExceptionResponse)
@@ -486,6 +492,32 @@ class Configurator(
_get_introspector, _set_introspector, _del_introspector
)
+ def get_predlist(self, name):
+ predlist = self.registry.queryUtility(IPredicateList, name=name)
+ if predlist is None:
+ predlist = PredicateList()
+ self.registry.registerUtility(predlist, IPredicateList, name=name)
+ return predlist
+
+ def _add_predicate(self, type, name, factory, weighs_more_than=None,
+ weighs_less_than=None):
+ discriminator = ('%s predicate' % type, name)
+ intr = self.introspectable(
+ '%s predicates' % type,
+ discriminator,
+ '%s predicate named %s' % (type, name),
+ '%s predicate' % type)
+ intr['name'] = name
+ intr['factory'] = factory
+ intr['weighs_more_than'] = weighs_more_than
+ intr['weighs_less_than'] = weighs_less_than
+ def register():
+ predlist = self.get_predlist(type)
+ predlist.add(name, factory, weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than)
+ self.action(discriminator, register, introspectables=(intr,),
+ order=PHASE1_CONFIG) # must be registered early
+
@property
def action_info(self):
info = self.info # usually a ZCML action (ParserInfo) if self.info
@@ -547,6 +579,9 @@ class Configurator(
introspectables = ()
if autocommit:
+ # callables can depend on the side effects of resolving a
+ # deferred discriminator
+ undefer(discriminator)
if callable is not None:
callable(*args, **kw)
for introspectable in introspectables:
@@ -776,10 +811,9 @@ class Configurator(
c, action_wrap = c
if action_wrap:
c = action_method(c)
- if PY3: # pragma: no cover
- m = types.MethodType(c, self)
- else:
- m = types.MethodType(c, self, self.__class__)
+ # Create a bound method (works on both Py2 and Py3)
+ # http://stackoverflow.com/a/1015405/209039
+ m = c.__get__(self, self.__class__)
return m
def with_package(self, package):
@@ -1070,73 +1104,96 @@ def resolveConflicts(actions):
other conflicting actions.
"""
- # organize actions by discriminators
- unique = {}
- output = []
- for i, action in enumerate(actions):
- if not isinstance(action, dict):
+ def orderandpos(v):
+ n, v = v
+ if not isinstance(v, dict):
+ # old-style tuple action
+ v = expand_action(*v)
+ return (v['order'] or 0, n)
+
+ sactions = sorted(enumerate(actions), key=orderandpos)
+
+ def orderonly(v):
+ n, v = v
+ if not isinstance(v, dict):
# old-style tuple action
- action = expand_action(*action)
+ v = expand_action(*v)
+ return v['order'] or 0
+ for order, actiongroup in itertools.groupby(sactions, orderonly):
# "order" is an integer grouping. Actions in a lower order will be
- # executed before actions in a higher order. Within an order,
- # actions are executed sequentially based on original action ordering
- # ("i").
- order = action['order'] or 0
- discriminator = action['discriminator']
-
- # "ainfo" is a tuple of (order, i, action) where "order" is a
- # user-supplied grouping, "i" is an integer expressing the relative
- # position of this action in the action list being resolved, and
- # "action" is an action dictionary. The purpose of an ainfo is to
- # associate an "order" and an "i" with a particular action; "order"
- # and "i" exist for sorting purposes after conflict resolution.
- ainfo = (order, i, action)
-
- if discriminator is None:
- # The discriminator is None, so this action can never conflict.
- # We can add it directly to the result.
+ # executed before actions in a higher order. All of the actions in
+ # one grouping will be executed (its callable, if any will be called)
+ # before any of the actions in the next.
+
+ unique = {}
+ output = []
+
+ for i, action in actiongroup:
+ # Within an order, actions are executed sequentially based on
+ # original action ordering ("i").
+
+ if not isinstance(action, dict):
+ # old-style tuple action
+ action = expand_action(*action)
+
+ # "ainfo" is a tuple of (order, i, action) where "order" is a
+ # user-supplied grouping, "i" is an integer expressing the relative
+ # position of this action in the action list being resolved, and
+ # "action" is an action dictionary. The purpose of an ainfo is to
+ # associate an "order" and an "i" with a particular action; "order"
+ # and "i" exist for sorting purposes after conflict resolution.
+ ainfo = (order, i, action)
+
+ discriminator = undefer(action['discriminator'])
+ action['discriminator'] = discriminator
+
+ if discriminator is None:
+ # The discriminator is None, so this action can never conflict.
+ # We can add it directly to the result.
+ output.append(ainfo)
+ continue
+
+ L = unique.setdefault(discriminator, [])
+ L.append(ainfo)
+
+ # Check for conflicts
+ conflicts = {}
+
+ for discriminator, ainfos in unique.items():
+ # We use (includepath, order, i) as a sort key because we need to
+ # sort the actions by the paths so that the shortest path with a
+ # given prefix comes first. The "first" action is the one with the
+ # shortest include path. We break sorting ties using "order", then
+ # "i".
+ def bypath(ainfo):
+ path, order, i = ainfo[2]['includepath'], ainfo[0], ainfo[1]
+ return path, order, i
+
+ ainfos.sort(key=bypath)
+ ainfo, rest = ainfos[0], ainfos[1:]
output.append(ainfo)
- continue
-
- L = unique.setdefault(discriminator, [])
- L.append(ainfo)
-
- # Check for conflicts
- conflicts = {}
-
- for discriminator, ainfos in unique.items():
-
- # We use (includepath, order, i) as a sort key because we need to
- # sort the actions by the paths so that the shortest path with a
- # given prefix comes first. The "first" action is the one with the
- # shortest include path. We break sorting ties using "order", then
- # "i".
- def bypath(ainfo):
- path, order, i = ainfo[2]['includepath'], ainfo[0], ainfo[1]
- return path, order, i
-
- ainfos.sort(key=bypath)
- ainfo, rest = ainfos[0], ainfos[1:]
- output.append(ainfo)
- _, _, action = ainfo
- basepath, baseinfo, discriminator = (action['includepath'],
- action['info'],
- action['discriminator'])
-
- for _, _, action in rest:
- includepath = action['includepath']
- # Test whether path is a prefix of opath
- if (includepath[:len(basepath)] != basepath # not a prefix
- or includepath == basepath):
- L = conflicts.setdefault(discriminator, [baseinfo])
- L.append(action['info'])
-
- if conflicts:
- raise ConfigurationConflictError(conflicts)
-
- # sort conflict-resolved actions by (order, i) and return them
- return [ x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]
+ _, _, action = ainfo
+ basepath, baseinfo, discriminator = (
+ action['includepath'],
+ action['info'],
+ action['discriminator'],
+ )
+
+ for _, _, action in rest:
+ includepath = action['includepath']
+ # Test whether path is a prefix of opath
+ if (includepath[:len(basepath)] != basepath # not a prefix
+ or includepath == basepath):
+ L = conflicts.setdefault(discriminator, [baseinfo])
+ L.append(action['info'])
+
+ if conflicts:
+ raise ConfigurationConflictError(conflicts)
+
+ # sort conflict-resolved actions by (order, i) and yield them one by one
+ for a in [x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]:
+ yield a
def expand_action(discriminator, callable=None, args=(), kw=None,
includepath=(), info=None, order=0, introspectables=()):
diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py
index 5f15f2e46..12c4de660 100644
--- a/pyramid/config/adapters.py
+++ b/pyramid/config/adapters.py
@@ -1,3 +1,5 @@
+from functools import update_wrapper
+
from zope.interface import Interface
from pyramid.interfaces import (
@@ -6,40 +8,116 @@ from pyramid.interfaces import (
IResourceURL,
)
-from pyramid.config.util import action_method
+from pyramid.config.util import (
+ action_method,
+ )
+
class AdaptersConfiguratorMixin(object):
@action_method
- def add_subscriber(self, subscriber, iface=None):
+ def add_subscriber(self, subscriber, iface=None, **predicates):
"""Add an event :term:`subscriber` for the event stream
- implied by the supplied ``iface`` interface. The
- ``subscriber`` argument represents a callable object (or a
- :term:`dotted Python name` which identifies a callable); it
- will be called with a single object ``event`` whenever
- :app:`Pyramid` emits an :term:`event` associated with the
- ``iface``, which may be an :term:`interface` or a class or a
- :term:`dotted Python name` to a global object representing an
- interface or a class. Using the default ``iface`` value,
- ``None`` will cause the subscriber to be registered for all
- event types. See :ref:`events_chapter` for more information
- about events and subscribers."""
+ implied by the supplied ``iface`` interface.
+
+ The ``subscriber`` argument represents a callable object (or a
+ :term:`dotted Python name` which identifies a callable); it will be
+ called with a single object ``event`` whenever :app:`Pyramid` emits
+ an :term:`event` associated with the ``iface``, which may be an
+ :term:`interface` or a class or a :term:`dotted Python name` to a
+ global object representing an interface or a class.
+
+ Using the default ``iface`` value, ``None`` will cause the subscriber
+ to be registered for all event types. See :ref:`events_chapter` for
+ more information about events and subscribers.
+
+ Any number of predicate keyword arguments may be passed in
+ ``**predicates``. Each predicate named will narrow the set of
+ circumstances that the subscriber will be invoked. Each named
+ predicate must have been registered via
+ :meth:`pyramid.config.Configurator.add_subscriber_predicate` before it
+ can be used. See :ref:`subscriber_predicates` for more information.
+
+ .. note::
+
+ THe ``**predicates`` argument is new as of Pyramid 1.4.
+ """
dotted = self.maybe_dotted
subscriber, iface = dotted(subscriber), dotted(iface)
if iface is None:
iface = (Interface,)
if not isinstance(iface, (tuple, list)):
iface = (iface,)
+
def register():
- self.registry.registerHandler(subscriber, iface)
- intr = self.introspectable('subscribers',
- id(subscriber),
- self.object_description(subscriber),
- 'subscriber')
+ predlist = self.get_predlist('subscriber')
+ order, preds, phash = predlist.make(self, **predicates)
+ intr.update({'phash':phash, 'order':order, 'predicates':preds})
+ derived_subscriber = self._derive_subscriber(subscriber, preds)
+ self.registry.registerHandler(derived_subscriber, iface)
+
+ intr = self.introspectable(
+ 'subscribers',
+ id(subscriber),
+ self.object_description(subscriber),
+ 'subscriber'
+ )
+
intr['subscriber'] = subscriber
intr['interfaces'] = iface
+
self.action(None, register, introspectables=(intr,))
return subscriber
+ def _derive_subscriber(self, subscriber, predicates):
+ if not predicates:
+ return subscriber
+ def subscriber_wrapper(*arg):
+ # We need to accept *arg and pass it along because zope
+ # subscribers are designed poorly. Notification will always call
+ # an associated subscriber with all of the objects involved in
+ # the subscription lookup, despite the fact that the event sender
+ # always has the option to attach those objects to the event
+ # object itself (and usually does). It would be much saner if the
+ # registry just used extra args passed to notify to do the lookup
+ # but only called event subscribers with the actual event object,
+ # or if we had been smart enough early on to always wrap
+ # subscribers in something that threw away the extra args, but
+ # c'est la vie.
+ if all((predicate(*arg) for predicate in predicates)):
+ return subscriber(*arg)
+ if hasattr(subscriber, '__name__'):
+ update_wrapper(subscriber_wrapper, subscriber)
+ return subscriber_wrapper
+
+ @action_method
+ def add_subscriber_predicate(self, name, factory, weighs_more_than=None,
+ weighs_less_than=None):
+ """
+ Adds a subscriber predicate factory. The associated subscriber
+ predicate can later be named as a keyword argument to
+ :meth:`pyramid.config.Configurator.add_subscriber` in the
+ ``**predicates`` anonyous keyword argument dictionary.
+
+ ``name`` should be the name of the predicate. It must be a valid
+ Python identifier (it will be used as a ``**predicates`` keyword
+ argument to :meth:`~pyramid.config.Configurator.add_subscriber`).
+
+ ``factory`` should be a :term:`predicate factory`.
+
+ See :ref:`subscriber_predicates` for more information.
+
+ .. note::
+
+ This method is new as of Pyramid 1.4.
+ """
+ self._add_predicate(
+ 'subscriber',
+ name,
+ factory,
+ weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than
+ )
+
@action_method
def add_response_adapter(self, adapter, type_or_iface):
""" When an object of type (or interface) ``type_or_iface`` is
@@ -203,4 +281,3 @@ class AdaptersConfiguratorMixin(object):
intr['resource_iface'] = resource_iface
self.action(discriminator, register, introspectables=(intr,))
-
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index ccbf3bbe9..01b1fb22e 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -1,10 +1,11 @@
+from zope.interface import implementer
+
from pyramid.config.util import action_method
from pyramid.interfaces import (
IDefaultRootFactory,
- INewRequest,
IRequestFactory,
- IRequestProperties,
+ IRequestExtensions,
IRootFactory,
ISessionFactory,
)
@@ -73,9 +74,9 @@ class FactoriesConfiguratorMixin(object):
:class:`pyramid.request.Request` class (particularly
``__call__``, and ``blank``).
- See :meth:`pyramid.config.Configurator.set_request_property`
+ See :meth:`pyramid.config.Configurator.add_request_method`
for a less intrusive way to extend the request objects with
- custom properties.
+ custom methods and properties.
.. note::
@@ -93,55 +94,106 @@ class FactoriesConfiguratorMixin(object):
self.action(IRequestFactory, register, introspectables=(intr,))
@action_method
- def set_request_property(self, callable, name=None, reify=False):
- """ Add a property to the request object.
+ def add_request_method(self,
+ callable=None,
+ name=None,
+ property=False,
+ reify=False):
+ """ Add a property or method to the request object.
+
+ When adding a method to the request, ``callable`` may be any
+ function that receives the request object as the first
+ parameter. If ``name`` is ``None`` then it will be computed
+ from the name of the ``callable``.
- ``callable`` can either be a callable that accepts the request
- as its single positional parameter, or it can be a property
- descriptor. It may also be a :term:`dotted Python name` which
- refers to either a callable or a property descriptor.
+ When adding a property to the request, ``callable`` can either
+ be a callable that accepts the request as its single positional
+ parameter, or it can be a property descriptor. If ``name`` is
+ ``None``, the name of the property will be computed from the
+ name of the ``callable``.
If the ``callable`` is a property descriptor a ``ValueError``
will be raised if ``name`` is ``None`` or ``reify`` is ``True``.
- If ``name`` is None, the name of the property will be computed
- from the name of the ``callable``.
-
See :meth:`pyramid.request.Request.set_property` for more
- information on its usage.
+ details on ``property`` vs ``reify``. When ``reify`` is
+ ``True``, the value of ``property`` is assumed to also be
+ ``True``.
+
+ In all cases, ``callable`` may also be a
+ :term:`dotted Python name` which refers to either a callable or
+ a property descriptor.
+
+ If ``callable`` is ``None`` then the method is only used to
+ assist in conflict detection between different addons requesting
+ the same attribute on the request object.
This is the recommended method for extending the request object
and should be used in favor of providing a custom request
factory via
:meth:`pyramid.config.Configurator.set_request_factory`.
- .. versionadded:: 1.3
+ .. versionadded:: 1.4
"""
- callable = self.maybe_dotted(callable)
+ if callable is not None:
+ callable = self.maybe_dotted(callable)
- name, callable = InstancePropertyMixin._make_property(
- callable, name=name, reify=reify)
+ property = property or reify
+ if property:
+ name, callable = InstancePropertyMixin._make_property(
+ callable, name=name, reify=reify)
+ elif name is None:
+ name = callable.__name__
def register():
- plist = self.registry.queryUtility(IRequestProperties)
-
- if plist is None:
- plist = []
- self.registry.registerUtility(plist, IRequestProperties)
- self.registry.registerHandler(_set_request_properties,
- (INewRequest,))
-
- plist.append((name, callable))
-
- intr = self.introspectable('request properties', name,
- self.object_description(callable),
- 'request property')
- intr['callable'] = callable
- intr['reify'] = reify
- self.action(('request properties', name), register,
- introspectables=(intr,))
-
-def _set_request_properties(event):
- request = event.request
- plist = request.registry.queryUtility(IRequestProperties)
- request._set_properties(plist)
+ exts = self.registry.queryUtility(IRequestExtensions)
+
+ if exts is None:
+ exts = _RequestExtensions()
+ self.registry.registerUtility(exts, IRequestExtensions)
+
+ plist = exts.descriptors if property else exts.methods
+ plist[name] = callable
+
+ if callable is None:
+ self.action(('request extensions', name), None)
+ elif property:
+ intr = self.introspectable('request extensions', name,
+ self.object_description(callable),
+ 'request property')
+ intr['callable'] = callable
+ intr['property'] = True
+ intr['reify'] = reify
+ self.action(('request extensions', name), register,
+ introspectables=(intr,))
+ else:
+ intr = self.introspectable('request extensions', name,
+ self.object_description(callable),
+ 'request method')
+ intr['callable'] = callable
+ intr['property'] = False
+ intr['reify'] = False
+ self.action(('request extensions', name), register,
+ introspectables=(intr,))
+
+ @action_method
+ def set_request_property(self, callable, name=None, reify=False):
+ """ Add a property to the request object.
+
+ .. warning::
+
+ This method has been docs-deprecated as of Pyramid 1.4.
+ :meth:`pyramid.config.Configurator.add_request_method` should be
+ used instead.
+
+ .. versionadded:: 1.3
+ """
+ self.add_request_method(
+ callable, name=name, property=not reify, reify=reify)
+
+@implementer(IRequestExtensions)
+class _RequestExtensions(object):
+ def __init__(self):
+ self.descriptors = {}
+ self.methods = {}
+
diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py
new file mode 100644
index 000000000..e31425899
--- /dev/null
+++ b/pyramid/config/predicates.py
@@ -0,0 +1,289 @@
+import re
+
+from pyramid.exceptions import ConfigurationError
+
+from pyramid.compat import is_nonstr_iter
+
+from pyramid.traversal import (
+ find_interface,
+ traversal_path,
+ resource_path_tuple
+ )
+
+from pyramid.urldispatch import _compile_route
+from pyramid.util import object_description
+from pyramid.session import check_csrf_token
+from pyramid.security import effective_principals
+
+from .util import as_sorted_tuple
+
+class XHRPredicate(object):
+ def __init__(self, val, config):
+ self.val = bool(val)
+
+ def text(self):
+ return 'xhr = %s' % self.val
+
+ phash = text
+
+ def __call__(self, context, request):
+ return bool(request.is_xhr) is self.val
+
+class RequestMethodPredicate(object):
+ def __init__(self, val, config):
+ request_method = as_sorted_tuple(val)
+ if 'GET' in request_method and 'HEAD' not in request_method:
+ # GET implies HEAD too
+ request_method = as_sorted_tuple(request_method + ('HEAD',))
+ self.val = request_method
+
+ def text(self):
+ return 'request_method = %s' % (','.join(self.val))
+
+ phash = text
+
+ def __call__(self, context, request):
+ return request.method in self.val
+
+class PathInfoPredicate(object):
+ def __init__(self, val, config):
+ self.orig = val
+ try:
+ val = re.compile(val)
+ except re.error as why:
+ raise ConfigurationError(why.args[0])
+ self.val = val
+
+ def text(self):
+ return 'path_info = %s' % (self.orig,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val.match(request.upath_info) is not None
+
+class RequestParamPredicate(object):
+ def __init__(self, val, config):
+ val = as_sorted_tuple(val)
+ reqs = []
+ for p in val:
+ k = p
+ v = None
+ if '=' in p:
+ k, v = p.split('=', 1)
+ k, v = k.strip(), v.strip()
+ reqs.append((k, v))
+ self.val = val
+ self.reqs = reqs
+
+ def text(self):
+ return 'request_param %s' % ','.join(
+ ['%s=%s' % (x,y) if y else x for x, y in self.reqs]
+ )
+
+ phash = text
+
+ def __call__(self, context, request):
+ for k, v in self.reqs:
+ actual = request.params.get(k)
+ if actual is None:
+ return False
+ if v is not None and actual != v:
+ return False
+ return True
+
+class HeaderPredicate(object):
+ def __init__(self, val, config):
+ name = val
+ v = None
+ if ':' in name:
+ name, val_str = name.split(':', 1)
+ try:
+ v = re.compile(val_str)
+ except re.error as why:
+ raise ConfigurationError(why.args[0])
+ if v is None:
+ self._text = 'header %s' % (name,)
+ else:
+ self._text = 'header %s=%s' % (name, val_str)
+ self.name = name
+ self.val = v
+
+ def text(self):
+ return self._text
+
+ phash = text
+
+ def __call__(self, context, request):
+ if self.val is None:
+ return self.name in request.headers
+ val = request.headers.get(self.name)
+ if val is None:
+ return False
+ return self.val.match(val) is not None
+
+class AcceptPredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'accept = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val in request.accept
+
+class ContainmentPredicate(object):
+ def __init__(self, val, config):
+ self.val = config.maybe_dotted(val)
+
+ def text(self):
+ return 'containment = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ ctx = getattr(request, 'context', context)
+ return find_interface(ctx, self.val) is not None
+
+class RequestTypePredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'request_type = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val.providedBy(request)
+
+class MatchParamPredicate(object):
+ def __init__(self, val, config):
+ val = as_sorted_tuple(val)
+ self.val = val
+ reqs = [ p.split('=', 1) for p in val ]
+ self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ]
+
+ def text(self):
+ return 'match_param %s' % ','.join(
+ ['%s=%s' % (x,y) for x, y in self.reqs]
+ )
+
+ phash = text
+
+ def __call__(self, context, request):
+ for k, v in self.reqs:
+ if request.matchdict.get(k) != v:
+ return False
+ return True
+
+class CustomPredicate(object):
+ def __init__(self, func, config):
+ self.func = func
+
+ def text(self):
+ return getattr(
+ self.func,
+ '__text__',
+ 'custom predicate: %s' % object_description(self.func)
+ )
+
+ def phash(self):
+ # using hash() here rather than id() is intentional: we
+ # want to allow custom predicates that are part of
+ # frameworks to be able to define custom __hash__
+ # functions for custom predicates, so that the hash output
+ # of predicate instances which are "logically the same"
+ # may compare equal.
+ return 'custom:%r' % hash(self.func)
+
+ def __call__(self, context, request):
+ return self.func(context, request)
+
+
+class TraversePredicate(object):
+ # Can only be used as a *route* "predicate"; it adds 'traverse' to the
+ # matchdict if it's specified in the routing args. This causes the
+ # ResourceTreeTraverser to use the resolved traverse pattern as the
+ # traversal path.
+ def __init__(self, val, config):
+ _, self.tgenerate = _compile_route(val)
+ self.val = val
+
+ def text(self):
+ return 'traverse matchdict pseudo-predicate'
+
+ def phash(self):
+ # This isn't actually a predicate, it's just a infodict modifier that
+ # injects ``traverse`` into the matchdict. As a result, we don't
+ # need to update the hash.
+ return ''
+
+ def __call__(self, context, request):
+ if 'traverse' in context:
+ return True
+ m = context['match']
+ tvalue = self.tgenerate(m) # tvalue will be urlquoted string
+ m['traverse'] = traversal_path(tvalue)
+ # This isn't actually a predicate, it's just a infodict modifier that
+ # injects ``traverse`` into the matchdict. As a result, we just
+ # return True.
+ return True
+
+class CheckCSRFTokenPredicate(object):
+
+ check_csrf_token = staticmethod(check_csrf_token) # testing
+
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'check_csrf = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ val = self.val
+ if val:
+ if val is True:
+ val = 'csrf_token'
+ return self.check_csrf_token(request, val, raises=False)
+ return True
+
+class PhysicalPathPredicate(object):
+ def __init__(self, val, config):
+ if is_nonstr_iter(val):
+ self.val = tuple(val)
+ else:
+ val = tuple(filter(None, val.split('/')))
+ self.val = ('',) + val
+
+ def text(self):
+ return 'physical_path = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return resource_path_tuple(context) == self.val
+
+class EffectivePrincipalsPredicate(object):
+ def __init__(self, val, config):
+ if is_nonstr_iter(val):
+ self.val = set(val)
+ else:
+ self.val = set((val,))
+
+ def text(self):
+ return 'effective_principals = %s' % sorted(list(self.val))
+
+ phash = text
+
+ def __call__(self, context, request):
+ req_principals = effective_principals(request)
+ if is_nonstr_iter(req_principals):
+ rpset = set(req_principals)
+ if self.val.issubset(rpset):
+ return True
+ return False
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
index ea39b6805..7c61d5912 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -8,15 +8,17 @@ from pyramid.interfaces import (
)
from pyramid.exceptions import ConfigurationError
+from pyramid.registry import predvalseq
from pyramid.request import route_request_iface
from pyramid.urldispatch import RoutesMapper
from pyramid.config.util import (
action_method,
- make_predicates,
as_sorted_tuple,
)
+import pyramid.config.predicates
+
class RoutesConfiguratorMixin(object):
@action_method
def add_route(self,
@@ -28,7 +30,7 @@ class RoutesConfiguratorMixin(object):
factory=None,
for_=None,
header=None,
- xhr=False,
+ xhr=None,
accept=None,
path_info=None,
request_method=None,
@@ -44,7 +46,7 @@ class RoutesConfiguratorMixin(object):
path=None,
pregenerator=None,
static=False,
- ):
+ **predicates):
""" Add a :term:`route configuration` to the current
configuration state, as well as possibly a :term:`view
configuration` to be used to specify a :term:`view callable`
@@ -236,6 +238,19 @@ class RoutesConfiguratorMixin(object):
request, this predicate will be true. If this predicate
returns ``False``, route matching continues.
+ effective_principals
+
+ If specified, this value should be a :term:`principal` identifier or
+ a sequence of principal identifiers. If the
+ :func:`pyramid.security.effective_principals` method indicates that
+ every principal named in the argument list is present in the current
+ request, this predicate will return True; otherwise it will return
+ False. For example:
+ ``effective_principals=pyramid.security.Authenticated`` or
+ ``effective_principals=('fred', 'group:admins')``.
+
+ .. versionadded:: 1.4a4
+
custom_predicates
This value should be a sequence of references to custom
@@ -254,6 +269,15 @@ class RoutesConfiguratorMixin(object):
:ref:`custom_route_predicates` for more information about
``info``.
+ predicates
+
+ Pass a key/value pair here to use a third-party predicate
+ registered via
+ :meth:`pyramid.config.Configurator.add_view_predicate`. More than
+ one key/value pair can be used at the same time. See
+ :ref:`view_and_route_predicates` for more information about
+ third-party predicates. This argument is new as of Pyramid 1.4.
+
View-Related Arguments
.. warning::
@@ -351,17 +375,6 @@ class RoutesConfiguratorMixin(object):
if request_method is not None:
request_method = as_sorted_tuple(request_method)
- ignored, predicates, ignored = make_predicates(
- xhr=xhr,
- request_method=request_method,
- path_info=path_info,
- request_param=request_param,
- header=header,
- accept=accept,
- traverse=traverse,
- custom=custom_predicates
- )
-
factory = self.maybe_dotted(factory)
if pattern is None:
pattern = path
@@ -417,8 +430,24 @@ class RoutesConfiguratorMixin(object):
request_iface, IRouteRequest, name=name)
def register_connect():
+ pvals = predicates.copy()
+ pvals.update(
+ dict(
+ xhr=xhr,
+ request_method=request_method,
+ path_info=path_info,
+ request_param=request_param,
+ header=header,
+ accept=accept,
+ traverse=traverse,
+ custom=predvalseq(custom_predicates),
+ )
+ )
+
+ predlist = self.get_predlist('route')
+ _, preds, _ = predlist.make(self, **pvals)
route = mapper.connect(
- name, pattern, factory, predicates=predicates,
+ name, pattern, factory, predicates=preds,
pregenerator=pregenerator, static=static
)
intr['object'] = route
@@ -447,6 +476,48 @@ class RoutesConfiguratorMixin(object):
attr=view_attr,
)
+ @action_method
+ def add_route_predicate(self, name, factory, weighs_more_than=None,
+ weighs_less_than=None):
+ """ Adds a route predicate factory. The view predicate can later be
+ named as a keyword argument to
+ :meth:`pyramid.config.Configurator.add_route`.
+
+ ``name`` should be the name of the predicate. It must be a valid
+ Python identifier (it will be used as a keyword argument to
+ ``add_view``).
+
+ ``factory`` should be a :term:`predicate factory`.
+
+ See :ref:`view_and_route_predicates` for more information.
+
+ .. note::
+
+ This method is new as of Pyramid 1.4.
+ """
+ self._add_predicate(
+ 'route',
+ name,
+ factory,
+ weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than
+ )
+
+ def add_default_route_predicates(self):
+ p = pyramid.config.predicates
+ for (name, factory) in (
+ ('xhr', p.XHRPredicate),
+ ('request_method', p.RequestMethodPredicate),
+ ('path_info', p.PathInfoPredicate),
+ ('request_param', p.RequestParamPredicate),
+ ('header', p.HeaderPredicate),
+ ('accept', p.AcceptPredicate),
+ ('effective_principals', p.EffectivePrincipalsPredicate),
+ ('custom', p.CustomPredicate),
+ ('traverse', p.TraversePredicate),
+ ):
+ self.add_route_predicate(name, factory)
+
def get_routes_mapper(self):
""" Return the :term:`routes mapper` object associated with
this configurator's :term:`registry`."""
diff --git a/pyramid/config/security.py b/pyramid/config/security.py
index e8ef1518d..567999cc4 100644
--- a/pyramid/config/security.py
+++ b/pyramid/config/security.py
@@ -137,3 +137,23 @@ class SecurityConfiguratorMixin(object):
introspectables=(intr, perm_intr,))
+ def add_permission(self, permission_name):
+ """
+ A configurator directive which registers a free-standing
+ permission without associating it with a view callable. This can be
+ used so that the permission shows up in the introspectable data under
+ the ``permissions`` category (permissions mentioned via ``add_view``
+ already end up in there). For example::
+
+ config = Configurator()
+ config.add_permission('view')
+ """
+ intr = self.introspectable(
+ 'permissions',
+ permission_name,
+ permission_name,
+ 'permission'
+ )
+ intr['value'] = permission_name
+ self.action(None, introspectables=(intr,))
+
diff --git a/pyramid/config/testing.py b/pyramid/config/testing.py
index f40cf25a7..abbbffc10 100644
--- a/pyramid/config/testing.py
+++ b/pyramid/config/testing.py
@@ -19,7 +19,8 @@ from pyramid.config.util import action_method
class TestingConfiguratorMixin(object):
# testing API
def testing_securitypolicy(self, userid=None, groupids=(),
- permissive=True):
+ permissive=True, remember_result=None,
+ forget_result=None):
"""Unit/integration testing helper: Registers a pair of faux
:app:`Pyramid` security policies: a :term:`authentication
policy` and a :term:`authorization policy`.
@@ -31,6 +32,24 @@ class TestingConfiguratorMixin(object):
nonpermissive :term:`authorization policy` is registered; this
policy denies all access.
+ ``remember_result``, if provided, should be the result returned by
+ the ``remember`` method of the faux authentication policy. If it is
+ not provided (or it is provided, and is ``None``), the default value
+ ``[]`` (the empty list) will be returned by ``remember``.
+
+ .. note::
+
+ ``remember_result`` is new as of Pyramid 1.4.
+
+ ``forget_result``, if provided, should be the result returned by
+ the ``forget`` method of the faux authentication policy. If it is
+ not provided (or it is provided, and is ``None``), the default value
+ ``[]`` (the empty list) will be returned by ``forget``.
+
+ .. note::
+
+ ``forget_result`` is new as of Pyramid 1.4.
+
The behavior of the registered :term:`authentication policy`
depends on the values provided for the ``userid`` and
``groupids`` argument. The authentication policy will return
@@ -47,9 +66,12 @@ class TestingConfiguratorMixin(object):
:func:`pyramid.security.principals_allowed_by_permission`.
"""
from pyramid.testing import DummySecurityPolicy
- policy = DummySecurityPolicy(userid, groupids, permissive)
+ policy = DummySecurityPolicy(
+ userid, groupids, permissive, remember_result, forget_result
+ )
self.registry.registerUtility(policy, IAuthorizationPolicy)
self.registry.registerUtility(policy, IAuthenticationPolicy)
+ return policy
def testing_resources(self, resources):
"""Unit/integration testing helper: registers a dictionary of
diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py
index 1a83f0de9..1bc6dc95c 100644
--- a/pyramid/config/tweens.py
+++ b/pyramid/config/tweens.py
@@ -16,7 +16,10 @@ from pyramid.tweens import (
EXCVIEW,
)
-from pyramid.config.util import action_method
+from pyramid.config.util import (
+ action_method,
+ TopologicalSorter,
+ )
class TweensConfiguratorMixin(object):
def add_tween(self, tween_factory, under=None, over=None):
@@ -177,119 +180,24 @@ class TweensConfiguratorMixin(object):
introspectables.append(intr)
self.action(discriminator, register, introspectables=introspectables)
-class CyclicDependencyError(Exception):
- def __init__(self, cycles):
- self.cycles = cycles
-
- def __str__(self):
- L = []
- cycles = self.cycles
- for cycle in cycles:
- dependent = cycle
- dependees = cycles[cycle]
- L.append('%r sorts over %r' % (dependent, dependees))
- msg = 'Implicit tween ordering cycle:' + '; '.join(L)
- return msg
-
@implementer(ITweens)
class Tweens(object):
def __init__(self):
+ self.sorter = TopologicalSorter(
+ default_before=None,
+ default_after=INGRESS,
+ first=INGRESS,
+ last=MAIN)
self.explicit = []
- self.names = []
- self.req_over = set()
- self.req_under = set()
- self.factories = {}
- self.order = []
def add_explicit(self, name, factory):
self.explicit.append((name, factory))
def add_implicit(self, name, factory, under=None, over=None):
- self.names.append(name)
- self.factories[name] = factory
- if under is None and over is None:
- under = INGRESS
- if under is not None:
- if not is_nonstr_iter(under):
- under = (under,)
- self.order += [(u, name) for u in under]
- self.req_under.add(name)
- if over is not None:
- if not is_nonstr_iter(over):
- over = (over,)
- self.order += [(name, o) for o in over]
- self.req_over.add(name)
+ self.sorter.add(name, factory, after=under, before=over)
def implicit(self):
- order = [(INGRESS, MAIN)]
- roots = []
- graph = {}
- names = [INGRESS, MAIN]
- names.extend(self.names)
-
- for a, b in self.order:
- order.append((a, b))
-
- def add_node(node):
- if not node in graph:
- roots.append(node)
- graph[node] = [0] # 0 = number of arcs coming into this node
-
- def add_arc(fromnode, tonode):
- graph[fromnode].append(tonode)
- graph[tonode][0] += 1
- if tonode in roots:
- roots.remove(tonode)
-
- for name in names:
- add_node(name)
-
- has_over, has_under = set(), set()
- for a, b in order:
- if a in names and b in names: # deal with missing dependencies
- add_arc(a, b)
- has_over.add(a)
- has_under.add(b)
-
- if not self.req_over.issubset(has_over):
- raise ConfigurationError(
- 'Detected tweens with no satisfied over dependencies: %s'
- % (', '.join(sorted(self.req_over - has_over)))
- )
- if not self.req_under.issubset(has_under):
- raise ConfigurationError(
- 'Detected tweens with no satisfied under dependencies: %s'
- % (', '.join(sorted(self.req_under - has_under)))
- )
-
- sorted_names = []
-
- while roots:
- root = roots.pop(0)
- sorted_names.append(root)
- children = graph[root][1:]
- for child in children:
- arcs = graph[child][0]
- arcs -= 1
- graph[child][0] = arcs
- if arcs == 0:
- roots.insert(0, child)
- del graph[root]
-
- if graph:
- # loop in input
- cycledeps = {}
- for k, v in graph.items():
- cycledeps[k] = v[1:]
- raise CyclicDependencyError(cycledeps)
-
- result = []
-
- for name in sorted_names:
- if name in self.names:
- result.append((name, self.factories[name]))
-
- return result
+ return self.sorter.sorted()
def __call__(self, handler, registry):
if self.explicit:
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index b8d0f2319..1c6e1ca15 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -1,6 +1,7 @@
-import re
import traceback
+from functools import update_wrapper
+
from zope.interface import implementer
from pyramid.interfaces import IActionInfo
@@ -11,11 +12,8 @@ from pyramid.compat import (
)
from pyramid.exceptions import ConfigurationError
-
-from pyramid.traversal import (
- find_interface,
- traversal_path,
- )
+from pyramid.registry import predvalseq
+from pyramid.util import TopologicalSorter
from hashlib import md5
@@ -59,233 +57,11 @@ def action_method(wrapped):
finally:
self._ainfo.pop()
return result
- wrapper.__name__ = wrapped.__name__
- wrapper.__doc__ = wrapped.__doc__
- wrapper.__docobj__ = wrapped # for sphinx
- return wrapper
-
-def make_predicates(xhr=None, request_method=None, path_info=None,
- request_param=None, match_param=None, header=None,
- accept=None, containment=None, request_type=None,
- traverse=None, custom=()):
-
- # PREDICATES
- # ----------
- #
- # Given an argument list, a predicate list is computed.
- # Predicates are added to a predicate list in (presumed)
- # computation expense order. All predicates associated with a
- # view or route must evaluate true for the view or route to
- # "match" during a request. Elsewhere in the code, we evaluate
- # predicates using a generator expression. The fastest predicate
- # should be evaluated first, then the next fastest, and so on, as
- # if one returns false, the remainder of the predicates won't need
- # to be evaluated.
- #
- # While we compute predicates, we also compute a predicate hash
- # (aka phash) that can be used by a caller to identify identical
- # predicate lists.
- #
- # ORDERING
- # --------
- #
- # A "order" is computed for the predicate list. An order is
- # a scoring.
- #
- # Each predicate is associated with a weight value, which is a
- # multiple of 2. The weight of a predicate symbolizes the
- # relative potential "importance" of the predicate to all other
- # predicates. A larger weight indicates greater importance.
- #
- # All weights for a given predicate list are bitwise ORed together
- # to create a "score"; this score is then subtracted from
- # MAX_ORDER and divided by an integer representing the number of
- # predicates+1 to determine the order.
- #
- # The order represents the ordering in which a "multiview" ( a
- # collection of views that share the same context/request/name
- # triad but differ in other ways via predicates) will attempt to
- # call its set of views. Views with lower orders will be tried
- # first. The intent is to a) ensure that views with more
- # predicates are always evaluated before views with fewer
- # predicates and b) to ensure a stable call ordering of views that
- # share the same number of predicates. Views which do not have
- # any predicates get an order of MAX_ORDER, meaning that they will
- # be tried very last.
-
- predicates = []
- weights = []
- h = md5()
-
- if xhr:
- def xhr_predicate(context, request):
- return request.is_xhr
- xhr_predicate.__text__ = "xhr = True"
- weights.append(1 << 1)
- predicates.append(xhr_predicate)
- h.update(bytes_('xhr:%r' % bool(xhr)))
-
- if request_method is not None:
- if not is_nonstr_iter(request_method):
- request_method = (request_method,)
- request_method = sorted(request_method)
- def request_method_predicate(context, request):
- return request.method in request_method
- text = "request method = %r" % request_method
- request_method_predicate.__text__ = text
- weights.append(1 << 2)
- predicates.append(request_method_predicate)
- for m in request_method:
- h.update(bytes_('request_method:%r' % m))
-
- if path_info is not None:
- try:
- path_info_val = re.compile(path_info)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- def path_info_predicate(context, request):
- return path_info_val.match(request.upath_info) is not None
- text = "path_info = %s"
- path_info_predicate.__text__ = text % path_info
- weights.append(1 << 3)
- predicates.append(path_info_predicate)
- h.update(bytes_('path_info:%r' % path_info))
- if request_param is not None:
- request_param_val = None
- if '=' in request_param:
- request_param, request_param_val = request_param.split('=', 1)
- if request_param_val is None:
- text = "request_param %s" % request_param
- else:
- text = "request_param %s = %s" % (request_param, request_param_val)
- def request_param_predicate(context, request):
- if request_param_val is None:
- return request_param in request.params
- return request.params.get(request_param) == request_param_val
- request_param_predicate.__text__ = text
- weights.append(1 << 4)
- predicates.append(request_param_predicate)
- h.update(
- bytes_('request_param:%r=%r' % (request_param, request_param_val)))
-
- if header is not None:
- header_name = header
- header_val = None
- if ':' in header:
- header_name, header_val = header.split(':', 1)
- try:
- header_val = re.compile(header_val)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- if header_val is None:
- text = "header %s" % header_name
- else:
- text = "header %s = %s" % (header_name, header_val)
- def header_predicate(context, request):
- if header_val is None:
- return header_name in request.headers
- val = request.headers.get(header_name)
- if val is None:
- return False
- return header_val.match(val) is not None
- header_predicate.__text__ = text
- weights.append(1 << 5)
- predicates.append(header_predicate)
- h.update(bytes_('header:%r=%r' % (header_name, header_val)))
-
- if accept is not None:
- def accept_predicate(context, request):
- return accept in request.accept
- accept_predicate.__text__ = "accept = %s" % accept
- weights.append(1 << 6)
- predicates.append(accept_predicate)
- h.update(bytes_('accept:%r' % accept))
-
- if containment is not None:
- def containment_predicate(context, request):
- ctx = getattr(request, 'context', context)
- return find_interface(ctx, containment) is not None
- containment_predicate.__text__ = "containment = %s" % containment
- weights.append(1 << 7)
- predicates.append(containment_predicate)
- h.update(bytes_('containment:%r' % hash(containment)))
-
- if request_type is not None:
- def request_type_predicate(context, request):
- return request_type.providedBy(request)
- text = "request_type = %s"
- request_type_predicate.__text__ = text % request_type
- weights.append(1 << 8)
- predicates.append(request_type_predicate)
- h.update(bytes_('request_type:%r' % hash(request_type)))
-
- if match_param is not None:
- if not is_nonstr_iter(match_param):
- match_param = (match_param,)
- match_param = sorted(match_param)
- text = "match_param %s" % repr(match_param)
- reqs = [p.split('=', 1) for p in match_param]
- def match_param_predicate(context, request):
- for k, v in reqs:
- if request.matchdict.get(k) != v:
- return False
- return True
- match_param_predicate.__text__ = text
- weights.append(1 << 9)
- predicates.append(match_param_predicate)
- for p in match_param:
- h.update(bytes_('match_param:%r' % p))
-
- if custom:
- for num, predicate in enumerate(custom):
- if getattr(predicate, '__text__', None) is None:
- text = '<unknown custom predicate>'
- try:
- predicate.__text__ = text
- except AttributeError:
- # if this happens the predicate is probably a classmethod
- if hasattr(predicate, '__func__'):
- predicate.__func__.__text__ = text
- else: # pragma: no cover ; 2.5 doesn't have __func__
- predicate.im_func.__text__ = text
- predicates.append(predicate)
- # using hash() here rather than id() is intentional: we
- # want to allow custom predicates that are part of
- # frameworks to be able to define custom __hash__
- # functions for custom predicates, so that the hash output
- # of predicate instances which are "logically the same"
- # may compare equal.
- h.update(bytes_('custom%s:%r' % (num, hash(predicate))))
- weights.append(1 << 10)
-
- if traverse is not None:
- # ``traverse`` can only be used as a *route* "predicate"; it
- # adds 'traverse' to the matchdict if it's specified in the
- # routing args. This causes the ResourceTreeTraverser to use
- # the resolved traverse pattern as the traversal path.
- from pyramid.urldispatch import _compile_route
- _, tgenerate = _compile_route(traverse)
- def traverse_predicate(context, request):
- if 'traverse' in context:
- return True
- m = context['match']
- tvalue = tgenerate(m) # tvalue will be urlquoted string
- m['traverse'] = traversal_path(tvalue) # will be seq of unicode
- return True
- # This isn't actually a predicate, it's just a infodict
- # modifier that injects ``traverse`` into the matchdict. As a
- # result, the ``traverse_predicate`` function above always
- # returns True, and we don't need to update the hash or attach
- # a weight to it
- predicates.append(traverse_predicate)
-
- score = 0
- for bit in weights:
- score = score | bit
- order = (MAX_ORDER - score) / (len(predicates) + 1)
- phash = h.hexdigest()
- return order, predicates, phash
+ if hasattr(wrapped, '__name__'):
+ update_wrapper(wrapper, wrapped)
+ wrapper.__docobj__ = wrapped
+ return wrapper
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
@@ -293,3 +69,83 @@ def as_sorted_tuple(val):
val = tuple(sorted(val))
return val
+# under = after
+# over = before
+
+class PredicateList(object):
+
+ def __init__(self):
+ self.sorter = TopologicalSorter()
+ self.last_added = None
+
+ def add(self, name, factory, weighs_more_than=None, weighs_less_than=None):
+ # Predicates should be added to a predicate list in (presumed)
+ # computation expense order.
+ ## if weighs_more_than is None and weighs_less_than is None:
+ ## weighs_more_than = self.last_added or FIRST
+ ## weighs_less_than = LAST
+ self.last_added = name
+ self.sorter.add(name, factory, after=weighs_more_than,
+ before=weighs_less_than)
+
+ def make(self, config, **kw):
+ # Given a configurator and a list of keywords, a predicate list is
+ # computed. Elsewhere in the code, we evaluate predicates using a
+ # generator expression. All predicates associated with a view or
+ # route must evaluate true for the view or route to "match" during a
+ # request. The fastest predicate should be evaluated first, then the
+ # next fastest, and so on, as if one returns false, the remainder of
+ # the predicates won't need to be evaluated.
+ #
+ # While we compute predicates, we also compute a predicate hash (aka
+ # phash) that can be used by a caller to identify identical predicate
+ # lists.
+ ordered = self.sorter.sorted()
+ phash = md5()
+ weights = []
+ preds = []
+ for n, (name, predicate_factory) in enumerate(ordered):
+ vals = kw.pop(name, None)
+ if vals is None: # XXX should this be a sentinel other than None?
+ continue
+ if not isinstance(vals, predvalseq):
+ vals = (vals,)
+ for val in vals:
+ pred = predicate_factory(val, config)
+ hashes = pred.phash()
+ if not is_nonstr_iter(hashes):
+ hashes = [hashes]
+ for h in hashes:
+ phash.update(bytes_(h))
+ weights.append(1 << n+1)
+ preds.append(pred)
+ if kw:
+ raise ConfigurationError('Unknown predicate values: %r' % (kw,))
+ # A "order" is computed for the predicate list. An order is
+ # a scoring.
+ #
+ # Each predicate is associated with a weight value. The weight of a
+ # predicate symbolizes the relative potential "importance" of the
+ # predicate to all other predicates. A larger weight indicates
+ # greater importance.
+ #
+ # All weights for a given predicate list are bitwise ORed together
+ # to create a "score"; this score is then subtracted from
+ # MAX_ORDER and divided by an integer representing the number of
+ # predicates+1 to determine the order.
+ #
+ # For views, the order represents the ordering in which a "multiview"
+ # ( a collection of views that share the same context/request/name
+ # triad but differ in other ways via predicates) will attempt to call
+ # its set of views. Views with lower orders will be tried first.
+ # The intent is to a) ensure that views with more predicates are
+ # always evaluated before views with fewer predicates and b) to
+ # ensure a stable call ordering of views that share the same number
+ # of predicates. Views which do not have any predicates get an order
+ # of MAX_ORDER, meaning that they will be tried very last.
+ score = 0
+ for bit in weights:
+ score = score | bit
+ order = (MAX_ORDER - score) / (len(preds) + 1)
+ return order, preds, phash.hexdigest()
+
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index e8169e958..02d5b52bf 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -54,6 +54,11 @@ from pyramid.httpexceptions import (
HTTPNotFound,
)
+from pyramid.registry import (
+ predvalseq,
+ Deferred,
+ )
+
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
@@ -63,14 +68,16 @@ from pyramid.view import (
AppendSlashNotFoundViewFactory,
)
-from pyramid.util import object_description
+from pyramid.util import (
+ object_description,
+ )
+
+import pyramid.config.predicates
from pyramid.config.util import (
DEFAULT_PHASH,
MAX_ORDER,
action_method,
- as_sorted_tuple,
- make_predicates,
)
urljoin = urlparse.urljoin
@@ -272,20 +279,22 @@ class ViewDeriver(object):
@wraps_view
def predicated_view(self, view):
- predicates = self.kw.get('predicates', ())
- if not predicates:
+ preds = self.kw.get('predicates', ())
+ if not preds:
return view
def predicate_wrapper(context, request):
- if all((predicate(context, request) for predicate in predicates)):
- return view(context, request)
- view_name = getattr(view, '__name__', view)
- raise PredicateMismatch(
- 'predicate mismatch for view %s' % view_name)
+ for predicate in preds:
+ if not predicate(context, request):
+ view_name = getattr(view, '__name__', view)
+ raise PredicateMismatch(
+ 'predicate mismatch for view %s (%s)' % (
+ view_name, predicate.text()))
+ return view(context, request)
def checker(context, request):
return all((predicate(context, request) for predicate in
- predicates))
+ preds))
predicate_wrapper.__predicated__ = checker
- predicate_wrapper.__predicates__ = predicates
+ predicate_wrapper.__predicates__ = preds
return predicate_wrapper
@wraps_view
@@ -564,7 +573,7 @@ class MultiView(object):
return
else:
subset.append((order, view, phash))
- subset.sort()
+ subset.sort(key=operator.itemgetter(0))
accepts = set(self.accepts)
accepts.add(accept)
self.accepts = list(accepts) # dedupe
@@ -629,13 +638,32 @@ def viewdefaults(wrapped):
class ViewsConfiguratorMixin(object):
@viewdefaults
@action_method
- def add_view(self, view=None, name="", for_=None, permission=None,
- request_type=None, route_name=None, request_method=None,
- request_param=None, containment=None, attr=None,
- renderer=None, wrapper=None, xhr=False, accept=None,
- header=None, path_info=None, custom_predicates=(),
- context=None, decorator=None, mapper=None, http_cache=None,
- match_param=None):
+ def add_view(
+ self,
+ view=None,
+ name="",
+ for_=None,
+ permission=None,
+ request_type=None,
+ route_name=None,
+ request_method=None,
+ request_param=None,
+ containment=None,
+ attr=None,
+ renderer=None,
+ wrapper=None,
+ xhr=None,
+ accept=None,
+ header=None,
+ path_info=None,
+ custom_predicates=(),
+ context=None,
+ decorator=None,
+ mapper=None,
+ http_cache=None,
+ match_param=None,
+ check_csrf=None,
+ **predicates):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
down below into *predicate* arguments and *non-predicate*
@@ -658,24 +686,27 @@ class ViewsConfiguratorMixin(object):
permission
- The name of a :term:`permission` that the user must possess
- in order to invoke the :term:`view callable`. See
- :ref:`view_security_section` for more information about view
- security and permissions. If ``permission`` is omitted, a
- *default* permission may be used for this view registration
- if one was named as the
+ A :term:`permission` that the user must possess in order to invoke
+ the :term:`view callable`. See :ref:`view_security_section` for
+ more information about view security and permissions. This is
+ often a string like ``view`` or ``edit``.
+
+ If ``permission`` is omitted, a *default* permission may be used
+ for this view registration if one was named as the
:class:`pyramid.config.Configurator` constructor's
``default_permission`` argument, or if
- :meth:`pyramid.config.Configurator.set_default_permission`
- was used prior to this view registration. Pass the string
- :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the
- permission argument to explicitly indicate that the view should
- always be executable by entirely anonymous users, regardless of
- the default permission, bypassing any :term:`authorization
- policy` that may be in effect.
+ :meth:`pyramid.config.Configurator.set_default_permission` was used
+ prior to this view registration. Pass the value
+ :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the permission
+ argument to explicitly indicate that the view should always be
+ executable by entirely anonymous users, regardless of the default
+ permission, bypassing any :term:`authorization policy` that may be
+ in effect.
attr
+ This knob is most useful when the view definition is a class.
+
The view machinery defaults to using the ``__call__`` method
of the :term:`view callable` (or the function itself, if the
view callable is a function) to obtain a response. The
@@ -684,8 +715,7 @@ class ViewsConfiguratorMixin(object):
class, and the class has a method named ``index`` and you
wanted to use this method instead of the class' ``__call__``
method to return the response, you'd say ``attr="index"`` in the
- view configuration for the view. This is
- most useful when the view definition is a class.
+ view configuration for the view.
renderer
@@ -896,11 +926,12 @@ class ViewsConfiguratorMixin(object):
request_param
- This value can be any string. A view declaration with this
- argument ensures that the view will only be called when the
- :term:`request` has a key in the ``request.params``
+ This value can be any string or any sequence of strings. A view
+ declaration with this argument ensures that the view will only be
+ called when the :term:`request` has a key in the ``request.params``
dictionary (an HTTP ``GET`` or ``POST`` variable) that has a
- name which matches the supplied value. If the value
+ name which matches the supplied value (if the value is a string)
+ or values (if the value is a tuple). If any value
supplied has a ``=`` sign in it,
e.g. ``request_param="foo=123"``, then the key (``foo``)
must both exist in the ``request.params`` dictionary, *and*
@@ -982,6 +1013,58 @@ class ViewsConfiguratorMixin(object):
variable. If the regex matches, this predicate will be
``True``.
+ check_csrf
+
+ If specified, this value should be one of ``None``, ``True``,
+ ``False``, or a string representing the 'check name'. If the value
+ is ``True`` or a string, CSRF checking will be performed. If the
+ value is ``False`` or ``None``, CSRF checking will not be performed.
+
+ If the value provided is a string, that string will be used as the
+ 'check name'. If the value provided is ``True``, ``csrf_token`` will
+ be used as the check name.
+
+ If CSRF checking is performed, the checked value will be the value
+ of ``request.params[check_name]``. This value will be compared
+ against the value of ``request.session.get_csrf_token()``, and the
+ check will pass if these two values are the same. If the check
+ passes, the associated view will be permitted to execute. If the
+ check fails, the associated view will not be permitted to execute.
+
+ Note that using this feature requires a :term:`session factory` to
+ have been configured.
+
+ .. versionadded:: 1.4a2
+
+ physical_path
+
+ If specified, this value should be a string or a tuple representing
+ the :term:`physical path` of the context found via traversal for this
+ predicate to match as true. For example: ``physical_path='/'`` or
+ ``physical_path='/a/b/c'`` or ``physical_path=('', 'a', 'b', 'c')``.
+ This is not a path prefix match or a regex, it's a whole-path match.
+ It's useful when you want to always potentially show a view when some
+ object is traversed to, but you can't be sure about what kind of
+ object it will be, so you can't use the ``context`` predicate. The
+ individual path elements inbetween slash characters or in tuple
+ elements should be the Unicode representation of the name of the
+ resource and should not be encoded in any way.
+
+ .. versionadded:: 1.4a3
+
+ effective_principals
+
+ If specified, this value should be a :term:`principal` identifier or
+ a sequence of principal identifiers. If the
+ :func:`pyramid.security.effective_principals` method indicates that
+ every principal named in the argument list is present in the current
+ request, this predicate will return True; otherwise it will return
+ False. For example:
+ ``effective_principals=pyramid.security.Authenticated`` or
+ ``effective_principals=('fred', 'group:admins')``.
+
+ .. versionadded:: 1.4a4
+
custom_predicates
This value should be a sequence of references to custom
@@ -991,9 +1074,18 @@ class ViewsConfiguratorMixin(object):
Each custom predicate callable should accept two arguments:
``context`` and ``request`` and should return either
``True`` or ``False`` after doing arbitrary evaluation of
- the context and/or the request. If all callables return
- ``True``, the associated view callable will be considered
- viable for a given request.
+ the context and/or the request.
+
+ predicates
+
+ Pass a key/value pair here to use a third-party predicate
+ registered via
+ :meth:`pyramid.config.Configurator.add_view_predicate`. More than
+ one key/value pair can be used at the same time. See
+ :ref:`view_and_route_predicates` for more information about
+ third-party predicates.
+
+ .. versionadded: 1.4a1
"""
view = self.maybe_dotted(view)
@@ -1028,18 +1120,6 @@ class ViewsConfiguratorMixin(object):
raise ConfigurationError(
'request_type must be an interface, not %s' % request_type)
- if request_method is not None:
- request_method = as_sorted_tuple(request_method)
- if 'GET' in request_method and 'HEAD' not in request_method:
- # GET implies HEAD too
- request_method = as_sorted_tuple(request_method + ('HEAD',))
-
- order, predicates, phash = make_predicates(xhr=xhr,
- request_method=request_method, path_info=path_info,
- request_param=request_param, header=header, accept=accept,
- containment=containment, request_type=request_type,
- match_param=match_param, custom=custom_predicates)
-
if context is None:
context = for_
@@ -1054,18 +1134,43 @@ class ViewsConfiguratorMixin(object):
name=renderer, package=self.package,
registry = self.registry)
+ if accept is not None:
+ accept = accept.lower()
+
introspectables = []
- discriminator = [
- 'view', context, name, request_type, IView, containment,
- request_param, request_method, route_name, attr,
- xhr, accept, header, path_info, match_param]
- discriminator.extend(sorted([hash(x) for x in custom_predicates]))
- discriminator = tuple(discriminator)
+ pvals = predicates.copy()
+ pvals.update(
+ dict(
+ xhr=xhr,
+ request_method=request_method,
+ path_info=path_info,
+ request_param=request_param,
+ header=header,
+ accept=accept,
+ containment=containment,
+ request_type=request_type,
+ match_param=match_param,
+ check_csrf=check_csrf,
+ custom=predvalseq(custom_predicates),
+ )
+ )
+
+ def discrim_func():
+ # We need to defer the discriminator until we know what the phash
+ # is. It can't be computed any sooner because thirdparty
+ # predicates may not yet exist when add_view is called.
+ order, preds, phash = predlist.make(self, **pvals)
+ view_intr.update({'phash':phash, 'order':order, 'predicates':preds})
+ return ('view', context, name, route_name, phash)
+
+ discriminator = Deferred(discrim_func)
+
if inspect.isclass(view) and attr:
view_desc = 'method %r of %s' % (
attr, self.object_description(view))
else:
view_desc = self.object_description(view)
+
view_intr = self.introspectable('views',
discriminator,
view_desc,
@@ -1083,14 +1188,21 @@ class ViewsConfiguratorMixin(object):
header=header,
path_info=path_info,
match_param=match_param,
+ check_csrf=check_csrf,
callable=view,
mapper=mapper,
decorator=decorator,
)
)
+ view_intr.update(**predicates)
introspectables.append(view_intr)
+ predlist = self.get_predlist('view')
def register(permission=permission, renderer=renderer):
+ # the discrim_func above is guaranteed to have been called already
+ order = view_intr['order']
+ preds = view_intr['predicates']
+ phash = view_intr['phash']
request_iface = IRequest
if route_name is not None:
request_iface = self.registry.queryUtility(IRouteRequest,
@@ -1115,21 +1227,28 @@ class ViewsConfiguratorMixin(object):
# (reg'd in phase 1)
permission = self.registry.queryUtility(IDefaultPermission)
+ # added by discrim_func above during conflict resolving
+ preds = view_intr['predicates']
+ order = view_intr['order']
+ phash = view_intr['phash']
+
# __no_permission_required__ handled by _secure_view
- deriver = ViewDeriver(registry=self.registry,
- permission=permission,
- predicates=predicates,
- attr=attr,
- renderer=renderer,
- wrapper_viewname=wrapper,
- viewname=name,
- accept=accept,
- order=order,
- phash=phash,
- package=self.package,
- mapper=mapper,
- decorator=decorator,
- http_cache=http_cache)
+ deriver = ViewDeriver(
+ registry=self.registry,
+ permission=permission,
+ predicates=preds,
+ attr=attr,
+ renderer=renderer,
+ wrapper_viewname=wrapper,
+ viewname=name,
+ accept=accept,
+ order=order,
+ phash=phash,
+ package=self.package,
+ mapper=mapper,
+ decorator=decorator,
+ http_cache=http_cache,
+ )
derived_view = deriver(view)
derived_view.__discriminator__ = lambda *arg: discriminator
# __discriminator__ is used by superdynamic systems
@@ -1234,19 +1353,25 @@ class ViewsConfiguratorMixin(object):
IMultiView, name=name)
if mapper:
- mapper_intr = self.introspectable('view mappers',
- discriminator,
- 'view mapper for %s' % view_desc,
- 'view mapper')
+ mapper_intr = self.introspectable(
+ 'view mappers',
+ discriminator,
+ 'view mapper for %s' % view_desc,
+ 'view mapper'
+ )
mapper_intr['mapper'] = mapper
mapper_intr.relate('views', discriminator)
introspectables.append(mapper_intr)
if route_name:
view_intr.relate('routes', route_name) # see add_route
if renderer is not None and renderer.name and '.' in renderer.name:
- # it's a template
- tmpl_intr = self.introspectable('templates', discriminator,
- renderer.name, 'template')
+ # the renderer is a template
+ tmpl_intr = self.introspectable(
+ 'templates',
+ discriminator,
+ renderer.name,
+ 'template'
+ )
tmpl_intr.relate('views', discriminator)
tmpl_intr['name'] = renderer.name
tmpl_intr['type'] = renderer.type
@@ -1254,13 +1379,65 @@ class ViewsConfiguratorMixin(object):
tmpl_intr.relate('renderer factories', renderer.type)
introspectables.append(tmpl_intr)
if permission is not None:
- perm_intr = self.introspectable('permissions', permission,
- permission, 'permission')
+ # if a permission exists, register a permission introspectable
+ perm_intr = self.introspectable(
+ 'permissions',
+ permission,
+ permission,
+ 'permission'
+ )
perm_intr['value'] = permission
perm_intr.relate('views', discriminator)
introspectables.append(perm_intr)
self.action(discriminator, register, introspectables=introspectables)
+ @action_method
+ def add_view_predicate(self, name, factory, weighs_more_than=None,
+ weighs_less_than=None):
+ """ Adds a view predicate factory. The associated view predicate can
+ later be named as a keyword argument to
+ :meth:`pyramid.config.Configurator.add_view` in the
+ ``predicates`` anonyous keyword argument dictionary.
+
+ ``name`` should be the name of the predicate. It must be a valid
+ Python identifier (it will be used as a keyword argument to
+ ``add_view`` by others).
+
+ ``factory`` should be a :term:`predicate factory`.
+
+ See :ref:`view_and_route_predicates` for more information.
+
+ .. note::
+
+ This method is new as of Pyramid 1.4.
+ """
+ self._add_predicate(
+ 'view',
+ name,
+ factory,
+ weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than
+ )
+
+ def add_default_view_predicates(self):
+ p = pyramid.config.predicates
+ for (name, factory) in (
+ ('xhr', p.XHRPredicate),
+ ('request_method', p.RequestMethodPredicate),
+ ('path_info', p.PathInfoPredicate),
+ ('request_param', p.RequestParamPredicate),
+ ('header', p.HeaderPredicate),
+ ('accept', p.AcceptPredicate),
+ ('containment', p.ContainmentPredicate),
+ ('request_type', p.RequestTypePredicate),
+ ('match_param', p.MatchParamPredicate),
+ ('check_csrf', p.CheckCSRFTokenPredicate),
+ ('physical_path', p.PhysicalPathPredicate),
+ ('effective_principals', p.EffectivePrincipalsPredicate),
+ ('custom', p.CustomPredicate),
+ ):
+ self.add_view_predicate(name, factory)
+
def derive_view(self, view, attr=None, renderer=None):
"""
Create a :term:`view callable` using the function, instance,
@@ -1378,11 +1555,26 @@ class ViewsConfiguratorMixin(object):
@action_method
def add_forbidden_view(
- self, view=None, attr=None, renderer=None, wrapper=None,
- route_name=None, request_type=None, request_method=None,
- request_param=None, containment=None, xhr=None, accept=None,
- header=None, path_info=None, custom_predicates=(), decorator=None,
- mapper=None, match_param=None):
+ self,
+ view=None,
+ attr=None,
+ renderer=None,
+ wrapper=None,
+ route_name=None,
+ request_type=None,
+ request_method=None,
+ request_param=None,
+ containment=None,
+ xhr=None,
+ accept=None,
+ header=None,
+ path_info=None,
+ custom_predicates=(),
+ decorator=None,
+ mapper=None,
+ match_param=None,
+ **predicates
+ ):
""" Add a forbidden view to the current configuration state. The
view will be called when Pyramid or application code raises a
:exc:`pyramid.httpexceptions.HTTPForbidden` exception and the set of
@@ -1399,12 +1591,23 @@ class ViewsConfiguratorMixin(object):
All arguments have the same meaning as
:meth:`pyramid.config.Configurator.add_view` and each predicate
argument restricts the set of circumstances under which this notfound
- view will be invoked.
+ view will be invoked. Unlike
+ :meth:`pyramid.config.Configurator.add_view`, this method will raise
+ an exception if passed ``name``, ``permission``, ``context``,
+ ``for_``, or ``http_cache`` keyword arguments. These argument values
+ make no sense in the context of a forbidden view.
.. note::
This method is new as of Pyramid 1.3.
"""
+ for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
+ if arg in predicates:
+ raise ConfigurationError(
+ '%s may not be used as an argument to add_forbidden_view'
+ % arg
+ )
+
settings = dict(
view=view,
context=HTTPForbidden,
@@ -1426,17 +1629,34 @@ class ViewsConfiguratorMixin(object):
attr=attr,
renderer=renderer,
)
+ settings.update(predicates)
return self.add_view(**settings)
set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias
@action_method
def add_notfound_view(
- self, view=None, attr=None, renderer=None, wrapper=None,
- route_name=None, request_type=None, request_method=None,
- request_param=None, containment=None, xhr=None, accept=None,
- header=None, path_info=None, custom_predicates=(), decorator=None,
- mapper=None, match_param=None, append_slash=False):
+ self,
+ view=None,
+ attr=None,
+ renderer=None,
+ wrapper=None,
+ route_name=None,
+ request_type=None,
+ request_method=None,
+ request_param=None,
+ containment=None,
+ xhr=None,
+ accept=None,
+ header=None,
+ path_info=None,
+ custom_predicates=(),
+ decorator=None,
+ mapper=None,
+ match_param=None,
+ append_slash=False,
+ **predicates
+ ):
""" Add a default notfound view to the current configuration state.
The view will be called when Pyramid or application code raises an
:exc:`pyramid.httpexceptions.HTTPForbidden` exception (e.g. when a
@@ -1452,7 +1672,11 @@ class ViewsConfiguratorMixin(object):
All arguments except ``append_slash`` have the same meaning as
:meth:`pyramid.config.Configurator.add_view` and each predicate
argument restricts the set of circumstances under which this notfound
- view will be invoked.
+ view will be invoked. Unlike
+ :meth:`pyramid.config.Configurator.add_view`, this method will raise
+ an exception if passed ``name``, ``permission``, ``context``,
+ ``for_``, or ``http_cache`` keyword arguments. These argument values
+ make no sense in the context of a notfound view.
If ``append_slash`` is ``True``, when this notfound view is invoked,
and the current path info does not end in a slash, the notfound logic
@@ -1466,6 +1690,13 @@ class ViewsConfiguratorMixin(object):
This method is new as of Pyramid 1.3.
"""
+ for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
+ if arg in predicates:
+ raise ConfigurationError(
+ '%s may not be used as an argument to add_notfound_view'
+ % arg
+ )
+
settings = dict(
view=view,
context=HTTPNotFound,
@@ -1485,6 +1716,7 @@ class ViewsConfiguratorMixin(object):
route_name=route_name,
permission=NO_PERMISSION_REQUIRED,
)
+ settings.update(predicates)
if append_slash:
view = self._derive_view(view, attr=attr, renderer=renderer)
view = AppendSlashNotFoundViewFactory(view)
diff --git a/pyramid/configuration.py b/pyramid/configuration.py
deleted file mode 100644
index 802c10d1f..000000000
--- a/pyramid/configuration.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from pyramid.config import Configurator as BaseConfigurator
-from pyramid.exceptions import ConfigurationError # API
-from pyramid.config import DEFAULT_RENDERERS
-from pyramid.path import caller_package
-
-from zope.deprecation import deprecated
-
-ConfigurationError = ConfigurationError # pyflakes
-
-deprecated(
- 'ConfigurationError',
- 'pyramid.configuration.ConfigurationError is deprecated as of '
- 'Pyramid 1.0. Use ``pyramid.config.ConfigurationError`` instead.')
-
-class Configurator(BaseConfigurator):
- def __init__(self,
- registry=None,
- package=None,
- settings=None,
- root_factory=None,
- authentication_policy=None,
- authorization_policy=None,
- renderers=DEFAULT_RENDERERS,
- debug_logger=None,
- locale_negotiator=None,
- request_factory=None,
- renderer_globals_factory=None,
- default_permission=None,
- session_factory=None,
- autocommit=True,
- route_prefix=None,
- introspection=True,
- ):
- if package is None:
- package = caller_package()
- BaseConfigurator.__init__(
- self,
- registry=registry,
- package=package,
- settings=settings,
- root_factory=root_factory,
- authentication_policy=authentication_policy,
- authorization_policy=authorization_policy,
- renderers=renderers,
- debug_logger=debug_logger,
- locale_negotiator=locale_negotiator,
- request_factory=request_factory,
- renderer_globals_factory=renderer_globals_factory,
- default_permission=default_permission,
- session_factory=session_factory,
- autocommit=autocommit,
- route_prefix=route_prefix,
- introspection=introspection,
- )
-
-deprecated(
- 'Configurator',
- 'pyramid.configuration.Configurator is deprecated as of Pyramid 1.0. Use'
- '``pyramid.config.Configurator`` with ``autocommit=True`` instead.')
-
diff --git a/pyramid/decorator.py b/pyramid/decorator.py
index 98d7b36b5..82d2b1280 100644
--- a/pyramid/decorator.py
+++ b/pyramid/decorator.py
@@ -1,9 +1,31 @@
class reify(object):
+ """ Use as a class method decorator. It operates almost exactly like the
+ Python ``@property`` decorator, but it puts the result of the method it
+ decorates into the instance dict after the first call, effectively
+ replacing the function it decorates with an instance variable. It is, in
+ Python parlance, a non-data descriptor. An example:
- """ Put the result of a method which uses this (non-data)
- descriptor decorator in the instance dict after the first call,
- effectively replacing the decorator with an instance variable."""
+ .. code-block:: python
+ class Foo(object):
+ @reify
+ def jammy(self):
+ print 'jammy called'
+ return 1
+
+ And usage of Foo:
+
+ .. code-block:: text
+
+ >>> f = Foo()
+ >>> v = f.jammy
+ 'jammy called'
+ >>> print v
+ 1
+ >>> f.jammy
+ 1
+ >>> # jammy func not called the second time; it replaced itself with 1
+ """
def __init__(self, wrapped):
self.wrapped = wrapped
try:
diff --git a/pyramid/events.py b/pyramid/events.py
index db274823c..836466ba2 100644
--- a/pyramid/events.py
+++ b/pyramid/events.py
@@ -14,9 +14,10 @@ from pyramid.interfaces import (
)
class subscriber(object):
- """ Decorator activated via a :term:`scan` which treats the
- function being decorated as an event subscriber for the set of
- interfaces passed as ``*ifaces`` to the decorator constructor.
+ """ Decorator activated via a :term:`scan` which treats the function
+ being decorated as an event subscriber for the set of interfaces passed
+ as ``*ifaces`` and the set of predicate terms passed as ``**predicates``
+ to the decorator constructor.
For example:
@@ -61,16 +62,22 @@ class subscriber(object):
config = Configurator()
config.scan('somepackage_containing_subscribers')
+ Any ``**predicate`` arguments will be passed along to
+ :meth:`pyramid.config.Configurator.add_subscriber`. See
+ :ref:`subscriber_predicates` for a description of how predicates can
+ narrow the set of circumstances in which a subscriber will be called.
+
"""
venusian = venusian # for unit testing
- def __init__(self, *ifaces):
+ def __init__(self, *ifaces, **predicates):
self.ifaces = ifaces
+ self.predicates = predicates
def register(self, scanner, name, wrapped):
config = scanner.config
for iface in self.ifaces or (Interface,):
- config.add_subscriber(wrapped, iface)
+ config.add_subscriber(wrapped, iface, **self.predicates)
def __call__(self, wrapped):
self.venusian.attach(wrapped, self.register, category='pyramid')
diff --git a/pyramid/exceptions.py b/pyramid/exceptions.py
index 04b6e20b7..1c8f99f62 100644
--- a/pyramid/exceptions.py
+++ b/pyramid/exceptions.py
@@ -60,3 +60,21 @@ class ConfigurationExecutionError(ConfigurationError):
def __str__(self):
return "%s: %s\n in:\n %s" % (self.etype, self.evalue, self.info)
+
+class CyclicDependencyError(Exception):
+ """ The exception raised when the Pyramid topological sorter detects a
+ cyclic dependency."""
+ def __init__(self, cycles):
+ self.cycles = cycles
+
+ def __str__(self):
+ L = []
+ cycles = self.cycles
+ for cycle in cycles:
+ dependent = cycle
+ dependees = cycles[cycle]
+ L.append('%r sorts before %r' % (dependent, dependees))
+ msg = 'Implicit ordering cycle:' + '; '.join(L)
+ return msg
+
+
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 1445ee394..042b4487b 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -513,9 +513,13 @@ class IRequestHandler(Interface):
IRequest.combined = IRequest # for exception view lookups
-class IRequestProperties(Interface):
- """ Marker interface for storing a list of request properties which
- will be added to the request object."""
+class IRequestExtensions(Interface):
+ """ Marker interface for storing request extensions (properties and
+ methods) which will be added to the request object."""
+ descriptors = Attribute(
+ """A list of descriptors that will be added to each request.""")
+ methods = Attribute(
+ """A list of methods to be added to each request.""")
class IRouteRequest(Interface):
""" *internal only* interface used as in a utility lookup to find
@@ -1111,6 +1115,9 @@ class IJSONAdapter(Interface):
into a JSON-serializable primitive.
"""
+class IPredicateList(Interface):
+ """ Interface representing a predicate list """
+
# configuration phases: a lower phase number means the actions associated
# with this phase will be executed earlier than those with later phase
# numbers. The default phase number is 0, FTR.
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
index bb4ccb2f0..f2627c1fc 100644
--- a/pyramid/mako_templating.py
+++ b/pyramid/mako_templating.py
@@ -1,4 +1,5 @@
import os
+import posixpath
import re
import sys
import threading
@@ -37,16 +38,28 @@ class PkgResourceTemplateLookup(TemplateLookup):
isabs = os.path.isabs(uri)
if (not isabs) and (':' in uri):
return uri
+ if not(isabs) and ('$' in uri):
+ return uri.replace('$', ':')
+ if relativeto is not None:
+ relativeto = relativeto.replace('$', ':')
+ if not(':' in uri) and (':' in relativeto):
+ if uri.startswith('/'):
+ return uri
+ pkg, relto = relativeto.split(':')
+ _uri = posixpath.join(posixpath.dirname(relto), uri)
+ return '{0}:{1}'.format(pkg, _uri)
+ if not(':' in uri) and not(':' in relativeto):
+ return posixpath.join(posixpath.dirname(relativeto), uri)
return TemplateLookup.adjust_uri(self, uri, relativeto)
def get_template(self, uri):
"""Fetch a template from the cache, or check the filesystem
for it
-
+
In addition to the basic filesystem lookup, this subclass will
use pkg_resource to load a file using the asset
specification syntax.
-
+
"""
isabs = os.path.isabs(uri)
if (not isabs) and (':' in uri):
@@ -70,7 +83,7 @@ class PkgResourceTemplateLookup(TemplateLookup):
return TemplateLookup.get_template(self, uri)
-registry_lock = threading.Lock()
+registry_lock = threading.Lock()
class MakoRendererFactoryHelper(object):
def __init__(self, settings_prefix=None):
@@ -78,11 +91,13 @@ class MakoRendererFactoryHelper(object):
def __call__(self, info):
p = re.compile(
- r'(?P<asset>[\w_.:/]+)'
+ r'(?P<asset>[\w_.:/-]+)'
r'(?:\#(?P<defname>[\w_]+))?'
r'(\.(?P<ext>.*))'
)
- asset, defname, ext = p.match(info.name).group('asset', 'defname', 'ext')
+ asset, defname, ext = p.match(info.name).group(
+ 'asset', 'defname', 'ext'
+ )
path = '%s.%s' % (asset, ext)
registry = info.registry
settings = info.settings
@@ -141,12 +156,9 @@ class MakoRendererFactoryHelper(object):
preprocessor=preprocessor
)
- registry_lock.acquire()
- try:
- registry.registerUtility(lookup, IMakoLookup,
+ with registry_lock:
+ registry.registerUtility(lookup, IMakoLookup,
name=settings_prefix)
- finally:
- registry_lock.release()
return MakoLookupTemplateRenderer(path, defname, lookup)
@@ -166,15 +178,15 @@ class MakoLookupTemplateRenderer(object):
""" Render a :term:`Mako` template using the template
implied by the ``path`` argument.The ``path`` argument may be a
package-relative path, an absolute path, or a :term:`asset
- specification`. If a defname is defined, in the form of
- package:path/to/template#defname.mako, a function named ``defname``
+ specification`. If a defname is defined, in the form of
+ package:path/to/template#defname.mako, a function named ``defname``
inside the template will then be rendered.
"""
def __init__(self, path, defname, lookup):
self.path = path
self.defname = defname
self.lookup = lookup
-
+
def implementation(self):
return self.lookup.get_template(self.path)
diff --git a/pyramid/paster.py b/pyramid/paster.py
index 5102e7b9b..b0e4d7933 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -1,7 +1,5 @@
import os
-import zope.deprecation
-
from paste.deploy import (
loadapp,
appconfig,
@@ -10,14 +8,6 @@ from paste.deploy import (
from pyramid.compat import configparser
from logging.config import fileConfig
from pyramid.scripting import prepare
-from pyramid.scaffolds import PyramidTemplate # bw compat
-
-PyramidTemplate = PyramidTemplate # pyflakes
-
-zope.deprecation.deprecated(
- 'PyramidTemplate', ('pyramid.paster.PyramidTemplate was moved to '
- 'pyramid.scaffolds.PyramidTemplate in Pyramid 1.1'),
-)
def get_app(config_uri, name=None, loadapp=loadapp):
""" Return the WSGI application named ``name`` in the PasteDeploy
diff --git a/pyramid/registry.py b/pyramid/registry.py
index f0f9c83ea..606251a8d 100644
--- a/pyramid/registry.py
+++ b/pyramid/registry.py
@@ -191,14 +191,20 @@ class Introspectable(dict):
def unrelate(self, category_name, discriminator):
self._relations.append((False, category_name, discriminator))
+ def _assert_resolved(self):
+ assert undefer(self.discriminator) is self.discriminator
+
@property
def discriminator_hash(self):
+ self._assert_resolved()
return hash(self.discriminator)
def __hash__(self):
+ self._assert_resolved()
return hash((self.category_name,) + (self.discriminator,))
def __repr__(self):
+ self._assert_resolved()
return '<%s category %r, discriminator %r>' % (self.__class__.__name__,
self.category_name,
self.discriminator)
@@ -209,9 +215,11 @@ class Introspectable(dict):
__bool__ = __nonzero__ # py3
def register(self, introspector, action_info):
+ self.discriminator = undefer(self.discriminator)
self.action_info = action_info
introspector.add(self)
for relate, category_name, discriminator in self._relations:
+ discriminator = undefer(discriminator)
if relate:
method = introspector.relate
else:
@@ -221,4 +229,29 @@ class Introspectable(dict):
(category_name, discriminator)
)
+class Deferred(object):
+ """ Can be used by a third-party configuration extender to wrap a
+ :term:`discriminator` during configuration if an immediately hashable
+ discriminator cannot be computed because it relies on unresolved values.
+ The function should accept no arguments and should return a hashable
+ discriminator."""
+ def __init__(self, func):
+ self.func = func
+
+ def resolve(self):
+ return self.func()
+
+def undefer(v):
+ """ Function which accepts an object and returns it unless it is a
+ :class:`pyramid.registry.Deferred` instance. If it is an instance of
+ that class, its ``resolve`` method is called, and the result of the
+ method is returned."""
+ if isinstance(v, Deferred):
+ v = v.resolve()
+ return v
+
+class predvalseq(tuple):
+ """ A subtype of tuple used to represent a sequence of predicate values """
+ pass
+
global_registry = Registry('global')
diff --git a/pyramid/renderers.py b/pyramid/renderers.py
index e526f9997..6839d72f5 100644
--- a/pyramid/renderers.py
+++ b/pyramid/renderers.py
@@ -1,5 +1,6 @@
import json
import os
+import re
import pkg_resources
import threading
@@ -366,6 +367,12 @@ class JSONP(JSON):
@implementer(IChameleonLookup)
class ChameleonRendererLookup(object):
+ spec_re = re.compile(
+ r'(?P<asset>[\w_.:/-]+)'
+ r'(?:\#(?P<defname>[\w_]+))?'
+ r'(\.(?P<ext>.*))'
+ )
+
def __init__(self, impl, registry):
self.impl = impl
self.registry = registry
@@ -416,6 +423,12 @@ class ChameleonRendererLookup(object):
return False
return settings.get('reload_templates', False)
+ def _crack_spec(self, spec):
+ asset, macro, ext = self.spec_re.match(spec).group(
+ 'asset', 'defname', 'ext'
+ )
+ return asset, macro, ext
+
def __call__(self, info):
spec = self.get_spec(info.name, info.package)
registry = info.registry
@@ -426,40 +439,38 @@ class ChameleonRendererLookup(object):
raise ValueError('Missing template file: %s' % spec)
renderer = registry.queryUtility(ITemplateRenderer, name=spec)
if renderer is None:
- renderer = self.impl(spec, self)
+ renderer = self.impl(spec, self, macro=None)
# cache the template
- try:
- self.lock.acquire()
+ with self.lock:
registry.registerUtility(renderer,
ITemplateRenderer, name=spec)
- finally:
- self.lock.release()
else:
# spec is a package:relpath asset spec
renderer = registry.queryUtility(ITemplateRenderer, name=spec)
if renderer is None:
+ asset, macro, ext = self._crack_spec(spec)
+ spec_without_macro = '%s.%s' % (asset, ext)
try:
- package_name, filename = spec.split(':', 1)
+ package_name, filename = spec_without_macro.split(':', 1)
except ValueError: # pragma: no cover
# somehow we were passed a relative pathname; this
# should die
package_name = caller_package(4).__name__
- filename = spec
+ filename = spec_without_macro
abspath = pkg_resources.resource_filename(package_name,
filename)
if not pkg_resources.resource_exists(package_name, filename):
raise ValueError(
- 'Missing template asset: %s (%s)' % (spec, abspath))
- renderer = self.impl(abspath, self)
+ 'Missing template asset: %s (%s)' % (
+ spec_without_macro, abspath)
+ )
+ renderer = self.impl(abspath, self, macro=macro)
settings = info.settings
if not settings.get('reload_assets'):
# cache the template
- self.lock.acquire()
- try:
+ with self.lock:
registry.registerUtility(renderer, ITemplateRenderer,
name=spec)
- finally:
- self.lock.release()
return renderer
@@ -470,11 +481,8 @@ def template_renderer_factory(info, impl, lock=registry_lock):
lookup = registry.queryUtility(IChameleonLookup, name=info.type)
if lookup is None:
lookup = ChameleonRendererLookup(impl, registry)
- lock.acquire()
- try:
+ with lock:
registry.registerUtility(lookup, IChameleonLookup, name=info.type)
- finally:
- lock.release()
return lookup(info)
@implementer(IRendererInfo)
@@ -565,13 +573,11 @@ class RendererHelper(object):
response = response_factory()
- if result is None:
- result = ''
-
- if isinstance(result, text_type):
- response.text = result
- else:
- response.body = result
+ if result is not None:
+ if isinstance(result, text_type):
+ response.text = result
+ else:
+ response.body = result
if request is not None:
# deprecated mechanism to set up request.response_* attrs, see
diff --git a/pyramid/request.py b/pyramid/request.py
index 37fac6a46..9e275c2c0 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -328,6 +328,8 @@ class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin,
matchdict = None
matched_route = None
+ ResponseClass = Response
+
@reify
def tmpl_context(self):
# docs-deprecated template context for Pylons-like apps; do not
@@ -454,13 +456,4 @@ def call_app_with_subpath_as_path_info(request, app):
new_request.environ['SCRIPT_NAME'] = new_script_name
new_request.environ['PATH_INFO'] = new_path_info
- # In case downstream WSGI app is a Pyramid app, hack around existence of
- # these envars until we can safely remove them (see router.py); in any
- # case, even if these get removed, it might be better to not copy the
- # existing environ but to create a new one instead.
- if 'bfg.routes.route' in new_request.environ:
- del new_request.environ['bfg.routes.route']
- if 'bfg.routes.matchdict' in new_request.environ:
- del new_request.environ['bfg.routes.matchdict']
-
return new_request.get_response(app)
diff --git a/pyramid/router.py b/pyramid/router.py
index 0c115a1ac..0c7f61071 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -6,6 +6,7 @@ from zope.interface import (
from pyramid.interfaces import (
IDebugLogger,
IRequest,
+ IRequestExtensions,
IRootFactory,
IRouteRequest,
IRouter,
@@ -48,9 +49,11 @@ class Router(object):
self.root_factory = q(IRootFactory, default=DefaultRootFactory)
self.routes_mapper = q(IRoutesMapper)
self.request_factory = q(IRequestFactory, default=Request)
+ self.request_extensions = q(IRequestExtensions)
tweens = q(ITweens)
if tweens is None:
tweens = excview_tween_factory
+ self.orig_handle_request = self.handle_request
self.handle_request = tweens(self.handle_request, registry)
self.root_policy = self.root_factory # b/w compat
self.registry = registry
@@ -84,13 +87,6 @@ class Router(object):
request.url)
logger and logger.debug(msg)
else:
- # TODO: kill off bfg.routes.* environ keys
- # when traverser requires request arg, and
- # cant cope with environ anymore (they are
- # docs-deprecated as of BFG 1.3)
- environ = request.environ
- environ['bfg.routes.route'] = route
- environ['bfg.routes.matchdict'] = match
attrs['matchdict'] = match
attrs['matched_route'] = route
@@ -105,7 +101,8 @@ class Router(object):
request.url,
route.name,
request.path_info,
- route.pattern, match,
+ route.pattern,
+ match,
', '.join([p.__text__ for p in route.predicates]))
)
logger and logger.debug(msg)
@@ -165,32 +162,75 @@ class Router(object):
return response
- def __call__(self, environ, start_response):
+ def invoke_subrequest(self, request, use_tweens=False):
"""
- Accept ``environ`` and ``start_response``; create a
- :term:`request` and route the request to a :app:`Pyramid`
- view based on introspection of :term:`view configuration`
- within the application registry; call ``start_response`` and
- return an iterable.
+ Obtain a response object from the Pyramid application based on
+ information in the ``request`` object provided. The ``request``
+ object must be an object that implements the Pyramid request
+ interface (such as a :class:`pyramid.request.Request` instance). If
+ ``use_tweens`` is ``True``, the request will be sent to the
+ :term:`tween` in the tween stack closest to the request ingress. If
+ ``use_tweens`` is ``False``, the request will be sent to the main
+ router handler, and no tweens will be invoked. This function also:
+
+ - manages the threadlocal stack (so that
+ :func:`~pyramid.threadlocal.get_current_request` and
+ :func:`~pyramid.threadlocal.get_current_registry` work during a
+ request)
+
+ - Adds a ``registry`` attribute and a ``invoke_subrequest`` attribute
+ (a callable) to the request object it's handed.
+
+ - sets request extensions (such as those added via
+ :meth:`~pyramid.config.Configurator.add_request_method` or
+ :meth:`~pyramid.config.Configurator.set_request_property`) on the
+ request it's passed.
+
+ - causes a :class:`~pyramid.event.NewRequest` event to be sent at the
+ beginning of request processing.
+
+ - causes a :class:`~pyramid.event.ContextFound` event to be sent
+ when a context resource is found.
+
+ - causes a :class:`~pyramid.event.NewResponse` event to be sent when
+ the Pyramid application returns a response.
+
+ - Calls any :term:`response callback` functions defined within the
+ request's lifetime if a response is obtained from the Pyramid
+ application.
+
+ - Calls any :term:`finished callback` functions defined within the
+ request's lifetime.
+
+ See also :ref:`subrequest_chapter`.
"""
registry = self.registry
has_listeners = self.registry.has_listeners
notify = self.registry.notify
- request = self.request_factory(environ)
threadlocals = {'registry':registry, 'request':request}
manager = self.threadlocal_manager
manager.push(threadlocals)
request.registry = registry
+ request.invoke_subrequest = self.invoke_subrequest
+
+ if use_tweens:
+ handle_request = self.handle_request
+ else:
+ handle_request = self.orig_handle_request
+
try:
try:
- response = self.handle_request(request)
+ extensions = self.request_extensions
+ if extensions is not None:
+ request._set_extensions(extensions)
+ response = handle_request(request)
has_listeners and notify(NewResponse(request, response))
if request.response_callbacks:
request._process_response_callbacks(response)
- return response(request.environ, start_response)
+ return response
finally:
if request.finished_callbacks:
@@ -199,3 +239,15 @@ class Router(object):
finally:
manager.pop()
+ def __call__(self, environ, start_response):
+ """
+ Accept ``environ`` and ``start_response``; create a
+ :term:`request` and route the request to a :app:`Pyramid`
+ view based on introspection of :term:`view configuration`
+ within the application registry; call ``start_response`` and
+ return an iterable.
+ """
+ request = self.request_factory(environ)
+ response = self.invoke_subrequest(request, use_tweens=True)
+ return response(request.environ, start_response)
+
diff --git a/pyramid/scaffolds/alchemy/+package+/__init__.py b/pyramid/scaffolds/alchemy/+package+/__init__.py
index 253341563..e39f619ed 100644
--- a/pyramid/scaffolds/alchemy/+package+/__init__.py
+++ b/pyramid/scaffolds/alchemy/+package+/__init__.py
@@ -1,13 +1,17 @@
from pyramid.config import Configurator
from sqlalchemy import engine_from_config
-from .models import DBSession
+from .models import (
+ DBSession,
+ Base,
+ )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
config = Configurator(settings=settings)
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
diff --git a/pyramid/scaffolds/alchemy/+package+/static/pylons.css b/pyramid/scaffolds/alchemy/+package+/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/pyramid/scaffolds/alchemy/+package+/static/pylons.css
+++ b/pyramid/scaffolds/alchemy/+package+/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl
index ac0140789..99606fe0e 100644
--- a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl
@@ -32,7 +32,7 @@
<div class="bottom">
<div id="left" class="align-right">
<h2>Search documentation</h2>
- <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/search.html">
+ <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html">
<input type="text" id="q" name="q" value="" />
<input type="submit" id="x" value="Go" />
</form>
@@ -44,22 +44,22 @@
<a href="http://pylonsproject.org">Pylons Website</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#narrative-documentation">Narrative Documentation</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#reference-material">API Documentation</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#tutorials">Tutorials</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#detailed-change-history">Change History</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#sample-applications">Sample Applications</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#support-and-development">Support and Development</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a>
</li>
<li>
<a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl
index eebfbcc3e..bdf08171c 100644
--- a/pyramid/scaffolds/alchemy/development.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/development.ini_tmpl
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:{{project}}
@@ -12,12 +17,23 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/{{project}}.sqlite
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, {{package_logger}}, sqlalchemy
@@ -53,5 +69,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/pyramid/scaffolds/alchemy/production.ini_tmpl b/pyramid/scaffolds/alchemy/production.ini_tmpl
index 9488f1811..69b08e458 100644
--- a/pyramid/scaffolds/alchemy/production.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/production.ini_tmpl
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:{{project}}
@@ -16,7 +21,10 @@ use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, {{package_logger}}, sqlalchemy
@@ -52,5 +60,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/pyramid/scaffolds/starter/+package+/static/pylons.css b/pyramid/scaffolds/starter/+package+/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/pyramid/scaffolds/starter/+package+/static/pylons.css
+++ b/pyramid/scaffolds/starter/+package+/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl
index 743eab026..4a71dd992 100644
--- a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl
@@ -32,7 +32,7 @@
<div class="bottom">
<div id="left" class="align-right">
<h2>Search documentation</h2>
- <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/search.html">
+ <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html">
<input type="text" id="q" name="q" value="" />
<input type="submit" id="x" value="Go" />
</form>
@@ -44,22 +44,22 @@
<a href="http://pylonsproject.org">Pylons Website</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#narrative-documentation">Narrative Documentation</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#reference-material">API Documentation</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#tutorials">Tutorials</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#detailed-change-history">Change History</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#sample-applications">Sample Applications</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#support-and-development">Support and Development</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a>
</li>
<li>
<a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl
index c92e13906..33c454086 100644
--- a/pyramid/scaffolds/starter/development.ini_tmpl
+++ b/pyramid/scaffolds/starter/development.ini_tmpl
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:{{project}}
@@ -9,12 +14,23 @@ pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, {{package_logger}}
@@ -42,5 +58,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/pyramid/scaffolds/starter/production.ini_tmpl b/pyramid/scaffolds/starter/production.ini_tmpl
index 28957b5c1..dd2637e5b 100644
--- a/pyramid/scaffolds/starter/production.ini_tmpl
+++ b/pyramid/scaffolds/starter/production.ini_tmpl
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:{{project}}
@@ -7,12 +12,19 @@ pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, {{package_logger}}
@@ -40,5 +52,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/pyramid/scaffolds/zodb/+package+/static/pylons.css b/pyramid/scaffolds/zodb/+package+/static/pylons.css
index c54499ddd..4b1c017cd 100644
--- a/pyramid/scaffolds/zodb/+package+/static/pylons.css
+++ b/pyramid/scaffolds/zodb/+package+/static/pylons.css
@@ -1,65 +1,372 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%; /* 16px */
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body
+{
+ line-height: 1;
+}
+
+ol, ul
+{
+ list-style: none;
+}
+
+blockquote, q
+{
+ quotes: none;
+}
+
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+
+:focus
+{
+ outline: 0;
+}
+
+ins
+{
+ text-decoration: none;
+}
+
+del
+{
+ text-decoration: line-through;
+}
+
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+sub
+{
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup
+{
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+ul, menu, dir
+{
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol
+{
+ display: block;
+ list-style-type: decimal-leading-zero;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li
+{
+ display: list-item;
+}
+
+ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
+{
+ list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
+{
+ list-style-type: square;
+}
+
+.hidden
+{
+ display: none;
+}
+
+p
+{
+ line-height: 1.5em;
+}
+
+h1
+{
+ font-size: 1.75em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h2
+{
+ font-size: 1.5em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h3
+{
+ font-size: 1.25em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+h4
+{
+ font-size: 1em;
+ line-height: 1.7em;
+ font-family: helvetica, verdana;
+}
+
+html, body
+{
+ width: 100%;
+ height: 100%;
+}
+
+body
+{
+ margin: 0;
+ padding: 0;
+ background-color: #fff;
+ position: relative;
+ font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+}
+
+a
+{
+ color: #1b61d6;
+ text-decoration: none;
+}
+
+a:hover
+{
+ color: #e88f00;
+ text-decoration: underline;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6
+{
+ font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
+ font-weight: 400;
+ color: #373839;
+ font-style: normal;
+}
+
+#wrap
+{
+ min-height: 100%;
+}
+
+#header, #footer
+{
+ width: 100%;
+ color: #fff;
+ height: 40px;
+ position: absolute;
+ text-align: center;
+ line-height: 40px;
+ overflow: hidden;
+ font-size: 12px;
+ vertical-align: middle;
+}
+
+#header
+{
+ background: #000;
+ top: 0;
+ font-size: 14px;
+}
+
+#footer
+{
+ bottom: 0;
+ background: #000 url(footerbg.png) repeat-x 0 top;
+ position: relative;
+ margin-top: -40px;
+ clear: both;
+}
+
+.header, .footer
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.wrapper
+{
+ width: 100%;
+}
+
+#top, #top-small, #bottom
+{
+ width: 100%;
+}
+
+#top
+{
+ color: #000;
+ height: 230px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#top-small
+{
+ color: #000;
+ height: 60px;
+ background: #fff url(headerbg.png) repeat-x 0 top;
+ position: relative;
+}
+
+#bottom
+{
+ color: #222;
+ background-color: #fff;
+}
+
+.top, .top-small, .middle, .bottom
+{
+ width: 750px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.top
+{
+ padding-top: 40px;
+}
+
+.top-small
+{
+ padding-top: 10px;
+}
+
+#middle
+{
+ width: 100%;
+ height: 100px;
+ background: url(middlebg.png) repeat-x;
+ border-top: 2px solid #fff;
+ border-bottom: 2px solid #b2b2b2;
+}
+
+.app-welcome
+{
+ margin-top: 25px;
+}
+
+.app-name
+{
+ color: #000;
+ font-weight: 700;
+}
+
+.bottom
+{
+ padding-top: 50px;
+}
+
+#left
+{
+ width: 350px;
+ float: left;
+ padding-right: 25px;
+}
+
+#right
+{
+ width: 350px;
+ float: right;
+ padding-left: 25px;
+}
+
+.align-left
+{
+ text-align: left;
+}
+
+.align-right
+{
+ text-align: right;
+}
+
+.align-center
+{
+ text-align: center;
+}
+
+ul.links
+{
+ margin: 0;
+ padding: 0;
+}
+
+ul.links li
+{
+ list-style-type: none;
+ font-size: 14px;
+}
+
+form
+{
+ border-style: none;
+}
+
+fieldset
+{
+ border-style: none;
+}
+
+input
+{
+ color: #222;
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+input[type=text], input[type=password]
+{
+ width: 205px;
+}
+
+input[type=submit]
+{
+ background-color: #ddd;
+ font-weight: 700;
+}
+
/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
+body:before
+{
+ content: "";
+ height: 100%;
+ float: left;
+ width: 0;
+ margin-top: -32767px;
+}
diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt
index d64f18fca..5391509fe 100644
--- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt
+++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt
@@ -32,7 +32,7 @@
<div class="bottom">
<div id="left" class="align-right">
<h2>Search documentation</h2>
- <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/search.html">
+ <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html">
<input type="text" id="q" name="q" value="" />
<input type="submit" id="x" value="Go" />
</form>
@@ -44,22 +44,22 @@
<a href="http://pylonsproject.org">Pylons Website</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#narrative-documentation">Narrative Documentation</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#reference-material">API Documentation</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#tutorials">Tutorials</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#detailed-change-history">Change History</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#sample-applications">Sample Applications</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a>
</li>
<li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#support-and-development">Support and Development</a>
+ <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a>
</li>
<li>
<a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl
index 1260b4db3..746f7ded3 100644
--- a/pyramid/scaffolds/zodb/development.ini_tmpl
+++ b/pyramid/scaffolds/zodb/development.ini_tmpl
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:{{project}}
@@ -14,12 +19,23 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, {{package_logger}}
@@ -47,5 +63,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl
index 1640009fa..9ce639ec3 100644
--- a/pyramid/scaffolds/zodb/production.ini_tmpl
+++ b/pyramid/scaffolds/zodb/production.ini_tmpl
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:{{project}}
@@ -13,12 +18,19 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, {{package_logger}}
@@ -46,5 +58,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/pyramid/scripting.py b/pyramid/scripting.py
index f1dc24637..fdb4aa430 100644
--- a/pyramid/scripting.py
+++ b/pyramid/scripting.py
@@ -3,6 +3,7 @@ from pyramid.exceptions import ConfigurationError
from pyramid.request import Request
from pyramid.interfaces import (
+ IRequestExtensions,
IRequestFactory,
IRootFactory,
)
@@ -70,14 +71,22 @@ def prepare(request=None, registry=None):
'before trying to activate it.')
if request is None:
request = _make_request('/', registry)
- request.registry = registry
+ # NB: even though _make_request might have already set registry on
+ # request, we reset it in case someone has passed in their own
+ # request.
+ request.registry = registry
threadlocals = {'registry':registry, 'request':request}
threadlocal_manager.push(threadlocals)
+ extensions = registry.queryUtility(IRequestExtensions)
+ if extensions is not None:
+ request._set_extensions(extensions)
def closer():
threadlocal_manager.pop()
root_factory = registry.queryUtility(IRootFactory,
default=DefaultRootFactory)
root = root_factory(request)
+ if getattr(request, 'context', None) is None:
+ request.context = root
return {'root':root, 'closer':closer, 'registry':registry,
'request':request, 'root_factory':root_factory}
diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py
index 29ec9e72a..f64107d2b 100644
--- a/pyramid/scripts/proutes.py
+++ b/pyramid/scripts/proutes.py
@@ -15,10 +15,10 @@ class PRoutesCommand(object):
route, the pattern of the route, and the view callable which will be
invoked when the route is matched.
- This command accepts one positional argument named "config_uri". It
+ This command accepts one positional argument named 'config_uri'. It
specifies the PasteDeploy config file to use for the interactive
- shell. The format is "inifile#name". If the name is left off, "main"
- will be assumed. Example: "proutes myapp.ini".
+ shell. The format is 'inifile#name'. If the name is left off, 'main'
+ will be assumed. Example: 'proutes myapp.ini'.
"""
bootstrap = (bootstrap,)
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index ea2a4ae09..9fbf0729a 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -583,12 +583,8 @@ class LazyWriter(object):
def open(self):
if self.fileobj is None:
- self.lock.acquire()
- try:
- if self.fileobj is None:
- self.fileobj = open(self.filename, self.mode)
- finally:
- self.lock.release()
+ with self.lock:
+ self.fileobj = open(self.filename, self.mode)
return self.fileobj
def close(self):
diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py
index 72a9800c3..a9db59dc1 100644
--- a/pyramid/scripts/pviews.py
+++ b/pyramid/scripts/pviews.py
@@ -17,11 +17,11 @@ class PViewsCommand(object):
each route+predicate set, print each view that might match and its
predicates.
- This command accepts two positional arguments: "config_uri" specifies the
+ This command accepts two positional arguments: 'config_uri' specifies the
PasteDeploy config file to use for the interactive shell. The format is
- "inifile#name". If the name is left off, "main" will be assumed. "url"
+ 'inifile#name'. If the name is left off, 'main' will be assumed. 'url'
specifies the path info portion of a URL that will be used to find
- matching views. Example: "proutes myapp.ini#main /url"
+ matching views. Example: 'proutes myapp.ini#main /url'
"""
stdout = sys.stdout
@@ -223,7 +223,7 @@ class PViewsCommand(object):
self.out("%srequired permission = %s" % (indent, permission))
predicates = getattr(view_wrapper, '__predicates__', None)
if predicates is not None:
- predicate_text = ', '.join([p.__text__ for p in predicates])
+ predicate_text = ', '.join([p.text() for p in predicates])
self.out("%sview predicates (%s)" % (indent, predicate_text))
def run(self):
diff --git a/pyramid/security.py b/pyramid/security.py
index 4b929241e..3e25f9b2f 100644
--- a/pyramid/security.py
+++ b/pyramid/security.py
@@ -4,6 +4,7 @@ from pyramid.interfaces import (
IAuthenticationPolicy,
IAuthorizationPolicy,
ISecuredView,
+ IView,
IViewClassifier,
)
@@ -132,7 +133,13 @@ def view_execution_permitted(context, request, name=''):
view using the effective authentication/authorization policies and
the ``request``. Return a boolean result. If no
:term:`authorization policy` is in effect, or if the view is not
- protected by a permission, return ``True``."""
+ protected by a permission, return ``True``. If no view can view found,
+ an exception will be raised.
+
+ .. versionchanged:: 1.4a4
+ An exception is raised if no view is found.
+
+ """
try:
reg = request.registry
except AttributeError:
@@ -140,6 +147,11 @@ def view_execution_permitted(context, request, name=''):
provides = [IViewClassifier] + map_(providedBy, (request, context))
view = reg.adapters.lookup(provides, ISecuredView, name=name)
if view is None:
+ view = reg.adapters.lookup(provides, IView, name=name)
+ if view is None:
+ raise TypeError('No registered view satisfies the constraints. '
+ 'It would not make sense to claim that this view '
+ '"is" or "is not" permitted.')
return Allowed(
'Allowed: view name %r in context %r (no permission defined)' %
(name, context))
diff --git a/pyramid/session.py b/pyramid/session.py
index 76b2b30b1..a5e6a8d3a 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -15,6 +15,7 @@ from pyramid.compat import (
native_,
)
+from pyramid.httpexceptions import HTTPBadRequest
from pyramid.interfaces import ISession
from pyramid.util import strings_differ
@@ -33,6 +34,76 @@ def manage_accessed(wrapped):
accessed.__doc__ = wrapped.__doc__
return accessed
+def signed_serialize(data, secret):
+ """ Serialize any pickleable structure (``data``) and sign it
+ using the ``secret`` (must be a string). Return the
+ serialization, which includes the signature as its first 40 bytes.
+ The ``signed_deserialize`` method will deserialize such a value.
+
+ This function is useful for creating signed cookies. For example:
+
+ .. code-block:: python
+
+ cookieval = signed_serialize({'a':1}, 'secret')
+ response.set_cookie('signed_cookie', cookieval)
+ """
+ pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
+ sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
+ return sig + native_(base64.b64encode(pickled))
+
+def signed_deserialize(serialized, secret, hmac=hmac):
+ """ Deserialize the value returned from ``signed_serialize``. If
+ the value cannot be deserialized for any reason, a
+ :exc:`ValueError` exception will be raised.
+
+ This function is useful for deserializing a signed cookie value
+ created by ``signed_serialize``. For example:
+
+ .. code-block:: python
+
+ cookieval = request.cookies['signed_cookie']
+ data = signed_deserialize(cookieval, 'secret')
+ """
+ # hmac parameterized only for unit tests
+ try:
+ input_sig, pickled = (serialized[:40],
+ base64.b64decode(bytes_(serialized[40:])))
+ except (binascii.Error, TypeError) as e:
+ # Badly formed data can make base64 die
+ raise ValueError('Badly formed base64 data: %s' % e)
+
+ sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
+
+ # Avoid timing attacks (see
+ # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf)
+ if strings_differ(sig, input_sig):
+ raise ValueError('Invalid signature')
+
+ return pickle.loads(pickled)
+
+def check_csrf_token(request, token='csrf_token', raises=True):
+ """ Check the CSRF token in the request's session against the value in
+ ``request.params.get(token)``. If a ``token`` keyword is not supplied
+ to this function, the string ``csrf_token`` will be used to look up
+ the token within ``request.params``. If the value in
+ ``request.params.get(token)`` doesn't match the value supplied by
+ ``request.session.get_csrf_token()``, and ``raises`` is ``True``, this
+ function will raise an :exc:`pyramid.httpexceptions.HTTPBadRequest`
+ exception. If the check does succeed and ``raises`` is ``False``, this
+ function will return ``False``. If the CSRF check is successful, this
+ function will return ``True`` unconditionally.
+
+ Note that using this function requires that a :term:`session factory` is
+ configured.
+
+ .. versionadded:: 1.4a2
+ """
+ if request.params.get(token) != request.session.get_csrf_token():
+ if raises:
+ raise HTTPBadRequest('incorrect CSRF token')
+ return False
+ return True
+
def UnencryptedCookieSessionFactoryConfig(
secret,
timeout=1200,
@@ -43,6 +114,8 @@ def UnencryptedCookieSessionFactoryConfig(
cookie_secure=False,
cookie_httponly=False,
cookie_on_exception=True,
+ signed_serialize=signed_serialize,
+ signed_deserialize=signed_deserialize,
):
"""
Configure a :term:`session factory` which will provide unencrypted
@@ -89,6 +162,15 @@ def UnencryptedCookieSessionFactoryConfig(
If ``True``, set a session cookie even if an exception occurs
while rendering a view. Default: ``True``.
+ ``signed_serialize``
+ A callable which takes more or less arbitrary python data structure and
+ a secret and returns a signed serialization in bytes.
+ Default: ``signed_serialize`` (using pickle).
+
+ ``signed_deserialize``
+ A callable which takes a signed and serialized data structure in bytes
+ and a secret and returns the original data structure if the signature
+ is valid. Default: ``signed_deserialize`` (using pickle).
"""
@implementer(ISession)
@@ -225,51 +307,3 @@ def UnencryptedCookieSessionFactoryConfig(
return True
return UnencryptedCookieSessionFactory
-
-def signed_serialize(data, secret):
- """ Serialize any pickleable structure (``data``) and sign it
- using the ``secret`` (must be a string). Return the
- serialization, which includes the signature as its first 40 bytes.
- The ``signed_deserialize`` method will deserialize such a value.
-
- This function is useful for creating signed cookies. For example:
-
- .. code-block:: python
-
- cookieval = signed_serialize({'a':1}, 'secret')
- response.set_cookie('signed_cookie', cookieval)
- """
- pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
- sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
- return sig + native_(base64.b64encode(pickled))
-
-def signed_deserialize(serialized, secret, hmac=hmac):
- """ Deserialize the value returned from ``signed_serialize``. If
- the value cannot be deserialized for any reason, a
- :exc:`ValueError` exception will be raised.
-
- This function is useful for deserializing a signed cookie value
- created by ``signed_serialize``. For example:
-
- .. code-block:: python
-
- cookieval = request.cookies['signed_cookie']
- data = signed_deserialize(cookieval, 'secret')
- """
- # hmac parameterized only for unit tests
- try:
- input_sig, pickled = (serialized[:40],
- base64.b64decode(bytes_(serialized[40:])))
- except (binascii.Error, TypeError) as e:
- # Badly formed data can make base64 die
- raise ValueError('Badly formed base64 data: %s' % e)
-
- sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
-
- # Avoid timing attacks (see
- # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf)
- if strings_differ(sig, input_sig):
- raise ValueError('Invalid signature')
-
- return pickle.loads(pickled)
-
diff --git a/pyramid/settings.py b/pyramid/settings.py
index 86304307e..e2cb3cb3c 100644
--- a/pyramid/settings.py
+++ b/pyramid/settings.py
@@ -1,31 +1,5 @@
-from zope.deprecation import deprecated
-
-from pyramid.threadlocal import get_current_registry
from pyramid.compat import string_types
-def get_settings():
- """
- Return a :term:`deployment settings` object for the current application.
- The object is a dictionary-like object that contains key/value pairs
- based on the dictionary passed as the ``settings`` argument to the
- :class:`pyramid.config.Configurator` constructor or the
- :func:`pyramid.router.make_app` API.
-
- .. warning:: This method is deprecated as of Pyramid 1.0. Use
- ``pyramid.threadlocal.get_current_registry().settings`` instead or use
- the ``settings`` attribute of the registry available from the request
- (``request.registry.settings``).
- """
- reg = get_current_registry()
- return reg.settings
-
-deprecated(
- 'get_settings',
- '(pyramid.settings.get_settings is deprecated as of Pyramid 1.0. Use'
- '``pyramid.threadlocal.get_current_registry().settings`` instead or use '
- 'the ``settings`` attribute of the registry available from the request '
- '(``request.registry.settings``)).')
-
truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1'))
def asbool(s):
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 40e90cda6..cecf13469 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -1,20 +1,15 @@
import copy
import os
-
-from zope.deprecation import deprecated
+from contextlib import contextmanager
from zope.interface import (
implementer,
- Interface,
alsoProvides,
)
from pyramid.interfaces import (
IRequest,
IResponseFactory,
- ISecuredView,
- IView,
- IViewClassifier,
ISession,
)
@@ -26,14 +21,12 @@ from pyramid.compat import (
from pyramid.config import Configurator
from pyramid.decorator import reify
-from pyramid.httpexceptions import HTTPForbidden
from pyramid.response import Response
from pyramid.registry import Registry
from pyramid.security import (
Authenticated,
Everyone,
- has_permission,
)
from pyramid.threadlocal import (
@@ -47,373 +40,10 @@ from pyramid.request import (
)
from pyramid.url import URLMethodsMixin
+from pyramid.util import InstancePropertyMixin
_marker = object()
-def registerDummySecurityPolicy(userid=None, groupids=(), permissive=True):
- """ Registers a pair of faux :app:`Pyramid` security policies:
- a :term:`authentication policy` and a :term:`authorization
- policy`.
-
- The behavior of the registered :term:`authorization policy`
- depends on the ``permissive`` argument. If ``permissive`` is
- true, a permissive :term:`authorization policy` is registered;
- this policy allows all access. If ``permissive`` is false, a
- nonpermissive :term:`authorization policy` is registered; this
- policy denies all access.
-
- The behavior of the registered :term:`authentication policy`
- depends on the values provided for the ``userid`` and ``groupids``
- argument. The authentication policy will return the userid
- identifier implied by the ``userid`` argument and the group ids
- implied by the ``groupids`` argument when the
- :func:`pyramid.security.authenticated_userid` or
- :func:`pyramid.security.effective_principals` APIs are used.
-
- This function is most useful when testing code that uses the APIs named
- :func:`pyramid.security.has_permission`,
- :func:`pyramid.security.authenticated_userid`,
- :func:`pyramid.security.unauthenticated_userid`,
- :func:`pyramid.security.effective_principals`, and
- :func:`pyramid.security.principals_allowed_by_permission`.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.testing_securitypolicy`
- method in your unit and integration tests.
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- result = config.testing_securitypolicy(userid=userid, groupids=groupids,
- permissive=permissive)
- config.commit()
- return result
-
-deprecated('registerDummySecurityPolicy',
- 'The testing.registerDummySecurityPolicy API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_securitypolicy '
- 'method in your unit and integration tests.')
-
-def registerResources(resources):
- """ Registers a dictionary of :term:`resource` objects that can be
- resolved via the :func:`pyramid.traversal.find_resource` API.
-
- The :func:`pyramid.traversal.find_resource` API is called with a
- path as one of its arguments. If the dictionary you register when
- calling this method contains that path as a string key
- (e.g. ``/foo/bar`` or ``foo/bar``), the corresponding value will
- be returned to ``find_resource`` (and thus to your code) when
- :func:`pyramid.traversal.find_resource` is called with an
- equivalent path string or tuple.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.testing_resources`
- method in your unit and integration tests.
-
- .. note:: For ancient backward compatibility purposes, this API can also
- be accessed as :func:`pyramid.testing.registerModels`.
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- result = config.testing_resources(resources)
- config.commit()
- return result
-
-deprecated('registerResources',
- 'The testing.registerResources API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_resources '
- 'method in your unit and integration tests.')
-
-registerModels = registerResources
-
-deprecated('registerModels',
- 'The testing.registerModels API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_resources '
- 'method in your unit and integration tests.')
-
-def registerEventListener(event_iface=None):
- """ Registers an :term:`event` listener (aka :term:`subscriber`)
- listening for events of the type ``event_iface``. This method
- returns a list object which is appended to by the subscriber
- whenever an event is captured.
-
- When an event is dispatched that matches ``event_iface``, that
- event will be appended to the list. You can then compare the
- values in the list to expected event notifications. This method
- is useful when testing code that wants to call
- :meth:`pyramid.registry.Registry.notify`.
-
- The default value of ``event_iface`` (``None``) implies a
- subscriber registered for *any* kind of event.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.testing_add_subscriber`
- method in your unit and integration tests.
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- result = config.testing_add_subscriber(event_iface)
- config.commit()
- return result
-
-deprecated('registerEventListener',
- 'The testing.registerEventListener API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_add_subscriber '
- 'method in your unit and integration tests.')
-
-def registerTemplateRenderer(path, renderer=None):
- """ Register a template renderer at ``path`` (usually a relative
- filename ala ``templates/foo.pt``) and return the renderer object.
- If the ``renderer`` argument is None, a 'dummy' renderer will be
- used. This function is useful when testing code that calls the
- :func:`pyramid.renderers.render` function or
- :func:`pyramid.renderers.render_to_response` function or any
- other ``render_*`` or ``get_*`` API of the
- :mod:`pyramid.renderers` module.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.testing_add_renderer`
- method in your unit and integration tests.
-
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- result = config.testing_add_template(path, renderer)
- config.commit()
- return result
-
-deprecated('registerTemplateRenderer',
- 'The testing.registerTemplateRenderer API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_add_renderer '
- 'method in your unit and integration tests.')
-
-# registerDummyRenderer is a deprecated alias that should never be removed
-# (too much usage in the wild)
-registerDummyRenderer = registerTemplateRenderer
-
-deprecated('registerDummyRenderer',
- 'The testing.registerDummyRenderer API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_add_renderer '
- 'method in your unit and integration tests.')
-
-def registerView(name, result='', view=None, for_=(Interface, Interface),
- permission=None):
- """ Registers a :app:`Pyramid` :term:`view callable` under the
- name implied by the ``name`` argument. The view will return a
- :term:`WebOb` :term:`Response` object with the value implied by
- the ``result`` argument as its ``body`` attribute. To gain more
- control, if you pass in a non-``None`` ``view`` argument, this
- value will be used as a view callable instead of an automatically
- generated view callable (and ``result`` is not used).
-
- To protect the view using a :term:`permission`, pass in a
- non-``None`` value as ``permission``. This permission will be
- checked by any active :term:`authorization policy` when view
- execution is attempted.
-
- This function is useful when testing code which calls
- :func:`pyramid.view.render_view_to_response`.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.add_view`
- method in your unit and integration tests.
- """
- for_ = (IViewClassifier, ) + for_
- if view is None:
- def view(context, request):
- return Response(result)
- if permission is None:
- return registerAdapter(view, for_, IView, name)
- else:
- def _secure(context, request):
- if not has_permission(permission, context, request):
- raise HTTPForbidden('no permission')
- else:
- return view(context, request)
- _secure.__call_permissive__ = view
- def permitted(context, request):
- return has_permission(permission, context, request)
- _secure.__permitted__ = permitted
- return registerAdapter(_secure, for_, ISecuredView, name)
-
-deprecated('registerView',
- 'The registerView API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.add_view '
- 'method in your unit and integration tests.')
-
-def registerUtility(impl, iface=Interface, name=''):
- """ Register a ZCA utility component.
-
- The ``impl`` argument specifies the implementation of the utility.
- The ``iface`` argument specifies the :term:`interface` which will
- be later required to look up the utility
- (:class:`zope.interface.Interface`, by default). The ``name``
- argument implies the utility name; it is the empty string by
- default.
-
- See `The ZCA book <http://www.muthukadan.net/docs/zca.html>`_ for
- more information about ZCA utilities.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the :meth:`pyramid.Registry.registerUtility`
- method. The ``registry`` attribute of a :term:`Configurator`
- in your unit and integration tests is an instance of the
- :class:`pyramid.Registry` class.
- """
- reg = get_current_registry()
- reg.registerUtility(impl, iface, name=name)
- return impl
-
-deprecated('registerUtility',
- 'The registerUtility API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.registry.registerUtility method (via '
- 'e.g. "config.registry.registerUtility(..)" '
- 'method in your unit and integration tests.')
-
-def registerAdapter(impl, for_=Interface, provides=Interface, name=''):
- """ Register a ZCA adapter component.
-
- The ``impl`` argument specifies the implementation of the
- component (often a class). The ``for_`` argument implies the
- ``for`` interface type used for this registration; it is
- :class:`zope.interface.Interface` by default. If ``for`` is not a
- tuple or list, it will be converted to a one-tuple before being
- passed to underlying :meth:`pyramid.registry.registerAdapter`
- API.
-
- The ``provides`` argument specifies the ZCA 'provides' interface,
- :class:`zope.interface.Interface` by default.
-
- The ``name`` argument is the empty string by default; it implies
- the name under which the adapter is registered.
-
- See `The ZCA book <http://www.muthukadan.net/docs/zca.html>`_ for
- more information about ZCA adapters.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the :meth:`pyramid.Registry.registerAdapter`
- method. The ``registry`` attribute of a :term:`Configurator`
- in your unit and integration tests is an instance of the
- :class:`pyramid.Registry` class.
- """
- reg = get_current_registry()
- if not isinstance(for_, (tuple, list)):
- for_ = (for_,)
- reg.registerAdapter(impl, for_, provides, name=name)
- return impl
-
-deprecated('registerAdapter',
- 'The registerAdapter API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.registry.registerAdapter method (via '
- 'e.g. "config.registry.registerAdapter(..)" '
- 'method in your unit and integration tests.')
-
-def registerSubscriber(subscriber, iface=Interface):
- """ Register a ZCA subscriber component.
-
- The ``subscriber`` argument specifies the implementation of the
- subscriber component (often a function).
-
- The ``iface`` argument is the interface type for which the
- subscriber will be registered (:class:`zope.interface.Interface`
- by default). If ``iface`` is not a tuple or list, it will be
- converted to a one-tuple before being passed to the underlying ZCA
- :meth:`pyramid.registry.registerHandler` method.
-
- See `The ZCA book <http://www.muthukadan.net/docs/zca.html>`_ for
- more information about ZCA subscribers.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.add_subscriber`
- method in your unit and integration tests.
- """
- registry = get_current_registry()
- config = Configurator(registry)
- result = config.add_subscriber(subscriber, iface=iface)
- config.commit()
- return result
-
-deprecated('registerSubscriber',
- 'The testing.registerSubscriber API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.add_subscriber '
- 'method in your unit and integration tests.')
-
-def registerRoute(pattern, name, factory=None):
- """ Register a new :term:`route` using a pattern
- (e.g. ``:pagename``), a name (e.g. ``home``), and an optional root
- factory.
-
- The ``pattern`` argument implies the route pattern. The ``name``
- argument implies the route name. The ``factory`` argument implies
- a :term:`root factory` associated with the route.
-
- This API is useful for testing code that calls
- e.g. :func:`pyramid.url.route_url`.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.add_route`
- method in your unit and integration tests.
- """
- reg = get_current_registry()
- config = Configurator(registry=reg)
- result = config.add_route(name, pattern, factory=factory)
- config.commit()
- return result
-
-deprecated('registerRoute',
- 'The testing.registerRoute API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.add_route '
- 'method in your unit and integration tests.')
-
-def registerSettings(dictarg=None, **kw):
- """Register one or more 'setting' key/value pairs. A setting is
- a single key/value pair in the dictionary-ish object returned from
- the API :attr:`pyramid.registry.Registry.settings`.
-
- You may pass a dictionary::
-
- registerSettings({'external_uri':'http://example.com'})
-
- Or a set of key/value pairs::
-
- registerSettings(external_uri='http://example.com')
-
- Use of this function is required when you need to test code that calls
- the :attr:`pyramid.registry.Registry.settings` API and which uses return
- values from that API.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.add_settings`
- method in your unit and integration tests.
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- config.add_settings(dictarg, **kw)
-
-deprecated('registerSettings',
- 'The testing.registerSettings API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.add_settings '
- 'method in your unit and integration tests.')
-
class DummyRootFactory(object):
__parent__ = None
__name__ = None
@@ -423,10 +53,17 @@ class DummyRootFactory(object):
class DummySecurityPolicy(object):
""" A standin for both an IAuthentication and IAuthorization policy """
- def __init__(self, userid=None, groupids=(), permissive=True):
+ def __init__(self, userid=None, groupids=(), permissive=True,
+ remember_result=None, forget_result=None):
self.userid = userid
self.groupids = groupids
self.permissive = permissive
+ if remember_result is None:
+ remember_result = []
+ if forget_result is None:
+ forget_result = []
+ self.remember_result = remember_result
+ self.forget_result = forget_result
def authenticated_userid(self, request):
return self.userid
@@ -443,10 +80,12 @@ class DummySecurityPolicy(object):
return effective_principals
def remember(self, request, principal, **kw):
- return []
+ self.remembered = principal
+ return self.remember_result
def forget(self, request):
- return []
+ self.forgotten = True
+ return self.forget_result
def permits(self, context, principals, permission):
return self.permissive
@@ -645,7 +284,7 @@ class DummySession(dict):
@implementer(IRequest)
class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin,
- CallbackMethodsMixin):
+ CallbackMethodsMixin, InstancePropertyMixin):
""" A DummyRequest object (incompletely) imitates a :term:`request` object.
The ``params``, ``environ``, ``headers``, ``path``, and
@@ -664,7 +303,7 @@ class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin,
request. For example, by default, the DummyRequest ``GET`` and ``POST``
attributes are of type ``dict``, unlike a normal Request's GET and POST,
which are of type ``MultiDict``. If your code uses the features of
- MultiDict, you should either use a"real" :class:`pyramid.request.Request`
+ MultiDict, you should either use a real :class:`pyramid.request.Request`
or adapt your DummyRequest by replacing the attributes with ``MultiDict``
instances.
@@ -824,6 +463,8 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
# ``render_template`` and friends went behind the back of
# any existing renderer factory lookup system.
config.add_renderer(name, renderer)
+ config.add_default_view_predicates()
+ config.add_default_route_predicates()
config.commit()
global have_zca
try:
@@ -941,3 +582,37 @@ def skip_on(*platforms): # pragma: no cover
return wrapper
return decorator
skip_on.os_name = os.name # for testing
+
+@contextmanager
+def testConfig(registry=None,
+ request=None,
+ hook_zca=True,
+ autocommit=True,
+ settings=None):
+ """Returns a context manager for test set up.
+
+ This context manager calls :func:`pyramid.testing.testSetup` when
+ entering and :func:`pyramid.testing.tearDown` when exiting.
+
+ All arguments are passed directly to :func:`pyramid.testing.testSetup`.
+ If the ZCA is hooked, it will always be un-hooked in tearDown.
+
+ This context manager allows you to write test code like this:
+
+ .. code-block:: python
+ :linenos:
+
+ with testConfig() as config:
+ config.add_route('bar', '/bar/{id}')
+ req = DummyRequest()
+ resp = myview(req),
+ """
+ config = setUp(registry=registry,
+ request=request,
+ hook_zca=hook_zca,
+ autocommit=autocommit,
+ settings=settings)
+ try:
+ yield config
+ finally:
+ tearDown(unhook_zca=hook_zca)
diff --git a/pyramid/tests/fixtures/withmacro.pt b/pyramid/tests/fixtures/withmacro.pt
new file mode 100644
index 000000000..6fa654645
--- /dev/null
+++ b/pyramid/tests/fixtures/withmacro.pt
@@ -0,0 +1,7 @@
+<html>
+Outside macro
+<metal:m define-macro="foo">
+ Hello!
+</metal:m>
+</html>
+
diff --git a/pyramid/tests/pkgs/conflictapp/__init__.py b/pyramid/tests/pkgs/conflictapp/__init__.py
index 07bef0832..38116ab2f 100644
--- a/pyramid/tests/pkgs/conflictapp/__init__.py
+++ b/pyramid/tests/pkgs/conflictapp/__init__.py
@@ -18,6 +18,7 @@ def includeme(config):
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_authentication_policy(AuthTktAuthenticationPolicy(
+ 'seekri1t', hashalg='sha512'))
config.set_authorization_policy(ACLAuthorizationPolicy())
config.include('pyramid.tests.pkgs.conflictapp.included')
diff --git a/pyramid/tests/pkgs/defpermbugapp/__init__.py b/pyramid/tests/pkgs/defpermbugapp/__init__.py
index 1ce0ff32d..032e8c626 100644
--- a/pyramid/tests/pkgs/defpermbugapp/__init__.py
+++ b/pyramid/tests/pkgs/defpermbugapp/__init__.py
@@ -17,7 +17,7 @@ def z_view(request):
def includeme(config):
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.authentication import AuthTktAuthenticationPolicy
- authn_policy = AuthTktAuthenticationPolicy('seekt1t')
+ authn_policy = AuthTktAuthenticationPolicy('seekt1t', hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config.scan('pyramid.tests.pkgs.defpermbugapp')
config._set_authentication_policy(authn_policy)
diff --git a/pyramid/tests/pkgs/forbiddenapp/__init__.py b/pyramid/tests/pkgs/forbiddenapp/__init__.py
index 888dc9317..c378126fc 100644
--- a/pyramid/tests/pkgs/forbiddenapp/__init__.py
+++ b/pyramid/tests/pkgs/forbiddenapp/__init__.py
@@ -16,7 +16,7 @@ def forbidden_view(context, request):
def includeme(config):
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
- authn_policy = AuthTktAuthenticationPolicy('seekr1t')
+ authn_policy = AuthTktAuthenticationPolicy('seekr1t', hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config._set_authentication_policy(authn_policy)
config._set_authorization_policy(authz_policy)
diff --git a/pyramid/tests/pkgs/forbiddenview/__init__.py b/pyramid/tests/pkgs/forbiddenview/__init__.py
index 631a442d2..45fb8380b 100644
--- a/pyramid/tests/pkgs/forbiddenview/__init__.py
+++ b/pyramid/tests/pkgs/forbiddenview/__init__.py
@@ -20,7 +20,7 @@ def bar(request): # pragma: no cover
return Response('OK bar')
def includeme(config):
- authn_policy = AuthTktAuthenticationPolicy('seekri1')
+ authn_policy = AuthTktAuthenticationPolicy('seekri1', hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
diff --git a/pyramid/tests/pkgs/permbugapp/__init__.py b/pyramid/tests/pkgs/permbugapp/__init__.py
index 330d983ab..4868427a5 100644
--- a/pyramid/tests/pkgs/permbugapp/__init__.py
+++ b/pyramid/tests/pkgs/permbugapp/__init__.py
@@ -14,7 +14,7 @@ def test(context, request):
def includeme(config):
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
- authn_policy = AuthTktAuthenticationPolicy('seekt1t')
+ authn_policy = AuthTktAuthenticationPolicy('seekt1t', hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
diff --git a/pyramid/tests/pkgs/subrequestapp/__init__.py b/pyramid/tests/pkgs/subrequestapp/__init__.py
new file mode 100644
index 000000000..b8f44cd7f
--- /dev/null
+++ b/pyramid/tests/pkgs/subrequestapp/__init__.py
@@ -0,0 +1,50 @@
+from pyramid.config import Configurator
+from pyramid.request import Request
+
+def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq, use_tweens=False)
+ return response
+
+def view_two(request):
+ return 'This came from view_two'
+
+def view_three(request):
+ subreq = Request.blank('/view_four')
+ try:
+ return request.invoke_subrequest(subreq, use_tweens=True)
+ except: # pragma: no cover
+ request.response.body = b'Value error raised'
+ return request.response
+
+def view_four(request):
+ raise ValueError('foo')
+
+def view_five(request):
+ subreq = Request.blank('/view_four')
+ try:
+ return request.invoke_subrequest(subreq, use_tweens=False)
+ except ValueError:
+ request.response.body = b'Value error raised'
+ return request.response
+
+def excview(request):
+ request.response.status_int = 500
+ request.response.body = b'Bad stuff happened'
+ return request.response
+
+def main():
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_route('three', '/view_three')
+ config.add_route('four', '/view_four')
+ config.add_route('five', '/view_five')
+ config.add_view(excview, context=Exception)
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two', renderer='string')
+ config.add_view(view_three, route_name='three')
+ config.add_view(view_four, route_name='four')
+ config.add_view(view_five, route_name='five')
+ return config
+
diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py
index e513b9a48..123e4f9f5 100644
--- a/pyramid/tests/test_authentication.py
+++ b/pyramid/tests/test_authentication.py
@@ -1,4 +1,5 @@
import unittest
+import warnings
from pyramid import testing
from pyramid.compat import (
text_,
@@ -14,7 +15,7 @@ class TestCallbackAuthenticationPolicyDebugging(unittest.TestCase):
def tearDown(self):
del self.config
-
+
def debug(self, msg):
self.messages.append(msg)
@@ -76,6 +77,30 @@ class TestCallbackAuthenticationPolicyDebugging(unittest.TestCase):
"authenticated_userid: groupfinder callback returned []; "
"returning 'fred'")
+ def test_authenticated_userid_fails_cleaning_as_Authenticated(self):
+ request = DummyRequest(registry=self.config.registry)
+ policy = self._makeOne(userid='system.Authenticated')
+ self.assertEqual(policy.authenticated_userid(request), None)
+ self.assertEqual(len(self.messages), 1)
+ self.assertEqual(
+ self.messages[0],
+ "pyramid.tests.test_authentication.MyAuthenticationPolicy."
+ "authenticated_userid: use of userid 'system.Authenticated' is "
+ "disallowed by any built-in Pyramid security policy, returning "
+ "None")
+
+ def test_authenticated_userid_fails_cleaning_as_Everyone(self):
+ request = DummyRequest(registry=self.config.registry)
+ policy = self._makeOne(userid='system.Everyone')
+ self.assertEqual(policy.authenticated_userid(request), None)
+ self.assertEqual(len(self.messages), 1)
+ self.assertEqual(
+ self.messages[0],
+ "pyramid.tests.test_authentication.MyAuthenticationPolicy."
+ "authenticated_userid: use of userid 'system.Everyone' is "
+ "disallowed by any built-in Pyramid security policy, returning "
+ "None")
+
def test_effective_principals_no_unauthenticated_userid(self):
request = DummyRequest(registry=self.config.registry)
policy = self._makeOne()
@@ -144,6 +169,34 @@ class TestCallbackAuthenticationPolicyDebugging(unittest.TestCase):
"effective_principals: returning effective principals: "
"['system.Everyone', 'system.Authenticated', 'fred']")
+ def test_effective_principals_with_unclean_principal_Authenticated(self):
+ request = DummyRequest(registry=self.config.registry)
+ policy = self._makeOne(userid='system.Authenticated')
+ self.assertEqual(
+ policy.effective_principals(request),
+ ['system.Everyone'])
+ self.assertEqual(len(self.messages), 1)
+ self.assertEqual(
+ self.messages[0],
+ "pyramid.tests.test_authentication.MyAuthenticationPolicy."
+ "effective_principals: unauthenticated_userid returned disallowed "
+ "'system.Authenticated'; returning ['system.Everyone'] as if it "
+ "was None")
+
+ def test_effective_principals_with_unclean_principal_Everyone(self):
+ request = DummyRequest(registry=self.config.registry)
+ policy = self._makeOne(userid='system.Everyone')
+ self.assertEqual(
+ policy.effective_principals(request),
+ ['system.Everyone'])
+ self.assertEqual(len(self.messages), 1)
+ self.assertEqual(
+ self.messages[0],
+ "pyramid.tests.test_authentication.MyAuthenticationPolicy."
+ "effective_principals: unauthenticated_userid returned disallowed "
+ "'system.Everyone'; returning ['system.Everyone'] as if it "
+ "was None")
+
class TestRepozeWho1AuthenticationPolicy(unittest.TestCase):
def _getTargetClass(self):
from pyramid.authentication import RepozeWho1AuthenticationPolicy
@@ -151,7 +204,7 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase):
def _makeOne(self, identifier_name='auth_tkt', callback=None):
return self._getTargetClass()(identifier_name, callback)
-
+
def test_class_implements_IAuthenticationPolicy(self):
from zope.interface.verify import verifyClass
from pyramid.interfaces import IAuthenticationPolicy
@@ -184,6 +237,12 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase):
policy = self._makeOne()
self.assertEqual(policy.authenticated_userid(request), 'fred')
+ def test_authenticated_userid_repoze_who_userid_is_None(self):
+ request = DummyRequest(
+ {'repoze.who.identity':{'repoze.who.userid':None}})
+ policy = self._makeOne()
+ self.assertEqual(policy.authenticated_userid(request), None)
+
def test_authenticated_userid_with_callback_returns_None(self):
request = DummyRequest(
{'repoze.who.identity':{'repoze.who.userid':'fred'}})
@@ -200,6 +259,20 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase):
policy = self._makeOne(callback=callback)
self.assertEqual(policy.authenticated_userid(request), 'fred')
+ def test_authenticated_userid_unclean_principal_Authenticated(self):
+ request = DummyRequest(
+ {'repoze.who.identity':{'repoze.who.userid':'system.Authenticated'}}
+ )
+ policy = self._makeOne()
+ self.assertEqual(policy.authenticated_userid(request), None)
+
+ def test_authenticated_userid_unclean_principal_Everyone(self):
+ request = DummyRequest(
+ {'repoze.who.identity':{'repoze.who.userid':'system.Everyone'}}
+ )
+ policy = self._makeOne()
+ self.assertEqual(policy.authenticated_userid(request), None)
+
def test_effective_principals_None(self):
from pyramid.security import Everyone
request = DummyRequest({})
@@ -237,6 +310,31 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase):
policy = self._makeOne(callback=callback)
self.assertEqual(policy.effective_principals(request), [Everyone])
+ def test_effective_principals_repoze_who_userid_is_None(self):
+ from pyramid.security import Everyone
+ request = DummyRequest(
+ {'repoze.who.identity':{'repoze.who.userid':None}}
+ )
+ policy = self._makeOne()
+ self.assertEqual(policy.effective_principals(request), [Everyone])
+
+ def test_effective_principals_repoze_who_userid_is_unclean_Everyone(self):
+ from pyramid.security import Everyone
+ request = DummyRequest(
+ {'repoze.who.identity':{'repoze.who.userid':'system.Everyone'}}
+ )
+ policy = self._makeOne()
+ self.assertEqual(policy.effective_principals(request), [Everyone])
+
+ def test_effective_principals_repoze_who_userid_is_unclean_Authenticated(
+ self):
+ from pyramid.security import Everyone
+ request = DummyRequest(
+ {'repoze.who.identity':{'repoze.who.userid':'system.Authenticated'}}
+ )
+ policy = self._makeOne()
+ self.assertEqual(policy.effective_principals(request), [Everyone])
+
def test_remember_no_plugins(self):
request = DummyRequest({})
policy = self._makeOne()
@@ -251,7 +349,7 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase):
result = policy.remember(request, 'fred')
self.assertEqual(result[0], request.environ)
self.assertEqual(result[1], {'repoze.who.userid':'fred'})
-
+
def test_forget_no_plugins(self):
request = DummyRequest({})
policy = self._makeOne()
@@ -276,7 +374,7 @@ class TestRemoteUserAuthenticationPolicy(unittest.TestCase):
def _makeOne(self, environ_key='REMOTE_USER', callback=None):
return self._getTargetClass()(environ_key, callback)
-
+
def test_class_implements_IAuthenticationPolicy(self):
from zope.interface.verify import verifyClass
from pyramid.interfaces import IAuthenticationPolicy
@@ -301,7 +399,7 @@ class TestRemoteUserAuthenticationPolicy(unittest.TestCase):
request = DummyRequest({})
policy = self._makeOne()
self.assertEqual(policy.authenticated_userid(request), None)
-
+
def test_authenticated_userid(self):
request = DummyRequest({'REMOTE_USER':'fred'})
policy = self._makeOne()
@@ -326,14 +424,14 @@ class TestRemoteUserAuthenticationPolicy(unittest.TestCase):
policy = self._makeOne()
result = policy.remember(request, 'fred')
self.assertEqual(result, [])
-
+
def test_forget(self):
request = DummyRequest({'REMOTE_USER':'fred'})
policy = self._makeOne()
result = policy.forget(request)
self.assertEqual(result, [])
-class TestAutkTktAuthenticationPolicy(unittest.TestCase):
+class TestAuthTktAuthenticationPolicy(unittest.TestCase):
def _getTargetClass(self):
from pyramid.authentication import AuthTktAuthenticationPolicy
return AuthTktAuthenticationPolicy
@@ -343,23 +441,27 @@ class TestAutkTktAuthenticationPolicy(unittest.TestCase):
inst.cookie = DummyCookieHelper(cookieidentity)
return inst
+ def setUp(self):
+ self.warnings = warnings.catch_warnings()
+ self.warnings.__enter__()
+ warnings.simplefilter('ignore', DeprecationWarning)
+
+ def tearDown(self):
+ self.warnings.__exit__(None, None, None)
+
def test_allargs(self):
# pass all known args
inst = self._getTargetClass()(
'secret', callback=None, cookie_name=None, secure=False,
include_ip=False, timeout=None, reissue_time=None,
+ hashalg='sha512',
)
self.assertEqual(inst.callback, None)
- def test_class_implements_IAuthenticationPolicy(self):
- from zope.interface.verify import verifyClass
- from pyramid.interfaces import IAuthenticationPolicy
- verifyClass(IAuthenticationPolicy, self._getTargetClass())
-
- def test_instance_implements_IAuthenticationPolicy(self):
- from zope.interface.verify import verifyObject
- from pyramid.interfaces import IAuthenticationPolicy
- verifyObject(IAuthenticationPolicy, self._makeOne(None, None))
+ def test_hashalg_override(self):
+ # important to ensure hashalg is passed to cookie helper
+ inst = self._getTargetClass()('secret', hashalg='sha512')
+ self.assertEqual(inst.cookie.hashalg, 'sha512')
def test_unauthenticated_userid_returns_None(self):
request = DummyRequest({})
@@ -375,7 +477,7 @@ class TestAutkTktAuthenticationPolicy(unittest.TestCase):
request = DummyRequest({})
policy = self._makeOne(None, None)
self.assertEqual(policy.authenticated_userid(request), None)
-
+
def test_authenticated_userid_callback_returns_None(self):
request = DummyRequest({})
def callback(userid, request):
@@ -426,13 +528,23 @@ class TestAutkTktAuthenticationPolicy(unittest.TestCase):
result = policy.remember(request, 'fred', a=1, b=2)
self.assertEqual(policy.cookie.kw, {'a':1, 'b':2})
self.assertEqual(result, [])
-
+
def test_forget(self):
request = DummyRequest({})
policy = self._makeOne(None, None)
result = policy.forget(request)
self.assertEqual(result, [])
+ def test_class_implements_IAuthenticationPolicy(self):
+ from zope.interface.verify import verifyClass
+ from pyramid.interfaces import IAuthenticationPolicy
+ verifyClass(IAuthenticationPolicy, self._getTargetClass())
+
+ def test_instance_implements_IAuthenticationPolicy(self):
+ from zope.interface.verify import verifyObject
+ from pyramid.interfaces import IAuthenticationPolicy
+ verifyObject(IAuthenticationPolicy, self._makeOne(None, None))
+
class TestAuthTktCookieHelper(unittest.TestCase):
def _getTargetClass(self):
from pyramid.authentication import AuthTktCookieHelper
@@ -482,7 +594,7 @@ class TestAuthTktCookieHelper(unittest.TestCase):
request = self._makeRequest(None)
result = helper.identify(request)
self.assertEqual(result, None)
-
+
def test_identify_good_cookie_include_ip(self):
helper = self._makeOne('secret', include_ip=True)
request = self._makeRequest('ticket')
@@ -605,7 +717,7 @@ class TestAuthTktCookieHelper(unittest.TestCase):
request = self._makeRequest('ticket')
result = helper.identify(request)
self.assertEqual(result, None)
-
+
def test_identify_cookie_timed_out(self):
helper = self._makeOne('secret', timeout=1)
request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'})
@@ -828,7 +940,7 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertEqual(result[1][0], 'Set-Cookie')
self.assertTrue(result[1][1].endswith('; Path=/; Domain=example.com'))
self.assertTrue(result[1][1].startswith('auth_tkt='))
-
+
def test_remember_binary_userid(self):
import base64
helper = self._makeOne('secret')
@@ -971,6 +1083,14 @@ class TestAuthTicket(unittest.TestCase):
result = ticket.digest()
self.assertEqual(result, '126fd6224912187ee9ffa80e0b81420c')
+ def test_digest_sha512(self):
+ ticket = self._makeOne('secret', 'userid', '0.0.0.0',
+ time=10, hashalg='sha512')
+ result = ticket.digest()
+ self.assertEqual(result, '74770b2e0d5b1a54c2a466ec567a40f7d7823576aa49'\
+ '3c65fc3445e9b44097f4a80410319ef8cb256a2e60b9'\
+ 'c2002e48a9e33a3e8ee4379352c04ef96d2cb278')
+
def test_cookie_value(self):
ticket = self._makeOne('secret', 'userid', '0.0.0.0', time=10,
tokens=('a', 'b'))
@@ -989,13 +1109,13 @@ class TestBadTicket(unittest.TestCase):
self.assertTrue(isinstance(exc, Exception))
class Test_parse_ticket(unittest.TestCase):
- def _callFUT(self, secret, ticket, ip):
+ def _callFUT(self, secret, ticket, ip, hashalg='md5'):
from pyramid.authentication import parse_ticket
- return parse_ticket(secret, ticket, ip)
+ return parse_ticket(secret, ticket, ip, hashalg)
- def _assertRaisesBadTicket(self, secret, ticket, ip):
+ def _assertRaisesBadTicket(self, secret, ticket, ip, hashalg='md5'):
from pyramid.authentication import BadTicket
- self.assertRaises(BadTicket,self._callFUT, secret, ticket, ip)
+ self.assertRaises(BadTicket,self._callFUT, secret, ticket, ip, hashalg)
def test_bad_timestamp(self):
ticket = 'x' * 64
@@ -1014,6 +1134,13 @@ class Test_parse_ticket(unittest.TestCase):
result = self._callFUT('secret', ticket, '0.0.0.0')
self.assertEqual(result, (10, 'userid', ['a', 'b'], ''))
+ def test_correct_with_user_data_sha512(self):
+ ticket = '7d947cdef99bad55f8e3382a8bd089bb9dd0547f7925b7d189adc1160cab'\
+ '0ec0e6888faa41eba641a18522b26f19109f3ffafb769767ba8a26d02aae'\
+ 'ae56599a0000000auserid!a,b!'
+ result = self._callFUT('secret', ticket, '0.0.0.0', 'sha512')
+ self.assertEqual(result, (10, 'userid', ['a', 'b'], ''))
+
class TestSessionAuthenticationPolicy(unittest.TestCase):
def _getTargetClass(self):
from pyramid.authentication import SessionAuthenticationPolicy
@@ -1106,6 +1233,78 @@ class TestSessionAuthenticationPolicy(unittest.TestCase):
self.assertEqual(request.session.get('userid'), None)
self.assertEqual(result, [])
+class TestBasicAuthAuthenticationPolicy(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.authentication import BasicAuthAuthenticationPolicy as cls
+ return cls
+
+ def _makeOne(self, check):
+ return self._getTargetClass()(check, realm='SomeRealm')
+
+ def test_class_implements_IAuthenticationPolicy(self):
+ from zope.interface.verify import verifyClass
+ from pyramid.interfaces import IAuthenticationPolicy
+ verifyClass(IAuthenticationPolicy, self._getTargetClass())
+
+ def test_unauthenticated_userid(self):
+ import base64
+ request = testing.DummyRequest()
+ request.headers['Authorization'] = 'Basic %s' % base64.b64encode(
+ bytes_('chrisr:password')).decode('ascii')
+ policy = self._makeOne(None)
+ self.assertEqual(policy.unauthenticated_userid(request), 'chrisr')
+
+ def test_unauthenticated_userid_no_credentials(self):
+ request = testing.DummyRequest()
+ policy = self._makeOne(None)
+ self.assertEqual(policy.unauthenticated_userid(request), None)
+
+ def test_unauthenticated_bad_header(self):
+ request = testing.DummyRequest()
+ request.headers['Authorization'] = '...'
+ policy = self._makeOne(None)
+ self.assertEqual(policy.unauthenticated_userid(request), None)
+
+ def test_unauthenticated_userid_not_basic(self):
+ request = testing.DummyRequest()
+ request.headers['Authorization'] = 'Complicated things'
+ policy = self._makeOne(None)
+ self.assertEqual(policy.unauthenticated_userid(request), None)
+
+ def test_unauthenticated_userid_corrupt_base64(self):
+ request = testing.DummyRequest()
+ request.headers['Authorization'] = 'Basic chrisr:password'
+ policy = self._makeOne(None)
+ self.assertEqual(policy.unauthenticated_userid(request), None)
+
+ def test_authenticated_userid(self):
+ import base64
+ request = testing.DummyRequest()
+ request.headers['Authorization'] = 'Basic %s' % base64.b64encode(
+ bytes_('chrisr:password')).decode('ascii')
+ def check(username, password, request):
+ return []
+ policy = self._makeOne(check)
+ self.assertEqual(policy.authenticated_userid(request), 'chrisr')
+
+ def test_unauthenticated_userid_invalid_payload(self):
+ import base64
+ request = testing.DummyRequest()
+ request.headers['Authorization'] = 'Basic %s' % base64.b64encode(
+ bytes_('chrisrpassword')).decode('ascii')
+ policy = self._makeOne(None)
+ self.assertEqual(policy.unauthenticated_userid(request), None)
+
+ def test_remember(self):
+ policy = self._makeOne(None)
+ self.assertEqual(policy.remember(None, None), [])
+
+ def test_forget(self):
+ policy = self._makeOne(None)
+ self.assertEqual(policy.forget(None), [
+ ('WWW-Authenticate', 'Basic realm="SomeRealm"')])
+
+
class DummyContext:
pass
@@ -1130,7 +1329,7 @@ class DummyRequest:
class DummyWhoPlugin:
def remember(self, environ, identity):
return environ, identity
-
+
def forget(self, environ, identity):
return environ, identity
@@ -1150,13 +1349,14 @@ class DummyCookieHelper:
class DummyAuthTktModule(object):
def __init__(self, timestamp=0, userid='userid', tokens=(), user_data='',
- parse_raise=False):
+ parse_raise=False, hashalg="md5"):
self.timestamp = timestamp
self.userid = userid
self.tokens = tokens
self.user_data = user_data
self.parse_raise = parse_raise
- def parse_ticket(secret, value, remote_addr):
+ self.hashalg = hashalg
+ def parse_ticket(secret, value, remote_addr, hashalg):
self.secret = secret
self.value = value
self.remote_addr = remote_addr
@@ -1164,7 +1364,7 @@ class DummyAuthTktModule(object):
raise self.BadTicket()
return self.timestamp, self.userid, self.tokens, self.user_data
self.parse_ticket = parse_ticket
-
+
class AuthTicket(object):
def __init__(self, secret, userid, remote_addr, **kw):
self.secret = secret
@@ -1186,4 +1386,4 @@ class DummyAuthTktModule(object):
class DummyResponse:
def __init__(self):
self.headerlist = []
-
+
diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py
index 8d23c8753..d9f20f241 100644
--- a/pyramid/tests/test_chameleon_text.py
+++ b/pyramid/tests/test_chameleon_text.py
@@ -1,28 +1,32 @@
+import sys
import unittest
from pyramid.compat import binary_type
-from pyramid.testing import skip_on
from pyramid import testing
-class Base:
+class Base(object):
def setUp(self):
self.config = testing.setUp()
- from zope.deprecation import __show__
- __show__.off()
def tearDown(self):
testing.tearDown()
- from zope.deprecation import __show__
- __show__.on()
def _getTemplatePath(self, name):
import os
here = os.path.abspath(os.path.dirname(__file__))
return os.path.join(here, 'fixtures', name)
- def _registerUtility(self, utility, iface, name=''):
- reg = self.config.registry
- reg.registerUtility(utility, iface, name=name)
+class Test_renderer_factory(Base, unittest.TestCase):
+ def _callFUT(self, info):
+ from pyramid.chameleon_text import renderer_factory
+ return renderer_factory(info)
+
+ def test_it(self):
+ # this test is way too functional
+ from pyramid.chameleon_text import TextTemplateRenderer
+ info = DummyInfo()
+ result = self._callFUT(info)
+ self.assertEqual(result.__class__, TextTemplateRenderer)
class TextTemplateRendererTests(Base, unittest.TestCase):
def _getTargetClass(self):
@@ -45,7 +49,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
from pyramid.interfaces import ITemplateRenderer
verifyClass(ITemplateRenderer, self._getTargetClass())
- @skip_on('java')
def test_template_reified(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -54,7 +57,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template, instance.__dict__['template'])
- @skip_on('java')
def test_template_with_ichameleon_translate(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -63,7 +65,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.translate, lookup.translate)
- @skip_on('java')
def test_template_with_debug_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -73,7 +74,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, True)
- @skip_on('java')
def test_template_with_reload_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -83,7 +83,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, True)
- @skip_on('java')
def test_template_without_reload_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -93,7 +92,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, False)
- @skip_on('java')
def test_call(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -102,14 +100,12 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, binary_type))
self.assertEqual(result, b'Hello.\n')
- @skip_on('java')
def test_call_with_nondict_value(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
instance = self._makeOne(minimal, lookup)
self.assertRaises(ValueError, instance, None, {})
- @skip_on('java')
def test_call_nonminimal(self):
nonminimal = self._getTemplatePath('nonminimal.txt')
lookup = DummyLookup()
@@ -118,7 +114,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, binary_type))
self.assertEqual(result, b'Hello, Chris!\n')
- @skip_on('java')
def test_implementation(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -127,83 +122,24 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, binary_type))
self.assertEqual(result, b'Hello.\n')
-class RenderTemplateTests(Base, unittest.TestCase):
- def _callFUT(self, name, **kw):
- from pyramid.chameleon_text import render_template
- return render_template(name, **kw)
-
- @skip_on('java')
- def test_it(self):
- minimal = self._getTemplatePath('minimal.txt')
- result = self._callFUT(minimal)
- self.assertTrue(isinstance(result, binary_type))
- self.assertEqual(result, b'Hello.\n')
-
-class RenderTemplateToResponseTests(Base, unittest.TestCase):
- def _callFUT(self, name, **kw):
- from pyramid.chameleon_text import render_template_to_response
- return render_template_to_response(name, **kw)
-
- @skip_on('java')
- def test_minimal(self):
- minimal = self._getTemplatePath('minimal.txt')
- result = self._callFUT(minimal)
- from webob import Response
- self.assertTrue(isinstance(result, Response))
- self.assertEqual(result.app_iter, [b'Hello.\n'])
- self.assertEqual(result.status, '200 OK')
- self.assertEqual(len(result.headerlist), 2)
-
- @skip_on('java')
- def test_iresponsefactory_override(self):
- from webob import Response
- class Response2(Response):
- pass
- from pyramid.interfaces import IResponseFactory
- self._registerUtility(Response2, IResponseFactory)
- minimal = self._getTemplatePath('minimal.txt')
- result = self._callFUT(minimal)
- self.assertTrue(isinstance(result, Response2))
-
-class GetRendererTests(Base, unittest.TestCase):
- def _callFUT(self, name):
- from pyramid.chameleon_text import get_renderer
- return get_renderer(name)
-
- @skip_on('java')
- def test_it(self):
- from pyramid.interfaces import IRendererFactory
- class Dummy:
- template = object()
- def implementation(self): pass
- renderer = Dummy()
- def rf(spec):
- return renderer
- self._registerUtility(rf, IRendererFactory, name='foo')
- result = self._callFUT('foo')
- self.assertTrue(result is renderer)
-
-class GetTemplateTests(Base, unittest.TestCase):
- def _callFUT(self, name):
- from pyramid.chameleon_text import get_template
- return get_template(name)
-
- @skip_on('java')
- def test_it(self):
- from pyramid.interfaces import IRendererFactory
- class Dummy:
- template = object()
- def implementation(self):
- return self.template
- renderer = Dummy()
- def rf(spec):
- return renderer
- self._registerUtility(rf, IRendererFactory, name='foo')
- result = self._callFUT('foo')
- self.assertTrue(result is renderer.template)
-
class DummyLookup(object):
auto_reload=True
debug = True
def translate(self, msg): pass
+
+class DummyRegistry(object):
+ def queryUtility(self, iface, name):
+ self.queried = iface, name
+ return None
+
+ def registerUtility(self, impl, iface, name):
+ self.registered = impl, iface, name
+
+class DummyInfo(object):
+ def __init__(self):
+ self.registry = DummyRegistry()
+ self.type = '.pt'
+ self.name = 'fixtures/minimal.pt'
+ self.package = sys.modules[__name__]
+ self.settings = {}
diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py
index e7a1499e6..5ac57f869 100644
--- a/pyramid/tests/test_chameleon_zpt.py
+++ b/pyramid/tests/test_chameleon_zpt.py
@@ -1,30 +1,33 @@
+import sys
import unittest
-from pyramid.testing import skip_on
from pyramid import testing
from pyramid.compat import text_type
class Base(object):
def setUp(self):
self.config = testing.setUp()
- from zope.deprecation import __show__
- __show__.off()
def tearDown(self):
testing.tearDown()
- from zope.deprecation import __show__
- __show__.on()
def _getTemplatePath(self, name):
import os
here = os.path.abspath(os.path.dirname(__file__))
return os.path.join(here, 'fixtures', name)
- def _registerUtility(self, utility, iface, name=''):
- reg = self.config.registry
- reg.registerUtility(utility, iface, name=name)
- return reg
-
+class Test_renderer_factory(Base, unittest.TestCase):
+ def _callFUT(self, info):
+ from pyramid.chameleon_zpt import renderer_factory
+ return renderer_factory(info)
+
+ def test_it(self):
+ # this test is way too functional
+ from pyramid.chameleon_zpt import ZPTTemplateRenderer
+ info = DummyInfo()
+ result = self._callFUT(info)
+ self.assertEqual(result.__class__, ZPTTemplateRenderer)
+
class ZPTTemplateRendererTests(Base, unittest.TestCase):
def _getTargetClass(self):
from pyramid.chameleon_zpt import ZPTTemplateRenderer
@@ -46,7 +49,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
from pyramid.interfaces import ITemplateRenderer
verifyClass(ITemplateRenderer, self._getTargetClass())
- @skip_on('java')
def test_call(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -56,7 +58,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
self.assertEqual(result.rstrip('\n'),
'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
- @skip_on('java')
def test_template_reified(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -65,7 +66,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template, instance.__dict__['template'])
- @skip_on('java')
def test_template_with_ichameleon_translate(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -74,7 +74,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.translate, lookup.translate)
- @skip_on('java')
def test_template_with_debug_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -84,7 +83,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, True)
- @skip_on('java')
def test_template_without_debug_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -94,7 +92,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, False)
- @skip_on('java')
def test_template_with_reload_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -104,7 +101,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, True)
- @skip_on('java')
def test_template_without_reload_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -114,14 +110,12 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, False)
- @skip_on('java')
def test_call_with_nondict_value(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
instance = self._makeOne(minimal, lookup)
self.assertRaises(ValueError, instance, None, {})
- @skip_on('java')
def test_implementation(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -130,86 +124,40 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result.rstrip('\n'),
'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
-
-
-class RenderTemplateTests(Base, unittest.TestCase):
- def _callFUT(self, name, **kw):
- from pyramid.chameleon_zpt import render_template
- return render_template(name, **kw)
-
- @skip_on('java')
- def test_it(self):
- minimal = self._getTemplatePath('minimal.pt')
- result = self._callFUT(minimal)
- self.assertTrue(isinstance(result, text_type))
- self.assertEqual(result.rstrip('\n'),
- '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
-
-class RenderTemplateToResponseTests(Base, unittest.TestCase):
- def _callFUT(self, name, **kw):
- from pyramid.chameleon_zpt import render_template_to_response
- return render_template_to_response(name, **kw)
- @skip_on('java')
- def test_it(self):
- minimal = self._getTemplatePath('minimal.pt')
- result = self._callFUT(minimal)
- from webob import Response
- self.assertTrue(isinstance(result, Response))
- self.assertEqual(result.app_iter[0].rstrip(b'\n'),
- b'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
- self.assertEqual(result.status, '200 OK')
- self.assertEqual(len(result.headerlist), 2)
-
- @skip_on('java')
- def test_iresponsefactory_override(self):
- from webob import Response
- class Response2(Response):
- pass
- from pyramid.interfaces import IResponseFactory
- self._registerUtility(Response2, IResponseFactory)
- minimal = self._getTemplatePath('minimal.pt')
- result = self._callFUT(minimal)
- self.assertTrue(isinstance(result, Response2))
-
-class GetRendererTests(Base, unittest.TestCase):
- def _callFUT(self, name):
- from pyramid.chameleon_zpt import get_renderer
- return get_renderer(name)
-
- @skip_on('java')
- def test_it(self):
- from pyramid.interfaces import IRendererFactory
- class Dummy:
- template = object()
- def implementation(self): pass
- renderer = Dummy()
- def rf(spec):
- return renderer
- self._registerUtility(rf, IRendererFactory, name='foo')
- result = self._callFUT('foo')
- self.assertTrue(result is renderer)
-
-class GetTemplateTests(Base, unittest.TestCase):
- def _callFUT(self, name):
- from pyramid.chameleon_zpt import get_template
- return get_template(name)
-
- @skip_on('java')
- def test_it(self):
- from pyramid.interfaces import IRendererFactory
- class Dummy:
- template = object()
- def implementation(self):
- return self.template
- renderer = Dummy()
- def rf(spec):
- return renderer
- self._registerUtility(rf, IRendererFactory, name='foo')
- result = self._callFUT('foo')
- self.assertTrue(result is renderer.template)
+ def test_macro_supplied(self):
+ minimal = self._getTemplatePath('withmacro.pt')
+ lookup = DummyLookup()
+ instance = self._makeOne(minimal, lookup, macro='foo')
+ result = instance.implementation()()
+ self.assertEqual(result, '\n Hello!\n')
+
+ def test_macro_notsupplied(self):
+ minimal = self._getTemplatePath('withmacro.pt')
+ lookup = DummyLookup()
+ instance = self._makeOne(minimal, lookup)
+ result = instance.implementation()()
+ self.assertEqual(result,
+ '<html>\nOutside macro\n\n Hello!\n\n</html>\n\n')
class DummyLookup(object):
auto_reload=True
debug = True
def translate(self, msg): pass
+
+class DummyRegistry(object):
+ def queryUtility(self, iface, name):
+ self.queried = iface, name
+ return None
+
+ def registerUtility(self, impl, iface, name):
+ self.registered = impl, iface, name
+
+class DummyInfo(object):
+ def __init__(self):
+ self.registry = DummyRegistry()
+ self.type = '.pt'
+ self.name = 'fixtures/minimal.pt'
+ self.package = sys.modules[__name__]
+ self.settings = {}
+
diff --git a/pyramid/tests/test_config/__init__.py b/pyramid/tests/test_config/__init__.py
index 5b40a8c09..81d9f4965 100644
--- a/pyramid/tests/test_config/__init__.py
+++ b/pyramid/tests/test_config/__init__.py
@@ -43,3 +43,11 @@ def dummy_extend(config, discrim):
def dummy_extend2(config, discrim):
config.action(discrim, None, config.registry)
+from functools import partial
+dummy_partial = partial(dummy_extend, discrim='partial')
+
+class DummyCallable(object):
+ def __call__(self, config, discrim):
+ config.action(discrim, None, config.package)
+dummy_callable = DummyCallable()
+
diff --git a/pyramid/tests/test_config/test_adapters.py b/pyramid/tests/test_config/test_adapters.py
index 83ea0f05b..d47e012dc 100644
--- a/pyramid/tests/test_config/test_adapters.py
+++ b/pyramid/tests/test_config/test_adapters.py
@@ -81,6 +81,121 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase):
config.registry.subscribers((event.object, IDummy), None)
self.assertEqual(len(L), 1)
+ def test_add_subscriber_with_specific_type_and_predicates_True(self):
+ from zope.interface import implementer
+ from zope.interface import Interface
+ class IEvent(Interface):
+ pass
+ @implementer(IEvent)
+ class Event:
+ pass
+ L = []
+ def subscriber(event):
+ L.append(event)
+ config = self._makeOne(autocommit=True)
+ predlist = config.get_predlist('subscriber')
+ jam_predicate = predicate_maker('jam')
+ jim_predicate = predicate_maker('jim')
+ predlist.add('jam', jam_predicate)
+ predlist.add('jim', jim_predicate)
+ config.add_subscriber(subscriber, IEvent, jam=True, jim=True)
+ event = Event()
+ event.jam = True
+ event.jim = True
+ config.registry.notify(event)
+ self.assertEqual(len(L), 1)
+ self.assertEqual(L[0], event)
+ config.registry.notify(object())
+ self.assertEqual(len(L), 1)
+
+ def test_add_subscriber_with_default_type_predicates_True(self):
+ from zope.interface import implementer
+ from zope.interface import Interface
+ class IEvent(Interface):
+ pass
+ @implementer(IEvent)
+ class Event:
+ pass
+ L = []
+ def subscriber(event):
+ L.append(event)
+ config = self._makeOne(autocommit=True)
+ predlist = config.get_predlist('subscriber')
+ jam_predicate = predicate_maker('jam')
+ jim_predicate = predicate_maker('jim')
+ predlist.add('jam', jam_predicate)
+ predlist.add('jim', jim_predicate)
+ config.add_subscriber(subscriber, jam=True, jim=True)
+ event = Event()
+ event.jam = True
+ event.jim = True
+ config.registry.notify(event)
+ self.assertEqual(len(L), 1)
+ self.assertEqual(L[0], event)
+ config.registry.notify(object())
+ self.assertEqual(len(L), 1)
+
+ def test_add_subscriber_with_specific_type_and_predicates_False(self):
+ from zope.interface import implementer
+ from zope.interface import Interface
+ class IEvent(Interface):
+ pass
+ @implementer(IEvent)
+ class Event:
+ pass
+ L = []
+ def subscriber(event): L.append(event)
+ config = self._makeOne(autocommit=True)
+ predlist = config.get_predlist('subscriber')
+ jam_predicate = predicate_maker('jam')
+ jim_predicate = predicate_maker('jim')
+ predlist.add('jam', jam_predicate)
+ predlist.add('jim', jim_predicate)
+ config.add_subscriber(subscriber, IEvent, jam=True, jim=True)
+ event = Event()
+ event.jam = True
+ event.jim = False
+ config.registry.notify(event)
+ self.assertEqual(len(L), 0)
+
+ def test_add_subscriber_with_default_type_predicates_False(self):
+ from zope.interface import implementer
+ from zope.interface import Interface
+ class IEvent(Interface):
+ pass
+ @implementer(IEvent)
+ class Event:
+ pass
+ L = []
+ def subscriber(event): L.append(event)
+ config = self._makeOne(autocommit=True)
+ predlist = config.get_predlist('subscriber')
+ jam_predicate = predicate_maker('jam')
+ jim_predicate = predicate_maker('jim')
+ predlist.add('jam', jam_predicate)
+ predlist.add('jim', jim_predicate)
+ config.add_subscriber(subscriber, jam=True, jim=True)
+ event = Event()
+ event.jam = False
+ event.jim = True
+ config.registry.notify(event)
+ self.assertEqual(len(L), 0)
+
+ def test_add_subscriber_predicate(self):
+ config = self._makeOne()
+ L = []
+ def add_predicate(type, name, factory, weighs_less_than=None,
+ weighs_more_than=None):
+ self.assertEqual(type, 'subscriber')
+ self.assertEqual(name, 'name')
+ self.assertEqual(factory, 'factory')
+ self.assertEqual(weighs_more_than, 1)
+ self.assertEqual(weighs_less_than, 2)
+ L.append(1)
+ config._add_predicate = add_predicate
+ config.add_subscriber_predicate('name', 'factory', 1, 2)
+ self.assertTrue(L)
+
def test_add_response_adapter(self):
from pyramid.interfaces import IResponse
config = self._makeOne(autocommit=True)
@@ -228,4 +343,14 @@ class DummyResourceURL(object):
self.resource = resource
self.request = request
-
+def predicate_maker(name):
+ class Predicate(object):
+ def __init__(self, val, config):
+ self.val = val
+ def phash(self):
+ return 'phash'
+ text = phash
+ def __call__(self, event):
+ return getattr(event, name, None) == self.val
+ return Predicate
+
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index 1dfeda34c..e89fc077e 100644
--- a/pyramid/tests/test_config/test_factories.py
+++ b/pyramid/tests/test_config/test_factories.py
@@ -68,39 +68,40 @@ class TestFactoriesMixin(unittest.TestCase):
dummyfactory)
def test_set_request_property_with_callable(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
callable = lambda x: None
config.set_request_property(callable, name='foo')
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_request_property_with_unnamed_callable(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
def foo(self): pass
config.set_request_property(foo, reify=True)
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_request_property_with_property(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
callable = property(lambda x: None)
config.set_request_property(callable, name='foo')
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_multiple_request_properties(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne()
def foo(self): pass
bar = property(lambda x: None)
config.set_request_property(foo, reify=True)
config.set_request_property(bar, name='bar')
config.commit()
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
+ self.assertTrue('bar' in exts.descriptors)
def test_set_multiple_request_properties_conflict(self):
from pyramid.exceptions import ConfigurationConflictError
@@ -111,32 +112,48 @@ class TestFactoriesMixin(unittest.TestCase):
config.set_request_property(bar, name='bar')
self.assertRaises(ConfigurationConflictError, config.commit)
- def test_set_request_property_subscriber(self):
- from zope.interface import implementer
- from pyramid.interfaces import INewRequest
+ def test_add_request_method_with_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ callable = lambda x: None
+ config.add_request_method(callable, name='foo')
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.methods)
+
+ def test_add_request_method_with_unnamed_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ def foo(self): pass
+ config.add_request_method(foo)
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.methods)
+
+ def test_set_multiple_request_methods_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
config = self._makeOne()
- def foo(r): pass
- config.set_request_property(foo, name='foo')
- config.set_request_property(foo, name='bar', reify=True)
- config.commit()
- @implementer(INewRequest)
- class Event(object):
- request = DummyRequest(config.registry)
- event = Event()
- config.registry.notify(event)
- plist = event.request.plist
- self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar']))
-
-
-
-class DummyRequest(object):
- plist = None
-
- def __init__(self, registry):
- self.registry = registry
-
- def _set_properties(self, properties):
- if self.plist is None:
- self.plist = []
- self.plist.extend(properties)
+ def foo(self): pass
+ def bar(self): pass
+ config.add_request_method(foo, name='bar')
+ config.add_request_method(bar, name='bar')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_add_request_method_with_None_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ config.add_request_method(name='foo')
+ exts = config.registry.queryUtility(IRequestExtensions)
+ self.assertTrue(exts is None)
+
+ def test_add_request_method_with_None_callable_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
+ config = self._makeOne()
+ def bar(self): pass
+ config.add_request_method(name='foo')
+ config.add_request_method(bar, name='foo')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_add_request_method_with_None_callable_and_no_name(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(AttributeError, config.add_request_method)
+
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index 37c3de275..2cf9a269a 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -349,7 +349,7 @@ class ConfiguratorTests(unittest.TestCase):
config.setup_registry()
self.assertEqual(reg.has_listeners, True)
- def test_setup_registry_registers_default_exceptionresponse_view(self):
+ def test_setup_registry_registers_default_exceptionresponse_views(self):
from webob.exc import WSGIHTTPException
from pyramid.interfaces import IExceptionResponse
from pyramid.view import default_exceptionresponse_view
@@ -357,6 +357,7 @@ class ConfiguratorTests(unittest.TestCase):
config = self._makeOne(reg)
views = []
config.add_view = lambda *arg, **kw: views.append((arg, kw))
+ config.add_default_view_predicates = lambda *arg: None
config._add_tween = lambda *arg, **kw: False
config.setup_registry()
self.assertEqual(views[0], ((default_exceptionresponse_view,),
@@ -364,6 +365,16 @@ class ConfiguratorTests(unittest.TestCase):
self.assertEqual(views[1], ((default_exceptionresponse_view,),
{'context':WSGIHTTPException}))
+ def test_setup_registry_registers_default_view_predicates(self):
+ reg = DummyRegistry()
+ config = self._makeOne(reg)
+ vp_called = []
+ config.add_view = lambda *arg, **kw: None
+ config.add_default_view_predicates = lambda *arg: vp_called.append(True)
+ config._add_tween = lambda *arg, **kw: False
+ config.setup_registry()
+ self.assertTrue(vp_called)
+
def test_setup_registry_registers_default_webob_iresponse_adapter(self):
from webob import Response
from pyramid.interfaces import IResponse
@@ -912,12 +923,13 @@ pyramid.tests.test_config.dummy_include2""",
result = render_view_to_response(ctx, req, 'another_stacked_class2')
self.assertEqual(result, 'another_stacked_class')
- if not os.name.startswith('java'):
- # on Jython, a class without an __init__ apparently accepts
- # any number of arguments without raising a TypeError.
+ # NB: on Jython, a class without an __init__ apparently accepts
+ # any number of arguments without raising a TypeError, so the next
+ # assertion may fail there. We don't support Jython at the moment,
+ # this is just a note to a future self.
- self.assertRaises(TypeError,
- render_view_to_response, ctx, req, 'basemethod')
+ self.assertRaises(TypeError,
+ render_view_to_response, ctx, req, 'basemethod')
result = render_view_to_response(ctx, req, 'method1')
self.assertEqual(result, 'method1')
@@ -1385,13 +1397,9 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase):
try:
config.commit()
except ConfigurationConflictError as why:
- c1, c2, c3, c4, c5, c6 = _conflictFunctions(why)
+ c1, c2 = _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
@@ -1414,6 +1422,32 @@ class TestConfigurator_add_directive(unittest.TestCase):
self.assertEqual(action['callable'], None)
self.assertEqual(action['args'], test_config)
+ def test_add_directive_with_partial(self):
+ from pyramid.tests import test_config
+ config = self.config
+ config.add_directive(
+ 'dummy_partial', 'pyramid.tests.test_config.dummy_partial')
+ self.assertTrue(hasattr(config, 'dummy_partial'))
+ config.dummy_partial()
+ after = config.action_state
+ action = after.actions[-1]
+ self.assertEqual(action['discriminator'], 'partial')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
+
+ def test_add_directive_with_custom_callable(self):
+ from pyramid.tests import test_config
+ config = self.config
+ config.add_directive(
+ 'dummy_callable', 'pyramid.tests.test_config.dummy_callable')
+ self.assertTrue(hasattr(config, 'dummy_callable'))
+ config.dummy_callable('discrim')
+ after = config.action_state
+ action = after.actions[-1]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
+
def test_extend_with_python_callable(self):
from pyramid.tests import test_config
config = self.config
@@ -1682,6 +1716,7 @@ class Test_resolveConflicts(unittest.TestCase):
(3, f, (3,), {}, ('y',)),
(None, f, (5,), {}, ('y',)),
])
+ result = list(result)
self.assertEqual(
result,
[{'info': None,
@@ -1743,6 +1778,7 @@ class Test_resolveConflicts(unittest.TestCase):
expand_action(3, f, (3,), {}, ('y',)),
expand_action(None, f, (5,), {}, ('y',)),
])
+ result = list(result)
self.assertEqual(
result,
[{'info': None,
@@ -1794,32 +1830,31 @@ class Test_resolveConflicts(unittest.TestCase):
def test_it_conflict(self):
from pyramid.tests.test_config import dummyfactory as f
- self.assertRaises(
- ConfigurationConflictError,
- self._callFUT, [
- (None, f),
- (1, f, (2,), {}, ('x',), 'eek'),
- (1, f, (3,), {}, ('y',), 'ack'),
- (4, f, (4,), {}, ('y',)),
- (3, f, (3,), {}, ('y',)),
- (None, f, (5,), {}, ('y',)),
- ]
- )
+ result = self._callFUT([
+ (None, f),
+ (1, f, (2,), {}, ('x',), 'eek'), # will conflict
+ (1, f, (3,), {}, ('y',), 'ack'), # will conflict
+ (4, f, (4,), {}, ('y',)),
+ (3, f, (3,), {}, ('y',)),
+ (None, f, (5,), {}, ('y',)),
+ ])
+ self.assertRaises(ConfigurationConflictError, list, result)
def test_it_with_actions_grouped_by_order(self):
from pyramid.tests.test_config import dummyfactory as f
from pyramid.config import expand_action
result = self._callFUT([
- expand_action(None, f),
- expand_action(1, f, (1,), {}, (), 'third', 10),
+ expand_action(None, f), # X
+ expand_action(1, f, (1,), {}, (), 'third', 10), # X
expand_action(1, f, (2,), {}, ('x',), 'fourth', 10),
expand_action(1, f, (3,), {}, ('y',), 'fifth', 10),
- expand_action(2, f, (1,), {}, (), 'sixth', 10),
- expand_action(3, f, (1,), {}, (), 'seventh', 10),
- expand_action(5, f, (4,), {}, ('y',), 'eighth', 99999),
- expand_action(4, f, (3,), {}, (), 'first', 5),
+ expand_action(2, f, (1,), {}, (), 'sixth', 10), # X
+ expand_action(3, f, (1,), {}, (), 'seventh', 10), # X
+ expand_action(5, f, (4,), {}, ('y',), 'eighth', 99999), # X
+ expand_action(4, f, (3,), {}, (), 'first', 5), # X
expand_action(4, f, (5,), {}, ('y',), 'second', 5),
])
+ result = list(result)
self.assertEqual(len(result), 6)
# resolved actions should be grouped by (order, i)
self.assertEqual(
@@ -1940,10 +1975,11 @@ class DummyEvent:
pass
class DummyRegistry(object):
- def __init__(self, adaptation=None):
+ def __init__(self, adaptation=None, util=None):
self.utilities = []
self.adapters = []
self.adaptation = adaptation
+ self.util = util
def subscribers(self, events, name):
self.events = events
return events
@@ -1953,6 +1989,8 @@ class DummyRegistry(object):
self.adapters.append((arg, kw))
def queryAdapter(self, *arg, **kw):
return self.adaptation
+ def queryUtility(self, *arg, **kw):
+ return self.util
from pyramid.interfaces import IResponse
@implementer(IResponse)
diff --git a/pyramid/tests/test_config/test_predicates.py b/pyramid/tests/test_config/test_predicates.py
new file mode 100644
index 000000000..91dfb0fb6
--- /dev/null
+++ b/pyramid/tests/test_config/test_predicates.py
@@ -0,0 +1,502 @@
+import unittest
+
+from pyramid import testing
+
+from pyramid.compat import text_
+
+class TestXHRPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import XHRPredicate
+ return XHRPredicate(val, None)
+
+ def test___call___true(self):
+ inst = self._makeOne(True)
+ request = Dummy()
+ request.is_xhr = True
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne(True)
+ request = Dummy()
+ request.is_xhr = False
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text(self):
+ inst = self._makeOne(True)
+ self.assertEqual(inst.text(), 'xhr = True')
+
+ def test_phash(self):
+ inst = self._makeOne(True)
+ self.assertEqual(inst.phash(), 'xhr = True')
+
+class TestRequestMethodPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import RequestMethodPredicate
+ return RequestMethodPredicate(val, None)
+
+ def test_ctor_get_but_no_head(self):
+ inst = self._makeOne('GET')
+ self.assertEqual(inst.val, ('GET', 'HEAD'))
+
+ def test___call___true_single(self):
+ inst = self._makeOne('GET')
+ request = Dummy()
+ request.method = 'GET'
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___true_multi(self):
+ inst = self._makeOne(('GET','HEAD'))
+ request = Dummy()
+ request.method = 'GET'
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne(('GET','HEAD'))
+ request = Dummy()
+ request.method = 'POST'
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text(self):
+ inst = self._makeOne(('HEAD','GET'))
+ self.assertEqual(inst.text(), 'request_method = GET,HEAD')
+
+ def test_phash(self):
+ inst = self._makeOne(('HEAD','GET'))
+ self.assertEqual(inst.phash(), 'request_method = GET,HEAD')
+
+class TestPathInfoPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import PathInfoPredicate
+ return PathInfoPredicate(val, None)
+
+ def test_ctor_compilefail(self):
+ from pyramid.exceptions import ConfigurationError
+ self.assertRaises(ConfigurationError, self._makeOne, '\\')
+
+ def test___call___true(self):
+ inst = self._makeOne(r'/\d{2}')
+ request = Dummy()
+ request.upath_info = text_('/12')
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne(r'/\d{2}')
+ request = Dummy()
+ request.upath_info = text_('/n12')
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text(self):
+ inst = self._makeOne('/')
+ self.assertEqual(inst.text(), 'path_info = /')
+
+ def test_phash(self):
+ inst = self._makeOne('/')
+ self.assertEqual(inst.phash(), 'path_info = /')
+
+class TestRequestParamPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import RequestParamPredicate
+ return RequestParamPredicate(val, None)
+
+ def test___call___true_exists(self):
+ inst = self._makeOne('abc')
+ request = Dummy()
+ request.params = {'abc':1}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___true_withval(self):
+ inst = self._makeOne('abc=1')
+ request = Dummy()
+ request.params = {'abc':'1'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___true_multi(self):
+ inst = self._makeOne(('abc', 'def =2 '))
+ request = Dummy()
+ request.params = {'abc':'1', 'def': '2'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false_multi(self):
+ inst = self._makeOne(('abc=3', 'def =2 '))
+ request = Dummy()
+ request.params = {'abc':'3', 'def': '1'}
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test___call___false(self):
+ inst = self._makeOne('abc')
+ request = Dummy()
+ request.params = {}
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text_exists(self):
+ inst = self._makeOne('abc')
+ self.assertEqual(inst.text(), 'request_param abc')
+
+ def test_text_withval(self):
+ inst = self._makeOne('abc= 1')
+ self.assertEqual(inst.text(), 'request_param abc=1')
+
+ def test_text_multi(self):
+ inst = self._makeOne(('abc= 1', 'def'))
+ self.assertEqual(inst.text(), 'request_param abc=1,def')
+
+ def test_phash_exists(self):
+ inst = self._makeOne('abc')
+ self.assertEqual(inst.phash(), 'request_param abc')
+
+ def test_phash_withval(self):
+ inst = self._makeOne('abc= 1')
+ self.assertEqual(inst.phash(), "request_param abc=1")
+
+class TestMatchParamPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import MatchParamPredicate
+ return MatchParamPredicate(val, None)
+
+ def test___call___true_single(self):
+ inst = self._makeOne('abc=1')
+ request = Dummy()
+ request.matchdict = {'abc':'1'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+
+ def test___call___true_multi(self):
+ inst = self._makeOne(('abc=1', 'def=2'))
+ request = Dummy()
+ request.matchdict = {'abc':'1', 'def':'2'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne('abc=1')
+ request = Dummy()
+ request.matchdict = {}
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text(self):
+ inst = self._makeOne(('def= 1', 'abc =2'))
+ self.assertEqual(inst.text(), 'match_param abc=2,def=1')
+
+ def test_phash(self):
+ inst = self._makeOne(('def= 1', 'abc =2'))
+ self.assertEqual(inst.phash(), 'match_param abc=2,def=1')
+
+class TestCustomPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import CustomPredicate
+ return CustomPredicate(val, None)
+
+ def test___call___true(self):
+ def func(context, request):
+ self.assertEqual(context, None)
+ self.assertEqual(request, None)
+ return True
+ inst = self._makeOne(func)
+ result = inst(None, None)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ def func(context, request):
+ self.assertEqual(context, None)
+ self.assertEqual(request, None)
+ return False
+ inst = self._makeOne(func)
+ result = inst(None, None)
+ self.assertFalse(result)
+
+ def test_text_func_has___text__(self):
+ pred = predicate()
+ pred.__text__ = 'text'
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.text(), 'text')
+
+ def test_text_func_repr(self):
+ pred = predicate()
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.text(), 'custom predicate: object predicate')
+
+ def test_phash(self):
+ pred = predicate()
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.phash(), 'custom:1')
+
+class TestTraversePredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import TraversePredicate
+ return TraversePredicate(val, None)
+
+ def test___call__traverse_has_remainder_already(self):
+ inst = self._makeOne('/1/:a/:b')
+ info = {'traverse':'abc'}
+ request = Dummy()
+ result = inst(info, request)
+ self.assertEqual(result, True)
+ self.assertEqual(info, {'traverse':'abc'})
+
+ def test___call__traverse_matches(self):
+ inst = self._makeOne('/1/:a/:b')
+ info = {'match':{'a':'a', 'b':'b'}}
+ request = Dummy()
+ result = inst(info, request)
+ self.assertEqual(result, True)
+ self.assertEqual(info, {'match':
+ {'a':'a', 'b':'b', 'traverse':('1', 'a', 'b')}})
+
+ def test___call__traverse_matches_with_highorder_chars(self):
+ inst = self._makeOne(text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8'))
+ info = {'match':{'x':text_(b'Qu\xc3\xa9bec', 'utf-8')}}
+ request = Dummy()
+ result = inst(info, request)
+ self.assertEqual(result, True)
+ self.assertEqual(
+ info['match']['traverse'],
+ (text_(b'La Pe\xc3\xb1a', 'utf-8'),
+ text_(b'Qu\xc3\xa9bec', 'utf-8'))
+ )
+
+ def test_text(self):
+ inst = self._makeOne('/abc')
+ self.assertEqual(inst.text(), 'traverse matchdict pseudo-predicate')
+
+ def test_phash(self):
+ inst = self._makeOne('/abc')
+ self.assertEqual(inst.phash(), '')
+
+class Test_CheckCSRFTokenPredicate(unittest.TestCase):
+ def _makeOne(self, val, config):
+ from pyramid.config.predicates import CheckCSRFTokenPredicate
+ return CheckCSRFTokenPredicate(val, config)
+
+ def test_text(self):
+ inst = self._makeOne(True, None)
+ self.assertEqual(inst.text(), 'check_csrf = True')
+
+ def test_phash(self):
+ inst = self._makeOne(True, None)
+ self.assertEqual(inst.phash(), 'check_csrf = True')
+
+ def test_it_call_val_True(self):
+ inst = self._makeOne(True, None)
+ request = Dummy()
+ def check_csrf_token(req, val, raises=True):
+ self.assertEqual(req, request)
+ self.assertEqual(val, 'csrf_token')
+ self.assertEqual(raises, False)
+ return True
+ inst.check_csrf_token = check_csrf_token
+ result = inst(None, request)
+ self.assertEqual(result, True)
+
+ def test_it_call_val_str(self):
+ inst = self._makeOne('abc', None)
+ request = Dummy()
+ def check_csrf_token(req, val, raises=True):
+ self.assertEqual(req, request)
+ self.assertEqual(val, 'abc')
+ self.assertEqual(raises, False)
+ return True
+ inst.check_csrf_token = check_csrf_token
+ result = inst(None, request)
+ self.assertEqual(result, True)
+
+ def test_it_call_val_False(self):
+ inst = self._makeOne(False, None)
+ request = Dummy()
+ result = inst(None, request)
+ self.assertEqual(result, True)
+
+class TestHeaderPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import HeaderPredicate
+ return HeaderPredicate(val, None)
+
+ def test___call___true_exists(self):
+ inst = self._makeOne('abc')
+ request = Dummy()
+ request.headers = {'abc':1}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___true_withval(self):
+ inst = self._makeOne('abc:1')
+ request = Dummy()
+ request.headers = {'abc':'1'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___true_withregex(self):
+ inst = self._makeOne(r'abc:\d+')
+ request = Dummy()
+ request.headers = {'abc':'1'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false_withregex(self):
+ inst = self._makeOne(r'abc:\d+')
+ request = Dummy()
+ request.headers = {'abc':'a'}
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test___call___false(self):
+ inst = self._makeOne('abc')
+ request = Dummy()
+ request.headers = {}
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text_exists(self):
+ inst = self._makeOne('abc')
+ self.assertEqual(inst.text(), 'header abc')
+
+ def test_text_withval(self):
+ inst = self._makeOne('abc:1')
+ self.assertEqual(inst.text(), 'header abc=1')
+
+ def test_text_withregex(self):
+ inst = self._makeOne(r'abc:\d+')
+ self.assertEqual(inst.text(), r'header abc=\d+')
+
+ def test_phash_exists(self):
+ inst = self._makeOne('abc')
+ self.assertEqual(inst.phash(), 'header abc')
+
+ def test_phash_withval(self):
+ inst = self._makeOne('abc:1')
+ self.assertEqual(inst.phash(), "header abc=1")
+
+ def test_phash_withregex(self):
+ inst = self._makeOne(r'abc:\d+')
+ self.assertEqual(inst.phash(), r'header abc=\d+')
+
+class Test_PhysicalPathPredicate(unittest.TestCase):
+ def _makeOne(self, val, config):
+ from pyramid.config.predicates import PhysicalPathPredicate
+ return PhysicalPathPredicate(val, config)
+
+ def test_text(self):
+ inst = self._makeOne('/', None)
+ self.assertEqual(inst.text(), "physical_path = ('',)")
+
+ def test_phash(self):
+ inst = self._makeOne('/', None)
+ self.assertEqual(inst.phash(), "physical_path = ('',)")
+
+ def test_it_call_val_tuple_True(self):
+ inst = self._makeOne(('', 'abc'), None)
+ root = Dummy()
+ root.__name__ = ''
+ root.__parent__ = None
+ context = Dummy()
+ context.__name__ = 'abc'
+ context.__parent__ = root
+ self.assertTrue(inst(context, None))
+
+ def test_it_call_val_list_True(self):
+ inst = self._makeOne(['', 'abc'], None)
+ root = Dummy()
+ root.__name__ = ''
+ root.__parent__ = None
+ context = Dummy()
+ context.__name__ = 'abc'
+ context.__parent__ = root
+ self.assertTrue(inst(context, None))
+
+ def test_it_call_val_str_True(self):
+ inst = self._makeOne('/abc', None)
+ root = Dummy()
+ root.__name__ = ''
+ root.__parent__ = None
+ context = Dummy()
+ context.__name__ = 'abc'
+ context.__parent__ = root
+ self.assertTrue(inst(context, None))
+
+ def test_it_call_False(self):
+ inst = self._makeOne('/', None)
+ root = Dummy()
+ root.__name__ = ''
+ root.__parent__ = None
+ context = Dummy()
+ context.__name__ = 'abc'
+ context.__parent__ = root
+ self.assertFalse(inst(context, None))
+
+class Test_EffectivePrincipalsPredicate(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def _makeOne(self, val, config):
+ from pyramid.config.predicates import EffectivePrincipalsPredicate
+ return EffectivePrincipalsPredicate(val, config)
+
+ def test_text(self):
+ inst = self._makeOne(('verna', 'fred'), None)
+ self.assertEqual(inst.text(),
+ "effective_principals = ['fred', 'verna']")
+
+ def test_text_noniter(self):
+ inst = self._makeOne('verna', None)
+ self.assertEqual(inst.text(),
+ "effective_principals = ['verna']")
+
+ def test_phash(self):
+ inst = self._makeOne(('verna', 'fred'), None)
+ self.assertEqual(inst.phash(),
+ "effective_principals = ['fred', 'verna']")
+
+ def test_it_call_no_authentication_policy(self):
+ request = testing.DummyRequest()
+ inst = self._makeOne(('verna', 'fred'), None)
+ context = Dummy()
+ self.assertFalse(inst(context, request))
+
+ def test_it_call_authentication_policy_provides_superset(self):
+ request = testing.DummyRequest()
+ self.config.testing_securitypolicy('fred', groupids=('verna', 'bambi'))
+ inst = self._makeOne(('verna', 'fred'), None)
+ context = Dummy()
+ self.assertTrue(inst(context, request))
+
+ def test_it_call_authentication_policy_provides_superset_implicit(self):
+ from pyramid.security import Authenticated
+ request = testing.DummyRequest()
+ self.config.testing_securitypolicy('fred', groupids=('verna', 'bambi'))
+ inst = self._makeOne(Authenticated, None)
+ context = Dummy()
+ self.assertTrue(inst(context, request))
+
+ def test_it_call_authentication_policy_doesnt_provide_superset(self):
+ request = testing.DummyRequest()
+ self.config.testing_securitypolicy('fred')
+ inst = self._makeOne(('verna', 'fred'), None)
+ context = Dummy()
+ self.assertFalse(inst(context, request))
+
+class predicate(object):
+ def __repr__(self):
+ return 'predicate'
+ def __hash__(self):
+ return 1
+
+class Dummy(object):
+ def __init__(self, **kw):
+ self.__dict__.update(**kw)
+
diff --git a/pyramid/tests/test_config/test_routes.py b/pyramid/tests/test_config/test_routes.py
index bb47d2d7e..6fb5189f6 100644
--- a/pyramid/tests/test_config/test_routes.py
+++ b/pyramid/tests/test_config/test_routes.py
@@ -158,7 +158,7 @@ class RoutesConfiguratorMixinTests(unittest.TestCase):
def pred2(context, request): pass
config.add_route('name', 'path', custom_predicates=(pred1, pred2))
route = self._assertRoute(config, 'name', 'path', 2)
- self.assertEqual(route.predicates, [pred1, pred2])
+ self.assertEqual(len(route.predicates), 2)
def test_add_route_with_header(self):
config = self._makeOne(autocommit=True)
diff --git a/pyramid/tests/test_config/test_security.py b/pyramid/tests/test_config/test_security.py
index d05d1d471..817f6ce02 100644
--- a/pyramid/tests/test_config/test_security.py
+++ b/pyramid/tests/test_config/test_security.py
@@ -89,3 +89,12 @@ class ConfiguratorSecurityMethodsTests(unittest.TestCase):
self.assertEqual(config.registry.getUtility(IDefaultPermission),
'view')
+ def test_add_permission(self):
+ config = self._makeOne(autocommit=True)
+ config.add_permission('perm')
+ cat = config.registry.introspector.get_category('permissions')
+ self.assertEqual(len(cat), 1)
+ D = cat[0]
+ intr = D['introspectable']
+ self.assertEqual(intr['value'], 'perm')
+
diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py
index 0f5239915..c74f96375 100644
--- a/pyramid/tests/test_config/test_settings.py
+++ b/pyramid/tests/test_config/test_settings.py
@@ -47,6 +47,15 @@ class TestSettingsConfiguratorMixin(unittest.TestCase):
settings = reg.getUtility(ISettings)
self.assertEqual(settings['a'], 1)
+ def test_add_settings_settings_None(self):
+ from pyramid.registry import Registry
+ from pyramid.interfaces import ISettings
+ reg = Registry()
+ config = self._makeOne(reg)
+ config.add_settings(None, a=1)
+ settings = reg.getUtility(ISettings)
+ self.assertEqual(settings['a'], 1)
+
class TestSettings(unittest.TestCase):
def _getTargetClass(self):
diff --git a/pyramid/tests/test_config/test_testing.py b/pyramid/tests/test_config/test_testing.py
index 6c048b46d..1089f09fc 100644
--- a/pyramid/tests/test_config/test_testing.py
+++ b/pyramid/tests/test_config/test_testing.py
@@ -23,6 +23,30 @@ class TestingConfiguratorMixinTests(unittest.TestCase):
self.assertEqual(ut.groupids, ('group1', 'group2'))
self.assertEqual(ut.permissive, False)
+ def test_testing_securitypolicy_remember_result(self):
+ from pyramid.security import remember
+ config = self._makeOne(autocommit=True)
+ pol = config.testing_securitypolicy(
+ 'user', ('group1', 'group2'),
+ permissive=False, remember_result=True)
+ request = DummyRequest()
+ request.registry = config.registry
+ val = remember(request, 'fred')
+ self.assertEqual(pol.remembered, 'fred')
+ self.assertEqual(val, True)
+
+ def test_testing_securitypolicy_forget_result(self):
+ from pyramid.security import forget
+ config = self._makeOne(autocommit=True)
+ pol = config.testing_securitypolicy(
+ 'user', ('group1', 'group2'),
+ permissive=False, forget_result=True)
+ request = DummyRequest()
+ request.registry = config.registry
+ val = forget(request)
+ self.assertEqual(pol.forgotten, True)
+ self.assertEqual(val, True)
+
def test_testing_resources(self):
from pyramid.traversal import find_resource
from pyramid.interfaces import ITraverser
diff --git a/pyramid/tests/test_config/test_tweens.py b/pyramid/tests/test_config/test_tweens.py
index 0d96bf601..9c3433468 100644
--- a/pyramid/tests/test_config/test_tweens.py
+++ b/pyramid/tests/test_config/test_tweens.py
@@ -179,28 +179,12 @@ class TestTweens(unittest.TestCase):
('name2', 'factory2')])
def test_add_implicit(self):
- from pyramid.tweens import INGRESS
tweens = self._makeOne()
tweens.add_implicit('name', 'factory')
- self.assertEqual(tweens.names, ['name'])
- self.assertEqual(tweens.factories,
- {'name':'factory'})
- self.assertEqual(tweens.order, [(INGRESS, 'name')])
tweens.add_implicit('name2', 'factory2')
- self.assertEqual(tweens.names, ['name', 'name2'])
- self.assertEqual(tweens.factories,
- {'name':'factory', 'name2':'factory2'})
- self.assertEqual(tweens.order,
- [(INGRESS, 'name'), (INGRESS, 'name2')])
- tweens.add_implicit('name3', 'factory3', over='name2')
- self.assertEqual(tweens.names,
- ['name', 'name2', 'name3'])
- self.assertEqual(tweens.factories,
- {'name':'factory', 'name2':'factory2',
- 'name3':'factory3'})
- self.assertEqual(tweens.order,
- [(INGRESS, 'name'), (INGRESS, 'name2'),
- ('name3', 'name2')])
+ self.assertEqual(tweens.sorter.sorted(),
+ [('name2', 'factory2'),
+ ('name', 'factory')])
def test___call___explicit(self):
tweens = self._makeOne()
@@ -212,18 +196,13 @@ class TestTweens(unittest.TestCase):
self.assertEqual(tweens(None, None), '123')
def test___call___implicit(self):
- from pyramid.tweens import INGRESS
tweens = self._makeOne()
def factory1(handler, registry):
return handler
def factory2(handler, registry):
return '123'
- tweens.names = ['name', 'name2']
- tweens.alias_to_name = {'name':'name', 'name2':'name2'}
- tweens.name_to_alias = {'name':'name', 'name2':'name2'}
- tweens.req_under = set(['name', 'name2'])
- tweens.order = [(INGRESS, 'name'), (INGRESS, 'name2')]
- tweens.factories = {'name':factory1, 'name2':factory2}
+ tweens.add_implicit('name2', factory2)
+ tweens.add_implicit('name1', factory1)
self.assertEqual(tweens(None, None), '123')
def test_implicit_ordering_1(self):
@@ -413,7 +392,7 @@ class TestTweens(unittest.TestCase):
self.assertRaises(ConfigurationError, tweens.implicit)
def test_implicit_ordering_conflict_direct(self):
- from pyramid.config.tweens import CyclicDependencyError
+ from pyramid.exceptions import CyclicDependencyError
tweens = self._makeOne()
add = tweens.add_implicit
add('browserid', 'browserid_factory')
@@ -421,7 +400,7 @@ class TestTweens(unittest.TestCase):
self.assertRaises(CyclicDependencyError, tweens.implicit)
def test_implicit_ordering_conflict_indirect(self):
- from pyramid.config.tweens import CyclicDependencyError
+ from pyramid.exceptions import CyclicDependencyError
tweens = self._makeOne()
add = tweens.add_implicit
add('browserid', 'browserid_factory')
@@ -429,14 +408,3 @@ class TestTweens(unittest.TestCase):
add('dbt', 'dbt_factory', under='browserid', over='auth')
self.assertRaises(CyclicDependencyError, tweens.implicit)
-class TestCyclicDependencyError(unittest.TestCase):
- def _makeOne(self, cycles):
- from pyramid.config.tweens import CyclicDependencyError
- return CyclicDependencyError(cycles)
-
- def test___str__(self):
- exc = self._makeOne({'a':['c', 'd'], 'c':['a']})
- result = str(exc)
- self.assertTrue("'a' sorts over ['c', 'd']" in result)
- self.assertTrue("'c' sorts over ['a']" in result)
-
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index 1ad1fb3c1..8c3cd7455 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -1,10 +1,32 @@
import unittest
from pyramid.compat import text_
-class Test__make_predicates(unittest.TestCase):
+class TestPredicateList(unittest.TestCase):
+
+ def _makeOne(self):
+ from pyramid.config.util import PredicateList
+ from pyramid.config import predicates
+ inst = PredicateList()
+ for name, factory in (
+ ('xhr', predicates.XHRPredicate),
+ ('request_method', predicates.RequestMethodPredicate),
+ ('path_info', predicates.PathInfoPredicate),
+ ('request_param', predicates.RequestParamPredicate),
+ ('header', predicates.HeaderPredicate),
+ ('accept', predicates.AcceptPredicate),
+ ('containment', predicates.ContainmentPredicate),
+ ('request_type', predicates.RequestTypePredicate),
+ ('match_param', predicates.MatchParamPredicate),
+ ('custom', predicates.CustomPredicate),
+ ('traverse', predicates.TraversePredicate),
+ ):
+ inst.add(name, factory)
+ return inst
+
def _callFUT(self, **kw):
- from pyramid.config.util import make_predicates
- return make_predicates(**kw)
+ inst = self._makeOne()
+ config = DummyConfigurator()
+ return inst.make(config, **kw)
def test_ordering_xhr_and_request_method_trump_only_containment(self):
order1, _, _ = self._callFUT(xhr=True, request_method='GET')
@@ -12,6 +34,7 @@ class Test__make_predicates(unittest.TestCase):
self.assertTrue(order1 < order2)
def test_ordering_number_of_predicates(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
@@ -22,7 +45,7 @@ class Test__make_predicates(unittest.TestCase):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
order2, _, _ = self._callFUT(
xhr='xhr',
@@ -34,7 +57,7 @@ class Test__make_predicates(unittest.TestCase):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
order3, _, _ = self._callFUT(
xhr='xhr',
@@ -114,6 +137,7 @@ class Test__make_predicates(unittest.TestCase):
self.assertTrue(order12 > order10)
def test_ordering_importance_of_predicates(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
)
@@ -142,7 +166,7 @@ class Test__make_predicates(unittest.TestCase):
match_param='foo=bar',
)
order10, _, _ = self._callFUT(
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
self.assertTrue(order2 > order3)
@@ -155,12 +179,13 @@ class Test__make_predicates(unittest.TestCase):
self.assertTrue(order9 > order10)
def test_ordering_importance_and_number(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
)
order2, _, _ = self._callFUT(
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 < order2)
@@ -170,7 +195,7 @@ class Test__make_predicates(unittest.TestCase):
)
order2, _, _ = self._callFUT(
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
@@ -181,7 +206,7 @@ class Test__make_predicates(unittest.TestCase):
)
order2, _, _ = self._callFUT(
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 < order2)
@@ -193,18 +218,19 @@ class Test__make_predicates(unittest.TestCase):
order2, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
def test_different_custom_predicates_with_same_hash(self):
+ from pyramid.config.util import predvalseq
class PredicateWithHash(object):
def __hash__(self):
return 1
a = PredicateWithHash()
b = PredicateWithHash()
- _, _, a_phash = self._callFUT(custom=(a,))
- _, _, b_phash = self._callFUT(custom=(b,))
+ _, _, a_phash = self._callFUT(custom=predvalseq([a]))
+ _, _, b_phash = self._callFUT(custom=predvalseq([b]))
self.assertEqual(a_phash, b_phash)
def test_traverse_has_remainder_already(self):
@@ -244,12 +270,14 @@ class Test__make_predicates(unittest.TestCase):
)
def test_custom_predicates_can_affect_traversal(self):
+ from pyramid.config.util import predvalseq
def custom(info, request):
m = info['match']
m['dummy'] = 'foo'
return True
- _, predicates, _ = self._callFUT(custom=(custom,),
- traverse='/1/:dummy/:a')
+ _, predicates, _ = self._callFUT(
+ custom=predvalseq([custom]),
+ traverse='/1/:dummy/:a')
self.assertEqual(len(predicates), 2)
info = {'match':{'a':'a'}}
request = DummyRequest()
@@ -259,6 +287,7 @@ class Test__make_predicates(unittest.TestCase):
'traverse':('1', 'foo', 'a')}})
def test_predicate_text_is_correct(self):
+ from pyramid.config.util import predvalseq
_, predicates, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
@@ -268,23 +297,27 @@ class Test__make_predicates(unittest.TestCase):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),
+ custom=predvalseq(
+ [
+ DummyCustomPredicate(),
DummyCustomPredicate.classmethod_predicate,
- DummyCustomPredicate.classmethod_predicate_no_text),
+ DummyCustomPredicate.classmethod_predicate_no_text,
+ ]
+ ),
match_param='foo=bar')
- self.assertEqual(predicates[0].__text__, 'xhr = True')
- self.assertEqual(predicates[1].__text__,
- "request method = ['request_method']")
- self.assertEqual(predicates[2].__text__, 'path_info = path_info')
- self.assertEqual(predicates[3].__text__, 'request_param param')
- self.assertEqual(predicates[4].__text__, 'header header')
- self.assertEqual(predicates[5].__text__, 'accept = accept')
- self.assertEqual(predicates[6].__text__, 'containment = containment')
- self.assertEqual(predicates[7].__text__, 'request_type = request_type')
- self.assertEqual(predicates[8].__text__, "match_param ['foo=bar']")
- self.assertEqual(predicates[9].__text__, 'custom predicate')
- self.assertEqual(predicates[10].__text__, 'classmethod predicate')
- self.assertEqual(predicates[11].__text__, '<unknown custom predicate>')
+ self.assertEqual(predicates[0].text(), 'xhr = True')
+ self.assertEqual(predicates[1].text(),
+ "request_method = request_method")
+ self.assertEqual(predicates[2].text(), 'path_info = path_info')
+ self.assertEqual(predicates[3].text(), 'request_param param')
+ self.assertEqual(predicates[4].text(), 'header header')
+ self.assertEqual(predicates[5].text(), 'accept = accept')
+ self.assertEqual(predicates[6].text(), 'containment = containment')
+ self.assertEqual(predicates[7].text(), 'request_type = request_type')
+ self.assertEqual(predicates[8].text(), "match_param foo=bar")
+ self.assertEqual(predicates[9].text(), 'custom predicate')
+ self.assertEqual(predicates[10].text(), 'classmethod predicate')
+ self.assertTrue(predicates[11].text().startswith('custom predicate'))
def test_match_param_from_string(self):
_, predicates, _ = self._callFUT(match_param='foo=bar')
@@ -328,15 +361,10 @@ class Test__make_predicates(unittest.TestCase):
hash2, _, __= self._callFUT(request_method='GET')
self.assertEqual(hash1, hash2)
- def test_match_param_hashable(self):
- # https://github.com/Pylons/pyramid/issues/425
- import pyramid.testing
- def view(request): pass
- config = pyramid.testing.setUp(autocommit=False)
- config.add_route('foo', '/foo/{a}/{b}')
- config.add_view(view, route_name='foo', match_param='a=bar')
- config.add_view(view, route_name='foo', match_param=('a=bar', 'b=baz'))
- config.commit()
+ def test_unknown_predicate(self):
+ from pyramid.exceptions import ConfigurationError
+ self.assertRaises(ConfigurationError, self._callFUT, unknown=1)
+
class TestActionInfo(unittest.TestCase):
def _getTargetClass(self):
@@ -392,3 +420,7 @@ class DummyRequest:
self.params = {}
self.cookies = {}
+class DummyConfigurator(object):
+ def maybe_dotted(self, thing):
+ return thing
+
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 4435d341e..a62e5f2ea 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -118,7 +118,8 @@ class TestViewsConfigurationMixin(unittest.TestCase):
self.assertEqual(wrapper.__module__, view.__module__)
self.assertEqual(wrapper.__name__, view.__name__)
self.assertEqual(wrapper.__doc__, view.__doc__)
- self.assertEqual(wrapper.__discriminator__(None, None)[0], 'view')
+ self.assertEqual(wrapper.__discriminator__(None, None).resolve()[0],
+ 'view')
def test_add_view_view_callable_dottedname(self):
from pyramid.renderers import null_renderer
@@ -422,7 +423,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IMultiView
phash = md5()
- phash.update(b'xhr:True')
+ phash.update(b'xhr = True')
view = lambda *arg: 'NOT OK'
view.__phash__ = phash.hexdigest()
config = self._makeOne(autocommit=True)
@@ -446,7 +447,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
from pyramid.interfaces import IExceptionViewClassifier
from pyramid.interfaces import IMultiView
phash = md5()
- phash.update(b'xhr:True')
+ phash.update(b'xhr = True')
view = lambda *arg: 'NOT OK'
view.__phash__ = phash.hexdigest()
config = self._makeOne(autocommit=True)
@@ -667,6 +668,24 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request.accept = DummyAccept('text/html', 'text/html')
self.assertEqual(wrapper(None, request), 'OK2')
+ def test_add_view_mixed_case_replaces_existing_view(self):
+ from pyramid.renderers import null_renderer
+ def view(context, request): return 'OK'
+ def view2(context, request): return 'OK2'
+ def view3(context, request): return 'OK3'
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, renderer=null_renderer)
+ config.add_view(view=view2, accept='text/html', renderer=null_renderer)
+ config.add_view(view=view3, accept='text/HTML', renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ self.assertTrue(IMultiView.providedBy(wrapper))
+ self.assertEqual(len(wrapper.media_views.items()),1)
+ self.assertFalse('text/HTML' in wrapper.media_views)
+ self.assertEqual(wrapper(None, None), 'OK')
+ request = DummyRequest()
+ request.accept = DummyAccept('text/html', 'text/html')
+ self.assertEqual(wrapper(None, request), 'OK3')
+
def test_add_views_with_accept_multiview_replaces_existing(self):
from pyramid.renderers import null_renderer
def view(context, request): return 'OK'
@@ -992,8 +1011,10 @@ class TestViewsConfigurationMixin(unittest.TestCase):
wrapper = self._getViewCallable(config)
self.assertTrue(IMultiView.providedBy(wrapper))
request = self._makeRequest(config)
- self.assertEqual(wrapper.__discriminator__(foo, request)[5], IFoo)
- self.assertEqual(wrapper.__discriminator__(bar, request)[5], IBar)
+ self.assertNotEqual(
+ wrapper.__discriminator__(foo, request),
+ wrapper.__discriminator__(bar, request),
+ )
def test_add_view_with_template_renderer(self):
from pyramid.tests import test_config
@@ -1239,8 +1260,8 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def test_add_view_with_header_badregex(self):
view = lambda *arg: 'OK'
config = self._makeOne()
- self.assertRaises(ConfigurationError,
- config.add_view, view=view, header='Host:a\\')
+ config.add_view(view, header='Host:a\\')
+ self.assertRaises(ConfigurationError, config.commit)
def test_add_view_with_header_noval_match(self):
from pyramid.renderers import null_renderer
@@ -1345,8 +1366,8 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def test_add_view_with_path_info_badregex(self):
view = lambda *arg: 'OK'
config = self._makeOne()
- self.assertRaises(ConfigurationError,
- config.add_view, view=view, path_info='\\')
+ config.add_view(view, path_info='\\')
+ self.assertRaises(ConfigurationError, config.commit)
def test_add_view_with_path_info_match(self):
from pyramid.renderers import null_renderer
@@ -1720,6 +1741,38 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = view(None, request)
self.assertEqual(result, 'OK')
+ def test_add_forbidden_view_allows_other_predicates(self):
+ from pyramid.renderers import null_renderer
+ config = self._makeOne(autocommit=True)
+ # doesnt blow up
+ config.add_view_predicate('dummy', DummyPredicate)
+ config.add_forbidden_view(renderer=null_renderer, dummy='abc')
+
+ def test_add_forbidden_view_disallows_name(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, name='foo')
+
+ def test_add_forbidden_view_disallows_permission(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, permission='foo')
+
+ def test_add_forbidden_view_disallows_context(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, context='foo')
+
+ def test_add_forbidden_view_disallows_for_(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, for_='foo')
+
+ def test_add_forbidden_view_disallows_http_cache(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, http_cache='foo')
+
def test_add_notfound_view(self):
from pyramid.renderers import null_renderer
from zope.interface import implementedBy
@@ -1735,6 +1788,38 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = view(None, request)
self.assertEqual(result, (None, request))
+ def test_add_notfound_view_allows_other_predicates(self):
+ from pyramid.renderers import null_renderer
+ config = self._makeOne(autocommit=True)
+ # doesnt blow up
+ config.add_view_predicate('dummy', DummyPredicate)
+ config.add_notfound_view(renderer=null_renderer, dummy='abc')
+
+ def test_add_notfound_view_disallows_name(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, name='foo')
+
+ def test_add_notfound_view_disallows_permission(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, permission='foo')
+
+ def test_add_notfound_view_disallows_context(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, context='foo')
+
+ def test_add_notfound_view_disallows_for_(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, for_='foo')
+
+ def test_add_notfound_view_disallows_http_cache(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, http_cache='foo')
+
def test_add_notfound_view_append_slash(self):
from pyramid.response import Response
from pyramid.renderers import null_renderer
@@ -2077,11 +2162,28 @@ class TestMultiView(unittest.TestCase):
def test_add_with_phash_override_accept(self):
mv = self._makeOne()
- mv.add('view2', 100, accept='text/html', phash='abc')
- mv.add('view3', 100, accept='text/html', phash='abc')
- mv.add('view4', 99, accept='text/html', phash='def')
+ def view1(): pass
+ def view2(): pass
+ def view3(): pass
+ mv.add(view1, 100, accept='text/html', phash='abc')
+ mv.add(view2, 100, accept='text/html', phash='abc')
+ mv.add(view3, 99, accept='text/html', phash='def')
self.assertEqual(mv.media_views['text/html'],
- [(99, 'view4', 'def'), (100, 'view3', 'abc')])
+ [(99, view3, 'def'), (100, view2, 'abc')])
+
+ def test_add_with_phash_override_accept2(self):
+ mv = self._makeOne()
+ def view1(): pass
+ def view2(): pass
+ def view3(): pass
+ mv.add(view1, 100, accept='text/html', phash='abc')
+ mv.add(view2, 100, accept='text/html', phash='def')
+ mv.add(view3, 99, accept='text/html', phash='ghi')
+ self.assertEqual(mv.media_views['text/html'],
+ [(99, view3, 'ghi'),
+ (100, view1, 'abc'),
+ (100, view2, 'def')]
+ )
def test_multiple_with_functions_as_views(self):
# this failed on py3 at one point, because functions aren't orderable
@@ -2927,6 +3029,7 @@ class TestViewDeriver(unittest.TestCase):
view = lambda *arg: response
def predicate1(context, request):
return False
+ predicate1.text = lambda *arg: 'text'
deriver = self._makeOne(predicates=[predicate1])
result = deriver(view)
request = self._makeRequest()
@@ -2934,7 +3037,8 @@ class TestViewDeriver(unittest.TestCase):
try:
result(None, None)
except PredicateMismatch as e:
- self.assertEqual(e.detail, 'predicate mismatch for view <lambda>')
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view <lambda> (text)')
else: # pragma: no cover
raise AssertionError
@@ -2943,6 +3047,7 @@ class TestViewDeriver(unittest.TestCase):
def myview(request): pass
def predicate1(context, request):
return False
+ predicate1.text = lambda *arg: 'text'
deriver = self._makeOne(predicates=[predicate1])
result = deriver(myview)
request = self._makeRequest()
@@ -2950,7 +3055,29 @@ class TestViewDeriver(unittest.TestCase):
try:
result(None, None)
except PredicateMismatch as e:
- self.assertEqual(e.detail, 'predicate mismatch for view myview')
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view myview (text)')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_predicate_mismatch_exception_has_text_in_detail(self):
+ from pyramid.exceptions import PredicateMismatch
+ def myview(request): pass
+ def predicate1(context, request):
+ return True
+ predicate1.text = lambda *arg: 'pred1'
+ def predicate2(context, request):
+ return False
+ predicate2.text = lambda *arg: 'pred2'
+ deriver = self._makeOne(predicates=[predicate1, predicate2])
+ result = deriver(myview)
+ request = self._makeRequest()
+ request.method = 'POST'
+ try:
+ result(None, None)
+ except PredicateMismatch as e:
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view myview (pred2)')
else: # pragma: no cover
raise AssertionError
@@ -2996,9 +3123,11 @@ class TestViewDeriver(unittest.TestCase):
def predicate1(context, request):
predicates.append(True)
return True
+ predicate1.text = lambda *arg: 'text'
def predicate2(context, request):
predicates.append(True)
return False
+ predicate2.text = lambda *arg: 'text'
deriver = self._makeOne(predicates=[predicate1, predicate2])
result = deriver(view)
request = self._makeRequest()
@@ -3965,3 +4094,13 @@ class DummyViewDefaultsClass(object):
pass
def __call__(self):
return 'OK'
+
+class DummyPredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'dummy'
+
+ phash = text
+
diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py
deleted file mode 100644
index 0a98bcb5c..000000000
--- a/pyramid/tests/test_configuration.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import unittest
-
-class ConfiguratorTests(unittest.TestCase):
- def setUp(self):
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- from zope.deprecation import __show__
- __show__.on()
-
- def _makeOne(self, *arg, **kw):
- from pyramid.configuration import Configurator
- return Configurator(*arg, **kw)
-
- def test_autocommit_true(self):
- config = self._makeOne()
- self.assertEqual(config.autocommit, True)
-
- def test_package_is_not_None(self):
- import pyramid
- config = self._makeOne(package='pyramid')
- self.assertEqual(config.package, pyramid)
-
- def test_with_package(self):
- import pyramid
- config = self._makeOne()
- newconfig = config.with_package('pyramid')
- self.assertEqual(newconfig.package, pyramid)
-
-
diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py
index f35083c02..2c72c07e8 100644
--- a/pyramid/tests/test_events.py
+++ b/pyramid/tests/test_events.py
@@ -9,13 +9,13 @@ class NewRequestEventTests(unittest.TestCase):
def _makeOne(self, request):
return self._getTargetClass()(request)
- def test_class_implements(self):
+ def test_class_conforms_to_INewRequest(self):
from pyramid.interfaces import INewRequest
from zope.interface.verify import verifyClass
klass = self._getTargetClass()
verifyClass(INewRequest, klass)
- def test_instance_implements(self):
+ def test_instance_conforms_to_INewRequest(self):
from pyramid.interfaces import INewRequest
from zope.interface.verify import verifyObject
request = DummyRequest()
@@ -35,13 +35,13 @@ class NewResponseEventTests(unittest.TestCase):
def _makeOne(self, request, response):
return self._getTargetClass()(request, response)
- def test_class_implements(self):
+ def test_class_conforms_to_INewResponse(self):
from pyramid.interfaces import INewResponse
from zope.interface.verify import verifyClass
klass = self._getTargetClass()
verifyClass(INewResponse, klass)
- def test_instance_implements(self):
+ def test_instance_conforms_to_INewResponse(self):
from pyramid.interfaces import INewResponse
from zope.interface.verify import verifyObject
request = DummyRequest()
@@ -57,68 +57,72 @@ class NewResponseEventTests(unittest.TestCase):
self.assertEqual(inst.response, response)
class ApplicationCreatedEventTests(unittest.TestCase):
- def test_alias_object_implements(self):
- from pyramid.events import WSGIApplicationCreatedEvent
- event = WSGIApplicationCreatedEvent(object())
- from pyramid.interfaces import IWSGIApplicationCreatedEvent
- from pyramid.interfaces import IApplicationCreated
- from zope.interface.verify import verifyObject
- verifyObject(IWSGIApplicationCreatedEvent, event)
- verifyObject(IApplicationCreated, event)
+ def _getTargetClass(self):
+ from pyramid.events import ApplicationCreated
+ return ApplicationCreated
- def test_alias_class_implements(self):
- from pyramid.events import WSGIApplicationCreatedEvent
- from pyramid.interfaces import IWSGIApplicationCreatedEvent
+ def _makeOne(self, context=object()):
+ return self._getTargetClass()(context)
+
+ def test_class_conforms_to_IApplicationCreated(self):
from pyramid.interfaces import IApplicationCreated
from zope.interface.verify import verifyClass
- verifyClass(IWSGIApplicationCreatedEvent, WSGIApplicationCreatedEvent)
- verifyClass(IApplicationCreated, WSGIApplicationCreatedEvent)
+ verifyClass(IApplicationCreated, self._getTargetClass())
- def test_object_implements(self):
- from pyramid.events import ApplicationCreated
- event = ApplicationCreated(object())
+ def test_object_conforms_to_IApplicationCreated(self):
from pyramid.interfaces import IApplicationCreated
from zope.interface.verify import verifyObject
- verifyObject(IApplicationCreated, event)
+ verifyObject(IApplicationCreated, self._makeOne())
- def test_class_implements(self):
- from pyramid.events import ApplicationCreated
- from pyramid.interfaces import IApplicationCreated
+class WSGIApplicationCreatedEventTests(ApplicationCreatedEventTests):
+ def _getTargetClass(self):
+ from pyramid.events import WSGIApplicationCreatedEvent
+ return WSGIApplicationCreatedEvent
+
+ def test_class_conforms_to_IWSGIApplicationCreatedEvent(self):
+ from pyramid.interfaces import IWSGIApplicationCreatedEvent
from zope.interface.verify import verifyClass
- verifyClass(IApplicationCreated, ApplicationCreated)
+ verifyClass(IWSGIApplicationCreatedEvent, self._getTargetClass())
+
+ def test_object_conforms_to_IWSGIApplicationCreatedEvent(self):
+ from pyramid.interfaces import IWSGIApplicationCreatedEvent
+ from zope.interface.verify import verifyObject
+ verifyObject(IWSGIApplicationCreatedEvent, self._makeOne())
class ContextFoundEventTests(unittest.TestCase):
- def test_alias_class_implements(self):
+ def _getTargetClass(self):
+ from pyramid.events import ContextFound
+ return ContextFound
+
+ def _makeOne(self, request=None):
+ if request is None:
+ request = DummyRequest()
+ return self._getTargetClass()(request)
+
+ def test_class_conforms_to_IContextFound(self):
from zope.interface.verify import verifyClass
- from pyramid.events import AfterTraversal
- from pyramid.interfaces import IAfterTraversal
from pyramid.interfaces import IContextFound
- verifyClass(IAfterTraversal, AfterTraversal)
- verifyClass(IContextFound, AfterTraversal)
+ verifyClass(IContextFound, self._getTargetClass())
- def test_alias_instance_implements(self):
+ def test_instance_conforms_to_IContextFound(self):
from zope.interface.verify import verifyObject
- from pyramid.events import AfterTraversal
- from pyramid.interfaces import IAfterTraversal
from pyramid.interfaces import IContextFound
- request = DummyRequest()
- inst = AfterTraversal(request)
- verifyObject(IAfterTraversal, inst)
- verifyObject(IContextFound, inst)
+ verifyObject(IContextFound, self._makeOne())
- def test_class_implements(self):
+class AfterTraversalEventTests(ContextFoundEventTests):
+ def _getTargetClass(self):
+ from pyramid.events import AfterTraversal
+ return AfterTraversal
+
+ def test_class_conforms_to_IAfterTraversal(self):
from zope.interface.verify import verifyClass
- from pyramid.events import ContextFound
- from pyramid.interfaces import IContextFound
- verifyClass(IContextFound, ContextFound)
+ from pyramid.interfaces import IAfterTraversal
+ verifyClass(IAfterTraversal, self._getTargetClass())
- def test_instance_implements(self):
+ def test_instance_conforms_to_IAfterTraversal(self):
from zope.interface.verify import verifyObject
- from pyramid.events import ContextFound
- from pyramid.interfaces import IContextFound
- request = DummyRequest()
- inst = ContextFound(request)
- verifyObject(IContextFound, inst)
+ from pyramid.interfaces import IAfterTraversal
+ verifyObject(IAfterTraversal, self._makeOne())
class TestSubscriber(unittest.TestCase):
def setUp(self):
@@ -127,9 +131,9 @@ class TestSubscriber(unittest.TestCase):
def tearDown(self):
testing.tearDown()
- def _makeOne(self, *ifaces):
+ def _makeOne(self, *ifaces, **predicates):
from pyramid.events import subscriber
- return subscriber(*ifaces)
+ return subscriber(*ifaces, **predicates)
def test_register_single(self):
from zope.interface import Interface
@@ -186,6 +190,16 @@ class TestSubscriber(unittest.TestCase):
self.assertEqual(dummy_venusian.attached,
[(foo, dec.register, 'pyramid')])
+ def test_regsister_with_predicates(self):
+ from zope.interface import Interface
+ dec = self._makeOne(a=1)
+ def foo(): pass
+ config = DummyConfigurator()
+ scanner = Dummy()
+ scanner.config = config
+ dec.register(scanner, None, foo)
+ self.assertEqual(config.subscribed, [(foo, Interface, {'a':1})])
+
class TestBeforeRender(unittest.TestCase):
def _makeOne(self, system, val=None):
from pyramid.events import BeforeRender
@@ -260,8 +274,11 @@ class DummyConfigurator(object):
def __init__(self):
self.subscribed = []
- def add_subscriber(self, wrapped, ifaces):
- self.subscribed.append((wrapped, ifaces))
+ def add_subscriber(self, wrapped, ifaces, **predicates):
+ if not predicates:
+ self.subscribed.append((wrapped, ifaces))
+ else:
+ self.subscribed.append((wrapped, ifaces, predicates))
class DummyRegistry(object):
pass
diff --git a/pyramid/tests/test_exceptions.py b/pyramid/tests/test_exceptions.py
index 773767d89..aa5ebb376 100644
--- a/pyramid/tests/test_exceptions.py
+++ b/pyramid/tests/test_exceptions.py
@@ -74,3 +74,15 @@ class TestConfigurationExecutionError(unittest.TestCase):
exc = self._makeOne('etype', 'evalue', 'info')
self.assertEqual(str(exc), 'etype: evalue\n in:\n info')
+class TestCyclicDependencyError(unittest.TestCase):
+ def _makeOne(self, cycles):
+ from pyramid.exceptions import CyclicDependencyError
+ return CyclicDependencyError(cycles)
+
+ def test___str__(self):
+ exc = self._makeOne({'a':['c', 'd'], 'c':['a']})
+ result = str(exc)
+ self.assertTrue("'a' sorts before ['c', 'd']" in result)
+ self.assertTrue("'c' sorts before ['a']" in result)
+
+
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index 590ba0760..9a8f842aa 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -578,26 +578,49 @@ class WSGIApp2AppTest(unittest.TestCase):
res = self.testapp.get('/hello', status=200)
self.assertTrue(b'Hello' in res.body)
-if os.name != 'java': # uses chameleon
- class RendererScanAppTest(IntegrationBase, unittest.TestCase):
- package = 'pyramid.tests.pkgs.rendererscanapp'
- def test_root(self):
- res = self.testapp.get('/one', status=200)
- self.assertTrue(b'One!' in res.body)
-
- def test_two(self):
- res = self.testapp.get('/two', status=200)
- self.assertTrue(b'Two!' in res.body)
-
- def test_rescan(self):
- self.config.scan('pyramid.tests.pkgs.rendererscanapp')
- app = self.config.make_wsgi_app()
- from webtest import TestApp
- testapp = TestApp(app)
- res = testapp.get('/one', status=200)
- self.assertTrue(b'One!' in res.body)
- res = testapp.get('/two', status=200)
- self.assertTrue(b'Two!' in res.body)
+class SubrequestAppTest(unittest.TestCase):
+ def setUp(self):
+ from pyramid.tests.pkgs.subrequestapp import main
+ config = main()
+ app = config.make_wsgi_app()
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+ self.config = config
+
+ def tearDown(self):
+ self.config.end()
+
+ def test_one(self):
+ res = self.testapp.get('/view_one', status=200)
+ self.assertTrue(b'This came from view_two' in res.body)
+
+ def test_three(self):
+ res = self.testapp.get('/view_three', status=500)
+ self.assertTrue(b'Bad stuff happened' in res.body)
+
+ def test_five(self):
+ res = self.testapp.get('/view_five', status=200)
+ self.assertTrue(b'Value error raised' in res.body)
+
+class RendererScanAppTest(IntegrationBase, unittest.TestCase):
+ package = 'pyramid.tests.pkgs.rendererscanapp'
+ def test_root(self):
+ res = self.testapp.get('/one', status=200)
+ self.assertTrue(b'One!' in res.body)
+
+ def test_two(self):
+ res = self.testapp.get('/two', status=200)
+ self.assertTrue(b'Two!' in res.body)
+
+ def test_rescan(self):
+ self.config.scan('pyramid.tests.pkgs.rendererscanapp')
+ app = self.config.make_wsgi_app()
+ from webtest import TestApp
+ testapp = TestApp(app)
+ res = testapp.get('/one', status=200)
+ self.assertTrue(b'One!' in res.body)
+ res = testapp.get('/two', status=200)
+ self.assertTrue(b'Two!' in res.body)
class DummyContext(object):
pass
diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py
index 41fa9bdc4..37264aa48 100644
--- a/pyramid/tests/test_mako_templating.py
+++ b/pyramid/tests/test_mako_templating.py
@@ -31,6 +31,20 @@ class Test_renderer_factory(Base, unittest.TestCase):
from pyramid.mako_templating import IMakoLookup
return self.config.registry.getUtility(IMakoLookup, name=name)
+ def test_hyphen_filenames(self):
+ from pyramid.mako_templating import renderer_factory
+
+ info = DummyRendererInfo({
+ 'name':'app:moon-and-world.mak',
+ 'package':None,
+ 'registry':self.config.registry,
+ 'settings':{},
+ 'type': ''
+ })
+
+ result = renderer_factory(info)
+ self.assertEqual(result.path, 'app:moon-and-world.mak')
+
def test_no_directories(self):
info = DummyRendererInfo({
'name':'pyramid.tests:fixtures/helloworld.mak',
@@ -135,7 +149,7 @@ class Test_renderer_factory(Base, unittest.TestCase):
self._callFUT(info)
lookup = self._getLookup()
self.assertEqual(lookup.template_args['input_encoding'], 'utf-16')
-
+
def test_with_error_handler(self):
settings = {'mako.directories':self.templates_dir,
'mako.error_handler':'pyramid.tests'}
@@ -383,7 +397,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
result = instance.implementation().render_unicode()
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result, text_('result'))
-
+
class TestIntegration(unittest.TestCase):
def setUp(self):
import pyramid.mako_templating
@@ -406,7 +420,7 @@ class TestIntegration(unittest.TestCase):
self.config.add_settings({'reload_templates': True})
result = render('helloworld.mak', {'a':1}).replace('\r','')
self.assertEqual(result, text_('\nHello föö\n', 'utf-8'))
-
+
def test_render_inheritance(self):
from pyramid.renderers import render
result = render('helloinherit.mak', {}).replace('\r','')
@@ -434,7 +448,7 @@ class TestIntegration(unittest.TestCase):
{'a':1})
self.assertEqual(result.ubody.replace('\r', ''),
text_('\nHello föö\n', 'utf-8'))
-
+
def test_render_with_abs_path(self):
from pyramid.renderers import render
result = render('/helloworld.mak', {'a':1}).replace('\r','')
@@ -446,7 +460,7 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(
result.implementation().render_unicode().replace('\r',''),
text_('\nHello föö\n', 'utf-8'))
-
+
def test_template_not_found(self):
from pyramid.renderers import render
from mako.exceptions import TemplateLookupException
@@ -479,12 +493,42 @@ class TestPkgResourceTemplateLookup(unittest.TestCase):
result = inst.adjust_uri('a:b', None)
self.assertEqual(result, 'a:b')
+ def test_adjust_uri_asset_spec_with_modified_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('a$b', None)
+ self.assertEqual(result, 'a:b')
+
+ def test_adjust_uri_not_asset_spec_with_relativeto_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('c', 'a:b')
+ self.assertEqual(result, 'a:c')
+
+ def test_adjust_uri_not_asset_spec_with_relativeto_modified_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('c', 'a$b')
+ self.assertEqual(result, 'a:c')
+
+ def test_adjust_uri_not_asset_spec_with_relativeto_not_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('b', '../a')
+ self.assertEqual(result, '../b')
+
+ def test_adjust_uri_not_asset_spec_abs_with_relativeto_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('/c', 'a:b')
+ self.assertEqual(result, '/c')
+
+ def test_adjust_uri_asset_spec_with_relativeto_not_asset_spec_abs(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('a:b', '/c')
+ self.assertEqual(result, 'a:b')
+
def test_get_template_not_asset_spec(self):
fixturedir = self.get_fixturedir()
inst = self._makeOne(directories=[fixturedir])
result = inst.get_template('helloworld.mak')
self.assertFalse(result is None)
-
+
def test_get_template_asset_spec_with_filesystem_checks(self):
inst = self._makeOne(filesystem_checks=True)
result = inst.get_template('pyramid.tests:fixtures/helloworld.mak')
@@ -498,7 +542,7 @@ class TestPkgResourceTemplateLookup(unittest.TestCase):
self.assertFalse(result is None)
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
-
+
def test_get_template_asset_spec_missing(self):
from mako.exceptions import TopLevelLookupException
fixturedir = self.get_fixturedir()
@@ -510,7 +554,7 @@ class TestMakoRenderingException(unittest.TestCase):
def _makeOne(self, text):
from pyramid.mako_templating import MakoRenderingException
return MakoRenderingException(text)
-
+
def test_repr_and_str(self):
exc = self._makeOne('text')
self.assertEqual(str(exc), 'text')
@@ -519,7 +563,7 @@ class TestMakoRenderingException(unittest.TestCase):
class DummyLookup(object):
def __init__(self, exc=None):
self.exc = exc
-
+
def get_template(self, path):
self.path = path
return self
@@ -533,8 +577,8 @@ class DummyLookup(object):
raise self.exc
self.values = values
return text_('result')
-
+
class DummyRendererInfo(object):
def __init__(self, kw):
self.__dict__.update(kw)
-
+
diff --git a/pyramid/tests/test_path.py b/pyramid/tests/test_path.py
index 42b38d785..a07ebeffa 100644
--- a/pyramid/tests/test_path.py
+++ b/pyramid/tests/test_path.py
@@ -138,9 +138,9 @@ class TestPackageOf(unittest.TestCase):
self.assertEqual(result, tests)
def test_it_module(self):
- import pyramid.tests.test_configuration
+ import pyramid.tests.test_path
from pyramid import tests
- package = DummyPackageOrModule(pyramid.tests.test_configuration)
+ package = DummyPackageOrModule(pyramid.tests.test_path)
result = self._callFUT(package)
self.assertEqual(result, tests)
@@ -259,17 +259,15 @@ class TestPkgResourcesAssetDescriptor(unittest.TestCase):
def _makeOne(self, pkg='pyramid.tests', path='test_asset.py'):
return self._getTargetClass()(pkg, path)
- def test_class_implements(self):
+ def test_class_conforms_to_IAssetDescriptor(self):
from pyramid.interfaces import IAssetDescriptor
from zope.interface.verify import verifyClass
- klass = self._getTargetClass()
- verifyClass(IAssetDescriptor, klass)
+ verifyClass(IAssetDescriptor, self._getTargetClass())
- def test_instance_implements(self):
+ def test_instance_conforms_to_IAssetDescriptor(self):
from pyramid.interfaces import IAssetDescriptor
from zope.interface.verify import verifyObject
- inst = self._makeOne()
- verifyObject(IAssetDescriptor, inst)
+ verifyObject(IAssetDescriptor, self._makeOne())
def test_absspec(self):
inst = self._makeOne()
@@ -316,17 +314,15 @@ class TestFSAssetDescriptor(unittest.TestCase):
def _makeOne(self, path=os.path.join(here, 'test_asset.py')):
return self._getTargetClass()(path)
- def test_class_implements(self):
+ def test_class_conforms_to_IAssetDescriptor(self):
from pyramid.interfaces import IAssetDescriptor
from zope.interface.verify import verifyClass
- klass = self._getTargetClass()
- verifyClass(IAssetDescriptor, klass)
+ verifyClass(IAssetDescriptor, self._getTargetClass())
- def test_instance_implements(self):
+ def test_instance_conforms_to_IAssetDescriptor(self):
from pyramid.interfaces import IAssetDescriptor
from zope.interface.verify import verifyObject
- inst = self._makeOne()
- verifyObject(IAssetDescriptor, inst)
+ verifyObject(IAssetDescriptor, self._makeOne())
def test_absspec(self):
inst = self._makeOne()
@@ -399,7 +395,7 @@ class TestDottedNameResolver(unittest.TestCase):
self.assertEqual(result, self.__class__)
def test__zope_dottedname_style_resolve_relative_leading_dots(self):
- import pyramid.tests.test_configuration
+ import pyramid.tests.test_path
typ = self._makeOne()
result = typ._zope_dottedname_style(
'..tests.test_path.TestDottedNameResolver', pyramid.tests)
diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py
index 495d7dc23..befb714bd 100644
--- a/pyramid/tests/test_renderers.py
+++ b/pyramid/tests/test_renderers.py
@@ -292,7 +292,37 @@ class TestChameleonRendererLookup(unittest.TestCase):
if path.endswith('.pyc'): # pragma: no cover
path = path[:-1]
self.assertTrue(factory.path.startswith(path))
- self.assertEqual(factory.kw, {})
+ self.assertEqual(factory.kw, {'macro':None})
+
+ def test___call__spec_withmacro(self):
+ from pyramid.interfaces import ITemplateRenderer
+ import os
+ from pyramid import tests
+ module_name = tests.__name__
+ relpath = 'fixtures/withmacro#foo.pt'
+ renderer = {}
+ factory = DummyFactory(renderer)
+ spec = '%s:%s' % (module_name, relpath)
+ reg = self.config.registry
+ info = DummyRendererInfo({
+ 'name':spec,
+ 'package':None,
+ 'registry':reg,
+ 'settings':{},
+ 'type':'type',
+ })
+ lookup = self._makeOne(factory)
+ result = lookup(info)
+ self.assertTrue(result is renderer)
+ path = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'fixtures',
+ 'withmacro.pt')
+ self.assertTrue(factory.path.startswith(path))
+ self.assertEqual(factory.kw, {'macro':'foo'})
+ self.assertTrue(
+ reg.getUtility(ITemplateRenderer, name=spec) is renderer
+ )
def test___call__reload_assets_true(self):
import pyramid.tests
@@ -508,6 +538,7 @@ class TestRendererHelper(unittest.TestCase):
def renderer(*arg):
def respond(*arg):
return arg
+ renderer.respond = respond
return respond
self.config.registry.registerUtility(renderer, IRendererFactory,
name='.foo')
@@ -529,6 +560,11 @@ class TestRendererHelper(unittest.TestCase):
self.assertEqual(response.body[0], 'values')
self.assertEqual(response.body[1], {})
+ def test_get_renderer(self):
+ factory = self._registerRendererFactory()
+ helper = self._makeOne('loo.foo')
+ self.assertEqual(helper.get_renderer(), factory.respond)
+
def test_render_view(self):
self._registerRendererFactory()
self._registerResponseFactory()
@@ -627,6 +663,24 @@ class TestRendererHelper(unittest.TestCase):
response = helper._make_response(la.encode('utf-8'), request)
self.assertEqual(response.body, la.encode('utf-8'))
+ def test__make_response_result_is_None_no_body(self):
+ from pyramid.response import Response
+ request = testing.DummyRequest()
+ request.response = Response()
+ helper = self._makeOne('loo.foo')
+ response = helper._make_response(None, request)
+ self.assertEqual(response.body, b'')
+
+ def test__make_response_result_is_None_existing_body_not_molested(self):
+ from pyramid.response import Response
+ request = testing.DummyRequest()
+ response = Response()
+ response.body = b'abc'
+ request.response = response
+ helper = self._makeOne('loo.foo')
+ response = helper._make_response(None, request)
+ self.assertEqual(response.body, b'abc')
+
def test__make_response_with_content_type(self):
from pyramid.response import Response
request = testing.DummyRequest()
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index 6d5131013..945e36a7f 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -18,13 +18,15 @@ class TestRequest(unittest.TestCase):
def tearDown(self):
testing.tearDown()
- def _makeOne(self, environ):
- return self._getTargetClass()(environ)
-
def _getTargetClass(self):
from pyramid.request import Request
return Request
+ def _makeOne(self, environ=None):
+ if environ is None:
+ environ = {}
+ return self._getTargetClass()(environ)
+
def _registerResourceURL(self):
from pyramid.interfaces import IResourceURL
from zope.interface import Interface
@@ -36,6 +38,21 @@ class TestRequest(unittest.TestCase):
DummyResourceURL, (Interface, Interface),
IResourceURL)
+ def test_class_conforms_to_IRequest(self):
+ from zope.interface.verify import verifyClass
+ from pyramid.interfaces import IRequest
+ verifyClass(IRequest, self._getTargetClass())
+
+ def test_instance_conforms_to_IRequest(self):
+ from zope.interface.verify import verifyObject
+ from pyramid.interfaces import IRequest
+ verifyObject(IRequest, self._makeOne())
+
+ def test_ResponseClass_is_pyramid_Response(self):
+ from pyramid.response import Response
+ cls = self._getTargetClass()
+ self.assertEqual(cls.ResponseClass, Response)
+
def test_charset_defaults_to_utf8(self):
r = self._makeOne({'PATH_INFO':'/'})
self.assertEqual(r.charset, 'UTF-8')
@@ -61,25 +78,15 @@ class TestRequest(unittest.TestCase):
request.charset = None
self.assertEqual(request.GET['la'], text_(b'La Pe\xf1a'))
- def test_class_implements(self):
- from pyramid.interfaces import IRequest
- klass = self._getTargetClass()
- self.assertTrue(IRequest.implementedBy(klass))
-
- def test_instance_provides(self):
- from pyramid.interfaces import IRequest
- inst = self._makeOne({})
- self.assertTrue(IRequest.providedBy(inst))
-
def test_tmpl_context(self):
from pyramid.request import TemplateContext
- inst = self._makeOne({})
+ inst = self._makeOne()
result = inst.tmpl_context
self.assertEqual(result.__class__, TemplateContext)
def test_session_configured(self):
from pyramid.interfaces import ISessionFactory
- inst = self._makeOne({})
+ inst = self._makeOne()
def factory(request):
return 'orangejuice'
self.config.registry.registerUtility(factory, ISessionFactory)
@@ -88,12 +95,12 @@ class TestRequest(unittest.TestCase):
self.assertEqual(inst.__dict__['session'], 'orangejuice')
def test_session_not_configured(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
inst.registry = self.config.registry
self.assertRaises(AttributeError, getattr, inst, 'session')
def test_setattr_and_getattr_dotnotation(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
inst.foo = 1
self.assertEqual(inst.foo, 1)
@@ -105,7 +112,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(environ, {}) # make sure we're not using adhoc attrs
def test_add_response_callback(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
self.assertEqual(inst.response_callbacks, ())
def callback(request, response):
""" """
@@ -115,7 +122,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(inst.response_callbacks, [callback, callback])
def test__process_response_callbacks(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
def callback1(request, response):
request.called1 = True
response.called1 = True
@@ -132,7 +139,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(inst.response_callbacks, [])
def test_add_finished_callback(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
self.assertEqual(inst.finished_callbacks, ())
def callback(request):
""" """
@@ -142,7 +149,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(inst.finished_callbacks, [callback, callback])
def test__process_finished_callbacks(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
def callback1(request):
request.called1 = True
def callback2(request):
@@ -219,13 +226,13 @@ class TestRequest(unittest.TestCase):
('pyramid.tests:static/foo.css', request, {}) )
def test_is_response_false(self):
- request = self._makeOne({})
+ request = self._makeOne()
request.registry = self.config.registry
self.assertEqual(request.is_response('abc'), False)
def test_is_response_false_adapter_is_not_self(self):
from pyramid.interfaces import IResponse
- request = self._makeOne({})
+ request = self._makeOne()
request.registry = self.config.registry
def adapter(ob):
return object()
@@ -237,7 +244,7 @@ class TestRequest(unittest.TestCase):
def test_is_response_adapter_true(self):
from pyramid.interfaces import IResponse
- request = self._makeOne({})
+ request = self._makeOne()
request.registry = self.config.registry
class Foo(object):
pass
@@ -277,7 +284,7 @@ class TestRequest(unittest.TestCase):
self.assertRaises(ValueError, getattr, request, 'json_body')
def test_set_property(self):
- request = self._makeOne({})
+ request = self._makeOne()
opts = [2, 1]
def connect(obj):
return opts.pop()
@@ -286,7 +293,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(2, request.db)
def test_set_property_reify(self):
- request = self._makeOne({})
+ request = self._makeOne()
opts = [2, 1]
def connect(obj):
return opts.pop()
@@ -546,18 +553,6 @@ class Test_call_app_with_subpath_as_path_info(unittest.TestCase):
self.assertEqual(request.environ['SCRIPT_NAME'], '/' + encoded)
self.assertEqual(request.environ['PATH_INFO'], '/' + encoded)
- def test_it_removes_bfg_routes_info(self):
- request = DummyRequest({})
- request.environ['bfg.routes.route'] = True
- request.environ['bfg.routes.matchdict'] = True
- response = self._callFUT(request, 'app')
- self.assertTrue(request.copied)
- self.assertEqual(response, 'app')
- self.assertEqual(request.environ['SCRIPT_NAME'], '')
- self.assertEqual(request.environ['PATH_INFO'], '/')
- self.assertFalse('bfg.routes.route' in request.environ)
- self.assertFalse('bfg.routes.matchdict' in request.environ)
-
class DummyRequest:
def __init__(self, environ=None):
if environ is None:
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index eb9b7285d..778b27473 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -312,6 +312,38 @@ class TestRouter(unittest.TestCase):
self.assertEqual(app_iter, [b'abc'])
self.assertEqual(start_response.status, '200 OK')
+ def test_call_with_request_extensions(self):
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IRequestExtensions
+ from pyramid.interfaces import IRequest
+ from pyramid.request import Request
+ context = DummyContext()
+ self._registerTraverserFactory(context)
+ class Extensions(object):
+ def __init__(self):
+ self.methods = {}
+ self.descriptors = {}
+ extensions = Extensions()
+ L = []
+ request = Request.blank('/')
+ request.request_iface = IRequest
+ request.registry = self.registry
+ request._set_extensions = lambda *x: L.extend(x)
+ def request_factory(environ):
+ return request
+ self.registry.registerUtility(extensions, IRequestExtensions)
+ environ = self._makeEnviron()
+ response = DummyResponse()
+ response.app_iter = ['Hello world']
+ view = DummyView(response)
+ self._registerView(self.config.derive_view(view), '',
+ IViewClassifier, None, None)
+ router = self._makeOne()
+ router.request_factory = request_factory
+ start_response = DummyStartResponse()
+ router(environ, start_response)
+ self.assertEqual(L, [extensions])
+
def test_call_view_registered_nonspecific_default_path(self):
from pyramid.interfaces import IViewClassifier
context = DummyContext()
@@ -646,8 +678,6 @@ class TestRouter(unittest.TestCase):
self.assertEqual(request.context, context)
self.assertEqual(request.root, root)
matchdict = {'action':'action1', 'article':'article1'}
- self.assertEqual(environ['bfg.routes.matchdict'], matchdict)
- self.assertEqual(environ['bfg.routes.route'].name, 'foo')
self.assertEqual(request.matchdict, matchdict)
self.assertEqual(request.matched_route.name, 'foo')
self.assertEqual(len(logger.messages), 1)
@@ -712,8 +742,6 @@ class TestRouter(unittest.TestCase):
self.assertEqual(request.context, context)
self.assertEqual(request.root, root)
matchdict = {'action':'action1', 'article':'article1'}
- self.assertEqual(environ['bfg.routes.matchdict'], matchdict)
- self.assertEqual(environ['bfg.routes.route'].name, 'foo')
self.assertEqual(request.matchdict, matchdict)
self.assertEqual(request.matched_route.name, 'foo')
self.assertTrue(IFoo.providedBy(request))
diff --git a/pyramid/tests/test_scaffolds/test_copydir.py b/pyramid/tests/test_scaffolds/test_copydir.py
index 68cefbe6e..d757b837c 100644
--- a/pyramid/tests/test_scaffolds/test_copydir.py
+++ b/pyramid/tests/test_scaffolds/test_copydir.py
@@ -176,6 +176,14 @@ class Test_makedirs(unittest.TestCase):
self._callFUT(target, 2, None)
shutil.rmtree(tmpdir)
+ def test_makedirs_no_parent_dir(self):
+ import shutil
+ import tempfile
+ tmpdir = tempfile.mkdtemp()
+ target = os.path.join(tmpdir, 'nonexistent_subdir', 'non2')
+ self._callFUT(target, 2, None)
+ shutil.rmtree(tmpdir)
+
class Test_support_functions(unittest.TestCase):
def _call_html_quote(self, *arg, **kw):
diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py
index a99ae53fe..a36d1ed71 100644
--- a/pyramid/tests/test_scripting.py
+++ b/pyramid/tests/test_scripting.py
@@ -5,31 +5,37 @@ class Test_get_root(unittest.TestCase):
from pyramid.scripting import get_root
return get_root(app, request)
+ def _makeRegistry(self):
+ return DummyRegistry([DummyFactory])
+
def test_it_norequest(self):
- app = DummyApp(registry=dummy_registry)
+ registry = self._makeRegistry()
+ app = DummyApp(registry=registry)
root, closer = self._callFUT(app)
self.assertEqual(len(app.threadlocal_manager.pushed), 1)
pushed = app.threadlocal_manager.pushed[0]
- self.assertEqual(pushed['registry'], dummy_registry)
+ self.assertEqual(pushed['registry'], registry)
self.assertEqual(pushed['request'].registry, app.registry)
self.assertEqual(len(app.threadlocal_manager.popped), 0)
closer()
self.assertEqual(len(app.threadlocal_manager.popped), 1)
def test_it_withrequest(self):
- app = DummyApp(registry=dummy_registry)
+ registry = self._makeRegistry()
+ app = DummyApp(registry=registry)
request = DummyRequest({})
root, closer = self._callFUT(app, request)
self.assertEqual(len(app.threadlocal_manager.pushed), 1)
pushed = app.threadlocal_manager.pushed[0]
- self.assertEqual(pushed['registry'], dummy_registry)
+ self.assertEqual(pushed['registry'], registry)
self.assertEqual(pushed['request'], request)
self.assertEqual(len(app.threadlocal_manager.popped), 0)
closer()
self.assertEqual(len(app.threadlocal_manager.popped), 1)
def test_it_requestfactory_overridden(self):
- app = DummyApp(registry=dummy_registry)
+ registry = self._makeRegistry()
+ app = DummyApp(registry=registry)
root, closer = self._callFUT(app)
self.assertEqual(len(app.threadlocal_manager.pushed), 1)
pushed = app.threadlocal_manager.pushed[0]
@@ -40,8 +46,10 @@ class Test_prepare(unittest.TestCase):
from pyramid.scripting import prepare
return prepare(request, registry)
- def _makeRegistry(self):
- return DummyRegistry(DummyFactory)
+ def _makeRegistry(self, L=None):
+ if L is None:
+ L = [None, DummyFactory]
+ return DummyRegistry(L)
def setUp(self):
from pyramid.threadlocal import manager
@@ -53,21 +61,22 @@ class Test_prepare(unittest.TestCase):
self.assertRaises(ConfigurationError, self._callFUT)
def test_it_norequest(self):
- registry = self._makeRegistry()
+ registry = self._makeRegistry([DummyFactory, None, DummyFactory])
info = self._callFUT(registry=registry)
- root, closer = info['root'], info['closer']
+ root, closer, request = info['root'], info['closer'], info['request']
pushed = self.manager.get()
self.assertEqual(pushed['registry'], registry)
self.assertEqual(pushed['request'].registry, registry)
self.assertEqual(root.a, (pushed['request'],))
closer()
self.assertEqual(self.default, self.manager.get())
+ self.assertEqual(request.context, root)
- def test_it_withrequest(self):
+ def test_it_withrequest_hasregistry(self):
request = DummyRequest({})
registry = request.registry = self._makeRegistry()
info = self._callFUT(request=request)
- root, closer = info['root'], info['closer']
+ root, closer, request = info['root'], info['closer'], info['request']
pushed = self.manager.get()
self.assertEqual(pushed['request'], request)
self.assertEqual(pushed['registry'], registry)
@@ -75,12 +84,24 @@ class Test_prepare(unittest.TestCase):
self.assertEqual(root.a, (request,))
closer()
self.assertEqual(self.default, self.manager.get())
+ self.assertEqual(request.context, root)
+ self.assertEqual(request.registry, registry)
+
+ def test_it_withrequest_noregistry(self):
+ request = DummyRequest({})
+ registry = self._makeRegistry()
+ info = self._callFUT(request=request, registry=registry)
+ root, closer, request = info['root'], info['closer'], info['request']
+ closer()
+ self.assertEqual(request.context, root)
+ # should be set by prepare
+ self.assertEqual(request.registry, registry)
def test_it_with_request_and_registry(self):
request = DummyRequest({})
registry = request.registry = self._makeRegistry()
info = self._callFUT(request=request, registry=registry)
- root, closer = info['root'], info['closer']
+ root, closer, root = info['root'], info['closer'], info['root']
pushed = self.manager.get()
self.assertEqual(pushed['request'], request)
self.assertEqual(pushed['registry'], registry)
@@ -88,21 +109,44 @@ class Test_prepare(unittest.TestCase):
self.assertEqual(root.a, (request,))
closer()
self.assertEqual(self.default, self.manager.get())
+ self.assertEqual(request.context, root)
+
+ def test_it_with_request_context_already_set(self):
+ request = DummyRequest({})
+ context = Dummy()
+ request.context = context
+ registry = request.registry = self._makeRegistry()
+ info = self._callFUT(request=request, registry=registry)
+ root, closer, root = info['root'], info['closer'], info['root']
+ closer()
+ self.assertEqual(request.context, context)
+
+ def test_it_with_extensions(self):
+ exts = Dummy()
+ request = DummyRequest({})
+ registry = request.registry = self._makeRegistry([exts, DummyFactory])
+ info = self._callFUT(request=request, registry=registry)
+ self.assertEqual(request.extensions, exts)
+ root, closer = info['root'], info['closer']
+ closer()
class Test__make_request(unittest.TestCase):
def _callFUT(self, path='/', registry=None):
from pyramid.scripting import _make_request
return _make_request(path, registry)
+ def _makeRegistry(self):
+ return DummyRegistry([DummyFactory])
+
def test_it_with_registry(self):
- request = self._callFUT('/', dummy_registry)
+ registry = self._makeRegistry()
+ request = self._callFUT('/', registry)
self.assertEqual(request.environ['path'], '/')
- self.assertEqual(request.registry, dummy_registry)
+ self.assertEqual(request.registry, registry)
def test_it_with_no_registry(self):
from pyramid.config import global_registries
- # keep registry local so that global_registries is cleared after
- registry = DummyRegistry(DummyFactory)
+ registry = self._makeRegistry()
global_registries.add(registry)
try:
request = self._callFUT('/hello')
@@ -127,13 +171,13 @@ class DummyFactory(object):
self.kw = kw
class DummyRegistry(object):
- def __init__(self, factory=None):
- self.factory = factory
-
- def queryUtility(self, iface, default=None):
- return self.factory or default
+ def __init__(self, utilities):
+ self.utilities = utilities
-dummy_registry = DummyRegistry(DummyFactory)
+ def queryUtility(self, iface, default=None): # pragma: no cover
+ if self.utilities:
+ return self.utilities.pop(0)
+ return default
class DummyApp:
def __init__(self, registry=None):
@@ -156,6 +200,10 @@ class DummyThreadLocalManager:
self.popped.append(True)
class DummyRequest:
+ matchdict = None
+ matched_route = None
def __init__(self, environ):
self.environ = environ
-
+
+ def _set_extensions(self, exts):
+ self.extensions = exts
diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py
index 680d48cee..6a919c31b 100644
--- a/pyramid/tests/test_scripts/test_pviews.py
+++ b/pyramid/tests/test_scripts/test_pviews.py
@@ -309,7 +309,7 @@ class TestPViewsCommand(unittest.TestCase):
L = []
command.out = L.append
def predicate(): pass
- predicate.__text__ = "predicate = x"
+ predicate.text = lambda *arg: "predicate = x"
view = dummy.DummyView(context='context', view_name='a')
view.__predicates__ = [predicate]
command._find_view = lambda arg1, arg2: view
@@ -448,7 +448,7 @@ class TestPViewsCommand(unittest.TestCase):
L = []
command.out = L.append
def predicate(): pass
- predicate.__text__ = "predicate = x"
+ predicate.text = lambda *arg: "predicate = x"
view = dummy.DummyView(context='context')
view.__name__ = 'view'
view.__view_attr__ = 'call'
diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py
index ba9538b01..e530e33ca 100644
--- a/pyramid/tests/test_security.py
+++ b/pyramid/tests/test_security.py
@@ -131,19 +131,37 @@ class TestViewExecutionPermitted(unittest.TestCase):
return checker
def test_no_permission(self):
+ from zope.interface import Interface
from pyramid.threadlocal import get_current_registry
from pyramid.interfaces import ISettings
+ from pyramid.interfaces import IView
+ from pyramid.interfaces import IViewClassifier
settings = dict(debug_authorization=True)
reg = get_current_registry()
reg.registerUtility(settings, ISettings)
context = DummyContext()
request = DummyRequest({})
+ class DummyView(object):
+ pass
+ view = DummyView()
+ reg.registerAdapter(view, (IViewClassifier, Interface, Interface),
+ IView, '')
result = self._callFUT(context, request, '')
msg = result.msg
self.assertTrue("Allowed: view name '' in context" in msg)
self.assertTrue('(no permission defined)' in msg)
self.assertEqual(result, True)
+ def test_no_view_registered(self):
+ from pyramid.threadlocal import get_current_registry
+ from pyramid.interfaces import ISettings
+ settings = dict(debug_authorization=True)
+ reg = get_current_registry()
+ reg.registerUtility(settings, ISettings)
+ context = DummyContext()
+ request = DummyRequest({})
+ self.assertRaises(TypeError, self._callFUT, context, request, '')
+
def test_with_permission(self):
from zope.interface import Interface
from zope.interface import directlyProvides
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index 6d75c7950..b3e0e20c4 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -205,6 +205,48 @@ class TestUnencryptedCookieSession(unittest.TestCase):
self.assertTrue(token)
self.assertTrue('_csrft_' in session)
+ def test_serialize_option(self):
+ from pyramid.response import Response
+ secret = 'secret'
+ request = testing.DummyRequest()
+ session = self._makeOne(request,
+ signed_serialize=dummy_signed_serialize)
+ session['key'] = 'value'
+ response = Response()
+ self.assertEqual(session._set_cookie(response), True)
+ cookie = response.headerlist[-1][1]
+ expected_cookieval = dummy_signed_serialize(
+ (session.accessed, session.created, {'key': 'value'}), secret)
+ response = Response()
+ response.set_cookie('session', expected_cookieval)
+ expected_cookie = response.headerlist[-1][1]
+ self.assertEqual(cookie, expected_cookie)
+
+ def test_deserialize_option(self):
+ import time
+ secret = 'secret'
+ request = testing.DummyRequest()
+ accessed = time.time()
+ state = {'key': 'value'}
+ cookieval = dummy_signed_serialize((accessed, accessed, state), secret)
+ request.cookies['session'] = cookieval
+ session = self._makeOne(request,
+ signed_deserialize=dummy_signed_deserialize)
+ self.assertEqual(dict(session), state)
+
+def dummy_signed_serialize(data, secret):
+ import base64
+ from pyramid.compat import pickle, bytes_
+ pickled = pickle.dumps(data)
+ return base64.b64encode(bytes_(secret)) + base64.b64encode(pickled)
+
+def dummy_signed_deserialize(serialized, secret):
+ import base64
+ from pyramid.compat import pickle, bytes_
+ serialized_data = base64.b64decode(
+ serialized[len(base64.b64encode(bytes_(secret))):])
+ return pickle.loads(serialized_data)
+
class Test_manage_accessed(unittest.TestCase):
def _makeOne(self, wrapped):
from pyramid.session import manage_accessed
@@ -313,6 +355,31 @@ class Test_signed_deserialize(unittest.TestCase):
serialized = 'bad' + serialize('123', 'secret')
self.assertRaises(ValueError, self._callFUT, serialized, 'secret')
+class Test_check_csrf_token(unittest.TestCase):
+ def _callFUT(self, request, token, raises=True):
+ from ..session import check_csrf_token
+ return check_csrf_token(request, token, raises=raises)
+
+ def test_success(self):
+ request = testing.DummyRequest()
+ request.params['csrf_token'] = request.session.get_csrf_token()
+ self.assertEqual(self._callFUT(request, 'csrf_token'), True)
+
+ def test_success_default_token(self):
+ from ..session import check_csrf_token
+ request = testing.DummyRequest()
+ request.params['csrf_token'] = request.session.get_csrf_token()
+ self.assertEqual(check_csrf_token(request), True)
+
+ def test_failure_raises(self):
+ from pyramid.httpexceptions import HTTPBadRequest
+ request = testing.DummyRequest()
+ self.assertRaises(HTTPBadRequest, self._callFUT, request, 'csrf_token')
+
+ def test_failure_no_raises(self):
+ request = testing.DummyRequest()
+ result = self._callFUT(request, 'csrf_token', raises=False)
+ self.assertEqual(result, False)
class DummySessionFactory(dict):
_dirty = False
diff --git a/pyramid/tests/test_settings.py b/pyramid/tests/test_settings.py
index 2ef15f62a..a586cb6fd 100644
--- a/pyramid/tests/test_settings.py
+++ b/pyramid/tests/test_settings.py
@@ -1,30 +1,4 @@
import unittest
-from pyramid import testing
-
-class TestGetSettings(unittest.TestCase):
- def setUp(self):
- from pyramid.registry import Registry
- registry = Registry('testing')
- self.config = testing.setUp(registry=registry)
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- self.config.end()
- from zope.deprecation import __show__
- __show__.on()
-
- def _callFUT(self):
- from pyramid.settings import get_settings
- return get_settings()
-
- def test_it_nosettings(self):
- self.assertEqual(self._callFUT()['reload_templates'], False)
-
- def test_it_withsettings(self):
- settings = {'a':1}
- self.config.registry.settings = settings
- self.assertEqual(self._callFUT(), settings)
class Test_asbool(unittest.TestCase):
def _callFUT(self, s):
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 5b0073b81..7f14462a2 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -1,282 +1,4 @@
import unittest
-from pyramid.compat import text_
-
-class TestBase(unittest.TestCase):
- def setUp(self):
- from pyramid.threadlocal import manager
- from pyramid.registry import Registry
- manager.clear()
- registry = Registry('testing')
- self.registry = registry
- manager.push({'registry':registry, 'request':None})
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- from pyramid.threadlocal import manager
- manager.clear()
- from zope.deprecation import __show__
- __show__.on()
-
-class Test_registerDummySecurityPolicy(TestBase):
- def test_registerDummySecurityPolicy(self):
- from pyramid import testing
- testing.registerDummySecurityPolicy('user', ('group1', 'group2'),
- permissive=False)
- from pyramid.interfaces import IAuthenticationPolicy
- from pyramid.interfaces import IAuthorizationPolicy
- ut = self.registry.getUtility(IAuthenticationPolicy)
- from pyramid.testing import DummySecurityPolicy
- self.assertTrue(isinstance(ut, DummySecurityPolicy))
- ut = self.registry.getUtility(IAuthorizationPolicy)
- self.assertEqual(ut.userid, 'user')
- self.assertEqual(ut.groupids, ('group1', 'group2'))
- self.assertEqual(ut.permissive, False)
-
-class Test_registerResources(TestBase):
- def test_it(self):
- class Dummy:
- pass
- ob1 = Dummy()
- ob2 = Dummy()
- resources = {'/ob1':ob1, '/ob2':ob2}
- from pyramid import testing
- testing.registerResources(resources)
- from pyramid.interfaces import ITraverser
- adapter = self.registry.getAdapter(None, ITraverser)
- result = adapter(DummyRequest({'PATH_INFO':'/ob1'}))
- self.assertEqual(result['context'], ob1)
- self.assertEqual(result['view_name'], '')
- self.assertEqual(result['subpath'], ())
- self.assertEqual(result['traversed'], (text_('ob1'),))
- self.assertEqual(result['virtual_root'], ob1)
- self.assertEqual(result['virtual_root_path'], ())
- result = adapter(DummyRequest({'PATH_INFO':'/ob2'}))
- self.assertEqual(result['context'], ob2)
- self.assertEqual(result['view_name'], '')
- self.assertEqual(result['subpath'], ())
- self.assertEqual(result['traversed'], (text_('ob2'),))
- self.assertEqual(result['virtual_root'], ob2)
- self.assertEqual(result['virtual_root_path'], ())
- self.assertRaises(KeyError, adapter, DummyRequest({'PATH_INFO':'/ob3'}))
- from pyramid.traversal import find_resource
- self.assertEqual(find_resource(None, '/ob1'), ob1)
-
-class Test_registerTemplateRenderer(TestBase):
- def test_registerTemplateRenderer(self):
- from pyramid import testing
- renderer = testing.registerTemplateRenderer('templates/foo')
- from pyramid.testing import DummyTemplateRenderer
- self.assertTrue(isinstance(renderer, DummyTemplateRenderer))
- from pyramid.renderers import render_to_response
- render_to_response('templates/foo', dict(foo=1, bar=2))
- renderer.assert_(foo=1)
- renderer.assert_(bar=2)
-
- def test_registerTemplateRenderer_explicitrenderer(self):
- from pyramid import testing
- def renderer(kw, system):
- self.assertEqual(kw, {'foo':1, 'bar':2})
- renderer = testing.registerTemplateRenderer('templates/foo', renderer)
- from pyramid.renderers import render_to_response
- render_to_response('templates/foo', dict(foo=1, bar=2))
-
-class Test_registerEventListener(TestBase):
- def test_registerEventListener_single(self):
- from pyramid import testing
- L = testing.registerEventListener(IDummy)
- event = DummyEvent()
- self.registry.notify(event)
- self.assertEqual(len(L), 1)
- self.assertEqual(L[0], event)
- self.registry.notify(object())
- self.assertEqual(len(L), 1)
-
- def test_registerEventListener_multiple(self):
- from pyramid import testing
- L = testing.registerEventListener((Interface, IDummy))
- event = DummyEvent()
- event.object = 'foo'
- # the below is the equivalent of z.c.event.objectEventNotify(event)
- self.registry.subscribers((event.object, event), None)
- self.assertEqual(len(L), 2)
- self.assertEqual(L[0], 'foo')
- self.assertEqual(L[1], event)
-
- def test_registerEventListener_defaults(self):
- from pyramid import testing
- L = testing.registerEventListener()
- event = object()
- self.registry.notify(event)
- self.assertEqual(L[-1], event)
- event2 = object()
- self.registry.notify(event2)
- self.assertEqual(L[-1], event2)
-
-class Test_registerView(TestBase):
- def test_registerView_defaults(self):
- from pyramid import testing
- view = testing.registerView('moo.html')
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- response = render_view_to_response(None, request, 'moo.html')
- self.assertEqual(view(None, None).body, response.body)
-
- def test_registerView_withresult(self):
- from pyramid import testing
- view = testing.registerView('moo.html', 'yo')
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- response = render_view_to_response(None, request, 'moo.html')
- self.assertEqual(response.body, b'yo')
-
- def test_registerView_custom(self):
- from pyramid import testing
- def view(context, request):
- from webob import Response
- return Response('123')
- view = testing.registerView('moo.html', view=view)
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- response = render_view_to_response(None, request, 'moo.html')
- self.assertEqual(response.body, b'123')
-
- def test_registerView_with_permission_denying(self):
- from pyramid import testing
- from pyramid.httpexceptions import HTTPForbidden
- def view(context, request):
- """ """
- view = testing.registerView('moo.html', view=view, permission='bar')
- testing.registerDummySecurityPolicy(permissive=False)
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- self.assertRaises(HTTPForbidden, render_view_to_response,
- None, request, 'moo.html')
-
- def test_registerView_with_permission_denying2(self):
- from pyramid import testing
- from pyramid.security import view_execution_permitted
- def view(context, request):
- """ """
- view = testing.registerView('moo.html', view=view, permission='bar')
- testing.registerDummySecurityPolicy(permissive=False)
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- result = view_execution_permitted(None, None, 'moo.html')
- self.assertEqual(result, False)
-
- def test_registerView_with_permission_allowing(self):
- from pyramid import testing
- def view(context, request):
- from webob import Response
- return Response('123')
- view = testing.registerView('moo.html', view=view, permission='bar')
- testing.registerDummySecurityPolicy(permissive=True)
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- result = render_view_to_response(None, request, 'moo.html')
- self.assertEqual(result.app_iter, [b'123'])
-
-
-class Test_registerAdapter(TestBase):
- def test_registerAdapter(self):
- from zope.interface import Interface
- class provides(Interface):
- pass
- class Provider:
- pass
- class for_(Interface):
- pass
- from pyramid import testing
- testing.registerAdapter(Provider, (for_, for_), provides, name='foo')
- adapter = self.registry.adapters.lookup(
- (for_, for_), provides, name='foo')
- self.assertEqual(adapter, Provider)
-
- def test_registerAdapter_notlist(self):
- from zope.interface import Interface
- class provides(Interface):
- pass
- class Provider:
- pass
- class for_(Interface):
- pass
- from pyramid import testing
- testing.registerAdapter(Provider, for_, provides, name='foo')
- adapter = self.registry.adapters.lookup(
- (for_,), provides, name='foo')
- self.assertEqual(adapter, Provider)
-
-class Test_registerUtility(TestBase):
- def test_registerUtility(self):
- from zope.interface import implementer
- from zope.interface import Interface
- class iface(Interface):
- pass
- @implementer(iface)
- class impl:
- def __call__(self):
- return 'foo'
- utility = impl()
- from pyramid import testing
- testing.registerUtility(utility, iface, name='mudge')
- self.assertEqual(self.registry.getUtility(iface, name='mudge')(), 'foo')
-
-class Test_registerSubscriber(TestBase):
- def test_it(self):
- from pyramid import testing
- L = []
- def subscriber(event):
- L.append(event)
- testing.registerSubscriber(subscriber, iface=IDummy)
- event = DummyEvent()
- self.registry.notify(event)
- self.assertEqual(len(L), 1)
- self.assertEqual(L[0], event)
- self.registry.notify(object())
- self.assertEqual(len(L), 1)
-
-class Test_registerRoute(TestBase):
- def test_registerRoute(self):
- from pyramid.request import Request
- from pyramid.interfaces import IRoutesMapper
- from pyramid.testing import registerRoute
- registerRoute(':pagename', 'home', DummyFactory)
- mapper = self.registry.getUtility(IRoutesMapper)
- self.assertEqual(len(mapper.routelist), 1)
- request = Request.blank('/')
- request.registry = self.registry
- self.assertEqual(request.route_url('home', pagename='abc'),
- 'http://localhost/abc')
-
-class Test_registerSettings(TestBase):
- def test_registerSettings(self):
- from pyramid.interfaces import ISettings
- from pyramid.testing import registerSettings
- registerSettings({'a':1, 'b':2})
- settings = self.registry.getUtility(ISettings)
- self.assertEqual(settings['a'], 1)
- self.assertEqual(settings['b'], 2)
- registerSettings(b=3, c=4)
- settings = self.registry.getUtility(ISettings)
- self.assertEqual(settings['a'], 1)
- self.assertEqual(settings['b'], 3)
- self.assertEqual(settings['c'], 4)
class TestDummyRootFactory(unittest.TestCase):
def _makeOne(self, environ):
@@ -910,14 +632,6 @@ class IDummy(Interface):
class DummyEvent:
pass
-
-class DummyRequest:
- application_url = 'http://example.com'
- def __init__(self, environ=None):
- if environ is None:
- environ = {}
- self.environ = environ
-
class DummyFactory:
def __init__(self, environ):
""" """
@@ -932,3 +646,58 @@ class DummyRendererInfo(object):
def __init__(self, kw):
self.__dict__.update(kw)
+class Test_testConfig(unittest.TestCase):
+
+ def _setUp(self, **kw):
+ self._log.append(('setUp', kw))
+ return 'fake config'
+
+ def _tearDown(self, **kw):
+ self._log.append(('tearDown', kw))
+
+ def setUp(self):
+ from pyramid import testing
+ self._log = []
+ self._orig_setUp = testing.setUp
+ testing.setUp = self._setUp
+ self._orig_tearDown = testing.tearDown
+ testing.tearDown = self._tearDown
+
+ def tearDown(self):
+ from pyramid import testing
+ testing.setUp = self._orig_setUp
+ testing.tearDown = self._orig_tearDown
+
+ def _callFUT(self, inner, **kw):
+ from pyramid.testing import testConfig
+ with testConfig(**kw) as config:
+ inner(config)
+
+ def test_ok_calls(self):
+ self.assertEqual(self._log, [])
+ def inner(config):
+ self.assertEqual(self._log,
+ [('setUp',
+ {'autocommit': True,
+ 'hook_zca': True,
+ 'registry': None,
+ 'request': None,
+ 'settings': None})])
+ self._log.pop()
+ self._callFUT(inner)
+ self.assertEqual(self._log,
+ [('tearDown', {'unhook_zca': True})])
+
+ def test_teardown_called_on_exception(self):
+ class TestException(Exception):
+ pass
+ def inner(config):
+ self._log = []
+ raise TestException('oops')
+ self.assertRaises(TestException, self._callFUT, inner)
+ self.assertEqual(self._log[0][0], 'tearDown')
+
+ def test_ok_get_config(self):
+ def inner(config):
+ self.assertEqual(config, 'fake config')
+ self._callFUT(inner)
diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py
index 8e0bb2494..2e45ae1a9 100644
--- a/pyramid/tests/test_traversal.py
+++ b/pyramid/tests/test_traversal.py
@@ -128,10 +128,23 @@ class ResourceTreeTraverserTests(unittest.TestCase):
context = DummyContext()
verifyObject(ITraverser, self._makeOne(context))
- def test_call_with_no_pathinfo(self):
+ def test_call_with_empty_pathinfo(self):
policy = self._makeOne(None)
environ = self._getEnviron()
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info='')
+ result = policy(request)
+ self.assertEqual(result['context'], None)
+ self.assertEqual(result['view_name'], '')
+ self.assertEqual(result['subpath'], ())
+ self.assertEqual(result['traversed'], ())
+ self.assertEqual(result['root'], policy.root)
+ self.assertEqual(result['virtual_root'], policy.root)
+ self.assertEqual(result['virtual_root_path'], ())
+
+ def test_call_with_pathinfo_KeyError(self):
+ policy = self._makeOne(None)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, toraise=KeyError)
result = policy(request)
self.assertEqual(result['context'], None)
self.assertEqual(result['view_name'], '')
@@ -142,31 +155,25 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], ())
def test_call_with_pathinfo_highorder(self):
- foo = DummyContext(None, text_(b'Qu\xc3\xa9bec', 'utf-8'))
+ path = text_(b'/Qu\xc3\xa9bec', 'utf-8')
+ foo = DummyContext(None, path)
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- if PY3: # pragma: no cover
- path_info = b'/Qu\xc3\xa9bec'.decode('latin-1')
- else:
- path_info = b'/Qu\xc3\xa9bec'
- environ = self._getEnviron(PATH_INFO=path_info)
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=path)
result = policy(request)
self.assertEqual(result['context'], foo)
self.assertEqual(result['view_name'], '')
self.assertEqual(result['subpath'], ())
- self.assertEqual(
- result['traversed'],
- (text_(b'Qu\xc3\xa9bec', 'utf-8'),)
- )
+ self.assertEqual(result['traversed'], (path[1:],))
self.assertEqual(result['root'], policy.root)
self.assertEqual(result['virtual_root'], policy.root)
self.assertEqual(result['virtual_root_path'], ())
def test_call_pathel_with_no_getitem(self):
policy = self._makeOne(None)
- environ = self._getEnviron(PATH_INFO='/foo/bar')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_('/foo/bar'))
result = policy(request)
self.assertEqual(result['context'], None)
self.assertEqual(result['view_name'], 'foo')
@@ -179,8 +186,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_call_withconn_getitem_emptypath_nosubpath(self):
root = DummyContext()
policy = self._makeOne(root)
- environ = self._getEnviron(PATH_INFO='')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_(''))
result = policy(request)
self.assertEqual(result['context'], root)
self.assertEqual(result['view_name'], '')
@@ -194,8 +201,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
foo = DummyContext()
root = DummyContext(foo)
policy = self._makeOne(root)
- environ = self._getEnviron(PATH_INFO='/foo/bar')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_('/foo/bar'))
result = policy(request)
self.assertEqual(result['context'], foo)
self.assertEqual(result['view_name'], 'bar')
@@ -209,8 +216,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
foo = DummyContext()
root = DummyContext(foo)
policy = self._makeOne(root)
- environ = self._getEnviron(PATH_INFO='/foo/bar/baz/buz')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_('/foo/bar/baz/buz'))
result = policy(request)
self.assertEqual(result['context'], foo)
self.assertEqual(result['view_name'], 'bar')
@@ -224,8 +231,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
foo = DummyContext()
root = DummyContext(foo)
policy = self._makeOne(root)
- environ = self._getEnviron(PATH_INFO='/@@foo')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_('/@@foo'))
result = policy(request)
self.assertEqual(result['context'], root)
self.assertEqual(result['view_name'], 'foo')
@@ -236,14 +243,13 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], ())
def test_call_with_vh_root(self):
- environ = self._getEnviron(PATH_INFO='/baz',
- HTTP_X_VHM_ROOT='/foo/bar')
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo/bar')
baz = DummyContext(None, 'baz')
bar = DummyContext(baz, 'bar')
foo = DummyContext(bar, 'foo')
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info=text_('/baz'))
result = policy(request)
self.assertEqual(result['context'], baz)
self.assertEqual(result['view_name'], '')
@@ -256,14 +262,13 @@ class ResourceTreeTraverserTests(unittest.TestCase):
(text_('foo'), text_('bar')))
def test_call_with_vh_root2(self):
- environ = self._getEnviron(PATH_INFO='/bar/baz',
- HTTP_X_VHM_ROOT='/foo')
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo')
baz = DummyContext(None, 'baz')
bar = DummyContext(baz, 'bar')
foo = DummyContext(bar, 'foo')
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info=text_('/bar/baz'))
result = policy(request)
self.assertEqual(result['context'], baz)
self.assertEqual(result['view_name'], '')
@@ -275,14 +280,13 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], (text_('foo'),))
def test_call_with_vh_root3(self):
- environ = self._getEnviron(PATH_INFO='/foo/bar/baz',
- HTTP_X_VHM_ROOT='/')
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/')
baz = DummyContext()
bar = DummyContext(baz)
foo = DummyContext(bar)
root = DummyContext(foo)
policy = self._makeOne(root)
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info=text_('/foo/bar/baz'))
result = policy(request)
self.assertEqual(result['context'], baz)
self.assertEqual(result['view_name'], '')
@@ -294,14 +298,13 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], ())
def test_call_with_vh_root4(self):
- environ = self._getEnviron(PATH_INFO='/',
- HTTP_X_VHM_ROOT='/foo/bar/baz')
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo/bar/baz')
baz = DummyContext(None, 'baz')
bar = DummyContext(baz, 'bar')
foo = DummyContext(bar, 'foo')
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info=text_('/'))
result = policy(request)
self.assertEqual(result['context'], baz)
self.assertEqual(result['view_name'], '')
@@ -315,9 +318,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_call_with_vh_root_path_root(self):
policy = self._makeOne(None)
- environ = self._getEnviron(HTTP_X_VHM_ROOT='/',
- PATH_INFO='/')
- request = DummyRequest(environ)
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/')
+ request = DummyRequest(environ, path_info=text_('/'))
result = policy(request)
self.assertEqual(result['context'], None)
self.assertEqual(result['view_name'], '')
@@ -328,57 +330,48 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], ())
def test_call_with_vh_root_highorder(self):
+ path = text_(b'Qu\xc3\xa9bec', 'utf-8')
bar = DummyContext(None, 'bar')
- foo = DummyContext(bar, text_(b'Qu\xc3\xa9bec', 'utf-8'))
+ foo = DummyContext(bar, path)
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
if PY3: # pragma: no cover
vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1')
else:
vhm_root = b'/Qu\xc3\xa9bec'
- environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root,
- PATH_INFO='/bar')
- request = DummyRequest(environ)
+ environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root)
+ request = DummyRequest(environ, path_info=text_('/bar'))
result = policy(request)
self.assertEqual(result['context'], bar)
self.assertEqual(result['view_name'], '')
self.assertEqual(result['subpath'], ())
self.assertEqual(
result['traversed'],
- (text_(b'Qu\xc3\xa9bec', 'utf-8'), text_('bar'))
+ (path, text_('bar'))
)
self.assertEqual(result['root'], policy.root)
self.assertEqual(result['virtual_root'], foo)
self.assertEqual(
result['virtual_root_path'],
- (text_(b'Qu\xc3\xa9bec', 'utf-8'),)
+ (path,)
)
- def test_non_utf8_path_segment_unicode_path_segments_fails(self):
- from pyramid.exceptions import URLDecodeError
- foo = DummyContext()
- root = DummyContext(foo)
- policy = self._makeOne(root)
- segment = native_(text_(b'LaPe\xc3\xb1a', 'utf-8'), 'utf-16')
- environ = self._getEnviron(PATH_INFO='/%s' % segment)
- request = DummyRequest(environ)
- self.assertRaises(URLDecodeError, policy, request)
-
- def test_non_utf8_path_segment_settings_unicode_path_segments_fails(self):
+ def test_path_info_raises_unicodedecodeerror(self):
from pyramid.exceptions import URLDecodeError
foo = DummyContext()
root = DummyContext(foo)
policy = self._makeOne(root)
- segment = native_(text_(b'LaPe\xc3\xb1a', 'utf-8'), 'utf-16')
- environ = self._getEnviron(PATH_INFO='/%s' % segment)
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ toraise = UnicodeDecodeError('ascii', b'a', 2, 3, '5')
+ request = DummyRequest(environ, toraise=toraise)
+ request.matchdict = None
self.assertRaises(URLDecodeError, policy, request)
def test_withroute_nothingfancy(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {}}
- request = DummyRequest(environ)
+ request = DummyRequest({})
+ request.matchdict = {}
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], '')
@@ -391,8 +384,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_with_subpath_string(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'subpath':'/a/b/c'}}
- request = DummyRequest(environ)
+ matchdict = {'subpath':'/a/b/c'}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], '')
@@ -405,8 +399,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_with_subpath_tuple(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'subpath':('a', 'b', 'c')}}
- request = DummyRequest(environ)
+ matchdict = {'subpath':('a', 'b', 'c')}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], '')
@@ -419,8 +414,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_and_traverse_string(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'traverse':'foo/bar'}}
- request = DummyRequest(environ)
+ matchdict = {'traverse':text_('foo/bar')}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], 'foo')
@@ -433,8 +429,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_and_traverse_tuple(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'traverse':('foo', 'bar')}}
- request = DummyRequest(environ)
+ matchdict = {'traverse':('foo', 'bar')}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], 'foo')
@@ -447,8 +444,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_and_traverse_empty(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'traverse':''}}
- request = DummyRequest(environ)
+ matchdict = {'traverse':''}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], '')
@@ -458,21 +456,6 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root'], resource)
self.assertEqual(result['virtual_root_path'], ())
- def test_call_with_environ(self):
- with warnings.catch_warnings(record=True) as w:
- warnings.filterwarnings('always')
- policy = self._makeOne(None)
- environ = self._getEnviron()
- result = policy(environ)
- self.assertEqual(result['context'], None)
- self.assertEqual(result['view_name'], '')
- self.assertEqual(result['subpath'], ())
- self.assertEqual(result['traversed'], ())
- self.assertEqual(result['root'], policy.root)
- self.assertEqual(result['virtual_root'], policy.root)
- self.assertEqual(result['virtual_root_path'], ())
- self.assertEqual(len(w), 1)
-
class FindInterfaceTests(unittest.TestCase):
def _callFUT(self, context, iface):
from pyramid.traversal import find_interface
@@ -1310,11 +1293,28 @@ class DummyContext(object):
return '<DummyContext with name %s at id %s>'%(self.__name__, id(self))
class DummyRequest:
+
application_url = 'http://example.com:5432' # app_url never ends with slash
- def __init__(self, environ=None):
+ matchdict = None
+ matched_route = None
+
+ def __init__(self, environ=None, path_info=text_('/'), toraise=None):
if environ is None:
environ = {}
self.environ = environ
+ self._set_path_info(path_info)
+ self.toraise = toraise
+
+ def _get_path_info(self):
+ if self.toraise:
+ raise self.toraise
+ return self._path_info
+
+ def _set_path_info(self, v):
+ self._path_info = v
+
+ path_info = property(_get_path_info, _set_path_info)
+
class DummyContextURL:
def __init__(self, context, request):
diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py
index 50deb63f3..a7a565356 100644
--- a/pyramid/tests/test_url.py
+++ b/pyramid/tests/test_url.py
@@ -2,10 +2,8 @@ import os
import unittest
import warnings
-from pyramid.testing import (
- setUp,
- tearDown,
- )
+from pyramid import testing
+
from pyramid.compat import (
text_,
native_,
@@ -14,10 +12,10 @@ from pyramid.compat import (
class TestURLMethodsMixin(unittest.TestCase):
def setUp(self):
- self.config = setUp()
+ self.config = testing.setUp()
def tearDown(self):
- tearDown()
+ testing.tearDown()
def _makeOne(self, environ=None):
from pyramid.url import URLMethodsMixin
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index e15242f75..b2164717e 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -311,6 +311,14 @@ class TestCompileRoute(unittest.TestCase):
self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}),
'/foo/1/biz/2.html')
+
+ def test_custom_regex_with_colons(self):
+ matcher, generator = self._callFUT('foo/{baz}/biz/{buz:(?:[^/\.]+)}.{bar}')
+ self.assertEqual(matcher('/foo/baz/biz/buz.bar'),
+ {'baz':'baz', 'buz':'buz', 'bar':'bar'})
+ self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
+ self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}),
+ '/foo/1/biz/2.html')
def test_mixed_newstyle_oldstyle_pattern_defaults_to_newstyle(self):
# pattern: '\\/foo\\/(?P<baz>abc)\\/biz\\/(?P<buz>[^/]+)\\/bar$'
diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py
index e83ad5922..785950230 100644
--- a/pyramid/tests/test_util.py
+++ b/pyramid/tests/test_util.py
@@ -132,6 +132,20 @@ class Test_InstancePropertyMixin(unittest.TestCase):
self.assertEqual(1, foo.x)
self.assertEqual(2, foo.y)
+ def test__set_extensions(self):
+ inst = self._makeOne()
+ def foo(self, result):
+ return result
+ n, bar = inst._make_property(lambda _: 'bar', name='bar')
+ class Extensions(object):
+ def __init__(self):
+ self.methods = {'foo':foo}
+ self.descriptors = {'bar':bar}
+ extensions = Extensions()
+ inst._set_extensions(extensions)
+ self.assertEqual(inst.bar, 'bar')
+ self.assertEqual(inst.foo('abc'), 'abc')
+
class Test_WeakOrderedSet(unittest.TestCase):
def _makeOne(self):
from pyramid.config import WeakOrderedSet
@@ -274,6 +288,263 @@ class Test_object_description(unittest.TestCase):
self._callFUT(inst),
str(inst)[:100] + ' ... ]')
+class TestTopologicalSorter(unittest.TestCase):
+ def _makeOne(self, *arg, **kw):
+ from pyramid.util import TopologicalSorter
+ return TopologicalSorter(*arg, **kw)
+
+ def test_remove(self):
+ inst = self._makeOne()
+ inst.names.append('name')
+ inst.name2val['name'] = 1
+ inst.req_after.add('name')
+ inst.req_before.add('name')
+ inst.name2after['name'] = ('bob',)
+ inst.name2before['name'] = ('fred',)
+ inst.order.append(('bob', 'name'))
+ inst.order.append(('name', 'fred'))
+ inst.remove('name')
+ self.assertFalse(inst.names)
+ self.assertFalse(inst.req_before)
+ self.assertFalse(inst.req_after)
+ self.assertFalse(inst.name2before)
+ self.assertFalse(inst.name2after)
+ self.assertFalse(inst.name2val)
+ self.assertFalse(inst.order)
+
+ def test_add(self):
+ from pyramid.util import LAST
+ sorter = self._makeOne()
+ sorter.add('name', 'factory')
+ self.assertEqual(sorter.names, ['name'])
+ self.assertEqual(sorter.name2val,
+ {'name':'factory'})
+ self.assertEqual(sorter.order, [('name', LAST)])
+ sorter.add('name2', 'factory2')
+ self.assertEqual(sorter.names, ['name', 'name2'])
+ self.assertEqual(sorter.name2val,
+ {'name':'factory', 'name2':'factory2'})
+ self.assertEqual(sorter.order,
+ [('name', LAST), ('name2', LAST)])
+ sorter.add('name3', 'factory3', before='name2')
+ self.assertEqual(sorter.names,
+ ['name', 'name2', 'name3'])
+ self.assertEqual(sorter.name2val,
+ {'name':'factory', 'name2':'factory2',
+ 'name3':'factory3'})
+ self.assertEqual(sorter.order,
+ [('name', LAST), ('name2', LAST),
+ ('name3', 'name2')])
+
+ def test_sorted_ordering_1(self):
+ sorter = self._makeOne()
+ sorter.add('name1', 'factory1')
+ sorter.add('name2', 'factory2')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('name1', 'factory1'),
+ ('name2', 'factory2'),
+ ])
+
+ def test_sorted_ordering_2(self):
+ from pyramid.util import FIRST
+ sorter = self._makeOne()
+ sorter.add('name1', 'factory1')
+ sorter.add('name2', 'factory2', after=FIRST)
+ self.assertEqual(sorter.sorted(),
+ [
+ ('name2', 'factory2'),
+ ('name1', 'factory1'),
+ ])
+
+ def test_sorted_ordering_3(self):
+ from pyramid.util import FIRST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('auth', 'auth_factory', after='browserid')
+ add('dbt', 'dbt_factory')
+ add('retry', 'retry_factory', before='txnmgr', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('txnmgr', 'txnmgr_factory', after='exceptionview')
+ add('exceptionview', 'excview_factory', after=FIRST)
+ self.assertEqual(sorter.sorted(),
+ [
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ('dbt', 'dbt_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ])
+
+ def test_sorted_ordering_4(self):
+ from pyramid.util import FIRST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', after=FIRST)
+ add('auth', 'auth_factory', after='browserid')
+ add('retry', 'retry_factory', before='txnmgr', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('txnmgr', 'txnmgr_factory', after='exceptionview')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_sorted_ordering_5(self):
+ from pyramid.util import LAST, FIRST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory')
+ add('auth', 'auth_factory', after=FIRST)
+ add('retry', 'retry_factory', before='txnmgr', after='exceptionview')
+ add('browserid', 'browserid_factory', after=FIRST)
+ add('txnmgr', 'txnmgr_factory', after='exceptionview', before=LAST)
+ add('dbt', 'dbt_factory')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_sorted_ordering_missing_before_partial(self):
+ from pyramid.exceptions import ConfigurationError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('dbt', 'dbt_factory')
+ add('auth', 'auth_factory', after='browserid')
+ add('retry', 'retry_factory', before='txnmgr', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ self.assertRaises(ConfigurationError, sorter.sorted)
+
+ def test_sorted_ordering_missing_after_partial(self):
+ from pyramid.exceptions import ConfigurationError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('dbt', 'dbt_factory')
+ add('auth', 'auth_factory', after='txnmgr')
+ add('retry', 'retry_factory', before='dbt', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ self.assertRaises(ConfigurationError, sorter.sorted)
+
+ def test_sorted_ordering_missing_before_and_after_partials(self):
+ from pyramid.exceptions import ConfigurationError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('dbt', 'dbt_factory')
+ add('auth', 'auth_factory', after='browserid')
+ add('retry', 'retry_factory', before='foo', after='txnmgr')
+ add('browserid', 'browserid_factory')
+ self.assertRaises(ConfigurationError, sorter.sorted)
+
+ def test_sorted_ordering_missing_before_partial_with_fallback(self):
+ from pyramid.util import LAST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', before=LAST)
+ add('auth', 'auth_factory', after='browserid')
+ add('retry', 'retry_factory', before=('txnmgr', LAST),
+ after='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_sorted_ordering_missing_after_partial_with_fallback(self):
+ from pyramid.util import FIRST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', after=FIRST)
+ add('auth', 'auth_factory', after=('txnmgr','browserid'))
+ add('retry', 'retry_factory', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_sorted_ordering_with_partial_fallbacks(self):
+ from pyramid.util import LAST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', before=('wontbethere', LAST))
+ add('retry', 'retry_factory', after='exceptionview')
+ add('browserid', 'browserid_factory', before=('wont2', 'exceptionview'))
+ self.assertEqual(sorter.sorted(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ])
+
+ def test_sorted_ordering_with_multiple_matching_fallbacks(self):
+ from pyramid.util import LAST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', before=LAST)
+ add('retry', 'retry_factory', after='exceptionview')
+ add('browserid', 'browserid_factory', before=('retry', 'exceptionview'))
+ self.assertEqual(sorter.sorted(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ])
+
+ def test_sorted_ordering_with_missing_fallbacks(self):
+ from pyramid.exceptions import ConfigurationError
+ from pyramid.util import LAST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', before=LAST)
+ add('retry', 'retry_factory', after='exceptionview')
+ add('browserid', 'browserid_factory', before=('txnmgr', 'auth'))
+ self.assertRaises(ConfigurationError, sorter.sorted)
+
+ def test_sorted_ordering_conflict_direct(self):
+ from pyramid.exceptions import CyclicDependencyError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('browserid', 'browserid_factory')
+ add('auth', 'auth_factory', before='browserid', after='browserid')
+ self.assertRaises(CyclicDependencyError, sorter.sorted)
+
+ def test_sorted_ordering_conflict_indirect(self):
+ from pyramid.exceptions import CyclicDependencyError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('browserid', 'browserid_factory')
+ add('auth', 'auth_factory', before='browserid')
+ add('dbt', 'dbt_factory', after='browserid', before='auth')
+ self.assertRaises(CyclicDependencyError, sorter.sorted)
+
+class TestSentinel(unittest.TestCase):
+ def test_repr(self):
+ from pyramid.util import Sentinel
+ r = repr(Sentinel('ABC'))
+ self.assertEqual(r, 'ABC')
+
def dummyfunc(): pass
class Dummy(object):
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index 35fa132a8..0af941e0d 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -3,17 +3,14 @@ import sys
from zope.interface import implementer
-from pyramid.testing import (
- setUp,
- tearDown,
- )
+from pyramid import testing
class BaseTest(object):
def setUp(self):
- self.config = setUp()
+ self.config = testing.setUp()
def tearDown(self):
- tearDown()
+ testing.tearDown()
def _registerView(self, reg, app, name):
from pyramid.interfaces import IRequest
@@ -90,7 +87,7 @@ class Test_notfound_view_config(BaseTest, unittest.TestCase):
config = call_venusian(venusian)
settings = config.settings
self.assertEqual(len(settings), 1)
- self.assertEqual(len(settings[0]), 5)
+ self.assertEqual(len(settings[0]), 4)
self.assertEqual(settings[0]['venusian'], venusian)
self.assertEqual(settings[0]['view'], None) # comes from call_venusian
self.assertEqual(settings[0]['attr'], 'view')
@@ -334,10 +331,10 @@ class TestIsResponse(unittest.TestCase):
class TestViewConfigDecorator(unittest.TestCase):
def setUp(self):
- setUp()
+ testing.setUp()
def tearDown(self):
- tearDown()
+ testing.tearDown()
def _getTargetClass(self):
from pyramid.view import view_config
@@ -372,6 +369,10 @@ class TestViewConfigDecorator(unittest.TestCase):
self.assertEqual(decorator.decorator, 'decorator')
self.assertEqual(decorator.match_param, 'match_param')
+ def test_create_with_other_predicates(self):
+ decorator = self._makeOne(foo=1)
+ self.assertEqual(decorator.foo, 1)
+
def test_create_decorator_tuple(self):
decorator = self._makeOne(decorator=('decorator1', 'decorator2'))
self.assertEqual(decorator.decorator, ('decorator1', 'decorator2'))
@@ -522,6 +523,14 @@ class TestViewConfigDecorator(unittest.TestCase):
self.assertTrue(renderer is renderer_helper)
self.assertEqual(config.pkg, pyramid.tests)
+ def test_call_withdepth(self):
+ decorator = self._makeOne(_depth=2)
+ venusian = DummyVenusian()
+ decorator.venusian = venusian
+ def foo(): pass
+ decorator(foo)
+ self.assertEqual(venusian.depth, 2)
+
class Test_append_slash_notfound_view(BaseTest, unittest.TestCase):
def _callFUT(self, context, request):
from pyramid.view import append_slash_notfound_view
@@ -749,8 +758,9 @@ class DummyVenusian(object):
self.info = info
self.attachments = []
- def attach(self, wrapped, callback, category=None):
+ def attach(self, wrapped, callback, category=None, depth=1):
self.attachments.append((wrapped, callback, category))
+ self.depth = depth
return self.info
class DummyRegistry(object):
diff --git a/pyramid/traversal.py b/pyramid/traversal.py
index b514d4c16..6832ce69a 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -614,6 +614,7 @@ else:
_segment_cache[(segment, safe)] = result
return result
+slash = text_('/')
@implementer(ITraverser)
class ResourceTreeTraverser(object):
@@ -629,27 +630,17 @@ class ResourceTreeTraverser(object):
self.root = root
def __call__(self, request):
- try:
- environ = request.environ
- except AttributeError:
- # In BFG 1.0 and before, this API expected an environ
- # rather than a request; some bit of code may still be
- # passing us an environ. If so, deal.
- environ = request
- depwarn = ('Passing an environ dictionary directly to a traverser '
- 'is deprecated in Pyramid 1.1. Pass a request object '
- 'instead.')
- warnings.warn(depwarn, DeprecationWarning, 2)
-
- if 'bfg.routes.matchdict' in environ:
- matchdict = environ['bfg.routes.matchdict']
-
- path = matchdict.get('traverse', '/') or '/'
+ environ = request.environ
+ matchdict = request.matchdict
+
+ if matchdict is not None:
+
+ path = matchdict.get('traverse', slash) or slash
if is_nonstr_iter(path):
# this is a *traverse stararg (not a {traverse})
# routing has already decoded these elements, so we just
# need to join them
- path = '/'.join(path) or '/'
+ path = slash.join(path) or slash
subpath = matchdict.get('subpath', ())
if not is_nonstr_iter(subpath):
@@ -663,9 +654,10 @@ class ResourceTreeTraverser(object):
subpath = ()
try:
# empty if mounted under a path in mod_wsgi, for example
- path = decode_path_info(environ['PATH_INFO'] or '/')
+ path = request.path_info or slash
except KeyError:
- path = '/'
+ # if environ['PATH_INFO'] is just not there
+ path = slash
except UnicodeDecodeError as e:
raise URLDecodeError(e.encoding, e.object, e.start, e.end,
e.reason)
@@ -684,7 +676,7 @@ class ResourceTreeTraverser(object):
root = self.root
ob = vroot = root
- if vpath == '/': # invariant: vpath must not be empty
+ if vpath == slash: # invariant: vpath must not be empty
# prevent a call to traversal_path if we know it's going
# to return the empty tuple
vpath_tuple = ()
diff --git a/pyramid/tweens.py b/pyramid/tweens.py
index 73a95e1b8..cf2238deb 100644
--- a/pyramid/tweens.py
+++ b/pyramid/tweens.py
@@ -2,6 +2,7 @@ import sys
from pyramid.interfaces import (
IExceptionViewClassifier,
+ IRequest,
IView,
)
@@ -28,7 +29,9 @@ def excview_tween_factory(handler, registry):
# sane (e.g. caching headers)
if 'response' in attrs:
del attrs['response']
- request_iface = attrs['request_iface']
+ # we use .get instead of .__getitem__ below due to
+ # https://github.com/Pylons/pyramid/issues/700
+ request_iface = attrs.get('request_iface', IRequest)
provides = providedBy(exc)
for_ = (IExceptionViewClassifier, request_iface.combined, provides)
view_callable = adapters.lookup(for_, IView, default=None)
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index cccff14ba..4182ea665 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -148,7 +148,9 @@ def _compile_route(route):
name = pat.pop() # unicode
name = name[1:-1]
if ':' in name:
- name, reg = name.split(':')
+ # reg may contain colons as well,
+ # so we must strictly split name into two parts
+ name, reg = name.split(':', 1)
else:
reg = '[^/]+'
gen.append('%%(%s)s' % native_(name)) # native
diff --git a/pyramid/util.py b/pyramid/util.py
index 7d5c97814..d83837322 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -1,7 +1,14 @@
import inspect
import weakref
+from pyramid.exceptions import (
+ ConfigurationError,
+ CyclicDependencyError,
+ )
+
from pyramid.compat import (
+ iteritems_,
+ is_nonstr_iter,
integer_types,
string_types,
text_,
@@ -74,6 +81,12 @@ class InstancePropertyMixin(object):
cls = type(parent.__name__, (parent, object), attrs)
self.__class__ = cls
+ def _set_extensions(self, extensions):
+ for name, fn in iteritems_(extensions.methods):
+ method = fn.__get__(self, self.__class__)
+ setattr(self, name, method)
+ self._set_properties(extensions.descriptors)
+
def set_property(self, callable, name=None, reify=False):
""" Add a callable or a property descriptor to the instance.
@@ -280,3 +293,163 @@ def shortrepr(object, closer):
if len(r) > 100:
r = r[:100] + ' ... %s' % closer
return r
+
+class Sentinel(object):
+ def __init__(self, repr):
+ self.repr = repr
+
+ def __repr__(self):
+ return self.repr
+
+FIRST = Sentinel('FIRST')
+LAST = Sentinel('LAST')
+
+class TopologicalSorter(object):
+ """ A utility class which can be used to perform topological sorts against
+ tuple-like data."""
+ def __init__(
+ self,
+ default_before=LAST,
+ default_after=None,
+ first=FIRST,
+ last=LAST,
+ ):
+ self.names = []
+ self.req_before = set()
+ self.req_after = set()
+ self.name2before = {}
+ self.name2after = {}
+ self.name2val = {}
+ self.order = []
+ self.default_before = default_before
+ self.default_after = default_after
+ self.first = first
+ self.last = last
+
+ def remove(self, name):
+ """ Remove a node from the sort input """
+ self.names.remove(name)
+ del self.name2val[name]
+ after = self.name2after.pop(name, [])
+ if after:
+ self.req_after.remove(name)
+ for u in after:
+ self.order.remove((u, name))
+ before = self.name2before.pop(name, [])
+ if before:
+ self.req_before.remove(name)
+ for u in before:
+ self.order.remove((name, u))
+
+ def add(self, name, val, after=None, before=None):
+ """ Add a node to the sort input. The ``name`` should be a string or
+ any other hashable object, the ``val`` should be the sortable (doesn't
+ need to be hashable). ``after`` and ``before`` represents the name of
+ one of the other sortables (or a sequence of such named) or one of the
+ special sentinel values :attr:`pyramid.util.FIRST`` or
+ :attr:`pyramid.util.LAST` representing the first or last positions
+ respectively. ``FIRST`` and ``LAST`` can also be part of a sequence
+ passed as ``before`` or ``after``. A sortable should not be added
+ after LAST or before FIRST. An example::
+
+ sorter = TopologicalSorter()
+ sorter.add('a', {'a':1}, before=LAST, after='b')
+ sorter.add('b', {'b':2}, before=LAST, after='c')
+ sorter.add('c', {'c':3})
+
+ sorter.sorted() # will be {'c':3}, {'b':2}, {'a':1}
+
+ """
+ if name in self.names:
+ self.remove(name)
+ self.names.append(name)
+ self.name2val[name] = val
+ if after is None and before is None:
+ before = self.default_before
+ after = self.default_after
+ if after is not None:
+ if not is_nonstr_iter(after):
+ after = (after,)
+ self.name2after[name] = after
+ self.order += [(u, name) for u in after]
+ self.req_after.add(name)
+ if before is not None:
+ if not is_nonstr_iter(before):
+ before = (before,)
+ self.name2before[name] = before
+ self.order += [(name, o) for o in before]
+ self.req_before.add(name)
+
+
+ def sorted(self):
+ """ Returns the sort input values in topologically sorted order"""
+ order = [(self.first, self.last)]
+ roots = []
+ graph = {}
+ names = [self.first, self.last]
+ names.extend(self.names)
+
+ for a, b in self.order:
+ order.append((a, b))
+
+ def add_node(node):
+ if not node in graph:
+ roots.append(node)
+ graph[node] = [0] # 0 = number of arcs coming into this node
+
+ def add_arc(fromnode, tonode):
+ graph[fromnode].append(tonode)
+ graph[tonode][0] += 1
+ if tonode in roots:
+ roots.remove(tonode)
+
+ for name in names:
+ add_node(name)
+
+ has_before, has_after = set(), set()
+ for a, b in order:
+ if a in names and b in names: # deal with missing dependencies
+ add_arc(a, b)
+ has_before.add(a)
+ has_after.add(b)
+
+ if not self.req_before.issubset(has_before):
+ raise ConfigurationError(
+ 'Unsatisfied before dependencies: %s'
+ % (', '.join(sorted(self.req_before - has_before)))
+ )
+ if not self.req_after.issubset(has_after):
+ raise ConfigurationError(
+ 'Unsatisfied after dependencies: %s'
+ % (', '.join(sorted(self.req_after - has_after)))
+ )
+
+ sorted_names = []
+
+ while roots:
+ root = roots.pop(0)
+ sorted_names.append(root)
+ children = graph[root][1:]
+ for child in children:
+ arcs = graph[child][0]
+ arcs -= 1
+ graph[child][0] = arcs
+ if arcs == 0:
+ roots.insert(0, child)
+ del graph[root]
+
+ if graph:
+ # loop in input
+ cycledeps = {}
+ for k, v in graph.items():
+ cycledeps[k] = v[1:]
+ raise CyclicDependencyError(cycledeps)
+
+ result = []
+
+ for name in sorted_names:
+ if name in self.names:
+ result.append((name, self.name2val[name]))
+
+ return result
+
diff --git a/pyramid/view.py b/pyramid/view.py
index bb531c326..835982e79 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -138,15 +138,6 @@ def render_view(context, request, name='', secure=True):
return None
return ''.join(iterable)
-class _default(object):
- def __nonzero__(self):
- return False
- __bool__ = __nonzero__
- def __repr__(self): # pragma: no cover
- return '(default)'
-
-default = _default()
-
class view_config(object):
""" A function, class or method :term:`decorator` which allows a
developer to create view registrations nearer to a :term:`view
@@ -174,46 +165,45 @@ class view_config(object):
backwards compatibility purposes, as the name
:class:`pyramid.view.bfg_view`.
- The following arguments are supported to
+ The following keyword arguments are supported to
:class:`pyramid.view.view_config`: ``context``, ``permission``, ``name``,
``request_type``, ``route_name``, ``request_method``, ``request_param``,
``containment``, ``xhr``, ``accept``, ``header``, ``path_info``,
``custom_predicates``, ``decorator``, ``mapper``, ``http_cache``,
- and ``match_param``.
+ ``match_param``, ``csrf_token``, ``physical_path``, and ``predicates``.
The meanings of these arguments are the same as the arguments passed to
:meth:`pyramid.config.Configurator.add_view`. If any argument is left
out, its default will be the equivalent ``add_view`` default.
+ An additional keyword argument named ``_depth`` is provided for people who
+ wish to reuse this class from another decorator. It will be passed in to
+ the :term:`venusian` ``attach`` function as the depth of the callstack when
+ Venusian checks if the decorator is being used in a class or module
+ context. It's not often used, but it can be useful in this circumstance.
+ See the ``attach`` function in Venusian for more information.
+
See :ref:`mapping_views_using_a_decorator_section` for details about
using :class:`view_config`.
"""
venusian = venusian # for testing injection
- def __init__(self, name=default, request_type=default, for_=default,
- permission=default, route_name=default,
- request_method=default, request_param=default,
- containment=default, attr=default, renderer=default,
- wrapper=default, xhr=default, accept=default,
- header=default, path_info=default,
- custom_predicates=default, context=default,
- decorator=default, mapper=default, http_cache=default,
- match_param=default):
- L = locals()
- if (context is not default) or (for_ is not default):
- L['context'] = context or for_
- for k, v in L.items():
- if k not in ('self', 'L') and v is not default:
- setattr(self, k, v)
+ def __init__(self, **settings):
+ if 'for_' in settings:
+ if settings.get('context') is None:
+ settings['context'] = settings['for_']
+ self.__dict__.update(settings)
def __call__(self, wrapped):
settings = self.__dict__.copy()
+ depth = settings.pop('_depth', 1)
def callback(context, name, ob):
config = context.config.with_package(info.module)
config.add_view(view=ob, **settings)
- info = self.venusian.attach(wrapped, callback, category='pyramid')
+ info = self.venusian.attach(wrapped, callback, category='pyramid',
+ depth=depth)
if info.scope == 'class':
# if the decorator was attached to a method in a class, or
@@ -326,7 +316,7 @@ class notfound_view_config(object):
The notfound_view_config constructor accepts most of the same arguments
as the constructor of :class:`pyramid.view.view_config`. It can be used
in the same places, and behaves in largely the same way, except it always
- registers a not found exception view instead of a "normal" view.
+ registers a not found exception view instead of a 'normal' view.
Example:
@@ -360,17 +350,8 @@ class notfound_view_config(object):
venusian = venusian
- def __init__(self, request_type=default, request_method=default,
- route_name=default, request_param=default, attr=default,
- renderer=default, containment=default, wrapper=default,
- xhr=default, accept=default, header=default,
- path_info=default, custom_predicates=default,
- decorator=default, mapper=default, match_param=default,
- append_slash=False):
- L = locals()
- for k, v in L.items():
- if k not in ('self', 'L') and v is not default:
- self.__dict__[k] = v
+ def __init__(self, **settings):
+ self.__dict__.update(settings)
def __call__(self, wrapped):
settings = self.__dict__.copy()
@@ -400,7 +381,7 @@ class forbidden_view_config(object):
The forbidden_view_config constructor accepts most of the same arguments
as the constructor of :class:`pyramid.view.view_config`. It can be used
in the same places, and behaves in largely the same way, except it always
- registers a forbidden exception view instead of a "normal" view.
+ registers a forbidden exception view instead of a 'normal' view.
Example:
@@ -413,9 +394,9 @@ class forbidden_view_config(object):
def notfound(request):
return Response('You are not allowed', status='401 Unauthorized')
- All have the same meaning as :meth:`pyramid.view.view_config` and each
- predicate argument restricts the set of circumstances under which this
- notfound view will be invoked.
+ All arguments passed to this function have the same meaning as
+ :meth:`pyramid.view.view_config` and each predicate argument restricts
+ the set of circumstances under which this notfound view will be invoked.
See :ref:`changing_the_forbidden_view` for detailed usage information.
@@ -426,16 +407,8 @@ class forbidden_view_config(object):
venusian = venusian
- def __init__(self, request_type=default, request_method=default,
- route_name=default, request_param=default, attr=default,
- renderer=default, containment=default, wrapper=default,
- xhr=default, accept=default, header=default,
- path_info=default, custom_predicates=default,
- decorator=default, mapper=default, match_param=default):
- L = locals()
- for k, v in L.items():
- if k not in ('self', 'L') and v is not default:
- self.__dict__[k] = v
+ def __init__(self, **settings):
+ self.__dict__.update(settings)
def __call__(self, wrapped):
settings = self.__dict__.copy()
diff --git a/setup.py b/setup.py
index cbc1075bd..4ea63a3ee 100644
--- a/setup.py
+++ b/setup.py
@@ -39,7 +39,7 @@ install_requires=[
'setuptools',
'Chameleon >= 1.2.3',
'Mako >= 0.3.6', # strict_undefined
- 'WebOb >= 1.2dev', # response.text / py3 compat
+ 'WebOb >= 1.2b3', # request.path_info is unicode
'repoze.lru >= 0.4', # py3 compat
'zope.interface >= 3.8.0', # has zope.interface.registry
'zope.deprecation >= 3.5.0', # py3 compat
@@ -68,7 +68,7 @@ testing_extras = tests_require + [
]
setup(name='pyramid',
- version='1.4dev',
+ version='1.4a3',
description=('The Pyramid web application development framework, a '
'Pylons project'),
long_description=README + '\n\n' + CHANGES,
@@ -79,6 +79,7 @@ setup(name='pyramid',
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2",
+ "Programming Language :: Python :: 3.3",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Framework :: Pyramid",
diff --git a/tox.ini b/tox.ini
index 85bd41bda..b50e56544 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,45 +4,16 @@ envlist =
[testenv]
commands =
+ python setup.py dev
python setup.py test -q
-deps =
- zope.component
- Sphinx
- repoze.sphinx.autointerface
- WebTest
- virtualenv
- venusian
-
-[testenv:py32]
-commands =
- python setup.py test -q
-deps =
- WebTest
- virtualenv
- venusian
-
-[testenv:py33]
-commands =
- python setup.py test -q
-deps =
- WebTest
- virtualenv
- venusian
[testenv:cover]
basepython =
python2.6
commands =
+ python setup.py dev
python setup.py nosetests --with-xunit --with-xcoverage
deps =
- zope.component
- Sphinx
- WebTest
- repoze.sphinx.autointerface
- virtualenv
- venusian
- nose
- coverage
nosexcover
# we separate coverage into its own testenv because a) "last run wins" wrt