summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt37
-rw-r--r--docs/api/config.rst2
-rw-r--r--docs/conf.py2
-rw-r--r--docs/index.rst4
-rw-r--r--docs/narr/hellotraversal.py22
-rw-r--r--docs/narr/hellotraversal.rst69
-rw-r--r--docs/narr/introduction.rst2
-rw-r--r--docs/narr/muchadoabouttraversal.rst4
-rw-r--r--docs/narr/project.rst6
-rw-r--r--docs/narr/security.rst69
-rw-r--r--docs/narr/traversal.rst7
-rw-r--r--docs/narr/viewconfig.rst6
-rw-r--r--docs/narr/views.rst2
-rw-r--r--docs/tutorials/wiki/authorization.rst4
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki2/authorization.rst4
-rw-r--r--docs/tutorials/wiki2/definingviews.rst8
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/__init__.py6
-rw-r--r--docs/whatsnew-1.3.rst15
-rw-r--r--pyramid/config/__init__.py8
-rw-r--r--pyramid/config/views.py12
-rw-r--r--pyramid/scripts/prequest.py5
-rw-r--r--pyramid/scripts/pserve.py7
-rw-r--r--pyramid/tests/test_config/test_init.py5
-rw-r--r--pyramid/tests/test_config/test_views.py59
-rw-r--r--pyramid/tests/test_scripts/test_prequest.py14
-rw-r--r--setup.py2
29 files changed, 314 insertions, 85 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index ba90c3bad..9edcbdbe8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,30 @@
-Unreleased
-==========
+Next release
+============
+
+Features
+--------
+
+- More informative error message when a ``config.include`` cannot find an
+ ``includeme``. See https://github.com/Pylons/pyramid/pull/392.
+
+Bug Fixes
+---------
+
+- The process will now restart when ``pserve`` is used with the ``--reload``
+ flag when the ``development.ini`` file (or any other .ini file in use) is
+ changed. See https://github.com/Pylons/pyramid/issues/377 and
+ https://github.com/Pylons/pyramid/pull/411
+
+- The ``prequest`` script would fail when used against URLs which did not
+ return HTML or text. See https://github.com/Pylons/pyramid/issues/381
+
+Documentation
+-------------
+
+- Add a traversal hello world example to the narrative docs.
+
+1.3a6 (2012-01-20)
+==================
Features
--------
@@ -21,6 +46,14 @@ Bug Fixes
a different view that had the same predicate arguments. See
https://github.com/Pylons/pyramid/pull/404 for more information.
+- When using a dotted name for a ``view`` argument to
+ ``Configurator.add_view`` that pointed to a class with a ``view_defaults``
+ decorator, the view defaults would not be applied. See
+ https://github.com/Pylons/pyramid/issues/396 .
+
+- Static URL paths were URL-quoted twice. See
+ https://github.com/Pylons/pyramid/issues/407 .
+
1.3a5 (2012-01-09)
==================
diff --git a/docs/api/config.rst b/docs/api/config.rst
index dbfbb1761..d16930cc0 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -72,6 +72,8 @@
.. automethod:: set_request_factory
+ .. automethod:: set_request_property
+
.. automethod:: set_root_factory
.. automethod:: set_view_mapper
diff --git a/docs/conf.py b/docs/conf.py
index 2ab56cadf..3496bd38c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -80,7 +80,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year
# other places throughout the built documents.
#
# The short X.Y version.
-version = '1.3a5'
+version = '1.3a6'
# The full version, including alpha/beta/rc tags.
release = version
diff --git a/docs/index.rst b/docs/index.rst
index be03448c9..cb632d5b2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -83,6 +83,7 @@ Narrative documentation in chapter form explaining how to use
narr/vhosting
narr/testing
narr/resources
+ narr/hellotraversal
narr/muchadoabouttraversal
narr/traversal
narr/security
@@ -152,7 +153,8 @@ application. Check this application out via:
`virginia <https://github.com/Pylons/virginia>`_ is a very simple dynamic
file rendering application. It is willing to render structured text
-documents, HTML documents, and images from a filesystem directory. An
+documents, HTML documents, and images from a filesystem directory.
+It's also a good example of :term:`traversal`. An
earlier version of this application runs the `repoze.org
<http://repoze.org>`_ website. Check this application out via:
diff --git a/docs/narr/hellotraversal.py b/docs/narr/hellotraversal.py
new file mode 100644
index 000000000..1ef7525e6
--- /dev/null
+++ b/docs/narr/hellotraversal.py
@@ -0,0 +1,22 @@
+from wsgiref.simple_server import make_server
+from pyramid.config import Configurator
+from pyramid.response import Response
+
+class Resource(dict):
+ pass
+
+def get_root(request):
+ return Resource({'a': Resource({'b': Resource({'c': Resource()})})})
+
+def hello_world_of_resources(context, request):
+ output = "Here's a resource and its children: %s" % context
+ return Response(output)
+
+if __name__ == '__main__':
+ config = Configurator(root_factory=get_root)
+ config.add_view(hello_world_of_resources, context=Resource)
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+
diff --git a/docs/narr/hellotraversal.rst b/docs/narr/hellotraversal.rst
new file mode 100644
index 000000000..142c24f54
--- /dev/null
+++ b/docs/narr/hellotraversal.rst
@@ -0,0 +1,69 @@
+.. _hello_traversal_chapter:
+
+Hello Traversal World
+======================
+
+
+.. index::
+ single: traversal quick example
+
+Traversal is an alternative to URL dispatch which allows Pyramid
+applications to map URLs to code.
+
+If code speaks louder than words, maybe this will help. Here is a
+single-file Pyramid application that uses traversal:
+
+.. literalinclude:: hellotraversal.py
+ :linenos:
+
+You may notice that this application is intentionally very similar to
+the "hello world" app from :doc:`firstapp`.
+
+On lines 5-6, we create a trivial :term:`resource` class that's just a
+dictionary subclass.
+
+On lines 8-9, we hard-code a :term:`resource tree` in our :term:`root
+factory` function.
+
+On lines 11-13 we define a single :term:`view callable` that can
+display a single instance of our Resource class, passed as the
+``context`` argument.
+
+The rest of the file sets up and serves our pyramid WSGI app. Line 18
+is where our view gets configured for use whenever the traversal ends
+with an instance of our Resource class.
+
+Interestingly, there are no URLs explicitly configured in this
+application. Instead, the URL space is defined entirely by the keys in
+the resource tree.
+
+Example requests
+----------------
+
+If this example is running on http://localhost:8080, and the user
+browses to http://localhost:8080/a/b, Pyramid will call
+``get_root(request)`` to get the root resource, then traverse the tree
+from there by key; starting from the root, it will find the child with
+key ``"a"``, then its child with key ``"b"``; then use that as the
+``context`` argument for calling ``hello_world_of_resources``.
+
+Or, if the user browses to http://localhost:8080/ , Pyramid will
+stop at the root - the outermost Resource instance, in this case - and
+use that as the ``context`` argument to the same view.
+
+Or, if the user browses to a key that doesn't exist in this resource
+tree, like http://localhost:8080/xyz or
+http://localhost:8080/a/b/c/d, the traversal will end by raising a
+KeyError, and Pyramid will turn that into a 404 HTTP response.
+
+A more complicated application could have many types of resources,
+with different view callables defined for each type, and even multiple
+views for each type.
+
+See Also
+---------
+
+Full technical details may be found in :doc:`traversal`.
+
+For more about *why* you might use traversal, see :doc:`muchadoabouttraversal`.
+
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index 7c6ad00f3..92955aafd 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -597,7 +597,7 @@ content management systems and document management systems. Traversal also lend
systems that require very granular security ("Bob can edit *this* document"
as opposed to "Bob can edit documents").
-Example: :ref:`much_ado_about_traversal_chapter`.
+Example: :ref:`hello_traversal_chapter` and :ref:`much_ado_about_traversal_chapter`.
Tweens
~~~~~~
diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst
index 4a249ed0d..40d498391 100644
--- a/docs/narr/muchadoabouttraversal.rst
+++ b/docs/narr/muchadoabouttraversal.rst
@@ -4,6 +4,8 @@
Much Ado About Traversal
========================
+(Or, why you should care about it)
+
.. note::
This chapter was adapted, with permission, from a blog post by `Rob
@@ -15,7 +17,7 @@ Traversal is an alternative to :term:`URL dispatch` which allows
.. note::
- Ex-Zope users whom are already familiar with traversal and view lookup
+ Ex-Zope users who are already familiar with traversal and view lookup
conceptually may want to skip directly to the :ref:`traversal_chapter`
chapter, which discusses technical details. This chapter is mostly aimed
at people who have previous :term:`Pylons` experience or experience in
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 5696b0b73..ea0045ca7 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -118,11 +118,11 @@ your application, or install your application for deployment or development.
A ``.ini`` file named ``development.ini`` will be created in the project
directory. You will use this ``.ini`` file to configure a server, to run
-your application, and to debug your application. It sports configuration
+your application, and to debug your application. It contains configuration
that enables an interactive debugger and settings optimized for development.
Another ``.ini`` file named ``production.ini`` will also be created in the
-project directory. It sports configuration that disables any interactive
+project directory. It contains configuration that disables any interactive
debugger (to prevent inappropriate access and disclosure), and turns off a
number of debugging settings. You can use this file to put your application
into production.
@@ -709,7 +709,7 @@ also informs Python that the directory which contains it is a *package*.
#. Line 1 imports the :term:`Configurator` class from :mod:`pyramid.config`
that we use later.
-#. Lines 3-16 define a function named ``main`` that returns a :app:`Pyramid`
+#. Lines 3-10 define a function named ``main`` that returns a :app:`Pyramid`
WSGI application. This function is meant to be called by the
:term:`PasteDeploy` framework as a result of running ``pserve``.
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index 1ad35b961..07ec0f21e 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -73,16 +73,15 @@ to enable an authorization policy.
Enabling an Authorization Policy Imperatively
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Passing an ``authorization_policy`` argument to the constructor of the
-:class:`~pyramid.config.Configurator` class enables an
-authorization policy.
+Use the :meth:`~pyramid.config.Configurator.set_authorization_policy` method
+of the :class:`~pyramid.config.Configurator` to enable an authorization
+policy.
-You must also enable an :term:`authentication policy` in order to
-enable the authorization policy. This is because authorization, in
-general, depends upon authentication. Use the
-``authentication_policy`` argument to the
-:class:`~pyramid.config.Configurator` class during
-application setup to specify an authentication policy.
+You must also enable an :term:`authentication policy` in order to enable the
+authorization policy. This is because authorization, in general, depends
+upon authentication. Use the
+:meth:`~pyramid.config.Configurator.set_authentication_policy` and method
+during application setup to specify the authentication policy.
For example:
@@ -95,13 +94,14 @@ For example:
from pyramid.authorization import ACLAuthorizationPolicy
authentication_policy = AuthTktAuthenticationPolicy('seekrit')
authorization_policy = ACLAuthorizationPolicy()
- config = Configurator(authentication_policy=authentication_policy,
- authorization_policy=authorization_policy)
+ config = Configurator()
+ config.set_authentication_policy(authentication_policy)
+ config.set_authorization_policy(authorization_policy)
.. note:: the ``authentication_policy`` and ``authorization_policy``
- arguments may also be passed to the Configurator as :term:`dotted
- Python name` values, each representing the dotted name path to a
- suitable implementation global defined at Python module scope.
+ arguments may also be passed to their respective methods mentioned above
+ as :term:`dotted Python name` values, each representing the dotted name
+ path to a suitable implementation global defined at Python module scope.
The above configuration enables a policy which compares the value of an "auth
ticket" cookie passed in the request's environment which contains a reference
@@ -110,9 +110,9 @@ to a single :term:`principal` against the principals present in any
:term:`view`.
While it is possible to mix and match different authentication and
-authorization policies, it is an error to pass an authentication
-policy without the authorization policy or vice versa to a
-:term:`Configurator` constructor.
+authorization policies, it is an error to configure a Pyramid application
+with an authentication policy but without the authorization policy or vice
+versa. If you do this, you'll receive an error at application startup time.
See also the :mod:`pyramid.authorization` and
:mod:`pyramid.authentication` modules for alternate implementations
@@ -188,13 +188,8 @@ In support of making it easier to configure applications which are
the permission string to all view registrations which don't otherwise
name a ``permission`` argument.
-These APIs are in support of configuring a default permission for an
-application:
-
-- The ``default_permission`` constructor argument to the
- :mod:`~pyramid.config.Configurator` constructor.
-
-- The :meth:`pyramid.config.Configurator.set_default_permission` method.
+The :meth:`pyramid.config.Configurator.set_default_permission` method
+supports configuring a default permission for an application.
When a default permission is registered:
@@ -605,8 +600,8 @@ that implements the following interface:
current user on subsequent requests. """
After you do so, you can pass an instance of such a class into the
-:class:`~pyramid.config.Configurator` class at configuration
-time as ``authentication_policy`` to use it.
+:class:`~pyramid.config.Configurator.set_authentication_policy` method
+configuration time to use it.
.. index::
single: authorization policy (creating)
@@ -616,18 +611,16 @@ time as ``authentication_policy`` to use it.
Creating Your Own Authorization Policy
--------------------------------------
-An authorization policy is a policy that allows or denies access after
-a user has been authenticated. By default, :app:`Pyramid` will use
-the :class:`pyramid.authorization.ACLAuthorizationPolicy` if an
-authentication policy is activated and an authorization policy isn't
-otherwise specified.
+An authorization policy is a policy that allows or denies access after a user
+has been authenticated. Most :app:`Pyramid` applications will use the
+default :class:`pyramid.authorization.ACLAuthorizationPolicy`.
-In some cases, it's useful to be able to use a different
+However, in some cases, it's useful to be able to use a different
authorization policy than the default
-:class:`~pyramid.authorization.ACLAuthorizationPolicy`. For
-example, it might be desirable to construct an alternate authorization
-policy which allows the application to use an authorization mechanism
-that does not involve :term:`ACL` objects.
+:class:`~pyramid.authorization.ACLAuthorizationPolicy`. For example, it
+might be desirable to construct an alternate authorization policy which
+allows the application to use an authorization mechanism that does not
+involve :term:`ACL` objects.
:app:`Pyramid` ships with only a single default authorization
policy, so you'll need to create your own if you'd like to use a
@@ -655,5 +648,5 @@ following interface:
used."""
After you do so, you can pass an instance of such a class into the
-:class:`~pyramid.config.Configurator` class at configuration
-time as ``authorization_policy`` to use it.
+:class:`~pyramid.config.Configurator.set_authorization_policy` method at
+configuration time to use it.
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
index ef875c8f0..8c5d950c1 100644
--- a/docs/narr/traversal.rst
+++ b/docs/narr/traversal.rst
@@ -3,6 +3,13 @@
Traversal
=========
+This chapter explains the technical details of how traversal works in
+Pyramid.
+
+For a quick example, see :doc:`hellotraversal`.
+
+For more about *why* you might use traversal, see :doc:`muchadoabouttraversal`.
+
A :term:`traversal` uses the URL (Universal Resource Locator) to find a
:term:`resource` located in a :term:`resource tree`, which is a set of
nested dictionary-like objects. Traversal is done by using each segment
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 03000629c..763c0e131 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -280,8 +280,8 @@ configured view.
*This is an advanced feature, not often used by "civilians"*.
``request_method``
- This value can be one of the strings ``GET``, ``POST``, ``PUT``,
- ``DELETE``, or ``HEAD`` representing an HTTP ``REQUEST_METHOD``. A view
+ This value can be a string (typically ``"GET"``, ``"POST"``, ``"PUT"``,
+ ``"DELETE"``, or ``"HEAD"``) representing an HTTP ``REQUEST_METHOD``. A view
declaration with this argument ensures that the view will only be called
when the request's ``method`` attribute (aka the ``REQUEST_METHOD`` of the
WSGI environment) string matches the supplied value.
@@ -926,7 +926,7 @@ there's a ``should_cache`` GET or POST variable:
return response
Note that the ``http_cache`` machinery will overwrite or add to caching
-headers you set within the view itself unless you use ``preserve_auto``.
+headers you set within the view itself unless you use ``prevent_auto``.
You can also turn of the effect of ``http_cache`` entirely for the duration
of a Pyramid application lifetime. To do so, set the
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index a3fd61098..fa34cca61 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -233,7 +233,7 @@ provided within :mod:`pyramid.httpexceptions`.
How Pyramid Uses HTTP Exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-HTTP exceptions are meant to be used directly by application application
+HTTP exceptions are meant to be used directly by application
developers. However, Pyramid itself will raise two HTTP exceptions at
various points during normal operations:
:exc:`pyramid.httpexceptions.HTTPNotFound` and
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index fa18d4a41..8f583ece7 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -132,14 +132,14 @@ We'll add these views to the existing ``views.py`` file we have in our
project. Here's what the ``login`` view callable will look like:
.. literalinclude:: src/authorization/tutorial/views.py
- :pyobject: login
+ :lines: 83-111
:linenos:
:language: python
Here's what the ``logout`` view callable will look like:
.. literalinclude:: src/authorization/tutorial/views.py
- :pyobject: logout
+ :lines: 113-117
:linenos:
:language: python
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 20ee685ee..6989145d8 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -17,9 +17,9 @@ def main(global_config, **settings):
authn_policy = AuthTktAuthenticationPolicy(secret='sosecret',
callback=groupfinder)
authz_policy = ACLAuthorizationPolicy()
- config = Configurator(root_factory=root_factory, settings=settings,
- authentication_policy=authn_policy,
- authorization_policy=authz_policy)
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
index 20ee685ee..6989145d8 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -17,9 +17,9 @@ def main(global_config, **settings):
authn_policy = AuthTktAuthenticationPolicy(secret='sosecret',
callback=groupfinder)
authz_policy = ACLAuthorizationPolicy()
- config = Configurator(root_factory=root_factory, settings=settings,
- authentication_policy=authn_policy,
- authorization_policy=authz_policy)
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index ab04ea405..56237a1b9 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -159,14 +159,14 @@ logged in user and redirect back to the front page.
The ``login`` view callable will look something like this:
.. literalinclude:: src/authorization/tutorial/views.py
- :pyobject: login
+ :lines: 90-116
:linenos:
:language: python
The ``logout`` view callable will look something like this:
.. literalinclude:: src/authorization/tutorial/views.py
- :pyobject: logout
+ :lines: 118-122
:linenos:
:language: python
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index 7f533b635..bda0a2eb7 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -104,7 +104,7 @@ when a request is made to the root URL of our wiki. It always redirects to
a URL which represents the path to our "FrontPage".
.. literalinclude:: src/views/tutorial/views.py
- :pyobject: view_wiki
+ :lines: 18-21
:linenos:
:language: python
@@ -126,7 +126,7 @@ HTML anchor for each *WikiWord* reference in the rendered HTML using a
compiled regular expression.
.. literalinclude:: src/views/tutorial/views.py
- :pyobject: view_page
+ :lines: 23-44
:linenos:
:language: python
@@ -161,7 +161,7 @@ The ``matchdict`` attribute of the request passed to the ``add_page`` view
will have the values we need to construct URLs and find model objects.
.. literalinclude:: src/views/tutorial/views.py
- :pyobject: add_page
+ :lines: 46-58
:linenos:
:language: python
@@ -197,7 +197,7 @@ request passed to the ``edit_page`` view will have a ``'pagename'`` key
matching the name of the page the user wants to edit.
.. literalinclude:: src/views/tutorial/views.py
- :pyobject: edit_page
+ :lines: 60-73
:linenos:
:language: python
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
index 04dd5fe82..7e290a1e1 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -17,9 +17,9 @@ def main(global_config, **settings):
'sosecret', callback=groupfinder)
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
- root_factory='tutorial.models.RootFactory',
- authentication_policy=authn_policy,
- authorization_policy=authz_policy)
+ root_factory='tutorial.models.RootFactory')
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('login', '/login')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
index 04dd5fe82..7e290a1e1 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
@@ -17,9 +17,9 @@ def main(global_config, **settings):
'sosecret', callback=groupfinder)
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
- root_factory='tutorial.models.RootFactory',
- authentication_policy=authn_policy,
- authorization_policy=authz_policy)
+ root_factory='tutorial.models.RootFactory')
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('login', '/login')
diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst
index cdb7a3b4f..2793471a3 100644
--- a/docs/whatsnew-1.3.rst
+++ b/docs/whatsnew-1.3.rst
@@ -242,6 +242,16 @@ Minor Feature Additions
- We allow extra keyword arguments to be passed to the
:meth:`pyramid.config.Configurator.action` method.
+- New API: :meth:`pyramid.config.Configurator.set_request_property`. Add lazy
+ property descriptors to a request without changing the request factory.
+ This method provides conflict detection and is the suggested way to add
+ properties to a request.
+
+- Responses generated by Pyramid's :class:`pyramid.views.static_view` now use
+ a ``wsgi.file_wrapper`` (see
+ http://www.python.org/dev/peps/pep-0333/#optional-platform-specific-file-handling)
+ when one is provided by the web server.
+
Backwards Incompatibilities
---------------------------
@@ -302,6 +312,11 @@ Backwards Incompatibilities
``add_route`` as a pattern, it will now fail at startup time. Use Unicode
instead.
+- The ``path_info`` route and view predicates now match against
+ ``request.upath_info`` (Unicode) rather than ``request.path_info``
+ (indeterminate value based on Python 3 vs. Python 2). This has to be done
+ to normalize matching on Python 2 and Python 3.
+
Documentation Enhancements
--------------------------
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 04f9b6fb5..57ab7e13a 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -704,7 +704,13 @@ class Configurator(
c = self.maybe_dotted(callable)
module = inspect.getmodule(c)
if module is c:
- c = getattr(module, 'includeme')
+ try:
+ c = getattr(module, 'includeme')
+ except AttributeError:
+ raise ConfigurationError(
+ "module %r has no attribute 'includeme'" % (module.__name__)
+ )
+
spec = module.__name__ + ':' + c.__name__
sourcefile = inspect.getsourcefile(c)
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 89a0d77c5..0359c46f7 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -567,17 +567,18 @@ class MultiView(object):
raise PredicateMismatch(self.name)
def viewdefaults(wrapped):
- def wrapper(*arg, **kw):
+ def wrapper(self, *arg, **kw):
defaults = {}
- if len(arg) > 1:
- view = arg[1]
+ if arg:
+ view = arg[0]
else:
view = kw.get('view')
+ view = self.maybe_dotted(view)
if inspect.isclass(view):
defaults = getattr(view, '__view_defaults__', {}).copy()
defaults.update(kw)
defaults['_backframes'] = 3 # for action_method
- return wrapped(*arg, **defaults)
+ return wrapped(self, *arg, **defaults)
return wraps(wrapped)(wrapper)
class ViewsConfiguratorMixin(object):
@@ -1544,11 +1545,12 @@ class StaticURLInfo(object):
registry = get_current_registry()
for (url, spec, route_name) in self._get_registrations(registry):
if path.startswith(spec):
- subpath = url_quote(path[len(spec):])
+ subpath = path[len(spec):]
if url is None:
kw['subpath'] = subpath
return request.route_url(route_name, **kw)
else:
+ subpath = url_quote(subpath)
return urljoin(url, subpath)
raise ValueError('No static URL definition matching %s' % path)
diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py
index a102dda7e..5423a6316 100644
--- a/pyramid/scripts/prequest.py
+++ b/pyramid/scripts/prequest.py
@@ -140,5 +140,8 @@ class PRequestCommand(object):
self.out(response.status)
for name, value in response.headerlist:
self.out('%s: %s' % (name, value))
- self.out(response.ubody)
+ if response.charset:
+ self.out(response.ubody)
+ else:
+ self.out(response.body)
return 0
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index a73cbde3a..087549cd2 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -173,7 +173,7 @@ class PServeCommand(object):
if os.environ.get(self._reloader_environ_key):
if self.verbose > 1:
self.out('Running reloading file monitor')
- install_reloader(int(self.options.reload_interval))
+ install_reloader(int(self.options.reload_interval), [app_spec])
# if self.requires_config_file:
# watch_file(self.args[0])
else:
@@ -667,7 +667,7 @@ def _turn_sigterm_into_systemexit(): # pragma: no cover
raise SystemExit
signal.signal(signal.SIGTERM, handle_term)
-def install_reloader(poll_interval=1): # pragma: no cover
+def install_reloader(poll_interval=1, extra_files=None): # pragma: no cover
"""
Install the reloading monitor.
@@ -677,6 +677,9 @@ def install_reloader(poll_interval=1): # pragma: no cover
which causes the whole application to shut-down (rudely).
"""
mon = Monitor(poll_interval=poll_interval)
+ if extra_files is None:
+ extra_files = []
+ mon.extra_files.extend(extra_files)
t = threading.Thread(target=mon.periodic_reload)
t.setDaemon(True)
t.start()
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index 3aa2c7810..a866bed55 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -712,6 +712,11 @@ pyramid.tests.test_config.dummy_include2""",
self.assertEqual(action['callable'], None)
self.assertEqual(action['args'], test_config)
+ def test_include_with_module_defaults_to_includeme_missing(self):
+ from pyramid.exceptions import ConfigurationError
+ config = self._makeOne()
+ self.assertRaises(ConfigurationError, config.include, 'pyramid.tests')
+
def test_include_with_route_prefix(self):
root_config = self._makeOne(autocommit=True)
def dummy_subapp(config):
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index dd96579b7..4af29325a 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -1474,6 +1474,40 @@ class TestViewsConfigurationMixin(unittest.TestCase):
context = DummyContext()
request = self._makeRequest(config)
self.assertRaises(PredicateMismatch, wrapper, context, request)
+
+ def test_add_view_with_view_defaults_viewname_is_dottedname_kwarg(self):
+ from pyramid.renderers import null_renderer
+ from pyramid.exceptions import PredicateMismatch
+ from zope.interface import directlyProvides
+ config = self._makeOne(autocommit=True)
+ config.add_view(
+ view='pyramid.tests.test_config.test_views.DummyViewDefaultsClass',
+ renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ context = DummyContext()
+ directlyProvides(context, IDummy)
+ request = self._makeRequest(config)
+ self.assertEqual(wrapper(context, request), 'OK')
+ context = DummyContext()
+ request = self._makeRequest(config)
+ self.assertRaises(PredicateMismatch, wrapper, context, request)
+
+ def test_add_view_with_view_defaults_viewname_is_dottedname_nonkwarg(self):
+ from pyramid.renderers import null_renderer
+ from pyramid.exceptions import PredicateMismatch
+ from zope.interface import directlyProvides
+ config = self._makeOne(autocommit=True)
+ config.add_view(
+ 'pyramid.tests.test_config.test_views.DummyViewDefaultsClass',
+ renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ context = DummyContext()
+ directlyProvides(context, IDummy)
+ request = self._makeRequest(config)
+ self.assertEqual(wrapper(context, request), 'OK')
+ context = DummyContext()
+ request = self._makeRequest(config)
+ self.assertRaises(PredicateMismatch, wrapper, context, request)
def test_add_view_with_view_config_and_view_defaults_doesnt_conflict(self):
from pyramid.renderers import null_renderer
@@ -3508,6 +3542,18 @@ class TestStaticURLInfo(unittest.TestCase):
result = inst.generate('package:path/', request)
self.assertEqual(result, 'http://example.com/foo/')
+ def test_generate_quoting(self):
+ config = testing.setUp()
+ try:
+ config.add_static_view('images', path='mypkg:templates')
+ inst = self._makeOne()
+ request = testing.DummyRequest()
+ request.registry = config.registry
+ result = inst.generate('mypkg:templates/foo%2Fbar', request)
+ self.assertEqual(result, 'http://example.com/images/foo%252Fbar')
+ finally:
+ testing.tearDown()
+
def test_generate_route_url(self):
inst = self._makeOne()
registrations = [(None, 'package:path/', '__viewname/')]
@@ -3521,13 +3567,13 @@ class TestStaticURLInfo(unittest.TestCase):
result = inst.generate('package:path/abc', request, a=1)
self.assertEqual(result, 'url')
- def test_generate_url_quoted_local(self):
+ def test_generate_url_unquoted_local(self):
inst = self._makeOne()
registrations = [(None, 'package:path/', '__viewname/')]
inst._get_registrations = lambda *x: registrations
def route_url(n, **kw):
self.assertEqual(n, '__viewname/')
- self.assertEqual(kw, {'subpath':'abc%20def', 'a':1})
+ self.assertEqual(kw, {'subpath':'abc def', 'a':1})
return 'url'
request = self._makeRequest()
request.route_url = route_url
@@ -3745,3 +3791,12 @@ class DummyStaticURLInfo:
def add(self, config, name, spec, **kw):
self.added.append((config, name, spec, kw))
+
+class DummyViewDefaultsClass(object):
+ __view_defaults__ = {
+ 'containment':'pyramid.tests.test_config.IDummy'
+ }
+ def __init__(self, request):
+ pass
+ def __call__(self):
+ return 'OK'
diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py
index 7f24174ef..cf7af4218 100644
--- a/pyramid/tests/test_scripts/test_prequest.py
+++ b/pyramid/tests/test_scripts/test_prequest.py
@@ -5,9 +5,10 @@ class TestPRequestCommand(unittest.TestCase):
from pyramid.scripts.prequest import PRequestCommand
return PRequestCommand
- def _makeOne(self, argv):
+ def _makeOne(self, argv, headers=None):
cmd = self._getTargetClass()(argv)
cmd.get_app = self.get_app
+ self._headers = headers or []
self._out = []
cmd.out = self.out
return cmd
@@ -18,7 +19,7 @@ class TestPRequestCommand(unittest.TestCase):
def helloworld(environ, start_request):
self._environ = environ
self._path_info = environ['PATH_INFO']
- start_request('200 OK', [])
+ start_request('200 OK', self._headers)
return [b'abc']
return helloworld
@@ -131,6 +132,15 @@ class TestPRequestCommand(unittest.TestCase):
self._out,
['200 OK', 'Content-Type: text/html; charset=UTF-8', 'abc'])
+ def test_command_response_has_no_charset(self):
+ command = self._makeOne(['', '--method=GET', 'development.ini', '/'],
+ headers=[('Content-Type', 'image/jpeg')])
+ command.run()
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, [b'abc'])
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.prequest import main
diff --git a/setup.py b/setup.py
index 91b056778..64c0ad419 100644
--- a/setup.py
+++ b/setup.py
@@ -56,7 +56,7 @@ if not PY3:
])
setup(name='pyramid',
- version='1.3a5',
+ version='1.3a6',
description=('The Pyramid web application development framework, a '
'Pylons project'),
long_description=README + '\n\n' + CHANGES,