diff options
| -rw-r--r-- | CHANGES.txt | 24 | ||||
| -rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
| -rw-r--r-- | HACKING.txt | 32 | ||||
| -rw-r--r-- | docs/designdefense.rst | 2 | ||||
| -rw-r--r-- | docs/narr/events.rst | 6 | ||||
| -rw-r--r-- | docs/narr/extending.rst | 2 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 104 | ||||
| -rw-r--r-- | docs/narr/hybrid.rst | 2 | ||||
| -rw-r--r-- | docs/narr/i18n.rst | 8 | ||||
| -rw-r--r-- | docs/narr/introduction.rst | 6 | ||||
| -rw-r--r-- | docs/narr/introspector.rst | 4 | ||||
| -rw-r--r-- | docs/narr/logging.rst | 2 | ||||
| -rw-r--r-- | docs/narr/resources.rst | 2 | ||||
| -rw-r--r-- | docs/narr/scaffolding.rst | 10 | ||||
| -rw-r--r-- | docs/narr/upgrading.rst | 4 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 10 | ||||
| -rw-r--r-- | docs/narr/vhosting.rst | 4 | ||||
| -rw-r--r-- | docs/narr/views.rst | 2 | ||||
| -rw-r--r-- | docs/tutorials/wiki/authorization.rst | 4 | ||||
| -rw-r--r-- | docs/whatsnew-1.5.rst | 73 | ||||
| -rw-r--r-- | pyramid/config/views.py | 18 | ||||
| -rw-r--r-- | pyramid/encode.py | 23 | ||||
| -rw-r--r-- | pyramid/i18n.py | 3 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_views.py | 21 | ||||
| -rw-r--r-- | pyramid/tests/test_encode.py | 5 | ||||
| -rw-r--r-- | pyramid/tests/test_url.py | 55 | ||||
| -rw-r--r-- | pyramid/url.py | 161 |
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: |
