summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt24
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--HACKING.txt32
-rw-r--r--docs/designdefense.rst2
-rw-r--r--docs/narr/events.rst6
-rw-r--r--docs/narr/extending.rst2
-rw-r--r--docs/narr/hooks.rst104
-rw-r--r--docs/narr/hybrid.rst2
-rw-r--r--docs/narr/i18n.rst8
-rw-r--r--docs/narr/introduction.rst6
-rw-r--r--docs/narr/introspector.rst4
-rw-r--r--docs/narr/logging.rst2
-rw-r--r--docs/narr/resources.rst2
-rw-r--r--docs/narr/scaffolding.rst10
-rw-r--r--docs/narr/upgrading.rst4
-rw-r--r--docs/narr/urldispatch.rst10
-rw-r--r--docs/narr/vhosting.rst4
-rw-r--r--docs/narr/views.rst2
-rw-r--r--docs/tutorials/wiki/authorization.rst4
-rw-r--r--docs/whatsnew-1.5.rst73
-rw-r--r--pyramid/config/views.py18
-rw-r--r--pyramid/encode.py23
-rw-r--r--pyramid/i18n.py3
-rw-r--r--pyramid/tests/test_config/test_views.py21
-rw-r--r--pyramid/tests/test_encode.py5
-rw-r--r--pyramid/tests/test_url.py55
-rw-r--r--pyramid/url.py161
27 files changed, 402 insertions, 187 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 9f780fe45..40efecce1 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -48,6 +48,24 @@ Features
timeouts, and conformance with the ``ISession`` API.
See https://github.com/Pylons/pyramid/pull/1142
+- The anchor argument to ``pyramid.request.Request.route_url`` and
+ ``pyramid.request.Request.resource_url`` and their derivatives will now be
+ escaped to ensure minimal conformance. See
+ https://github.com/Pylons/pyramid/pull/1183
+
+- Allow sending of ``_query`` and ``_anchor`` options to
+ ``pyramid.request.Request.static_url`` when an external URL is being
+ generated.
+ See https://github.com/Pylons/pyramid/pull/1183
+
+- You can now send a string as the ``_query`` argument to
+ ``pyramid.request.Request.route_url`` and
+ ``pyramid.request.Request.resource_url`` and their derivatives. When a
+ string is sent instead of a list or dictionary. it is not URL-encoded or
+ quoted; the caller must perform this job before passing it in. This is
+ useful if you want to be able to use a different query string format than
+ ``x-www-form-urlencoded``. See https://github.com/Pylons/pyramid/pull/1183
+
Bug Fixes
---------
@@ -119,12 +137,6 @@ Deprecations
- The ``pyramid.security.has_permission`` API is now deprecated. Instead, use
the newly-added ``has_permission`` method of the request object.
-- The ``pyramid.security.forget`` API is now deprecated. Instead, use
- the newly-added ``forget_userid`` method of the request object.
-
-- The ``pyramid.security.remember`` API is now deprecated. Instead, use
- the newly-added ``remember_userid`` method of the request object.
-
- The ``pyramid.security.effective_principals`` API is now deprecated.
Instead, use the newly-added ``effective_principals`` attribute of the
request object.
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 6dba1076e..63528e662 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -226,3 +226,5 @@ Contributors
- Karl O. Pinc, 2013/09/27
- Matthew Russell, 2013/10/14
+
+- Antti Haapala, 2013/11/15
diff --git a/HACKING.txt b/HACKING.txt
index b32a8a957..12f2d68e2 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -6,9 +6,9 @@ Here are some guidelines about hacking on Pyramid.
Using a Development Checkout
----------------------------
-You'll have to create a development environment to hack on Pyramid, using a
-Pyramid checkout. You can either do this by hand or, if you have ``tox``
-installed (it's on PyPI), you can (ab)use tox to get a working development
+You'll have to create a development environment to hack on Pyramid, using a
+Pyramid checkout. You can either do this by hand or, if you have ``tox``
+installed (it's on PyPI), you can (ab)use tox to get a working development
environment. Each installation method is described below.
By Hand
@@ -25,15 +25,15 @@ By Hand
$ cd ~/hack-on-pyramid
$ virtualenv -ppython2.7 env
- Note that very old versions of virtualenv (virtualenv versions below, say,
+ Note that very old versions of virtualenv (virtualenv versions below, say,
1.10 or thereabouts) require you to pass a ``--no-site-packages`` flag to
get a completely isolated environment.
- You can choose which Python version you want to use by passing a ``-p``
+ You can choose which Python version you want to use by passing a ``-p``
flag to ``virtualenv``. For example, ``virtualenv -ppython2.7``
chooses the Python 2.7 interpreter to be installed.
- From here on in within these instructions, the ``~/hack-on-pyramid/env``
+ From here on in within these instructions, the ``~/hack-on-pyramid/env``
virtual environment you created above will be referred to as ``$VENV``.
To use the instructions in the steps that follow literally, use the
``export VENV=~/hack-on-pyramid/env`` command.
@@ -132,7 +132,7 @@ Coding Style
- PEP8 compliance. Whitespace rules are relaxed: not necessary to put
2 newlines between classes. But 80-column lines, in particular, are
- mandatory. See
+ mandatory. See
http://docs.pylonsproject.org/en/latest/community/codestyle.html for more
information.
@@ -142,14 +142,14 @@ Coding Style
Running Tests
--------------
-- To run all tests for Pyramid on a single Python version, run ``nosetests``
+- To run all tests for Pyramid on a single Python version, run ``nosetests``
from your development virtualenv (See *Using a Development Checkout* above).
- To run individual tests (i.e. during development) you can use a regular
expression with the ``-t`` parameter courtesy of the `nose-selecttests
- <https://pypi.python.org/pypi/nose-selecttests/>`_ plugin that's been
- installed (along with nose itself) via ``python setup.py dev``. The
- easiest usage is to simply provide the verbatim name of the test you're
+ <https://pypi.python.org/pypi/nose-selecttests/>`_ plugin that's been
+ installed (along with nose itself) via ``python setup.py dev``. The
+ easiest usage is to simply provide the verbatim name of the test you're
working on.
- To run the full set of Pyramid tests on all platforms, install ``tox``
@@ -191,8 +191,8 @@ or adds the feature.
To build and review docs (where ``$VENV`` refers to the virtualenv you're
using to develop Pyramid):
-1. After following the steps above in "Using a Development Checkout", cause
- Sphinx and all development requirements to be installed in your
+1. After following the steps above in "Using a Development Checkout", cause
+ Sphinx and all development requirements to be installed in your
virtualenv::
$ cd ~/hack-on-pyramid
@@ -212,9 +212,9 @@ using to develop Pyramid):
$ cd ~/hack-on-pyramid/pyramid/docs
$ make clean html SPHINXBUILD=$VENV/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.
+ The ``SPHINXBUILD=...`` argument tells Sphinx to use the virtualenv Python,
+ which will have both Sphinx and Pyramid (for API documentation generation)
+ installed.
4. Open the ``docs/_build/html/index.html`` file to see the resulting HTML
rendering.
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index bbce3e29c..2f3c14881 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -1078,7 +1078,7 @@ The contents of ``app2.py``:
The contents of ``config.py``:
.. code-block:: python
- :linenos:
+ :linenos:
L = []
diff --git a/docs/narr/events.rst b/docs/narr/events.rst
index 2accb3dbe..50484761d 100644
--- a/docs/narr/events.rst
+++ b/docs/narr/events.rst
@@ -172,7 +172,7 @@ track of the information that subscribers will need. Here are some
example custom event classes:
.. code-block:: python
- :linenos:
+ :linenos:
class DocCreated(object):
def __init__(self, doc, request):
@@ -196,7 +196,7 @@ also use custom events with :ref:`subscriber predicates
event with a decorator:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.events import subscriber
from .events import DocCreated
@@ -215,7 +215,7 @@ To fire your custom events use the
accessed as ``request.registry.notify``. For example:
.. code-block:: python
- :linenos:
+ :linenos:
from .events import DocCreated
diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst
index a60a49fea..8462a9da7 100644
--- a/docs/narr/extending.rst
+++ b/docs/narr/extending.rst
@@ -234,7 +234,7 @@ For example, if the original application has the following
``configure_views`` configuration method:
.. code-block:: python
- :linenos:
+ :linenos:
def configure_views(config):
config.add_view('theoriginalapp.views.theview', name='theview')
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 0c450fad7..f2542f1d7 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -363,7 +363,7 @@ and modify the set of :term:`renderer globals` before they are passed to a
that can be used for this purpose. For example:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.events import subscriber
from pyramid.events import BeforeRender
@@ -963,8 +963,8 @@ For full details, please read the `Venusian documentation
.. _registering_tweens:
-Registering "Tweens"
---------------------
+Registering Tweens
+------------------
.. versionadded:: 1.2
Tweens
@@ -976,26 +976,80 @@ feature that may be used by Pyramid framework extensions, to provide, for
example, Pyramid-specific view timing support bookkeeping code that examines
exceptions before they are returned to the upstream WSGI application. Tweens
behave a bit like :term:`WSGI` :term:`middleware` but they have the benefit of
-running in a context in which they have access to the Pyramid
-:term:`application registry` as well as the Pyramid rendering machinery.
+running in a context in which they have access to the Pyramid :term:`request`,
+:term:`response` and :term:`application registry` as well as the Pyramid
+rendering machinery.
-Creating a Tween Factory
-~~~~~~~~~~~~~~~~~~~~~~~~
+Creating a Tween
+~~~~~~~~~~~~~~~~
-To make use of tweens, you must construct a "tween factory". A tween factory
+To create a tween, you must write a "tween factory". A tween factory
must be a globally importable callable which accepts two arguments:
``handler`` and ``registry``. ``handler`` will be the either the main
Pyramid request handling function or another tween. ``registry`` will be the
Pyramid :term:`application registry` represented by this Configurator. A
-tween factory must return a tween when it is called.
+tween factory must return the tween (a callable object) when it is called.
-A tween is a callable which accepts a :term:`request` object and returns
-a :term:`response` object.
+A tween is called with a single argument, ``request``, which is the
+:term:`request` created by Pyramid's router when it receives a WSGI request.
+A tween should return a :term:`response`, usually the one generated by the
+downstream Pyramid application.
-Here's an example of a tween factory:
+You can write the tween factory as a simple closure-returning function:
.. code-block:: python
- :linenos:
+ :linenos:
+
+ def simple_tween_factory(handler, registry):
+ # one-time configuration code goes here
+
+ def simple_tween(request):
+ # code to be executed for each request before
+ # the actual application code goes here
+
+ response = handler(request)
+
+ # code to be executed for each request after
+ # the actual application code goes here
+
+ return response
+
+ return simple_tween
+
+Alternatively, the tween factory can be a class with the ``__call__`` magic
+method:
+
+.. code-block:: python
+ :linenos:
+
+ class simple_tween_factory(object):
+ def __init__(handler, registry):
+ self.handler = handler
+ self.registry = registry
+
+ # one-time configuration code goes here
+
+ def __call__(self, request):
+ # code to be executed for each request before
+ # the actual application code goes here
+
+ response = self.handler(request)
+
+ # code to be executed for each request after
+ # the actual application code goes here
+
+ return response
+
+The closure style performs slightly better and enables you to conditionally
+omit the tween from the request processing pipeline (see the following timing
+tween example), whereas the class style makes it easier to have shared mutable
+state, and it allows subclassing.
+
+Here's a complete example of a tween that logs the time spent processing each
+request:
+
+.. code-block:: python
+ :linenos:
# in a module named myapp.tweens
@@ -1022,12 +1076,6 @@ Here's an example of a tween factory:
# handler
return handler
-If you remember, a tween is an object which accepts a :term:`request` object
-and which returns a :term:`response` argument. The ``request`` argument to a
-tween will be the request created by Pyramid's router when it receives a WSGI
-request. The response object will be generated by the downstream Pyramid
-application and it should be returned by the tween.
-
In the above example, the tween factory defines a ``timing_tween`` tween and
returns it if ``asbool(registry.settings.get('do_timing'))`` is true. It
otherwise simply returns the handler it was given. The ``registry.settings``
@@ -1053,7 +1101,7 @@ Here's an example of registering a tween factory as an "implicit" tween in a
Pyramid application:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.config import Configurator
config = Configurator()
@@ -1087,7 +1135,7 @@ chain (the tween generated by the very last tween factory added) as its
request handler function. For example:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.config import Configurator
@@ -1132,8 +1180,10 @@ Allowable values for ``under`` or ``over`` (or both) are:
fallbacks if the desired tween is not included, as well as compatibility
with multiple other tweens.
-Effectively, ``under`` means "closer to the main Pyramid application than",
-``over`` means "closer to the request ingress than".
+Effectively, ``over`` means "closer to the request ingress than" and
+``under`` means "closer to the main Pyramid application than".
+You can think of an onion with outer layers over the inner layers,
+the application being under all the layers at the center.
For example, the following call to
:meth:`~pyramid.config.Configurator.add_tween` will attempt to place the
@@ -1329,7 +1379,7 @@ route predicate factory is most often a class with a constructor
method. For example:
.. code-block:: python
- :linenos:
+ :linenos:
class ContentTypePredicate(object):
def __init__(self, val, config):
@@ -1392,7 +1442,7 @@ with a subscriber that subscribes to the :class:`pyramid.events.NewRequest`
event type.
.. code-block:: python
- :linenos:
+ :linenos:
class RequestPathStartsWith(object):
def __init__(self, val, config):
@@ -1421,7 +1471,7 @@ previously registered ``request_path_startswith`` predicate in a call to
:meth:`~pyramid.config.Configurator.add_subscriber`:
.. code-block:: python
- :linenos:
+ :linenos:
# define a subscriber in your code
@@ -1437,7 +1487,7 @@ Here's the same subscriber/predicate/event-type combination used via
:class:`~pyramid.events.subscriber`.
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.events import subscriber
diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst
index a29ccb2ac..4a3258d35 100644
--- a/docs/narr/hybrid.rst
+++ b/docs/narr/hybrid.rst
@@ -63,7 +63,7 @@ An application that uses only traversal will have view configuration
declarations that look like this:
.. code-block:: python
- :linenos:
+ :linenos:
# config is an instance of pyramid.config.Configurator
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index b62c16ff0..c9b782c08 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -309,7 +309,7 @@ In particular, add the ``Babel`` and ``lingua`` distributions to the
application's ``setup.py`` file:
.. code-block:: python
- :linenos:
+ :linenos:
setup(name="mypackage",
# ...
@@ -370,7 +370,7 @@ file of a ``pcreate`` -generated :app:`Pyramid` application has stanzas in it
that look something like the following:
.. code-block:: ini
- :linenos:
+ :linenos:
[compile_catalog]
directory = myproject/locale
@@ -398,7 +398,7 @@ that you'd like the domain of your translations to be ``mydomain``
instead, change the ``setup.cfg`` file stanzas to look like so:
.. code-block:: ini
- :linenos:
+ :linenos:
[compile_catalog]
directory = myproject/locale
@@ -1041,7 +1041,7 @@ if no locale can be determined.
Here's an implementation of a simple locale negotiator:
.. code-block:: python
- :linenos:
+ :linenos:
def my_locale_negotiator(request):
locale_name = request.params.get('my_locale')
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index a9c5fdfbd..8acbab3a0 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -336,7 +336,7 @@ For example, instead of returning a ``Response`` object from a
``render_to_response`` call:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.renderers import render_to_response
@@ -347,7 +347,7 @@ For example, instead of returning a ``Response`` object from a
You can return a Python dictionary:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.view import view_config
@@ -827,7 +827,7 @@ Here's an example of using Pyramid's introspector from within a view
callable:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.view import view_config
from pyramid.response import Response
diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst
index 3c0a6744f..a7bde4cf7 100644
--- a/docs/narr/introspector.rst
+++ b/docs/narr/introspector.rst
@@ -24,7 +24,7 @@ Here's an example of using Pyramid's introspector from within a view
callable:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.view import view_config
from pyramid.response import Response
@@ -100,7 +100,7 @@ its ``__getitem__``, ``get``, ``keys``, ``values``, or ``items`` methods.
For example:
.. code-block:: python
- :linenos:
+ :linenos:
route_intr = introspector.get('routes', 'edit_user')
pattern = route_intr['pattern']
diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst
index b3bfb8a1e..75428d513 100644
--- a/docs/narr/logging.rst
+++ b/docs/narr/logging.rst
@@ -179,7 +179,7 @@ file, simply create a logger object using the ``__name__`` builtin and call
methods on it.
.. code-block:: python
- :linenos:
+ :linenos:
import logging
log = logging.getLogger(__name__)
diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst
index 34d75f2cc..f3ff1dc4c 100644
--- a/docs/narr/resources.rst
+++ b/docs/narr/resources.rst
@@ -83,7 +83,7 @@ works against resource instances.
Here's a sample resource tree, represented by a variable named ``root``:
.. code-block:: python
- :linenos:
+ :linenos:
class Resource(dict):
pass
diff --git a/docs/narr/scaffolding.rst b/docs/narr/scaffolding.rst
index 534b2caf4..f924d0d62 100644
--- a/docs/narr/scaffolding.rst
+++ b/docs/narr/scaffolding.rst
@@ -39,9 +39,9 @@ named ``__init__.py`` with something like the following:
from pyramid.scaffolds import PyramidTemplate
- class CoolExtensionTemplate(PyramidTemplate):
- _template_dir = 'coolextension_scaffold'
- summary = 'My cool extension'
+ class CoolExtensionTemplate(PyramidTemplate):
+ _template_dir = 'coolextension_scaffold'
+ summary = 'My cool extension'
Once this is done, within the ``scaffolds`` directory, create a template
directory. Our example used a template directory named
@@ -89,7 +89,7 @@ For example:
[pyramid.scaffold]
coolextension=coolextension.scaffolds:CoolExtensionTemplate
"""
- )
+ )
Run your distribution's ``setup.py develop`` or ``setup.py install``
command. After that, you should be able to see your scaffolding template
@@ -112,7 +112,7 @@ want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X,
defining your scaffold template:
.. code-block:: python
- :linenos:
+ :linenos:
try: # pyramid 1.0.X
# "pyramid.paster.paste_script..." doesn't exist past 1.0.X
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
index 64343ca3e..eb3194a65 100644
--- a/docs/narr/upgrading.rst
+++ b/docs/narr/upgrading.rst
@@ -137,7 +137,7 @@ 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:
+ :linenos:
from pyramid.view import view_config
@@ -148,7 +148,7 @@ 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:
+ :linenos:
from pyramid.view import view_config
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 61849c3c0..96ee5758e 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -492,7 +492,7 @@ The simplest route declaration which configures a route match to *directly*
result in a particular view callable being invoked:
.. code-block:: python
- :linenos:
+ :linenos:
config.add_route('idea', 'site/{id}')
config.add_view('mypackage.views.site_view', route_name='idea')
@@ -901,7 +901,7 @@ Details of the route matching decision for a particular request to the
which you started the application from. For example:
.. code-block:: text
- :linenos:
+ :linenos:
$ PYRAMID_DEBUG_ROUTEMATCH=true $VENV/bin/pserve development.ini
Starting server in PID 13586.
@@ -1060,7 +1060,7 @@ A custom route predicate may also *modify* the ``match`` dictionary. For
instance, a predicate might do some type conversion of values:
.. code-block:: python
- :linenos:
+ :linenos:
def integers(*segment_names):
def predicate(info, request):
@@ -1086,7 +1086,7 @@ To avoid the try/except uncertainty, the route pattern can contain regular
expressions specifying requirements for that marker. For instance:
.. code-block:: python
- :linenos:
+ :linenos:
def integers(*segment_names):
def predicate(info, request):
@@ -1128,7 +1128,7 @@ name. The ``pattern`` attribute is the route pattern. An example of using
the route in a set of route predicates:
.. code-block:: python
- :linenos:
+ :linenos:
def twenty_ten(info, request):
if info['route'].name in ('ymd', 'ym', 'y'):
diff --git a/docs/narr/vhosting.rst b/docs/narr/vhosting.rst
index d37518052..53f6888b3 100644
--- a/docs/narr/vhosting.rst
+++ b/docs/narr/vhosting.rst
@@ -109,7 +109,7 @@ An example of an Apache ``mod_proxy`` configuration that will host the
is below:
.. code-block:: apache
- :linenos:
+ :linenos:
NameVirtualHost *:80
@@ -130,7 +130,7 @@ For a :app:`Pyramid` application running under :term:`mod_wsgi`,
the same can be achieved using ``SetEnv``:
.. code-block:: apache
- :linenos:
+ :linenos:
<Location />
SetEnv HTTP_X_VHM_ROOT /cms
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index b2dd549ce..a746eb043 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -536,7 +536,7 @@ The following types work as view callables in this style:
e.g.:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.response import Response
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 62b1164e3..93cd0c18e 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -149,8 +149,8 @@ to the ``@view_config`` decorator for ``add_page()`` and
``edit_page()``, for example:
.. code-block:: python
- :linenos:
- :emphasize-lines: 3
+ :linenos:
+ :emphasize-lines: 3
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
index 57f93cbff..23613896a 100644
--- a/docs/whatsnew-1.5.rst
+++ b/docs/whatsnew-1.5.rst
@@ -316,6 +316,48 @@ The feature additions in Pyramid 1.5 follow.
- :func:`pyramid.path.package_name` no longer thows an exception when resolving
the package name for namespace packages that have no ``__file__`` attribute.
+- An authorization API has been added as a method of the request:
+ :meth:`pyramid.request.Request.has_permission`. It is a method-based
+ alternative to the :func:`pyramid.security.has_permission` API and works
+ exactly the same. The older API is now deprecated.
+
+- Property API attributes have been added to the request for easier access to
+ authentication data: :attr:`pyramid.request.Request.authenticated_userid`,
+ :attr:`pyramid.request.Request.unauthenticated_userid`, and
+ :attr:`pyramid.request.Request.effective_principals`. These are analogues,
+ respectively, of :func:`pyramid.security.authenticated_userid`,
+ :func:`pyramid.security.unauthenticated_userid`, and
+ :func:`pyramid.security.effective_principals`. They operate exactly the
+ same, except they are attributes of the request instead of functions
+ accepting a request. They are properties, so they cannot be assigned to.
+ The older function-based APIs are now deprecated.
+
+- Pyramid's console scripts (``pserve``, ``pviews``, etc) can now be run
+ directly, allowing custom arguments to be sent to the python interpreter
+ at runtime. For example::
+
+ python -3 -m pyramid.scripts.pserve development.ini
+
+- Added a specific subclass of :class:`pyramid.httpexceptions.HTTPBadRequest`
+ named :class:`pyramid.exceptions.BadCSRFToken` which will now be raised in
+ response to failures in the ``check_csrf_token`` view predicate. See
+ https://github.com/Pylons/pyramid/pull/1149
+
+- Added a new ``SignedCookieSessionFactory`` which is very similar to the
+ ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on
+ signing content. The custom serializer arguments to this function should
+ only focus on serializing, unlike its predecessor which required the
+ serializer to also perform signing.
+ See https://github.com/Pylons/pyramid/pull/1142
+
+- Added a new ``BaseCookieSessionFactory`` which acts as a generic cookie
+ factory that can be used by framework implementors to create their own
+ session implementations. It provides a reusable API which focuses strictly
+ on providing a dictionary-like object that properly handles renewals,
+ timeouts, and conformance with the ``ISession`` API.
+ See https://github.com/Pylons/pyramid/pull/1142
+
+
Other Backwards Incompatibilities
---------------------------------
@@ -404,6 +446,13 @@ Other Backwards Incompatibilities
Pyramid narrative documentation instead of providing renderer globals values
to the configurator.
+- The key/values in the ``_query`` parameter of
+ :meth:`pyramid.request.Request.route_url` and the ``query`` parameter of
+ :meth:`pyramid.request.Request.resource_url` (and their variants), used to
+ encode a value of ``None`` as the string ``'None'``, leaving the resulting
+ query string to be ``a=b&key=None``. The value is now dropped in this
+ situation, leaving a query string of ``a=b&key=``. See
+ https://github.com/Pylons/pyramid/issues/1119
Deprecations
------------
@@ -417,12 +466,36 @@ Deprecations
a deprecation warning when used. It had been docs-deprecated in 1.4
but did not issue a deprecation warning when used.
+- :func:`pyramid.security.has_permission` is now deprecated in favor of using
+ :meth:`pyramid.request.Request.has_permission`.
+
+- The :func:`pyramid.security.authenticated_userid`,
+ :func:`pyramid.security.unauthenticated_userid`, and
+ :func:`pyramid.security.effective_principals` functions have been
+ deprecated. Use :attr:`pyramid.request.Request.authenticated_userid`,
+ :attr:`pyramid.request.Request.unauthenticated_userid` and
+ :attr:`pyramid.request.Request.effective_principals` instead.
+
+- Deprecate the ``pyramid.interfaces.ITemplateRenderer`` interface. It was
+ ill-defined and became unused when Mako and Chameleon template bindings were
+ split into their own packages.
+
+- The ``pyramid.session.UnencryptedCookieSessionFactoryConfig`` API has been
+ deprecated and is superseded by the
+ ``pyramid.session.SignedCookieSessionFactory``. Note that while the cookies
+ generated by the ``UnencryptedCookieSessionFactoryConfig``
+ are compatible with cookies generated by old releases, cookies generated by
+ the SignedCookieSessionFactory are not. See
+ https://github.com/Pylons/pyramid/pull/1142
+
Documentation Enhancements
--------------------------
- A new documentation chapter named :ref:`quick_tour` was added. It describes
starting out with Pyramid from a high level.
+- Added a :ref:`quick_tutorial` to go with the Quick Tour
+
- Many other enhancements.
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index a3f885504..72dc3f414 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -44,6 +44,11 @@ from pyramid.compat import (
is_nonstr_iter
)
+from pyramid.encode import (
+ quote_plus,
+ urlencode,
+)
+
from pyramid.exceptions import (
ConfigurationError,
PredicateMismatch,
@@ -65,6 +70,8 @@ from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
+from pyramid.url import parse_url_overrides
+
from pyramid.view import (
render_view_to_response,
AppendSlashNotFoundViewFactory,
@@ -1895,14 +1902,15 @@ class StaticURLInfo(object):
kw['subpath'] = subpath
return request.route_url(route_name, **kw)
else:
+ app_url, scheme, host, port, qs, anchor = \
+ parse_url_overrides(kw)
parsed = url_parse(url)
if not parsed.scheme:
- # parsed.scheme is readonly, so we have to parse again
- # to change the scheme, sigh.
- url = urlparse.urlunparse(url_parse(
- url, scheme=request.environ['wsgi.url_scheme']))
+ url = urlparse.urlunparse(parsed._replace(
+ scheme=request.environ['wsgi.url_scheme']))
subpath = url_quote(subpath)
- return urljoin(url, subpath)
+ result = urljoin(url, subpath)
+ return result + qs + anchor
raise ValueError('No static URL definition matching %s' % path)
diff --git a/pyramid/encode.py b/pyramid/encode.py
index 9e190bc21..0be0107b3 100644
--- a/pyramid/encode.py
+++ b/pyramid/encode.py
@@ -3,11 +3,16 @@ from pyramid.compat import (
binary_type,
is_nonstr_iter,
url_quote as _url_quote,
- url_quote_plus as quote_plus, # bw compat api (dnr)
+ url_quote_plus as _quote_plus,
)
-def url_quote(s, safe=''): # bw compat api
- return _url_quote(s, safe=safe)
+def url_quote(val, safe=''): # bw compat api
+ cls = val.__class__
+ if cls is text_type:
+ val = val.encode('utf-8')
+ elif cls is not binary_type:
+ val = str(val).encode('utf-8')
+ return _url_quote(val, safe=safe)
def urlencode(query, doseq=True):
"""
@@ -47,28 +52,28 @@ def urlencode(query, doseq=True):
prefix = ''
for (k, v) in query:
- k = _enc(k)
+ k = quote_plus(k)
if is_nonstr_iter(v):
for x in v:
- x = _enc(x)
+ x = quote_plus(x)
result += '%s%s=%s' % (prefix, k, x)
prefix = '&'
elif v is None:
result += '%s%s=' % (prefix, k)
else:
- v = _enc(v)
+ v = quote_plus(v)
result += '%s%s=%s' % (prefix, k, v)
prefix = '&'
return result
-def _enc(val):
+# bw compat api (dnr)
+def quote_plus(val, safe=''):
cls = val.__class__
if cls is text_type:
val = val.encode('utf-8')
elif cls is not binary_type:
val = str(val).encode('utf-8')
- return quote_plus(val)
-
+ return _quote_plus(val, safe=safe)
diff --git a/pyramid/i18n.py b/pyramid/i18n.py
index cdedbc877..6ffd93e8f 100644
--- a/pyramid/i18n.py
+++ b/pyramid/i18n.py
@@ -107,7 +107,8 @@ def default_locale_negotiator(request):
- First, the negotiator looks for the ``_LOCALE_`` attribute of
the request object (possibly set by a view or a listener for an
- :term:`event`).
+ :term:`event`). If the attribute exists and it is not ``None``,
+ its value will be used.
- Then it looks for the ``request.params['_LOCALE_']`` value.
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 051961d25..57bb5e9d0 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -3820,6 +3820,27 @@ class TestStaticURLInfo(unittest.TestCase):
result = inst.generate('package:path/abc def', request, a=1)
self.assertEqual(result, 'http://example.com/abc%20def')
+ def test_generate_url_with_custom_query(self):
+ inst = self._makeOne()
+ registrations = [('http://example.com/', 'package:path/', None)]
+ inst._get_registrations = lambda *x: registrations
+ request = self._makeRequest()
+ result = inst.generate('package:path/abc def', request, a=1,
+ _query='(openlayers)')
+ self.assertEqual(result,
+ 'http://example.com/abc%20def?(openlayers)')
+
+ def test_generate_url_with_custom_anchor(self):
+ inst = self._makeOne()
+ registrations = [('http://example.com/', 'package:path/', None)]
+ inst._get_registrations = lambda *x: registrations
+ request = self._makeRequest()
+ uc = text_(b'La Pe\xc3\xb1a', 'utf-8')
+ result = inst.generate('package:path/abc def', request, a=1,
+ _anchor=uc)
+ self.assertEqual(result,
+ 'http://example.com/abc%20def#La%20Pe%C3%B1a')
+
def test_add_already_exists(self):
inst = self._makeOne()
config = self._makeConfig(
diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py
index 908249877..8fb766d88 100644
--- a/pyramid/tests/test_encode.py
+++ b/pyramid/tests/test_encode.py
@@ -72,3 +72,8 @@ class URLQuoteTests(unittest.TestCase):
la = b'La/Pe\xc3\xb1a'
result = self._callFUT(la, '/')
self.assertEqual(result, 'La/Pe%C3%B1a')
+
+ def test_it_with_nonstr_nonbinary(self):
+ la = None
+ result = self._callFUT(la, '/')
+ self.assertEqual(result, 'None')
diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py
index f6117777f..0a788ba97 100644
--- a/pyramid/tests/test_url.py
+++ b/pyramid/tests/test_url.py
@@ -6,7 +6,6 @@ from pyramid import testing
from pyramid.compat import (
text_,
- native_,
WIN,
)
@@ -93,6 +92,14 @@ class TestURLMethodsMixin(unittest.TestCase):
result = request.resource_url(context, 'a b c')
self.assertEqual(result, 'http://example.com:5432/context/a%20b%20c')
+ def test_resource_url_with_query_str(self):
+ request = self._makeOne()
+ self._registerResourceURL(request.registry)
+ context = DummyContext()
+ result = request.resource_url(context, 'a', query='(openlayers)')
+ self.assertEqual(result,
+ 'http://example.com:5432/context/a?(openlayers)')
+
def test_resource_url_with_query_dict(self):
request = self._makeOne()
self._registerResourceURL(request.registry)
@@ -149,23 +156,18 @@ class TestURLMethodsMixin(unittest.TestCase):
request = self._makeOne()
self._registerResourceURL(request.registry)
context = DummyContext()
- uc = text_(b'La Pe\xc3\xb1a', 'utf-8')
+ uc = text_(b'La Pe\xc3\xb1a', 'utf-8')
result = request.resource_url(context, anchor=uc)
- self.assertEqual(
- result,
- native_(
- text_(b'http://example.com:5432/context/#La Pe\xc3\xb1a',
- 'utf-8'),
- 'utf-8')
- )
+ self.assertEqual(result,
+ 'http://example.com:5432/context/#La%20Pe%C3%B1a')
- def test_resource_url_anchor_is_not_urlencoded(self):
+ def test_resource_url_anchor_is_urlencoded_safe(self):
request = self._makeOne()
self._registerResourceURL(request.registry)
context = DummyContext()
- result = request.resource_url(context, anchor=' /#')
+ result = request.resource_url(context, anchor=' /#?&+')
self.assertEqual(result,
- 'http://example.com:5432/context/# /#')
+ 'http://example.com:5432/context/#%20/%23?&+')
def test_resource_url_no_IResourceURL_registered(self):
# falls back to ResourceURL
@@ -448,14 +450,8 @@ class TestURLMethodsMixin(unittest.TestCase):
request.registry.registerUtility(mapper, IRoutesMapper)
result = request.route_url('flub', _anchor=b"La Pe\xc3\xb1a")
- self.assertEqual(
- result,
- native_(
- text_(
- b'http://example.com:5432/1/2/3#La Pe\xc3\xb1a',
- 'utf-8'),
- 'utf-8')
- )
+ self.assertEqual(result,
+ 'http://example.com:5432/1/2/3#La%20Pe%C3%B1a')
def test_route_url_with_anchor_unicode(self):
from pyramid.interfaces import IRoutesMapper
@@ -465,14 +461,8 @@ class TestURLMethodsMixin(unittest.TestCase):
anchor = text_(b'La Pe\xc3\xb1a', 'utf-8')
result = request.route_url('flub', _anchor=anchor)
- self.assertEqual(
- result,
- native_(
- text_(
- b'http://example.com:5432/1/2/3#La Pe\xc3\xb1a',
- 'utf-8'),
- 'utf-8')
- )
+ self.assertEqual(result,
+ 'http://example.com:5432/1/2/3#La%20Pe%C3%B1a')
def test_route_url_with_query(self):
from pyramid.interfaces import IRoutesMapper
@@ -483,6 +473,15 @@ class TestURLMethodsMixin(unittest.TestCase):
self.assertEqual(result,
'http://example.com:5432/1/2/3?q=1')
+ def test_route_url_with_query_str(self):
+ from pyramid.interfaces import IRoutesMapper
+ request = self._makeOne()
+ mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
+ request.registry.registerUtility(mapper, IRoutesMapper)
+ result = request.route_url('flub', _query='(openlayers)')
+ self.assertEqual(result,
+ 'http://example.com:5432/1/2/3?(openlayers)')
+
def test_route_url_with_empty_query(self):
from pyramid.interfaces import IRoutesMapper
request = self._makeOne()
diff --git a/pyramid/url.py b/pyramid/url.py
index fda2c72c7..06e7e6a81 100644
--- a/pyramid/url.py
+++ b/pyramid/url.py
@@ -12,12 +12,13 @@ from pyramid.interfaces import (
)
from pyramid.compat import (
- native_,
bytes_,
- text_type,
- url_quote,
+ string_types,
)
-from pyramid.encode import urlencode
+from pyramid.encode import (
+ url_quote,
+ urlencode,
+)
from pyramid.path import caller_package
from pyramid.threadlocal import get_current_registry
@@ -27,6 +28,48 @@ from pyramid.traversal import (
)
PATH_SAFE = '/:@&+$,' # from webob
+QUERY_SAFE = '/?:@!$&\'()*+,;=' # RFC 3986
+ANCHOR_SAFE = QUERY_SAFE
+
+def parse_url_overrides(kw):
+ """Parse special arguments passed when generating urls.
+
+ The supplied dictionary is mutated, popping arguments as necessary.
+ Returns a 6-tuple of the format ``(app_url, scheme, host, port,
+ qs, anchor)``.
+ """
+ anchor = ''
+ qs = ''
+ app_url = None
+ host = None
+ scheme = None
+ port = None
+
+ if '_query' in kw:
+ query = kw.pop('_query')
+ if isinstance(query, string_types):
+ qs = '?' + query
+ elif query:
+ qs = '?' + urlencode(query, doseq=True)
+
+ if '_anchor' in kw:
+ anchor = kw.pop('_anchor')
+ anchor = url_quote(anchor, ANCHOR_SAFE)
+ anchor = '#' + anchor
+
+ if '_app_url' in kw:
+ app_url = kw.pop('_app_url')
+
+ if '_host' in kw:
+ host = kw.pop('_host')
+
+ if '_scheme' in kw:
+ scheme = kw.pop('_scheme')
+
+ if '_port' in kw:
+ port = kw.pop('_port')
+
+ return app_url, scheme, host, port, qs, anchor
class URLMethodsMixin(object):
""" Request methods mixin for BaseRequest having to do with URL
@@ -124,18 +167,21 @@ class URLMethodsMixin(object):
``*remainder`` replacement value, it is tacked on to the URL
after being URL-quoted-except-for-embedded-slashes.
- If no ``_query`` keyword argument is provided, the request
- query string will be returned in the URL. If it is present, it
- will be used to compose a query string that will be tacked on
- to the end of the URL, replacing any request query string.
- The value of ``_query`` must be a sequence of two-tuples *or*
- a data structure with an ``.items()`` method that returns a
- sequence of two-tuples (presumably a dictionary). This data
- structure will be turned into a query string per the
- documentation of :func:`pyramid.encode.urlencode` function.
- After the query data is turned into a query string, a leading
- ``?`` is prepended, and the resulting string is appended to
- the generated URL.
+ If no ``_query`` keyword argument is provided, the request query string
+ will be returned in the URL. If it is present, it will be used to
+ compose a query string that will be tacked on to the end of the URL,
+ replacing any request query string. The value of ``_query`` may be a
+ sequence of two-tuples *or* a data structure with an ``.items()``
+ method that returns a sequence of two-tuples (presumably a dictionary).
+ This data structure will be turned into a query string per the
+ documentation of :func:`pyramid.url.urlencode` function. This will
+ produce a query string in the ``x-www-form-urlencoded`` format. A
+ non-``x-www-form-urlencoded`` encoding may be used by passing a
+ *string* value as ``_query`` in which case it will be used without
+ quoting or encoding; it is left up to the caller to do both and if he
+ does not, an invalid URL may be generated. After the query data is
+ turned into a query string, a leading ``?`` is prepended, and the
+ resulting string is appended to the generated URL.
.. note::
@@ -146,8 +192,13 @@ class URLMethodsMixin(object):
as values, and a k=v pair will be placed into the query string for
each value.
+ .. versionchanged:: 1.5
+ Allow the ``_query`` option to be a string to enable alternative
+ encodings.
+
If a keyword argument ``_anchor`` is present, its string
- representation will be used as a named anchor in the generated URL
+ representation will be quoted per :rfc:`3986#section-3.5` and used as
+ a named anchor in the generated URL
(e.g. if ``_anchor`` is passed as ``foo`` and the route URL is
``http://example.com/route/url``, the resulting generated URL will
be ``http://example.com/route/url#foo``).
@@ -156,8 +207,11 @@ class URLMethodsMixin(object):
If ``_anchor`` is passed as a string, it should be UTF-8 encoded. If
``_anchor`` is passed as a Unicode object, it will be converted to
- UTF-8 before being appended to the URL. The anchor value is not
- quoted in any way before being appended to the generated URL.
+ UTF-8 before being appended to the URL.
+
+ .. versionchanged:: 1.5
+ The ``_anchor`` option will be escaped instead of using
+ its raw string representation.
If both ``_anchor`` and ``_query`` are specified, the anchor
element will always follow the query element,
@@ -213,34 +267,7 @@ class URLMethodsMixin(object):
if route.pregenerator is not None:
elements, kw = route.pregenerator(self, elements, kw)
- anchor = ''
- qs = ''
- app_url = None
- host = None
- scheme = None
- port = None
-
- if '_query' in kw:
- query = kw.pop('_query')
- if query:
- qs = '?' + urlencode(query, doseq=True)
-
- if '_anchor' in kw:
- anchor = kw.pop('_anchor')
- anchor = native_(anchor, 'utf-8')
- anchor = '#' + anchor
-
- if '_app_url' in kw:
- app_url = kw.pop('_app_url')
-
- if '_host' in kw:
- host = kw.pop('_host')
-
- if '_scheme' in kw:
- scheme = kw.pop('_scheme')
-
- if '_port' in kw:
- port = kw.pop('_port')
+ app_url, scheme, host, port, qs, anchor = parse_url_overrides(kw)
if app_url is None:
if (scheme is not None or host is not None or port is not None):
@@ -333,15 +360,19 @@ class URLMethodsMixin(object):
``elements`` are used, the generated URL will *not*
end in trailing a slash.
- If a keyword argument ``query`` is present, it will be used to
- compose a query string that will be tacked on to the end of the URL.
- The value of ``query`` must be a sequence of two-tuples *or* a data
- structure with an ``.items()`` method that returns a sequence of
- two-tuples (presumably a dictionary). This data structure will be
- turned into a query string per the documentation of
- ``pyramid.url.urlencode`` function. After the query data is turned
- into a query string, a leading ``?`` is prepended, and the resulting
- string is appended to the generated URL.
+ If a keyword argument ``query`` is present, it will be used to compose
+ a query string that will be tacked on to the end of the URL. The value
+ of ``query`` may be a sequence of two-tuples *or* a data structure with
+ an ``.items()`` method that returns a sequence of two-tuples
+ (presumably a dictionary). This data structure will be turned into a
+ query string per the documentation of :func:``pyramid.url.urlencode``
+ function. This will produce a query string in the
+ ``x-www-form-urlencoded`` encoding. A non-``x-www-form-urlencoded``
+ query string may be used by passing a *string* value as ``query`` in
+ which case it will be used without quoting or encoding; it is up to the
+ caller to do both and if he does not an invalid URL may be generated.
+ After the query data is turned into a query string, a leading ``?`` is
+ prepended, and the resulting string is appended to the generated URL.
.. note::
@@ -352,6 +383,10 @@ class URLMethodsMixin(object):
as values, and a k=v pair will be placed into the query string for
each value.
+ .. versionchanged:: 1.5
+ Allow the ``query`` option to be a string to enable alternative
+ encodings.
+
If a keyword argument ``anchor`` is present, its string
representation will be used as a named anchor in the generated URL
(e.g. if ``anchor`` is passed as ``foo`` and the resource URL is
@@ -362,8 +397,11 @@ class URLMethodsMixin(object):
If ``anchor`` is passed as a string, it should be UTF-8 encoded. If
``anchor`` is passed as a Unicode object, it will be converted to
- UTF-8 before being appended to the URL. The anchor value is not
- quoted in any way before being appended to the generated URL.
+ UTF-8 before being appended to the URL.
+
+ .. versionchanged:: 1.5
+ The ``anchor`` option will be escaped instead of using
+ its raw string representation.
If both ``anchor`` and ``query`` are specified, the anchor element
will always follow the query element,
@@ -580,13 +618,14 @@ class URLMethodsMixin(object):
if 'query' in kw:
query = kw['query']
- if query:
+ if isinstance(query, string_types):
+ qs = '?' + query
+ elif query:
qs = '?' + urlencode(query, doseq=True)
if 'anchor' in kw:
anchor = kw['anchor']
- if isinstance(anchor, text_type):
- anchor = native_(anchor, 'utf-8')
+ anchor = url_quote(anchor, ANCHOR_SAFE)
anchor = '#' + anchor
if elements: