summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--CHANGES.txt95
-rw-r--r--CONTRIBUTORS.txt4
-rw-r--r--HACKING.txt2
-rw-r--r--HISTORY.txt2
-rw-r--r--README.rst4
-rw-r--r--docs/Makefile9
-rw-r--r--docs/api/config.rst1
-rw-r--r--docs/api/request.rst4
-rw-r--r--docs/authorintro.rst2
-rw-r--r--docs/conf.py4
-rw-r--r--docs/foreword.rst2
-rw-r--r--docs/index.rst10
-rw-r--r--docs/latexindex.rst54
-rw-r--r--docs/narr/commandline.rst24
-rw-r--r--docs/narr/i18n.rst4
-rw-r--r--docs/narr/introduction.rst4
-rw-r--r--docs/narr/project.rst12
-rw-r--r--docs/narr/renderers.rst2
-rw-r--r--docs/narr/resources.rst2
-rw-r--r--docs/narr/sessions.rst109
-rw-r--r--docs/narr/traversal.rst2
-rw-r--r--docs/narr/urldispatch.rst2
-rw-r--r--docs/narr/viewconfig.rst43
-rw-r--r--docs/whatsnew-1.1.rst4
-rw-r--r--docs/whatsnew-1.5.rst155
-rw-r--r--pyramid/authentication.py40
-rw-r--r--pyramid/config/__init__.py5
-rw-r--r--pyramid/config/adapters.py4
-rw-r--r--pyramid/config/predicates.py1
-rw-r--r--pyramid/config/routes.py4
-rw-r--r--pyramid/config/util.py72
-rw-r--r--pyramid/interfaces.py2
-rw-r--r--pyramid/renderers.py4
-rw-r--r--pyramid/router.py7
-rw-r--r--pyramid/scaffolds/alchemy/+package+/models.py5
-rw-r--r--pyramid/scripts/pdistreport.py37
-rw-r--r--pyramid/scripts/prequest.py42
-rw-r--r--pyramid/scripts/pserve.py43
-rw-r--r--pyramid/session.py26
-rw-r--r--pyramid/testing.py4
-rw-r--r--pyramid/tests/test_authentication.py24
-rw-r--r--pyramid/tests/test_config/test_util.py47
-rw-r--r--pyramid/tests/test_router.py8
-rw-r--r--pyramid/tests/test_scripts/test_pdistreport.py74
-rw-r--r--pyramid/tests/test_scripts/test_prequest.py69
-rw-r--r--pyramid/tests/test_scripts/test_pserve.py2
-rw-r--r--pyramid/tests/test_session.py21
-rw-r--r--pyramid/tests/test_testing.py4
-rw-r--r--pyramid/url.py2
-rw-r--r--setup.py3
51 files changed, 919 insertions, 188 deletions
diff --git a/.travis.yml b/.travis.yml
index ab9c3ff30..00c293046 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,4 @@
+# Wire up travis
language: python
python:
diff --git a/CHANGES.txt b/CHANGES.txt
index a471addce..68799da4a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,9 +1,43 @@
-next release
+Next Release
============
Features
--------
+- Add ``pdistreport`` script, which prints the Python version in use, the
+ Pyramid version in use, and the version number and location of all Python
+ distributions currently installed.
+
+- Add the ability to invert the result of any view, route, or subscriber
+ predicate using the ``not_`` class. For example::
+
+ from pyramid.config import not_
+
+ @view_config(route_name='myroute', request_method=not_('POST'))
+ def myview(request): ...
+
+ The above example will ensure that the view is called if the request method
+ is not POST (at least if no other view is more specific).
+
+ The :class:`pyramid.config.not_` class can be used against any value that is
+ a predicate value passed in any of these contexts:
+
+ - ``pyramid.config.Configurator.add_view``
+
+ - ``pyramid.config.Configurator.add_route``
+
+ - ``pyramid.config.Configurator.add_subscriber``
+
+ - ``pyramid.view.view_config``
+
+ - ``pyramid.events.subscriber``
+
+- ``scripts/prequest.py``: add support for submitting ``PUT`` and ``PATCH``
+ requests. See https://github.com/Pylons/pyramid/pull/1033. add support for
+ submitting ``OPTIONS`` and ``PROPFIND`` requests, and allow users to specify
+ basic authentication credentials in the request via a ``--login`` argument to
+ the script. See https://github.com/Pylons/pyramid/pull/1039.
+
- ``ACLAuthorizationPolicy`` supports ``__acl__`` as a callable. This
removes the ambiguity between the potential ``AttributeError`` that would
be raised on the ``context`` when the property was not defined and the
@@ -15,6 +49,10 @@ Features
``pyramid.config.Configurator.add_static_view``. This allows
externally-hosted static URLs to be generated based on the current protocol.
+- The ``AuthTktAuthenticationPolicy`` has a new ``parent_domain`` option to
+ set the authentication cookie as a wildcard cookie on the parent domain. This
+ is useful if you have multiple sites sharing the same domain.
+
- The ``AuthTktAuthenticationPolicy`` now supports IPv6 addresses when using
the ``include_ip=True`` option. This is possibly incompatible with
alternative ``auth_tkt`` implementations, as the specification does not
@@ -29,21 +67,10 @@ Features
``initialize_myapp_db etc/development.ini a=1 b=2``.
See https://github.com/Pylons/pyramid/pull/911
-Bug Fixes
----------
-
-- Make the ``pyramid.config.assets.PackageOverrides`` object implement the API
- for ``__loader__`` objects specified in PEP 302. Proxies to the
- ``__loader__`` set by the importer, if present; otherwise, raises
- ``NotImplementedError``. This makes Pyramid static view overrides work
- properly under Python 3.3 (previously they would not). See
- https://github.com/Pylons/pyramid/pull/1015 for more information.
-
-- ``mako_templating``: added defensive workaround for non-importability of
- ``mako`` due to upstream ``markupsafe`` dropping Python 3.2 support. Mako
- templating will no longer work under the combination of MarkupSafe 0.17 and
- Python 3.2 (although the combination of MarkupSafe 0.17 and Python 3.3 or any
- supported Python 2 version will work OK).
+- The ``request.session.check_csrf_token()`` method and the ``check_csrf`` view
+ predicate now take into account the value of the HTTP header named
+ ``X-CSRF-Token`` (as well as the ``csrf_token`` form parameter, which they
+ always did). The header is tried when the form parameter does not exist.
- View lookup will now search for valid views based on the inheritance
hierarchy of the context. It tries to find views based on the most
@@ -52,7 +79,7 @@ Bug Fixes
In the past, only the most specific type containing views would be checked
and if no matching view could be found then a PredicateMismatch would be
raised. Now predicate mismatches don't hide valid views registered on
- super-types. Here's an example that now works::
+ super-types. Here's an example that now works:
.. code-block:: python
@@ -90,7 +117,31 @@ Bug Fixes
predicate mismatch error when trying to use GET or DELETE
methods. Now the views are found and no predicate mismatch is
raised.
- See https://github.com/Pylons/pyramid/pull/786
+ See https://github.com/Pylons/pyramid/pull/786 and
+ https://github.com/Pylons/pyramid/pull/1004 and
+ https://github.com/Pylons/pyramid/pull/1046
+
+- The ``pserve`` command now takes a ``-v`` (or ``--verbose``) flag and a
+ ``-q`` (or ``--quiet``) flag. Output from running ``pserve`` can be
+ controlled using these flags. ``-v`` can be specified multiple times to
+ increase verbosity. ``-q`` sets verbosity to ``0`` unconditionally. The
+ default verbosity level is ``1``.
+
+Bug Fixes
+---------
+
+- Make the ``pyramid.config.assets.PackageOverrides`` object implement the API
+ for ``__loader__`` objects specified in PEP 302. Proxies to the
+ ``__loader__`` set by the importer, if present; otherwise, raises
+ ``NotImplementedError``. This makes Pyramid static view overrides work
+ properly under Python 3.3 (previously they would not). See
+ https://github.com/Pylons/pyramid/pull/1015 for more information.
+
+- ``mako_templating``: added defensive workaround for non-importability of
+ ``mako`` due to upstream ``markupsafe`` dropping Python 3.2 support. Mako
+ templating will no longer work under the combination of MarkupSafe 0.17 and
+ Python 3.2 (although the combination of MarkupSafe 0.17 and Python 3.3 or any
+ supported Python 2 version will work OK).
- Spaces and dots may now be in mako renderer template paths. This was
broken when support for the new makodef syntax was added in 1.4a1.
@@ -106,6 +157,14 @@ Bug Fixes
files have now been removed. See
https://github.com/Pylons/pyramid/issues/981
+- ``pyramid.testing.DummyResource`` didn't define ``__bool__``, so code under
+ Python 3 would use ``__len__`` to find truthiness; this usually caused an
+ instance of DummyResource to be "falsy" instead of "truthy". See
+ https://github.com/Pylons/pyramid/pull/1032
+
+- The ``alchemy`` scaffold would break when the database was MySQL during
+ tables creation. See https://github.com/Pylons/pyramid/pull/1049
+
1.4 (2012-12-18)
================
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 97eb54f7b..d2b116f46 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -198,3 +198,7 @@ Contributors
- Georges Dubus, 2013/03/21
- Jason McKellar, 2013/03/28
+
+- Luke Cyca, 2013/05/30
+
+- Laurence Rowe, 2013/04/24
diff --git a/HACKING.txt b/HACKING.txt
index 26e85ee80..5b5dcc458 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -126,7 +126,7 @@ documentation in this package which references that API or behavior must
change to reflect the bug fix, ideally in the same commit that fixes the bug
or adds the feature.
-To build and review docs (where ``$yourvenv`` refers to the virtualenv you're
+To build and review docs (where ``$VENV`` refers to the virtualenv you're
using to develop Pyramid):
1. Run ``$VENV/bin/python setup.py dev docs``. This will cause Sphinx
diff --git a/HISTORY.txt b/HISTORY.txt
index 67de56998..bf054f9ed 100644
--- a/HISTORY.txt
+++ b/HISTORY.txt
@@ -2150,7 +2150,7 @@ Features
- Add ``wild_domain`` argument to AuthTktAuthenticationPolicy, which defaults
to ``True``. If it is set to ``False``, the feature of the policy which
- sets a cookie with a wilcard domain will be turned off.
+ sets a cookie with a wildcard domain will be turned off.
- Add a ``MANIFEST.in`` file to each paster template. See
https://github.com/Pylons/pyramid/issues#issue/95
diff --git a/README.rst b/README.rst
index 4d427a13d..a3458028b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,8 +1,8 @@
Pyramid
=======
-Pyramid is a small, fast, down-to-earth, open source Python web application
-development framework. It makes real-world web application development and
+Pyramid is a small, fast, down-to-earth, open source Python web framework.
+It makes real-world web application development and
deployment more fun, more predictable, and more productive.
Pyramid is produced by the `Pylons Project <http://pylonsproject.org/>`_.
diff --git a/docs/Makefile b/docs/Makefile
index c98fdc65e..12dc88bf8 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -63,8 +63,13 @@ latex:
cp _static/latex-note.png _build/latex
@echo
@echo "Build finished; the LaTeX files are in _build/latex."
- @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
- "run these through (pdf)latex."
+ @echo "Run \`make latexpdf' to build a PDF file from them."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C _build/latex all-pdf
+ @echo "pdflatex finished; the PDF file is in _build/latex."
changes:
mkdir -p _build/changes _build/doctrees
diff --git a/docs/api/config.rst b/docs/api/config.rst
index 39d504348..1f65be9f1 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -135,3 +135,4 @@
will only exist for the lifetime of the actual applications for which they
are being used.
+.. autoclass:: not_
diff --git a/docs/api/request.rst b/docs/api/request.rst
index b1f5918d7..a90cb1ac0 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -156,7 +156,7 @@
.. attribute:: matched_route
If a :term:`route` has matched during this request, this attribute will
- be an obect representing the route matched by the URL pattern
+ be an object representing the route matched by the URL pattern
associated with the route. If a route has not matched during this
request, the value of this attribute will be ``None``. See
:ref:`matched_route`.
@@ -238,7 +238,7 @@
.. attribute:: response_*
In Pyramid 1.0, you could set attributes on a
- :class:`pyramid.request.Request` which influenced the behavor of
+ :class:`pyramid.request.Request` which influenced the behavior of
*rendered* responses (views which use a :term:`renderer` and which
don't directly return a response). These attributes began with
``response_``, such as ``response_headerlist``. If you needed to
diff --git a/docs/authorintro.rst b/docs/authorintro.rst
index f1a9d1484..b3cd68494 100644
--- a/docs/authorintro.rst
+++ b/docs/authorintro.rst
@@ -73,7 +73,7 @@ This book is divided into three major parts:
concepts in terms of the sample. You should read the tutorials if
you want a guided tour of :app:`Pyramid`.
-:ref:`api_reference`
+:ref:`api_documentation`
Comprehensive reference material for every public API exposed by
:app:`Pyramid`. The API documentation is organized
diff --git a/docs/conf.py b/docs/conf.py
index 84b01a791..e50c0c60a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -19,6 +19,8 @@ import warnings
warnings.simplefilter('ignore', DeprecationWarning)
+import pkg_resources
+
# skip raw nodes
from sphinx.writers.text import TextTranslator
from sphinx.writers.latex import LaTeXTranslator
@@ -93,7 +95,7 @@ copyright = '2008-%s, Agendaless Consulting' % thisyear
# other places throughout the built documents.
#
# The short X.Y version.
-version = '1.4'
+version = pkg_resources.get_distribution('pyramid').version
# The full version, including alpha/beta/rc tags.
release = version
diff --git a/docs/foreword.rst b/docs/foreword.rst
index aa8d7c77b..cc8271bdf 100644
--- a/docs/foreword.rst
+++ b/docs/foreword.rst
@@ -1,3 +1,5 @@
+:orphan:
+
Foreword
========
diff --git a/docs/index.rst b/docs/index.rst
index bc711f8ff..5dfbbce1e 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -37,6 +37,7 @@ What's New
.. toctree::
:maxdepth: 1
+ whatsnew-1.5
whatsnew-1.4
whatsnew-1.3
whatsnew-1.2
@@ -45,7 +46,7 @@ What's New
.. _html_narrative_documentation:
-Narrative documentation
+Narrative Documentation
=======================
Narrative documentation in chapter form explaining how to use
@@ -110,7 +111,7 @@ platforms.
tutorials/modwsgi/index.rst
API Documentation
-==================
+=================
Comprehensive reference material for every public API exposed by :app:`Pyramid`:
@@ -215,13 +216,8 @@ Index and Glossary
* :ref:`genindex`
* :ref:`search`
-
-.. add glossary, foreword, and latexindex in a hidden toc to avoid warnings
-
.. toctree::
:hidden:
glossary
- foreword.rst
- latexindex.rst
diff --git a/docs/latexindex.rst b/docs/latexindex.rst
index 6bb875f73..e2ba108ee 100644
--- a/docs/latexindex.rst
+++ b/docs/latexindex.rst
@@ -1,13 +1,15 @@
+:orphan:
+
.. _latexindex:
-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
-The :app:`Pyramid` Web Application Framework
-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+=================================================
+The Pyramid Web Application Development Framework
+=================================================
.. frontmatter::
Front Matter
-@@@@@@@@@@@@
+============
.. toctree::
:maxdepth: 1
@@ -21,15 +23,15 @@ Front Matter
.. _narrative_documentation:
Narrative Documentation
-@@@@@@@@@@@@@@@@@@@@@@@
+=======================
.. toctree::
:maxdepth: 1
narr/introduction
narr/install
- narr/configuration
narr/firstapp
+ narr/configuration
narr/project
narr/startup
narr/router
@@ -50,6 +52,7 @@ Narrative Documentation
narr/vhosting
narr/testing
narr/resources
+ narr/hellotraversal
narr/muchadoabouttraversal
narr/traversal
narr/security
@@ -60,58 +63,39 @@ Narrative Documentation
narr/extending
narr/advconfig
narr/extconfig
+ narr/scaffolding
+ narr/upgrading
narr/threadlocals
narr/zca
.. _tutorials:
Tutorials
-@@@@@@@@@
+=========
.. toctree::
:maxdepth: 1
- tutorials/wiki/index.rst
tutorials/wiki2/index.rst
+ tutorials/wiki/index.rst
tutorials/bfg/index.rst
tutorials/modwsgi/index.rst
-.. _api_reference:
+.. _api_documentation:
-API Reference
-@@@@@@@@@@@@@
+API Documentation
+=================
.. toctree::
:maxdepth: 1
+ :glob:
- api/authorization
- api/authentication
- api/config
- api/events
- api/exceptions
- api/httpexceptions
- api/i18n
- api/interfaces
- api/location
- api/paster
- api/registry
- api/renderers
- api/request
- api/response
- api/scripting
- api/security
- api/settings
- api/testing
- api/threadlocal
- api/traversal
- api/url
- api/view
- api/wsgi
+ api/*
.. backmatter::
Glossary and Index
-@@@@@@@@@@@@@@@@@@
+==================
.. toctree::
:maxdepth: 1
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index e1347f3ca..17e5227fa 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -474,6 +474,30 @@ input of the ``prequest`` process is used as the ``POST`` body::
$ $VENV/bin/prequest -mPOST development.ini / < somefile
+Showing All Installed Distributions and their Versions
+------------------------------------------------------
+
+.. versionadded:: 1.5
+
+You can use the ``pdistreport`` command to show the Pyramid version in use, the
+Python version in use, and all installed versions of Python distributions in
+your Python environment::
+
+ $ $VENV/bin/pdistreport
+ Pyramid version: 1.5dev
+ Platform Linux-3.2.0-51-generic-x86_64-with-debian-wheezy-sid
+ Packages:
+ authapp 0.0
+ /home/chrism/projects/foo/src/authapp
+ beautifulsoup4 4.1.3
+ /home/chrism/projects/foo/lib/python2.7/site-packages/beautifulsoup4-4.1.3-py2.7.egg
+ ... more output ...
+
+``pdistreport`` takes no options. Its output is useful to paste into a
+pastebin when you are having problems and need someone with more familiarity
+with Python packaging and distribution than you have to look at your
+environment.
+
.. _writing_a_script:
Writing a Script
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index 74765f8e2..2964686d3 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -808,7 +808,7 @@ If this setting is supplied within the :app:`Pyramid` application
default_locale_name = settings['pyramid.default_locale_name']
.. index::
- single: detecting langauges
+ single: detecting languages
"Detecting" Available Languages
-------------------------------
@@ -984,7 +984,7 @@ requires no additional coding or configuration.
The default locale negotiator implementation named
:class:`~pyramid.i18n.default_locale_negotiator` uses the following
-set of steps to dermine the locale name.
+set of steps to determine the locale name.
- First, the negotiator looks for the ``_LOCALE_`` attribute of the
request object (possibly set directly by view code or by a listener
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index 48164d323..032f4be6b 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -217,6 +217,8 @@ that the Pyramid core doesn't. Add-on packages already exist which let you
easily send email, let you use the Jinja2 templating system, let you use
XML-RPC or JSON-RPC, let you integrate with jQuery Mobile, etc.
+Examples: http://docs.pylonsproject.org/en/latest/docs/pyramid.html#pyramid-add-on-documentation
+
Class-based and function-based views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -857,7 +859,7 @@ It's our goal that no Pyramid question go unanswered. Whether you ask a
question on IRC, on the Pylons-discuss maillist, or on StackOverflow, you're
likely to get a reasonably prompt response. We don't tolerate "support
trolls" or other people who seem to get their rocks off by berating fellow
-users in our various offical support channels. We try to keep it well-lit
+users in our various official support channels. We try to keep it well-lit
and new-user-friendly.
Example: Visit irc\://freenode.net#pyramid (the ``#pyramid`` channel on
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 9d69a65a5..e25f3012a 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -1005,12 +1005,12 @@ Pyramid application based on the data in the file.
application. As we saw in :ref:`firstapp_chapter`, ``pserve`` needn't be
invoked at all to run a :app:`Pyramid` application. The use of ``pserve`` to
run a :app:`Pyramid` application is purely conventional based on the output
-of its scaffolding. But we strongly recommend using while developing your
-application, because many other convenience introspection commands (such as
-``pviews``, ``prequest``, ``proutes`` and others) are also implemented in
-terms of configuration availability of this ``.ini`` file format. It also
-configures Pyramid logging and provides the ``--reload`` switch for
-convenient restarting of the server when code changes.
+of its scaffolding. But we strongly recommend using ``pserve`` while
+developing your application, because many other convenience introspection
+commands (such as ``pviews``, ``prequest``, ``proutes`` and others) are also
+implemented in terms of configuration availability of this ``.ini`` file
+format. It also configures Pyramid logging and provides the ``--reload``
+switch for convenient restarting of the server when code changes.
.. _alternate_wsgi_server:
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index 20a9eda31..a2811dbae 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -198,7 +198,7 @@ representing the JSON serialization of the return value:
.. code-block:: python
- '{"content": "Hello!"}'
+ {"content": "Hello!"}
The return value needn't be a dictionary, but the return value must contain
values serializable by the configured serializer (by default ``json.dumps``).
diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst
index 699a3d4ac..b1bb611e5 100644
--- a/docs/narr/resources.rst
+++ b/docs/narr/resources.rst
@@ -300,7 +300,7 @@ the resource by :meth:`~pyramid.request.Request.resource_url`.
The ``__resource_url__`` hook is passed two arguments: ``request`` and
``info``. ``request`` is the :term:`request` object passed to
:meth:`~pyramid.request.Request.resource_url`. ``info`` is a dictionary with
-two keys:
+the following keys:
``physical_path``
A string representing the "physical path" computed for the resource, as
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index c4f4b5f07..82440060c 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -148,6 +148,7 @@ Some gotchas:
.. index::
single: pyramid_beaker
single: Beaker
+ single: pyramid_redis_sessions
single: session factory (alternates)
.. _using_alternate_session_factories:
@@ -155,11 +156,17 @@ Some gotchas:
Using Alternate Session Factories
---------------------------------
-At the time of this writing, exactly one alternate session factory
-implementation exists, named ``pyramid_beaker``. This is a session factory
-that uses the `Beaker <http://beaker.groovie.org/>`_ library as a backend.
-Beaker has support for file-based sessions, database based sessions, and
-encrypted cookie-based sessions. See `the pyramid_beaker documentation
+At the time of this writing, exactly two alternate session factories
+exist.
+
+The first is named ``pyramid_redis_sessions``. It can be downloaded from PyPI.
+It uses Redis as a backend. It is the recommended persistent session solution
+at the time of this writing.
+
+The second is named ``pyramid_beaker``. This is a session factory that uses the
+`Beaker <http://beaker.groovie.org/>`_ library as a backend. Beaker has
+support for file-based sessions, database based sessions, and encrypted
+cookie-based sessions. See `the pyramid_beaker documentation
<http://docs.pylonsproject.org/projects/pyramid_beaker/en/latest/>`_ for more
information about ``pyramid_beaker``.
@@ -298,14 +305,15 @@ Preventing Cross-Site Request Forgery Attacks
`Cross-site request forgery
<http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a
-phenomenon whereby a user with an identity on your website might click on a
-URL or button on another website which secretly redirects the user to your
-application to perform some command that requires elevated privileges.
-
-You can avoid most of these attacks by making sure that the correct *CSRF
-token* has been set in an :app:`Pyramid` session object before performing any
-actions in code which requires elevated privileges that is invoked via a form
-post. To use CSRF token support, you must enable a :term:`session factory`
+phenomenon whereby a user who is logged in to your website might inadvertantly
+load a URL because it is linked from, or embedded in, an attacker's website.
+If the URL is one that may modify or delete data, the consequences can be dire.
+
+You can avoid most of these attacks by issuing a unique token to the browser
+and then requiring that it be present in all potentially unsafe requests.
+:app:`Pyramid` sessions provide facilities to create and check CSRF tokens.
+
+To use CSRF tokens, you must first enable a :term:`session factory`
as described in :ref:`using_the_default_session_factory` or
:ref:`using_alternate_session_factories`.
@@ -324,33 +332,82 @@ To get the current CSRF token from the session, use the
The ``session.get_csrf_token()`` method accepts no arguments. It returns a
CSRF *token* string. If ``session.get_csrf_token()`` or
-``session.new_csrf_token()`` was invoked previously for this session, the
+``session.new_csrf_token()`` was invoked previously for this session, then the
existing token will be returned. If no CSRF token previously existed for
-this session, a new token will be will be set into the session and returned.
+this session, then a new token will be will be set into the session and returned.
The newly created token will be opaque and randomized.
You can use the returned token as the value of a hidden field in a form that
-posts to a method that requires elevated privileges. The handler for the
-form post should use ``session.get_csrf_token()`` *again* to obtain the
-current CSRF token related to the user from the session, and compare it to
-the value of the hidden form field. For example, if your form rendering
-included the CSRF token obtained via ``session.get_csrf_token()`` as a hidden
-input field named ``csrf_token``:
+posts to a method that requires elevated privileges, or supply it as a request
+header in AJAX requests.
+
+For example, include the CSRF token as a hidden field:
+
+.. code-block:: html
+
+ <form method="post" action="/myview">
+ <input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}">
+ <input type="submit" value="Delete Everything">
+ </form>
+
+Or, include it as a header in a jQuery AJAX request:
+
+.. code-block:: javascript
+
+ var csrfToken = ${request.session.get_csrf_token()};
+ $.ajax({
+ type: "POST",
+ url: "/myview",
+ headers: { 'X-CSRF-Token': csrfToken }
+ }).done(function() {
+ alert("Deleted");
+ });
+
+
+The handler for the URL that receives the request
+should then require that the correct CSRF token is supplied.
+
+Using the ``session.check_csrf_token`` Method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In request handling code, you can check the presence and validity of a CSRF
+token with ``session.check_csrf_token(request)``. If the token is valid,
+it will return True, otherwise it will raise ``HTTPBadRequest``.
+
+By default, it checks for a GET or POST parameter named ``csrf_token`` or a
+header named ``X-CSRF-Token``.
.. code-block:: python
- :linenos:
- token = request.session.get_csrf_token()
- if token != request.POST['csrf_token']:
- raise ValueError('CSRF token did not match')
+ def myview(request):
+ session = request.session
+
+ # Require CSRF Token
+ session.check_csrf_token(request):
+
+ ...
.. index::
single: session.new_csrf_token
+Checking CSRF Tokens With A View Predicate
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A convenient way to require a valid CSRF Token for a particular view is to
+include ``check_csrf=True`` as a view predicate.
+See :meth:`pyramid.config.Configurator.add_route`.
+
+.. code-block:: python
+
+ @view_config(request_method='POST', check_csrf=True, ...)
+ def myview(request):
+ ...
+
+
Using the ``session.new_csrf_token`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To explicitly add a new CSRF token to the session, use the
+To explicitly create a new CSRF token, use the
``session.new_csrf_token()`` method. This differs only from
``session.get_csrf_token()`` inasmuch as it clears any existing CSRF token,
creates a new CSRF token, sets the token into the session, and returns the
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
index 2eb6ece13..a60c5ba56 100644
--- a/docs/narr/traversal.rst
+++ b/docs/narr/traversal.rst
@@ -289,7 +289,7 @@ system uses this algorithm to find a :term:`context` resource and a
return resource "C".
#. Traversal ends when a) the entire path is exhausted or b) when any
- resouce raises a :exc:`KeyError` from its ``__getitem__`` or c) when any
+ resource raises a :exc:`KeyError` from its ``__getitem__`` or c) when any
non-final path element traversal does not have a ``__getitem__`` method
(resulting in a :exc:`AttributeError`) or d) when any path element is
prefixed with the set of characters ``@@`` (indicating that the characters
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 18cb3e4db..310c160c0 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -865,7 +865,7 @@ Debugging Route Matching
------------------------
It's useful to be able to take a peek under the hood when requests that enter
-your application arent matching your routes as you expect them to. To debug
+your application aren't matching your routes as you expect them to. To debug
route matching, use the ``PYRAMID_DEBUG_ROUTEMATCH`` environment variable or the
``pyramid.debug_routematch`` configuration file setting (set either to ``true``).
Details of the route matching decision for a particular request to the
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 241ce62b5..6a064c209 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -290,9 +290,9 @@ configured view.
of the ``REQUEST_METHOD`` of the :term:`WSGI` environment.
``request_param``
- 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
+ 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
supplied value.
@@ -306,8 +306,6 @@ configured view.
consideration of keys and values in the ``request.params`` dictionary.
``match_param``
- .. versionadded:: 1.2
-
This param may be either a single string of the format "key=value" or a
dict of key/value pairs.
@@ -324,6 +322,8 @@ configured view.
If ``match_param`` is not supplied, the view will be invoked without
consideration of the keys and values in ``request.matchdict``.
+ .. versionadded:: 1.2
+
``containment``
This value should be a reference to a Python class or :term:`interface`
that a parent object in the context resource's :term:`lineage` must provide
@@ -505,7 +505,7 @@ configuration stanza:
.. code-block:: python
:linenos:
- config.add_view('mypackage.views.my_view', route_name='ok',
+ config.add_view('mypackage.views.my_view', route_name='ok',
request_method='POST', permission='read')
All arguments to ``view_config`` may be omitted. For example:
@@ -557,6 +557,35 @@ form of :term:`declarative configuration`, while
:meth:`pyramid.config.Configurator.add_view` is a form of :term:`imperative
configuration`. However, they both do the same thing.
+Inverting Predicate Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can invert the meaning of any predicate value by wrapping it in a call to
+:class:`pyramid.config.not_`.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import not_
+
+ config.add_view(
+ 'mypackage.views.my_view',
+ route_name='ok',
+ request_method=not_('POST')
+ )
+
+The above example will ensure that the view is called if the request method
+is *not* ``POST``, at least if no other view is more specific.
+
+This technique of wrapping a predicate value in ``not_`` can be used anywhere
+predicate values are accepted:
+
+- :meth:`pyramid.config.Configurator.add_view`
+
+- :meth:`pyramid.view.view_config`
+
+.. versionadded:: 1.5
+
.. index::
single: view_config placement
@@ -802,7 +831,7 @@ of this:
config.add_view(
RESTView, route_name='rest', attr='delete', request_method='DELETE')
-To reduce the amount of repetion in the ``config.add_view`` statements, we
+To reduce the amount of repetition in the ``config.add_view`` statements, we
can move the ``route_name='rest'`` argument to a ``@view_default`` class
decorator on the RESTView class:
diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst
index 5cba8dd3e..cc63017df 100644
--- a/docs/whatsnew-1.1.rst
+++ b/docs/whatsnew-1.1.rst
@@ -13,7 +13,7 @@ Terminology Changes
The term "template" used by the Pyramid documentation used to refer to both
"paster templates" and "rendered templates" (templates created by a rendering
engine. i.e. Mako, Chameleon, Jinja, etc.). "Paster templates" will now be
-refered to as "scaffolds", whereas the name for "rendered templates" will
+referred to as "scaffolds", whereas the name for "rendered templates" will
remain as "templates."
Major Feature Additions
@@ -397,7 +397,7 @@ Deprecations and Behavior Differences
shell you use to invoke ``paster serve`` to see these warnings, e.g. on
UNIX, ``PYTHONWARNINGS=all $VENV/bin/paster serve development.ini``.
Python 2.5 and 2.6 show deprecation warnings by default,
- so this is unecessary there.
+ so this is unnecessary there.
All deprecation warnings are emitted to the console.
- The :class:`pyramid.view.static` class has been deprecated in favor of the
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
new file mode 100644
index 000000000..b987fa77f
--- /dev/null
+++ b/docs/whatsnew-1.5.rst
@@ -0,0 +1,155 @@
+What's New In Pyramid 1.5
+=========================
+
+This article explains the new features in :app:`Pyramid` version 1.5 as
+compared to its predecessor, :app:`Pyramid` 1.4. It also documents backwards
+incompatibilities between the two versions and deprecations added to
+:app:`Pyramid` 1.5, as well as software dependency changes and notable
+documentation additions.
+
+Feature Additions
+-----------------
+
+The feature additions in Pyramid 1.5 follow.
+
+- Add ``pdistreport`` script, which prints the Python version in use, the
+ Pyramid version in use, and the version number and location of all Python
+ distributions currently installed.
+
+- Add the ability to invert the result of any view, route, or subscriber
+ predicate value using the ``not_`` class. For example:
+
+ .. code-block:: python
+
+ from pyramid.config import not_
+
+ @view_config(route_name='myroute', request_method=not_('POST'))
+ def myview(request): ...
+
+ The above example will ensure that the view is called if the request method
+ is not POST, at least if no other view is more specific.
+
+ The :class:`pyramid.config.not_` class can be used against any value that is
+ a predicate value passed in any of these contexts:
+
+ - :meth:`pyramid.config.Configurator.add_view`
+
+ - :meth:`pyramid.config.Configurator.add_route`
+
+ - :meth:`pyramid.config.Configurator.add_subscriber`
+
+ - :meth:`pyramid.view.view_config`
+
+ - :meth:`pyramid.events.subscriber`
+
+- View lookup will now search for valid views based on the inheritance
+ hierarchy of the context. It tries to find views based on the most specific
+ context first, and upon predicate failure, will move up the inheritance chain
+ to test views found by the super-type of the context. In the past, only the
+ most specific type containing views would be checked and if no matching view
+ could be found then a PredicateMismatch would be raised. Now predicate
+ mismatches don't hide valid views registered on super-types. Here's an
+ example that now works:
+
+ .. code-block:: python
+
+ class IResource(Interface):
+
+ ...
+
+ @view_config(context=IResource)
+ def get(context, request):
+
+ ...
+
+ @view_config(context=IResource, request_method='POST')
+ def post(context, request):
+
+ ...
+
+ @view_config(context=IResource, request_method='DELETE')
+ def delete(context, request):
+
+ ...
+
+ @implementor(IResource)
+ class MyResource:
+
+ ...
+
+ @view_config(context=MyResource, request_method='POST')
+ def override_post(context, request):
+
+ ...
+
+ Previously the override_post view registration would hide the get
+ and delete views in the context of MyResource -- leading to a
+ predicate mismatch error when trying to use GET or DELETE
+ methods. Now the views are found and no predicate mismatch is
+ raised.
+ See https://github.com/Pylons/pyramid/pull/786 and
+ https://github.com/Pylons/pyramid/pull/1004 and
+ https://github.com/Pylons/pyramid/pull/1046
+
+- ``scripts/prequest.py`` (aka the ``prequest`` console script): added support
+ for submitting ``PUT`` and ``PATCH`` requests. See
+ https://github.com/Pylons/pyramid/pull/1033. add support for submitting
+ ``OPTIONS`` and ``PROPFIND`` requests, and allow users to specify basic
+ authentication credentials in the request via a ``--login`` argument to the
+ script. See https://github.com/Pylons/pyramid/pull/1039.
+
+- :class:`pyramid.authorization.ACLAuthorizationPolicy` supports ``__acl__`` as
+ a callable. This removes the ambiguity between the potential
+ ``AttributeError`` that would be raised on the ``context`` when the property
+ was not defined and the ``AttributeError`` that could be raised from any
+ user-defined code within a dynamic property. It is recommended to define a
+ dynamic ACL as a callable to avoid this ambiguity. See
+ https://github.com/Pylons/pyramid/issues/735.
+
+- Allow a protocol-relative URL (e.g. ``//example.com/images``) to be passed to
+ :meth:`pyramid.config.Configurator.add_static_view`. This allows
+ externally-hosted static URLs to be generated based on the current protocol.
+
+- The :class:`pyramid.authentication.AuthTktAuthenticationPolicy` has a new
+ ``parent_domain`` option to set the authentication cookie as a wildcard
+ cookie on the parent domain. This is useful if you have multiple sites
+ sharing the same domain. It also now supports IPv6 addresses when using
+ the ``include_ip=True`` option. This is possibly incompatible with
+ alternative ``auth_tkt`` implementations, as the specification does not
+ define how to properly handle IPv6. See
+ https://github.com/Pylons/pyramid/issues/831.
+
+- Make it possible to use variable arguments via
+ :func:`pyramid.paster.get_appsettings`. This also allowed the generated
+ ``initialize_db`` script from the ``alchemy`` scaffold to grow support for
+ options in the form ``a=1 b=2`` so you can fill in values in a parameterized
+ ``.ini`` file, e.g. ``initialize_myapp_db etc/development.ini a=1 b=2``. See
+ https://github.com/Pylons/pyramid/pull/911
+
+- The ``request.session.check_csrf_token()`` method and the ``check_csrf`` view
+ predicate now take into account the value of the HTTP header named
+ ``X-CSRF-Token`` (as well as the ``csrf_token`` form parameter, which they
+ always did). The header is tried when the form parameter does not exist.
+
+Backwards Incompatibilities
+---------------------------
+
+This release has no known backwards incompatibilities with Pyramid 1.4.X.
+
+Deprecations
+------------
+
+This release has no new deprecations as compared to Pyramid 1.4.X.
+
+
+Documentation Enhancements
+--------------------------
+
+Many documentation enhancements have been added, but we did not track them as
+they were added.
+
+Dependency Changes
+------------------
+
+No dependency changes from Pyramid 1.4.X were made in Pyramid 1.5.
+
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index bc0286ed3..c1aa970bd 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -511,9 +511,23 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
``wild_domain``
Default: ``True``. An auth_tkt cookie will be generated for the
- wildcard domain.
+ wildcard domain. If your site is hosted as ``example.com`` this
+ will make the cookie available for sites underneath ``example.com``
+ such as ``www.example.com``.
Optional.
+ ``parent_domain``
+
+ Default: ``False``. An auth_tkt cookie will be generated for the
+ parent domain of the current site. For example if your site is
+ hosted under ``www.example.com`` a cookie will be generated for
+ ``.example.com``. This can be useful if you have multiple sites
+ sharing the same domain. This option supercedes the ``wild_domain``
+ option.
+ Optional.
+
+ This option is available as of :app:`Pyramid` 1.5.
+
``hashalg``
Default: ``md5`` (the literal string).
@@ -565,7 +579,8 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
http_only=False,
wild_domain=True,
debug=False,
- hashalg=_marker
+ hashalg=_marker,
+ parent_domain=False,
):
if hashalg is _marker:
hashalg = 'md5'
@@ -603,6 +618,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
path=path,
wild_domain=wild_domain,
hashalg=hashalg,
+ parent_domain=parent_domain,
)
self.callback = callback
self.debug = debug
@@ -800,7 +816,7 @@ class AuthTktCookieHelper(object):
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,
- hashalg='md5'):
+ hashalg='md5', parent_domain=False):
self.secret = secret
self.cookie_name = cookie_name
self.include_ip = include_ip
@@ -811,6 +827,7 @@ class AuthTktCookieHelper(object):
self.http_only = http_only
self.path = path
self.wild_domain = wild_domain
+ self.parent_domain = parent_domain
self.hashalg = hashalg
static_flags = []
@@ -850,16 +867,19 @@ class AuthTktCookieHelper(object):
cookies = [
('Set-Cookie', '%s="%s"; Path=%s%s%s' % (
- self.cookie_name, value, self.path, max_age, self.static_flags)),
- ('Set-Cookie', '%s="%s"; Path=%s; Domain=%s%s%s' % (
- self.cookie_name, value, self.path, cur_domain, max_age,
- self.static_flags)),
+ self.cookie_name, value, self.path, max_age, self.static_flags))
]
- if self.wild_domain:
- wild_domain = '.' + cur_domain
+ domains = []
+ if self.parent_domain and cur_domain.count('.') > 1:
+ domains.append('.' + cur_domain.split('.', 1)[1])
+ else:
+ domains.append(cur_domain)
+ if self.wild_domain:
+ domains.append('.' + cur_domain)
+ for domain in domains:
cookies.append(('Set-Cookie', '%s="%s"; Path=%s; Domain=%s%s%s' % (
- self.cookie_name, value, self.path, wild_domain, max_age,
+ self.cookie_name, value, self.path, domain, max_age,
self.static_flags)))
return cookies
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 5bb9987af..d52ee0e7a 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -70,7 +70,7 @@ from pyramid.config.security import SecurityConfiguratorMixin
from pyramid.config.settings import SettingsConfiguratorMixin
from pyramid.config.testing import TestingConfiguratorMixin
from pyramid.config.tweens import TweensConfiguratorMixin
-from pyramid.config.util import PredicateList
+from pyramid.config.util import PredicateList, not_
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
@@ -86,6 +86,9 @@ _marker = object()
ConfigurationError = ConfigurationError # pyflakes
+not_ = not_ # pyflakes, this is an API
+
+
class Configurator(
TestingConfiguratorMixin,
TweensConfiguratorMixin,
diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py
index fe8e973b1..5573b6748 100644
--- a/pyramid/config/adapters.py
+++ b/pyramid/config/adapters.py
@@ -216,7 +216,7 @@ class AdaptersConfiguratorMixin(object):
config.add_traverser(MyCustomTraverser)
This would cause the Pyramid superdefault traverser to never be used;
- intead all traversal would be done using your ``MyCustomTraverser``
+ instead all traversal would be done using your ``MyCustomTraverser``
class, no matter which object was returned by the :term:`root
factory` of this application. Note that we passed no arguments to
the ``iface`` keyword parameter. The default value of ``iface``,
@@ -228,7 +228,7 @@ class AdaptersConfiguratorMixin(object):
time. The traverser used can depend on the result of the :term:`root
factory`. For instance, if your root factory returns more than one
type of object conditionally, you could claim that an alternate
- traverser adapter should be used agsinst one particular class or
+ traverser adapter should be used against one particular class or
interface returned by that root factory. When the root factory
returned an object that implemented that class or interface, a custom
traverser would be used. Otherwise, the default traverser would be
diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py
index ded8fbfbf..c8f66e83d 100644
--- a/pyramid/config/predicates.py
+++ b/pyramid/config/predicates.py
@@ -294,3 +294,4 @@ class EffectivePrincipalsPredicate(object):
if self.val.issubset(rpset):
return True
return False
+
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
index f495794b4..c86e4a2dd 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -90,10 +90,10 @@ class RoutesConfiguratorMixin(object):
``traverse`` argument provided to ``add_route`` is
``/{article}``, when a request comes in that causes the route
to match in such a way that the ``article`` match value is
- '1' (when the request URI is ``/articles/1/edit``), the
+ ``'1'`` (when the request URI is ``/articles/1/edit``), the
traversal path will be generated as ``/1``. This means that
the root object's ``__getitem__`` will be called with the
- name ``1`` during the traversal phase. If the ``1`` object
+ name ``'1'`` during the traversal phase. If the ``'1'`` object
exists, it will become the :term:`context` of the request.
:ref:`traversal_chapter` has more information about
traversal.
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index af0dd1641..892592196 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -28,6 +28,69 @@ def as_sorted_tuple(val):
val = tuple(sorted(val))
return val
+class not_(object):
+ """
+
+ You can invert the meaning of any predicate value by wrapping it in a call
+ to :class:`pyramid.config.not_`.
+
+ .. code-block:: python
+ :linenos:
+
+ from pyramid.config import not_
+
+ config.add_view(
+ 'mypackage.views.my_view',
+ route_name='ok',
+ request_method=not_('POST')
+ )
+
+ The above example will ensure that the view is called if the request method
+ is *not* ``POST``, at least if no other view is more specific.
+
+ This technique of wrapping a predicate value in ``not_`` can be used
+ anywhere predicate values are accepted:
+
+ - :meth:`pyramid.config.Configurator.add_view`
+
+ - :meth:`pyramid.config.Configurator.add_route`
+
+ - :meth:`pyramid.config.Configurator.add_subscriber`
+
+ - :meth:`pyramid.view.view_config`
+
+ - :meth:`pyramid.events.subscriber`
+
+ .. versionadded:: 1.5
+ """
+ def __init__(self, value):
+ self.value = value
+
+class Notted(object):
+ def __init__(self, predicate):
+ self.predicate = predicate
+
+ def _notted_text(self, val):
+ # if the underlying predicate doesnt return a value, it's not really
+ # a predicate, it's just something pretending to be a predicate,
+ # so dont update the hash
+ if val:
+ val = '!' + val
+ return val
+
+ def text(self):
+ return self._notted_text(self.predicate.text())
+
+ def phash(self):
+ return self._notted_text(self.predicate.phash())
+
+ def __call__(self, context, request):
+ result = self.predicate(context, request)
+ phash = self.phash()
+ if phash:
+ result = not result
+ return result
+
# under = after
# over = before
@@ -74,7 +137,14 @@ class PredicateList(object):
if not isinstance(vals, predvalseq):
vals = (vals,)
for val in vals:
- pred = predicate_factory(val, config)
+ realval = val
+ notted = False
+ if isinstance(val, not_):
+ realval = val.value
+ notted = True
+ pred = predicate_factory(realval, config)
+ if notted:
+ pred = Notted(pred)
hashes = pred.phash()
if not is_nonstr_iter(hashes):
hashes = [hashes]
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index a57f61ddb..275209737 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -915,7 +915,7 @@ class ISession(IDict):
by ``queue``. An alternate flash message queue can used by passing
an optional ``queue``, which must be a string. If
``allow_duplicate`` is false, if the ``msg`` already exists in the
- queue, it will not be readded."""
+ queue, it will not be re-added."""
def pop_flash(queue=''):
""" Pop a queue from the flash storage. The queue is removed from
diff --git a/pyramid/renderers.py b/pyramid/renderers.py
index 64951a6ba..602655be8 100644
--- a/pyramid/renderers.py
+++ b/pyramid/renderers.py
@@ -114,7 +114,7 @@ def render_to_response(renderer_name, value, request=None, package=None):
top-level system names, such as ``request``, ``context``,
``renderer_name``, and ``view``. See :ref:`renderer_system_values` for
the full list. If :term:`renderer globals` have been specified, these
- will also be used to agument the value.
+ will also be used to argument the value.
Supply a ``request`` parameter in order to provide the renderer
with the most correct 'system' values (``request`` and ``context``
@@ -200,7 +200,7 @@ class JSON(object):
The default serializer uses ``json.JSONEncoder``. A different
serializer can be specified via the ``serializer`` argument.
Custom serializers should accept the object, a callback
- ``default``, and any extra ``kw`` keyword argments passed during
+ ``default``, and any extra ``kw`` keyword arguments passed during
renderer construction.
.. versionadded:: 1.4
diff --git a/pyramid/router.py b/pyramid/router.py
index 63c12a1af..1a991648b 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -1,5 +1,4 @@
from zope.interface import (
- Interface,
implementer,
providedBy,
)
@@ -165,10 +164,14 @@ class Router(object):
except PredicateMismatch:
# look for other views that meet the predicate
# criteria
- for iface in context_iface.flattened():
+ for iface in context_iface.__sro__[1:]:
+ previous_view_callable = view_callable
view_callable = adapters.lookup(
(IViewClassifier, request.request_iface, iface),
IView, name=view_name, default=None)
+ # intermediate bases may lookup same view_callable
+ if view_callable is previous_view_callable:
+ continue
if view_callable is not None:
try:
response = view_callable(context, request)
diff --git a/pyramid/scaffolds/alchemy/+package+/models.py b/pyramid/scaffolds/alchemy/+package+/models.py
index aeeb9df64..db1fee832 100644
--- a/pyramid/scaffolds/alchemy/+package+/models.py
+++ b/pyramid/scaffolds/alchemy/+package+/models.py
@@ -1,5 +1,6 @@
from sqlalchemy import (
Column,
+ Index,
Integer,
Text,
)
@@ -20,9 +21,11 @@ Base = declarative_base()
class MyModel(Base):
__tablename__ = 'models'
id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
+ name = Column(Text)
value = Column(Integer)
def __init__(self, name, value):
self.name = name
self.value = value
+
+Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/pyramid/scripts/pdistreport.py b/pyramid/scripts/pdistreport.py
new file mode 100644
index 000000000..10edb5715
--- /dev/null
+++ b/pyramid/scripts/pdistreport.py
@@ -0,0 +1,37 @@
+import sys
+import platform
+import pkg_resources
+import optparse
+from operator import itemgetter
+
+def out(*args): # pragma: no cover
+ for arg in args:
+ sys.stdout.write(arg)
+ sys.stdout.write(' ')
+ sys.stdout.write('\n')
+
+def main(argv=sys.argv, pkg_resources=pkg_resources, platform=platform.platform,
+ out=out):
+ # all args except argv are for unit testing purposes only
+ description = "Show Python distribution versions and locations in use"
+ usage = "usage: %prog"
+ parser = optparse.OptionParser(usage, description=description)
+ parser.parse_args(argv[1:])
+ packages = []
+ for distribution in pkg_resources.working_set:
+ name = distribution.project_name
+ packages.append(
+ {'version': distribution.version,
+ 'lowername': name.lower(),
+ 'name': name,
+ 'location':distribution.location}
+ )
+ packages = sorted(packages, key=itemgetter('lowername'))
+ pyramid_version = pkg_resources.get_distribution('pyramid').version
+ plat = platform()
+ out('Pyramid version:', pyramid_version)
+ out('Platform:', plat)
+ out('Packages:')
+ for package in packages:
+ out(' ', package['name'], package['version'])
+ out(' ', package['location'])
diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py
index da6b8cc14..8628d5a5a 100644
--- a/pyramid/scripts/prequest.py
+++ b/pyramid/scripts/prequest.py
@@ -1,3 +1,4 @@
+import base64
import optparse
import sys
import textwrap
@@ -18,9 +19,22 @@ class PRequestCommand(object):
This command makes an artifical request to a web application that uses a
PasteDeploy (.ini) configuration file for the server and application.
- Use "prequest config.ini /path" to request "/path". Use "prequest
- --method=POST config.ini /path < data" to do a POST with the given
- request body.
+ Use "prequest config.ini /path" to request "/path".
+
+ Use "prequest --method=POST config.ini /path < data" to do a POST with
+ the given request body.
+
+ Use "prequest --method=PUT config.ini /path < data" to do a
+ PUT with the given request body.
+
+ Use "prequest --method=PATCH config.ini /path < data" to do a
+ PATCH with the given request body.
+
+ Use "prequest --method=OPTIONS config.ini /path" to do an
+ OPTIONS request.
+
+ Use "prequest --method=PROPFIND config.ini /path" to do a
+ PROPFIND request.
If the path is relative (doesn't begin with "/") it is interpreted as
relative to "/". The path passed to this script should be URL-quoted.
@@ -59,9 +73,17 @@ class PRequestCommand(object):
parser.add_option(
'-m', '--method',
dest='method',
- choices=['GET', 'HEAD', 'POST', 'DELETE'],
+ choices=['GET', 'HEAD', 'POST', 'PUT', 'PATCH','DELETE',
+ 'PROPFIND', 'OPTIONS'],
type='choice',
- help='Request method type (GET, POST, DELETE)',
+ help='Request method type (GET, POST, PUT, PATCH, DELETE, '
+ 'PROPFIND, OPTIONS)',
+ )
+ parser.add_option(
+ '-l', '--login',
+ dest='login',
+ type='string',
+ help='HTTP basic auth username:password pair',
)
get_app = staticmethod(get_app)
@@ -92,6 +114,10 @@ class PRequestCommand(object):
path = url_unquote(path)
headers = {}
+ if self.options.login:
+ enc = base64.b64encode(self.options.login.encode('ascii'))
+ headers['Authorization'] = 'Basic ' + enc.decode('ascii')
+
if self.options.headers:
for item in self.options.headers:
if ':' not in item:
@@ -110,9 +136,9 @@ class PRequestCommand(object):
environ = {
'REQUEST_METHOD': request_method,
'SCRIPT_NAME': '', # may be empty if app is at the root
- 'PATH_INFO': path,
+ 'PATH_INFO': path,
'SERVER_NAME': 'localhost', # always mandatory
- 'SERVER_PORT': '80', # always mandatory
+ 'SERVER_PORT': '80', # always mandatory
'SERVER_PROTOCOL': 'HTTP/1.0',
'CONTENT_TYPE': 'text/plain',
'REMOTE_ADDR':'127.0.0.1',
@@ -127,7 +153,7 @@ class PRequestCommand(object):
'paste.command_request': True,
}
- if request_method == 'POST':
+ if request_method in ('POST', 'PUT', 'PATCH'):
environ['wsgi.input'] = self.stdin
environ['CONTENT_LENGTH'] = '-1'
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index b840fbdb9..cc368d721 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -65,7 +65,7 @@ class PServeCommand(object):
You can also include variable assignments like 'http_port=8080'
and then use %(http_port)s in your config files.
"""
- verbose = 1
+ default_verbosity = 1
parser = optparse.OptionParser(
usage,
@@ -125,6 +125,18 @@ class PServeCommand(object):
action='store_true',
dest='show_status',
help="Show the status of the (presumably daemonized) server")
+ parser.add_option(
+ '-v', '--verbose',
+ default=default_verbosity,
+ dest='verbose',
+ action='count',
+ help="Set verbose level (default "+str(default_verbosity)+")")
+ parser.add_option(
+ '-q', '--quiet',
+ action='store_const',
+ const=0,
+ dest='verbose',
+ help="Suppress verbose output")
if hasattr(os, 'setuid'):
# I don't think these are available on Windows
@@ -148,19 +160,18 @@ class PServeCommand(object):
_scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
- default_verbosity = 1
-
_reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
_monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
possible_subcommands = ('start', 'stop', 'restart', 'status')
def __init__(self, argv, quiet=False):
- self.quiet = quiet
self.options, self.args = self.parser.parse_args(argv[1:])
+ if quiet:
+ self.options.verbose = 0
def out(self, msg): # pragma: no cover
- if not self.quiet:
+ if self.options.verbose > 0:
print(msg)
def get_options(self):
@@ -197,7 +208,7 @@ class PServeCommand(object):
if self.options.reload:
if os.environ.get(self._reloader_environ_key):
- if self.verbose > 1:
+ if self.options.verbose > 1:
self.out('Running reloading file monitor')
install_reloader(int(self.options.reload_interval), [app_spec])
# if self.requires_config_file:
@@ -271,7 +282,7 @@ class PServeCommand(object):
try:
self.daemonize()
except DaemonizeException as ex:
- if self.verbose > 0:
+ if self.options.verbose > 0:
self.out(str(ex))
return 2
@@ -303,7 +314,7 @@ class PServeCommand(object):
app = self.loadapp(app_spec, name=app_name, relative_to=base,
global_conf=vars)
- if self.verbose > 0:
+ if self.options.verbose > 0:
if hasattr(os, 'getpid'):
msg = 'Starting server in PID %i.' % os.getpid()
else:
@@ -314,7 +325,7 @@ class PServeCommand(object):
try:
server(app)
except (SystemExit, KeyboardInterrupt) as e:
- if self.verbose > 1:
+ if self.options.verbose > 1:
raise
if str(e):
msg = ' ' + str(e)
@@ -358,7 +369,7 @@ class PServeCommand(object):
"Daemon is already running (PID: %s from PID file %s)"
% (pid, self.options.pid_file))
- if self.verbose > 0:
+ if self.options.verbose > 0:
self.out('Entering daemon mode')
pid = os.fork()
if pid:
@@ -433,11 +444,11 @@ class PServeCommand(object):
def record_pid(self, pid_file):
pid = os.getpid()
- if self.verbose > 1:
+ if self.options.verbose > 1:
self.out('Writing PID %s to %s' % (pid, pid_file))
with open(pid_file, 'w') as f:
f.write(str(pid))
- atexit.register(self._remove_pid_file, pid, pid_file, self.verbose)
+ atexit.register(self._remove_pid_file, pid, pid_file, self.options.verbose)
def stop_daemon(self): # pragma: no cover
pid_file = self.options.pid_file or 'pyramid.pid'
@@ -490,7 +501,7 @@ class PServeCommand(object):
self.restart_with_monitor(reloader=True)
def restart_with_monitor(self, reloader=False): # pragma: no cover
- if self.verbose > 0:
+ if self.options.verbose > 0:
if reloader:
self.out('Starting subprocess with file monitor')
else:
@@ -511,7 +522,7 @@ class PServeCommand(object):
proc = None
except KeyboardInterrupt:
self.out('^C caught in monitor process')
- if self.verbose > 1:
+ if self.options.verbose > 1:
raise
return 1
finally:
@@ -527,7 +538,7 @@ class PServeCommand(object):
# a monitor, any exit code will restart
if exit_code != 3:
return exit_code
- if self.verbose > 0:
+ if self.options.verbose > 0:
self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20))
def change_user_group(self, user, group): # pragma: no cover
@@ -559,7 +570,7 @@ class PServeCommand(object):
if not gid:
gid = entry.pw_gid
uid = entry.pw_uid
- if self.verbose > 0:
+ if self.options.verbose > 0:
self.out('Changing user to %s:%s (%s:%s)' % (
user, group or '(unknown)', uid, gid))
if gid:
diff --git a/pyramid/session.py b/pyramid/session.py
index 7db8c8e0e..3708ef879 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -81,15 +81,22 @@ def signed_deserialize(serialized, secret, hmac=hmac):
return pickle.loads(pickled)
-def check_csrf_token(request, token='csrf_token', raises=True):
+def check_csrf_token(request,
+ token='csrf_token',
+ header='X-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
+ ``request.params.get(token)`` or ``request.headers.get(header)``.
+ If a ``token`` keyword is not supplied to this function, the string
+ ``csrf_token`` will be used to look up the token in ``request.params``.
+ If a ``header`` keyword is not supplied to this function, the string
+ ``X-CSRF-Token`` will be used to look up the token in ``request.headers``.
+
+ If the value supplied by param or by header 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.
@@ -98,7 +105,8 @@ def check_csrf_token(request, token='csrf_token', raises=True):
.. versionadded:: 1.4a2
"""
- if request.params.get(token) != request.session.get_csrf_token():
+ supplied_token = request.params.get(token, request.headers.get(header))
+ if supplied_token != request.session.get_csrf_token():
if raises:
raise HTTPBadRequest('incorrect CSRF token')
return False
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 06b795f9a..14c6f4acf 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -222,6 +222,8 @@ class DummyResource:
def __nonzero__(self):
return True
+ __bool__ = __nonzero__
+
def __len__(self):
return len(self.subs)
@@ -409,7 +411,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
suitable testing analogue.
After ``setUp`` is finished, the registry returned by the
- :func:`pyramid.threadlocal.get_current_request` function will
+ :func:`pyramid.threadlocal.get_current_registry` function will
be the passed (or constructed) registry until
:func:`pyramid.testing.tearDown` is called (or
:func:`pyramid.testing.setUp` is called again) .
diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py
index cfabf9a9d..960a87a6a 100644
--- a/pyramid/tests/test_authentication.py
+++ b/pyramid/tests/test_authentication.py
@@ -947,6 +947,30 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost'))
self.assertTrue(result[1][1].startswith('auth_tkt='))
+ def test_remember_parent_domain(self):
+ helper = self._makeOne('secret', parent_domain=True)
+ request = self._makeRequest()
+ request.environ['HTTP_HOST'] = 'www.example.com'
+ result = helper.remember(request, 'other')
+ self.assertEqual(len(result), 2)
+
+ self.assertEqual(result[0][0], 'Set-Cookie')
+ self.assertTrue(result[0][1].endswith('; Path=/'))
+ self.assertTrue(result[0][1].startswith('auth_tkt='))
+
+ 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_parent_domain_supercedes_wild_domain(self):
+ helper = self._makeOne('secret', parent_domain=True, wild_domain=True)
+ request = self._makeRequest()
+ request.environ['HTTP_HOST'] = 'www.example.com'
+ result = helper.remember(request, 'other')
+ self.assertEqual(len(result), 2)
+ self.assertTrue(result[0][1].endswith('; Path=/'))
+ self.assertTrue(result[1][1].endswith('; Path=/; Domain=.example.com'))
+
def test_remember_domain_has_port(self):
helper = self._makeOne('secret', wild_domain=False)
request = self._makeRequest()
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index a984acfd0..f6cd414fb 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -364,6 +364,23 @@ class TestPredicateList(unittest.TestCase):
def test_unknown_predicate(self):
from pyramid.exceptions import ConfigurationError
self.assertRaises(ConfigurationError, self._callFUT, unknown=1)
+
+ def test_notted(self):
+ from pyramid.config import not_
+ from pyramid.testing import DummyRequest
+ request = DummyRequest()
+ _, predicates, _ = self._callFUT(
+ xhr='xhr',
+ request_method=not_('POST'),
+ header=not_('header'),
+ )
+ self.assertEqual(predicates[0].text(), 'xhr = True')
+ self.assertEqual(predicates[1].text(),
+ "!request_method = POST")
+ self.assertEqual(predicates[2].text(), '!header header')
+ self.assertEqual(predicates[1](None, request), True)
+ self.assertEqual(predicates[2](None, request), True)
+
class Test_takes_one_arg(unittest.TestCase):
def _callFUT(self, view, attr=None, argname=None):
@@ -551,7 +568,37 @@ class Test_takes_one_arg(unittest.TestCase):
foo = Foo()
self.assertTrue(self._callFUT(foo.method))
+class TestNotted(unittest.TestCase):
+ def _makeOne(self, predicate):
+ from pyramid.config.util import Notted
+ return Notted(predicate)
+
+ def test_it_with_phash_val(self):
+ pred = DummyPredicate('val')
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.text(), '!val')
+ self.assertEqual(inst.phash(), '!val')
+ self.assertEqual(inst(None, None), False)
+
+ def test_it_without_phash_val(self):
+ pred = DummyPredicate('')
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.text(), '')
+ self.assertEqual(inst.phash(), '')
+ self.assertEqual(inst(None, None), True)
+
+class DummyPredicate(object):
+ def __init__(self, result):
+ self.result = result
+
+ def text(self):
+ return self.result
+
+ phash = text
+ def __call__(self, context, request):
+ return True
+
class DummyCustomPredicate(object):
def __init__(self):
self.__text__ = 'custom predicate'
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index 432959147..b836d7d72 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -1180,11 +1180,9 @@ class TestRouter(unittest.TestCase):
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IRequest, IResponse
from pyramid.response import Response
- from zope.interface import Interface, implementer
- class IContext(Interface):
+ class BaseContext:
pass
- @implementer(IContext)
- class DummyContext:
+ class DummyContext(BaseContext):
pass
context = DummyContext()
self._registerTraverserFactory(context)
@@ -1193,7 +1191,7 @@ class TestRouter(unittest.TestCase):
DummyContext)
good_view = DummyView('abc')
self._registerView(self.config.derive_view(good_view),
- '', IViewClassifier, IRequest, IContext)
+ '', IViewClassifier, IRequest, BaseContext)
router = self._makeOne()
def make_response(s):
return Response(s)
diff --git a/pyramid/tests/test_scripts/test_pdistreport.py b/pyramid/tests/test_scripts/test_pdistreport.py
new file mode 100644
index 000000000..a8316c0e5
--- /dev/null
+++ b/pyramid/tests/test_scripts/test_pdistreport.py
@@ -0,0 +1,74 @@
+import unittest
+from pyramid.tests.test_scripts import dummy
+
+class TestPDistReportCommand(unittest.TestCase):
+ def _callFUT(self, **kw):
+ argv = []
+ from pyramid.scripts.pdistreport import main
+ return main(argv, **kw)
+
+ def test_no_dists(self):
+ def platform():
+ return 'myplatform'
+ pkg_resources = DummyPkgResources()
+ L = []
+ def out(*args):
+ L.extend(args)
+ result = self._callFUT(pkg_resources=pkg_resources, platform=platform,
+ out=out)
+ self.assertEqual(result, None)
+ self.assertEqual(
+ L,
+ ['Pyramid version:', '1',
+ 'Platform:', 'myplatform',
+ 'Packages:']
+ )
+
+ def test_with_dists(self):
+ def platform():
+ return 'myplatform'
+ working_set = (DummyDistribution('abc'), DummyDistribution('def'))
+ pkg_resources = DummyPkgResources(working_set)
+ L = []
+ def out(*args):
+ L.extend(args)
+ result = self._callFUT(pkg_resources=pkg_resources, platform=platform,
+ out=out)
+ self.assertEqual(result, None)
+ self.assertEqual(
+ L,
+ ['Pyramid version:',
+ '1',
+ 'Platform:',
+ 'myplatform',
+ 'Packages:',
+ ' ',
+ 'abc',
+ '1',
+ ' ',
+ '/projects/abc',
+ ' ',
+ 'def',
+ '1',
+ ' ',
+ '/projects/def']
+ )
+
+class DummyPkgResources(object):
+ def __init__(self, working_set=()):
+ self.working_set = working_set
+
+ def get_distribution(self, name):
+ return Version('1')
+
+class Version(object):
+ def __init__(self, version):
+ self.version = version
+
+class DummyDistribution(object):
+ def __init__(self, name):
+ self.project_name = name
+ self.version = '1'
+ self.location = '/projects/%s' % name
+
+
diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py
index 91d2b322a..37f1d3c0f 100644
--- a/pyramid/tests/test_scripts/test_prequest.py
+++ b/pyramid/tests/test_scripts/test_prequest.py
@@ -68,6 +68,19 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
+ def test_command_w_basic_auth(self):
+ command = self._makeOne(
+ ['', '--login=user:password',
+ '--header=name:value','development.ini', '/'])
+ command.run()
+ self.assertEqual(self._environ['HTTP_NAME'], 'value')
+ self.assertEqual(self._environ['HTTP_AUTHORIZATION'],
+ 'Basic dXNlcjpwYXNzd29yZA==')
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
def test_command_has_content_type_header_var(self):
command = self._makeOne(
['', '--header=content-type:app/foo','development.ini', '/'])
@@ -96,6 +109,7 @@ class TestPRequestCommand(unittest.TestCase):
def test_command_method_get(self):
command = self._makeOne(['', '--method=GET', 'development.ini', '/'])
command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'GET')
self.assertEqual(self._path_info, '/')
self.assertEqual(self._spec, 'development.ini')
self.assertEqual(self._app_name, None)
@@ -107,6 +121,35 @@ class TestPRequestCommand(unittest.TestCase):
stdin = NativeIO()
command.stdin = stdin
command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'POST')
+ self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
+ self.assertEqual(self._environ['wsgi.input'], stdin)
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
+ def test_command_method_put(self):
+ from pyramid.compat import NativeIO
+ command = self._makeOne(['', '--method=PUT', 'development.ini', '/'])
+ stdin = NativeIO()
+ command.stdin = stdin
+ command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'PUT')
+ self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
+ self.assertEqual(self._environ['wsgi.input'], stdin)
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
+ def test_command_method_patch(self):
+ from pyramid.compat import NativeIO
+ command = self._makeOne(['', '--method=PATCH', 'development.ini', '/'])
+ stdin = NativeIO()
+ command.stdin = stdin
+ command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'PATCH')
self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
self.assertEqual(self._environ['wsgi.input'], stdin)
self.assertEqual(self._path_info, '/')
@@ -114,6 +157,32 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
+ def test_command_method_propfind(self):
+ from pyramid.compat import NativeIO
+ command = self._makeOne(['', '--method=PROPFIND', 'development.ini',
+ '/'])
+ stdin = NativeIO()
+ command.stdin = stdin
+ command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'PROPFIND')
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
+ def test_command_method_options(self):
+ from pyramid.compat import NativeIO
+ command = self._makeOne(['', '--method=OPTIONS', 'development.ini',
+ '/'])
+ stdin = NativeIO()
+ command.stdin = stdin
+ command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'OPTIONS')
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
def test_command_with_query_string(self):
command = self._makeOne(['', 'development.ini', '/abc?a=1&b=2&c'])
command.run()
diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py
index 6e4b0f17d..107ff4c0a 100644
--- a/pyramid/tests/test_scripts/test_pserve.py
+++ b/pyramid/tests/test_scripts/test_pserve.py
@@ -156,7 +156,7 @@ class TestPServeCommand(unittest.TestCase):
self.pid_file = tempfile.mktemp()
pid = os.getpid()
inst = self._makeOne()
- inst.verbose = verbosity
+ inst.options.verbose = verbosity
try:
atexit.register = fake_atexit
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index b3e0e20c4..35e2b5c27 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -356,20 +356,29 @@ class Test_signed_deserialize(unittest.TestCase):
self.assertRaises(ValueError, self._callFUT, serialized, 'secret')
class Test_check_csrf_token(unittest.TestCase):
- def _callFUT(self, request, token, raises=True):
+ def _callFUT(self, *args, **kwargs):
from ..session import check_csrf_token
- return check_csrf_token(request, token, raises=raises)
+ return check_csrf_token(*args, **kwargs)
- def test_success(self):
+ def test_success_token(self):
request = testing.DummyRequest()
request.params['csrf_token'] = request.session.get_csrf_token()
- self.assertEqual(self._callFUT(request, 'csrf_token'), True)
+ self.assertEqual(self._callFUT(request, token='csrf_token'), True)
+
+ def test_success_header(self):
+ request = testing.DummyRequest()
+ request.headers['X-CSRF-Token'] = request.session.get_csrf_token()
+ self.assertEqual(self._callFUT(request, header='X-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)
+ self.assertEqual(self._callFUT(request), True)
+
+ def test_success_default_header(self):
+ request = testing.DummyRequest()
+ request.headers['X-CSRF-Token'] = request.session.get_csrf_token()
+ self.assertEqual(self._callFUT(request), True)
def test_failure_raises(self):
from pyramid.httpexceptions import HTTPBadRequest
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 7f14462a2..da57c6301 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -114,6 +114,10 @@ class TestDummyResource(unittest.TestCase):
resource = self._makeOne()
self.assertEqual(resource.__nonzero__(), True)
+ def test_bool(self):
+ resource = self._makeOne()
+ self.assertEqual(resource.__bool__(), True)
+
def test_ctor_with__provides__(self):
resource = self._makeOne(__provides__=IDummy)
self.assertTrue(IDummy.providedBy(resource))
diff --git a/pyramid/url.py b/pyramid/url.py
index 84b58ac45..83f0d1eab 100644
--- a/pyramid/url.py
+++ b/pyramid/url.py
@@ -387,7 +387,7 @@ class URLMethodsMixin(object):
resulting url of a resource that has a path of ``/baz/bar`` will be
``http://foo/baz/bar``. If you want to generate completely relative
URLs with no leading scheme, host, port, or initial path, you can
- pass ``app_url=''`. Passing ``app_url=''` when the resource path is
+ pass ``app_url=''``. Passing ``app_url=''`` when the resource path is
``/baz/bar`` will return ``/baz/bar``.
.. versionadded:: 1.3
diff --git a/setup.py b/setup.py
index f5768e23b..bab271159 100644
--- a/setup.py
+++ b/setup.py
@@ -72,7 +72,7 @@ testing_extras = tests_require + [
]
setup(name='pyramid',
- version='1.4',
+ version='1.5dev',
description=('The Pyramid web application development framework, a '
'Pylons project'),
long_description=README + '\n\n' + CHANGES,
@@ -120,6 +120,7 @@ setup(name='pyramid',
pviews = pyramid.scripts.pviews:main
ptweens = pyramid.scripts.ptweens:main
prequest = pyramid.scripts.prequest:main
+ pdistreport = pyramid.scripts.pdistreport:main
[paste.server_runner]
wsgiref = pyramid.scripts.pserve:wsgiref_server_runner
cherrypy = pyramid.scripts.pserve:cherrypy_server_runner