summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2014-11-12 12:23:48 -0500
committerChris McDonough <chrism@plope.com>2014-11-12 12:23:48 -0500
commitb1fac53cd0c3b930aec90e27f4d19c5f785f52e2 (patch)
treee71f8e716ab2d12cb692c5789120cd0984aaa340
parent0337f987466dd47696f4293604d071bf020f165e (diff)
parent4d19a6f383c802f9620b7d2fc239e5c6ad6c52f9 (diff)
downloadpyramid-b1fac53cd0c3b930aec90e27f4d19c5f785f52e2.tar.gz
pyramid-b1fac53cd0c3b930aec90e27f4d19c5f785f52e2.tar.bz2
pyramid-b1fac53cd0c3b930aec90e27f4d19c5f785f52e2.zip
Merge branch 'master' of github.com:Pylons/pyramid
-rw-r--r--CHANGES.txt40
-rw-r--r--docs/api/exceptions.rst12
-rw-r--r--docs/api/httpexceptions.rst94
-rw-r--r--docs/glossary.rst9
-rw-r--r--docs/narr/i18n.rst2
-rw-r--r--docs/quick_tutorial/debugtoolbar.rst52
-rw-r--r--docs/quick_tutorial/forms.rst2
-rw-r--r--docs/quick_tutorial/logging.rst2
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/views.py6
-rw-r--r--pyramid/config/__init__.py17
-rw-r--r--pyramid/config/factories.py9
-rw-r--r--pyramid/config/views.py15
-rw-r--r--pyramid/httpexceptions.py69
-rw-r--r--pyramid/request.py17
-rw-r--r--pyramid/scaffolds/alchemy/development.ini_tmpl2
-rw-r--r--pyramid/scaffolds/alchemy/production.ini_tmpl2
-rw-r--r--pyramid/scaffolds/starter/development.ini_tmpl2
-rw-r--r--pyramid/scaffolds/starter/production.ini_tmpl2
-rw-r--r--pyramid/scaffolds/zodb/development.ini_tmpl2
-rw-r--r--pyramid/scaffolds/zodb/production.ini_tmpl2
-rw-r--r--pyramid/scripts/pcreate.py3
-rw-r--r--pyramid/tests/test_config/test_init.py12
-rw-r--r--pyramid/tests/test_config/test_views.py30
-rw-r--r--pyramid/tests/test_request.py46
-rw-r--r--pyramid/tests/test_response.py11
-rw-r--r--pyramid/tests/test_router.py6
-rw-r--r--pyramid/tests/test_scripts/test_pcreate.py17
-rw-r--r--pyramid/tests/test_session.py2
-rw-r--r--pyramid/tests/test_testing.py2
29 files changed, 354 insertions, 133 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 63987d980..b5d08c8ff 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -6,22 +6,48 @@ Features
- Cache busting for static resources has been added and is available via a new
argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``.
+ See https://github.com/Pylons/pyramid/pull/1380
+
+- Add ``pyramid.config.Configurator.root_package`` attribute and init
+ parameter to assist with includeable packages that wish to resolve
+ resources relative to the package in which the ``Configurator`` was created.
+ This is especially useful for addons that need to load asset specs from
+ settings, in which case it is natural for a user to define things relative
+ to their own packages.
+ See https://github.com/Pylons/pyramid/pull/1337
+
+- Added line numbers to the log formatters in the scaffolds to assist with
+ debugging. See https://github.com/Pylons/pyramid/pull/1326
+
+- Add new HTTP exception objects for status codes
+ ``428 Precondition Required``, ``429 Too Many Requests`` and
+ ``431 Request Header Fields Too Large`` in ``pyramid.httpexceptions``.
+ See https://github.com/Pylons/pyramid/pull/1372/files
+
+- Make it simple to define notfound and forbidden views that wish to use
+ the default exception-response view but with altered predicates and other
+ configuration options. The ``view`` argument is now optional in
+ ``config.add_notfound_view`` and ``config.add_forbidden_view``..
+ See https://github.com/Pylons/pyramid/issues/494
Bug Fixes
---------
- ``pyramid.wsgi.wsgiapp`` and ``pyramid.wsgi.wsgiapp2`` now raise
``ValueError`` when accidentally passed ``None``.
+ See https://github.com/Pylons/pyramid/pull/1320
- Fix an issue whereby predicates would be resolved as maybe_dotted in the
introspectable but not when passed for registration. This would mean that
- add_route_predicate for example can not take a string and turn it into the
- actual callable function.
+ ``add_route_predicate`` for example can not take a string and turn it into
+ the actual callable function.
+ See https://github.com/Pylons/pyramid/pull/1306
- Fix ``pyramid.testing.setUp`` to return a ``Configurator`` with a proper
package. Previously it was not possible to do package-relative includes
using the returned ``Configurator`` during testing. There is now a
``package`` argument that can override this behavior as well.
+ See https://github.com/Pylons/pyramid/pull/1322
- Fix an issue where a ``pyramid.response.FileResponse`` may apply a charset
where it does not belong. See https://github.com/Pylons/pyramid/pull/1251
@@ -31,6 +57,16 @@ Bug Fixes
type, unlike any previous version of Python. See
https://github.com/Pylons/pyramid/issues/1360 for more information.
+- ``pcreate`` now normalizes the package name by converting hyphens to
+ underscores. See https://github.com/Pylons/pyramid/pull/1376
+
+- Fix an issue with the final response/finished callback being unable to
+ add another callback to the list. See
+ https://github.com/Pylons/pyramid/pull/1373
+
+- Fix a failing unittest caused by differing mimetypes across various OSs.
+ See https://github.com/Pylons/pyramid/issues/1405
+
Docs
----
diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst
index 0c630571f..faca0fbb6 100644
--- a/docs/api/exceptions.rst
+++ b/docs/api/exceptions.rst
@@ -5,14 +5,14 @@
.. automodule:: pyramid.exceptions
- .. autoclass:: BadCSRFToken
+ .. autoexception:: BadCSRFToken
- .. autoclass:: PredicateMismatch
+ .. autoexception:: PredicateMismatch
- .. autoclass:: Forbidden
+ .. autoexception:: Forbidden
- .. autoclass:: NotFound
+ .. autoexception:: NotFound
- .. autoclass:: ConfigurationError
+ .. autoexception:: ConfigurationError
- .. autoclass:: URLDecodeError
+ .. autoexception:: URLDecodeError
diff --git a/docs/api/httpexceptions.rst b/docs/api/httpexceptions.rst
index b50f10beb..d4cf97f1d 100644
--- a/docs/api/httpexceptions.rst
+++ b/docs/api/httpexceptions.rst
@@ -13,96 +13,96 @@
.. autofunction:: exception_response
- .. autoclass:: HTTPException
+ .. autoexception:: HTTPException
- .. autoclass:: HTTPOk
+ .. autoexception:: HTTPOk
- .. autoclass:: HTTPRedirection
+ .. autoexception:: HTTPRedirection
- .. autoclass:: HTTPError
+ .. autoexception:: HTTPError
- .. autoclass:: HTTPClientError
+ .. autoexception:: HTTPClientError
- .. autoclass:: HTTPServerError
+ .. autoexception:: HTTPServerError
- .. autoclass:: HTTPCreated
+ .. autoexception:: HTTPCreated
- .. autoclass:: HTTPAccepted
+ .. autoexception:: HTTPAccepted
- .. autoclass:: HTTPNonAuthoritativeInformation
+ .. autoexception:: HTTPNonAuthoritativeInformation
- .. autoclass:: HTTPNoContent
+ .. autoexception:: HTTPNoContent
- .. autoclass:: HTTPResetContent
+ .. autoexception:: HTTPResetContent
- .. autoclass:: HTTPPartialContent
+ .. autoexception:: HTTPPartialContent
- .. autoclass:: HTTPMultipleChoices
+ .. autoexception:: HTTPMultipleChoices
- .. autoclass:: HTTPMovedPermanently
+ .. autoexception:: HTTPMovedPermanently
- .. autoclass:: HTTPFound
+ .. autoexception:: HTTPFound
- .. autoclass:: HTTPSeeOther
+ .. autoexception:: HTTPSeeOther
- .. autoclass:: HTTPNotModified
+ .. autoexception:: HTTPNotModified
- .. autoclass:: HTTPUseProxy
+ .. autoexception:: HTTPUseProxy
- .. autoclass:: HTTPTemporaryRedirect
+ .. autoexception:: HTTPTemporaryRedirect
- .. autoclass:: HTTPBadRequest
+ .. autoexception:: HTTPBadRequest
- .. autoclass:: HTTPUnauthorized
+ .. autoexception:: HTTPUnauthorized
- .. autoclass:: HTTPPaymentRequired
+ .. autoexception:: HTTPPaymentRequired
- .. autoclass:: HTTPForbidden
+ .. autoexception:: HTTPForbidden
- .. autoclass:: HTTPNotFound
+ .. autoexception:: HTTPNotFound
- .. autoclass:: HTTPMethodNotAllowed
+ .. autoexception:: HTTPMethodNotAllowed
- .. autoclass:: HTTPNotAcceptable
+ .. autoexception:: HTTPNotAcceptable
- .. autoclass:: HTTPProxyAuthenticationRequired
+ .. autoexception:: HTTPProxyAuthenticationRequired
- .. autoclass:: HTTPRequestTimeout
+ .. autoexception:: HTTPRequestTimeout
- .. autoclass:: HTTPConflict
+ .. autoexception:: HTTPConflict
- .. autoclass:: HTTPGone
+ .. autoexception:: HTTPGone
- .. autoclass:: HTTPLengthRequired
+ .. autoexception:: HTTPLengthRequired
- .. autoclass:: HTTPPreconditionFailed
+ .. autoexception:: HTTPPreconditionFailed
- .. autoclass:: HTTPRequestEntityTooLarge
+ .. autoexception:: HTTPRequestEntityTooLarge
- .. autoclass:: HTTPRequestURITooLong
+ .. autoexception:: HTTPRequestURITooLong
- .. autoclass:: HTTPUnsupportedMediaType
+ .. autoexception:: HTTPUnsupportedMediaType
- .. autoclass:: HTTPRequestRangeNotSatisfiable
+ .. autoexception:: HTTPRequestRangeNotSatisfiable
- .. autoclass:: HTTPExpectationFailed
+ .. autoexception:: HTTPExpectationFailed
- .. autoclass:: HTTPUnprocessableEntity
+ .. autoexception:: HTTPUnprocessableEntity
- .. autoclass:: HTTPLocked
+ .. autoexception:: HTTPLocked
- .. autoclass:: HTTPFailedDependency
+ .. autoexception:: HTTPFailedDependency
- .. autoclass:: HTTPInternalServerError
+ .. autoexception:: HTTPInternalServerError
- .. autoclass:: HTTPNotImplemented
+ .. autoexception:: HTTPNotImplemented
- .. autoclass:: HTTPBadGateway
+ .. autoexception:: HTTPBadGateway
- .. autoclass:: HTTPServiceUnavailable
+ .. autoexception:: HTTPServiceUnavailable
- .. autoclass:: HTTPGatewayTimeout
+ .. autoexception:: HTTPGatewayTimeout
- .. autoclass:: HTTPVersionNotSupported
+ .. autoexception:: HTTPVersionNotSupported
- .. autoclass:: HTTPInsufficientStorage
+ .. autoexception:: HTTPInsufficientStorage
diff --git a/docs/glossary.rst b/docs/glossary.rst
index deb4c1c8b..ef7e9a9ae 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -749,9 +749,16 @@ Glossary
made. For example the word "java" might be translated
differently if the translation domain is "programming-languages"
than would be if the translation domain was "coffee". A
- translation domain is represnted by a collection of ``.mo`` files
+ translation domain is represented by a collection of ``.mo`` files
within one or more :term:`translation directory` directories.
+ Translation Context
+ A string representing the "context" in which a translation was
+ made within a given :term:`translation domain`. See the gettext
+ documentation, `11.2.5 Using contexts for solving ambiguities
+ <https://www.gnu.org/software/gettext/manual/gettext.html#Contexts>`_
+ for more information.
+
Translator
A callable which receives a :term:`translation string` and returns a
translated Unicode object for the purposes of internationalization. A
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index 3313f8dad..3c804a158 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -354,7 +354,7 @@ command from Gettext:
$ mkdir -p es/LC_MESSAGES
$ msginit -l es -o es/LC_MESSAGES/myapplication.po
-This will create a new the message catalog ``.po`` file will in:
+This will create a new message catalog ``.po`` file in:
``myapplication/locale/es/LC_MESSAGES/myapplication.po``.
diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst
index 90750c633..d138eb760 100644
--- a/docs/quick_tutorial/debugtoolbar.rst
+++ b/docs/quick_tutorial/debugtoolbar.rst
@@ -58,33 +58,31 @@ Steps
Analysis
========
-``pyramid_debugtoolbar`` is a full-fledged Python package,
-available on PyPI just like thousands of other Python packages. Thus we
-start by installing the ``pyramid_debugtoolbar`` package into our
-virtual environment using normal Python package installation commands.
-
-The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on,
-which means we need to include its add-on configuration into our web
-application. We could do this with imperative configuration in
-``tutorial/__init__.py`` by using ``config.include``. Pyramid also
-supports wiring in add-on configuration via our ``development.ini``
-using ``pyramid.includes``. We use this to load the configuration for
-the debugtoolbar.
-
-You'll now see an attractive button on the right side of
-your browser, which you may click to provide introspective access to debugging
-information in a new browser tab. Even better, if your web application
-generates an error,
-you will see a nice traceback on the screen. When you want to disable
-this toolbar, no need to change code: you can remove it from
-``pyramid.includes`` in the relevant ``.ini`` configuration file (thus
-showing why configuration files are handy.)
-
-Note injects a small amount of html/css into your app just before the closing
-``</body>`` tag in order to display itself. If you
-start to experience otherwise inexplicable client-side weirdness, you can shut
-it off by commenting out the ``pyramid_debugtoolbar`` line in
-``pyramid.includes`` temporarily.
+``pyramid_debugtoolbar`` is a full-fledged Python package, available on PyPI
+just like thousands of other Python packages. Thus we start by installing the
+``pyramid_debugtoolbar`` package into our virtual environment using normal
+Python package installation commands.
+
+The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on, which
+means we need to include its add-on configuration into our web application. We
+could do this with imperative configuration in ``tutorial/__init__.py`` by
+using ``config.include``. Pyramid also supports wiring in add-on configuration
+via our ``development.ini`` using ``pyramid.includes``. We use this to load
+the configuration for the debugtoolbar.
+
+You'll now see an attractive button on the right side of your browser, which
+you may click to provide introspective access to debugging information in a
+new browser tab. Even better, if your web application generates an error, you
+will see a nice traceback on the screen. When you want to disable this
+toolbar, no need to change code: you can remove it from ``pyramid.includes``
+in the relevant ``.ini`` configuration file (thus showing why configuration
+files are handy.)
+
+Note that the toolbar injects a small amount of html/css into your app just
+before the closing ``</body>`` tag in order to display itself. If you start to
+experience otherwise inexplicable client-side weirdness, you can shut it off
+by commenting out the ``pyramid_debugtoolbar`` line in ``pyramid.includes``
+temporarily.
.. seealso:: See also :ref:`pyramid_debugtoolbar <toolbar:overview>`.
diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst
index e8bc0c8b4..b08167edc 100644
--- a/docs/quick_tutorial/forms.rst
+++ b/docs/quick_tutorial/forms.rst
@@ -104,7 +104,7 @@ assets which need to be published. We don't have to know where on disk
it is located. We point at the package, then the path inside the package.
We just need to include a call to ``add_static_view`` to make that
-directory available at a URL. For Pyramid-specific pages,
+directory available at a URL. For Pyramid-specific packages,
Pyramid provides a facility (``config.include()``) which even makes
that unnecessary for consumers of a package. (Deform is not specific to
Pyramid.)
diff --git a/docs/quick_tutorial/logging.rst b/docs/quick_tutorial/logging.rst
index 855ded59f..e07d23d6d 100644
--- a/docs/quick_tutorial/logging.rst
+++ b/docs/quick_tutorial/logging.rst
@@ -16,7 +16,7 @@ we might need to detect problems when other people use the site. We
need *logging*.
Fortunately Pyramid uses the normal Python approach to logging. The
-scaffold generated, in your ``development.ini``, a number of lines that
+scaffold generated, in your ``development.ini``, has a number of lines that
configure the logging for you to some reasonable defaults. You then see
messages sent by Pyramid (for example, when a new request comes in.)
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/views.py b/docs/quick_tutorial/more_view_classes/tutorial/views.py
index 635de0520..156e468a9 100644
--- a/docs/quick_tutorial/more_view_classes/tutorial/views.py
+++ b/docs/quick_tutorial/more_view_classes/tutorial/views.py
@@ -5,7 +5,7 @@ from pyramid.view import (
@view_defaults(route_name='hello')
-class TutorialViews:
+class TutorialViews(object):
def __init__(self, request):
self.request = request
self.view_name = 'TutorialViews'
@@ -25,13 +25,13 @@ class TutorialViews:
def hello(self):
return {'page_title': 'Hello View'}
- # Posting to /home via the "Edit" submit button
+ # Posting to /howdy/first/last via the "Edit" submit button
@view_config(request_method='POST', renderer='edit.pt')
def edit(self):
new_name = self.request.params['new_name']
return {'page_title': 'Edit View', 'new_name': new_name}
- # Posting to /home via the "Delete" submit button
+ # Posting to /howdy/first/last via the "Delete" submit button
@view_config(request_method='POST', request_param='form.delete',
renderer='delete.pt')
def delete(self):
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index ebaae38a9..cfa35ec6c 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -125,6 +125,14 @@ class Configurator(
is passed (the default), the package is assumed to be the Python package
in which the *caller* of the ``Configurator`` constructor lives.
+ If the ``root_package`` is passed, it will propagate through the
+ configuration hierarchy as a way for included packages to locate
+ resources relative to the package in which the main ``Configurator`` was
+ created. If ``None`` is passed (the default), the ``root_package`` will
+ be derived from the ``package`` argument. The ``package`` attribute is
+ always pointing at the package being included when using :meth:`.include`,
+ whereas the ``root_package`` does not change.
+
If the ``settings`` argument is passed, it should be a Python dictionary
representing the :term:`deployment settings` for this application. These
are later retrievable using the
@@ -243,6 +251,9 @@ class Configurator(
.. versionadded:: 1.3
The ``introspection`` argument.
+
+ .. versionadded:: 1.6
+ The ``root_package`` argument.
"""
manager = manager # for testing injection
venusian = venusian # for testing injection
@@ -272,13 +283,17 @@ class Configurator(
exceptionresponse_view=default_exceptionresponse_view,
route_prefix=None,
introspection=True,
+ root_package=None,
):
if package is None:
package = caller_package()
+ if root_package is None:
+ root_package = package
name_resolver = DottedNameResolver(package)
self.name_resolver = name_resolver
self.package_name = name_resolver.get_package_name()
self.package = name_resolver.get_package()
+ self.root_package = root_package
self.registry = registry
self.autocommit = autocommit
self.route_prefix = route_prefix
@@ -747,6 +762,7 @@ class Configurator(
configurator = self.__class__(
registry=self.registry,
package=package_of(module),
+ root_package=self.root_package,
autocommit=self.autocommit,
route_prefix=route_prefix,
)
@@ -806,6 +822,7 @@ class Configurator(
configurator = self.__class__(
registry=self.registry,
package=package,
+ root_package=self.root_package,
autocommit=self.autocommit,
route_prefix=self.route_prefix,
introspection=self.introspection,
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index 1990c377a..5ce1081c6 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -1,4 +1,4 @@
-from zope.deprecation import deprecate
+from zope.deprecation import deprecated
from zope.interface import implementer
from pyramid.interfaces import (
@@ -180,8 +180,6 @@ class FactoriesConfiguratorMixin(object):
introspectables=(intr,))
@action_method
- @deprecate('set_request_propery() is deprecated as of Pyramid 1.5; use '
- 'add_request_method() with the property=True argument instead')
def set_request_property(self, callable, name=None, reify=False):
""" Add a property to the request object.
@@ -195,6 +193,11 @@ class FactoriesConfiguratorMixin(object):
self.add_request_method(
callable, name=name, property=not reify, reify=reify)
+ deprecated(
+ set_request_property,
+ 'set_request_propery() is deprecated as of Pyramid 1.5; use '
+ 'add_request_method() with the property=True argument instead')
+
@implementer(IRequestExtensions)
class _RequestExtensions(object):
def __init__(self):
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 5ca696069..e4171b0c5 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -53,6 +53,7 @@ from pyramid.exceptions import (
from pyramid.httpexceptions import (
HTTPForbidden,
HTTPNotFound,
+ default_exceptionresponse_view,
)
from pyramid.registry import (
@@ -1591,9 +1592,12 @@ class ViewsConfiguratorMixin(object):
config.add_forbidden_view(forbidden)
+ If ``view`` argument is not provided, the view callable defaults to
+ :func:`~pyramid.httpexceptions.default_exceptionresponse_view`.
+
All arguments have the same meaning as
:meth:`pyramid.config.Configurator.add_view` and each predicate
- argument restricts the set of circumstances under which this notfound
+ argument restricts the set of circumstances under which this forbidden
view will be invoked. Unlike
:meth:`pyramid.config.Configurator.add_view`, this method will raise
an exception if passed ``name``, ``permission``, ``context``,
@@ -1609,6 +1613,9 @@ class ViewsConfiguratorMixin(object):
% arg
)
+ if view is None:
+ view = default_exceptionresponse_view
+
settings = dict(
view=view,
context=HTTPForbidden,
@@ -1671,6 +1678,9 @@ class ViewsConfiguratorMixin(object):
config.add_notfound_view(notfound)
+ If ``view`` argument is not provided, the view callable defaults to
+ :func:`~pyramid.httpexceptions.default_exceptionresponse_view`.
+
All arguments except ``append_slash`` have the same meaning as
:meth:`pyramid.config.Configurator.add_view` and each predicate
argument restricts the set of circumstances under which this notfound
@@ -1697,6 +1707,9 @@ class ViewsConfiguratorMixin(object):
% arg
)
+ if view is None:
+ view = default_exceptionresponse_view
+
settings = dict(
view=view,
context=HTTPNotFound,
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index ebee39ada..a30129e16 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -52,6 +52,9 @@ Exception
* 422 - HTTPUnprocessableEntity
* 423 - HTTPLocked
* 424 - HTTPFailedDependency
+ * 428 - HTTPPreconditionRequired
+ * 429 - HTTPTooManyRequests
+ * 431 - HTTPRequestHeaderFieldsTooLarge
HTTPServerError
* 500 - HTTPInternalServerError
* 501 - HTTPNotImplemented
@@ -868,7 +871,12 @@ class HTTPUnprocessableEntity(HTTPClientError):
subclass of :class:`~HTTPClientError`
This indicates that the server is unable to process the contained
- instructions. Only for WebDAV.
+ instructions.
+
+ May be used to notify the client that their JSON/XML is well formed, but
+ not correct for the current request.
+
+ See RFC4918 section 11 for more information.
code: 422, title: Unprocessable Entity
"""
@@ -881,7 +889,7 @@ class HTTPLocked(HTTPClientError):
"""
subclass of :class:`~HTTPClientError`
- This indicates that the resource is locked. Only for WebDAV
+ This indicates that the resource is locked.
code: 423, title: Locked
"""
@@ -896,7 +904,6 @@ class HTTPFailedDependency(HTTPClientError):
This indicates that the method could not be performed because the
requested action depended on another action and that action failed.
- Only for WebDAV.
code: 424, title: Failed Dependency
"""
@@ -907,6 +914,62 @@ class HTTPFailedDependency(HTTPClientError):
'The method could not be performed because the requested '
'action dependended on another action and that action failed')
+class HTTPPreconditionRequired(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the origin server requires the
+ request to be conditional.
+
+ Its typical use is to avoid the "lost update" problem, where a client
+ GETs a resource's state, modifies it, and PUTs it back to the server,
+ when meanwhile a third party has modified the state on the server,
+ leading to a conflict. By requiring requests to be conditional, the
+ server can assure that clients are working with the correct copies.
+
+ RFC 6585.3
+
+ code: 428, title: Precondition Required
+ """
+ code = 428
+ title = 'Precondition Required'
+ explanation = (
+ 'The origin server requires the request to be conditional.')
+
+class HTTPTooManyRequests(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the user has sent too many
+ requests in a given amount of time ("rate limiting").
+
+ RFC 6585.4
+
+ code: 429, title: Too Many Requests
+ """
+ code = 429
+ title = 'Too Many Requests'
+ explanation = (
+ 'The action could not be performed because there were too '
+ 'many requests by the client.')
+
+class HTTPRequestHeaderFieldsTooLarge(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is unwilling to process
+ the request because its header fields are too large. The request MAY
+ be resubmitted after reducing the size of the request header fields.
+
+ RFC 6585.5
+
+ code: 431, title: Request Header Fields Too Large
+ """
+ code = 431
+ title = 'Request Header Fields Too Large'
+ explanation = (
+ 'The requests header fields were too large.')
+
############################################################
## 5xx Server Error
############################################################
diff --git a/pyramid/request.py b/pyramid/request.py
index 6318049ee..bc2889310 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -1,3 +1,4 @@
+from collections import deque
import json
from zope.interface import implementer
@@ -32,8 +33,8 @@ class TemplateContext(object):
pass
class CallbackMethodsMixin(object):
- response_callbacks = ()
- finished_callbacks = ()
+ response_callbacks = None
+ finished_callbacks = None
def add_response_callback(self, callback):
"""
Add a callback to the set of callbacks to be called by the
@@ -72,15 +73,15 @@ class CallbackMethodsMixin(object):
"""
callbacks = self.response_callbacks
- if not callbacks:
- callbacks = []
+ if callbacks is None:
+ callbacks = deque()
callbacks.append(callback)
self.response_callbacks = callbacks
def _process_response_callbacks(self, response):
callbacks = self.response_callbacks
while callbacks:
- callback = callbacks.pop(0)
+ callback = callbacks.popleft()
callback(self, response)
def add_finished_callback(self, callback):
@@ -132,15 +133,15 @@ class CallbackMethodsMixin(object):
"""
callbacks = self.finished_callbacks
- if not callbacks:
- callbacks = []
+ if callbacks is None:
+ callbacks = deque()
callbacks.append(callback)
self.finished_callbacks = callbacks
def _process_finished_callbacks(self):
callbacks = self.finished_callbacks
while callbacks:
- callback = callbacks.pop(0)
+ callback = callbacks.popleft()
callback(self)
@implementer(IRequest)
diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl
index e54a8609c..448803c8f 100644
--- a/pyramid/scaffolds/alchemy/development.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/development.ini_tmpl
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/alchemy/production.ini_tmpl b/pyramid/scaffolds/alchemy/production.ini_tmpl
index b316ec9ca..022bc0b7b 100644
--- a/pyramid/scaffolds/alchemy/production.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/production.ini_tmpl
@@ -59,4 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl
index 842cd61d9..c2a28e178 100644
--- a/pyramid/scaffolds/starter/development.ini_tmpl
+++ b/pyramid/scaffolds/starter/development.ini_tmpl
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/starter/production.ini_tmpl b/pyramid/scaffolds/starter/production.ini_tmpl
index 6a123abf5..b2681c71d 100644
--- a/pyramid/scaffolds/starter/production.ini_tmpl
+++ b/pyramid/scaffolds/starter/production.ini_tmpl
@@ -51,4 +51,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl
index f57d559bf..199ddfab4 100644
--- a/pyramid/scaffolds/zodb/development.ini_tmpl
+++ b/pyramid/scaffolds/zodb/development.ini_tmpl
@@ -62,4 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl
index c231e159d..522ff7651 100644
--- a/pyramid/scaffolds/zodb/production.ini_tmpl
+++ b/pyramid/scaffolds/zodb/production.ini_tmpl
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scripts/pcreate.py b/pyramid/scripts/pcreate.py
index 4c1f432fb..edf2c39f7 100644
--- a/pyramid/scripts/pcreate.py
+++ b/pyramid/scripts/pcreate.py
@@ -81,7 +81,8 @@ class PCreateCommand(object):
args = self.args
output_dir = os.path.abspath(os.path.normpath(args[0]))
project_name = os.path.basename(os.path.split(output_dir)[1])
- pkg_name = _bad_chars_re.sub('', project_name.lower())
+ pkg_name = _bad_chars_re.sub(
+ '', project_name.lower().replace('-', '_'))
safe_name = pkg_resources.safe_name(project_name)
egg_name = pkg_resources.to_filename(safe_name)
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index d6dba17f6..1e58e4d0f 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -736,6 +736,18 @@ pyramid.tests.test_config.dummy_include2""",
else: # pragma: no cover
raise AssertionError
+ def test_include_constant_root_package(self):
+ from pyramid import tests
+ from pyramid.tests import test_config
+ config = self._makeOne(root_package=tests)
+ results = {}
+ def include(config):
+ results['package'] = config.package
+ results['root_package'] = config.root_package
+ config.include(include)
+ self.assertEqual(results['root_package'], tests)
+ self.assertEqual(results['package'], test_config)
+
def test_action_branching_kw_is_None(self):
config = self._makeOne(autocommit=True)
self.assertEqual(config.action('discrim'), None)
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index a0d9ee0c3..39b8ba70d 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -1783,6 +1783,21 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = view(None, request)
self.assertEqual(result, 'OK')
+ def test_add_forbidden_view_no_view_argument(self):
+ from zope.interface import implementedBy
+ from pyramid.interfaces import IRequest
+ from pyramid.httpexceptions import HTTPForbidden
+ config = self._makeOne(autocommit=True)
+ config.setup_registry()
+ config.add_forbidden_view()
+ request = self._makeRequest(config)
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPForbidden),
+ request_iface=IRequest)
+ context = HTTPForbidden()
+ result = view(context, request)
+ self.assertEqual(result, context)
+
def test_add_forbidden_view_allows_other_predicates(self):
from pyramid.renderers import null_renderer
config = self._makeOne(autocommit=True)
@@ -1860,6 +1875,21 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = view(None, request)
self.assertEqual(result, (None, request))
+ def test_add_notfound_view_no_view_argument(self):
+ from zope.interface import implementedBy
+ from pyramid.interfaces import IRequest
+ from pyramid.httpexceptions import HTTPNotFound
+ config = self._makeOne(autocommit=True)
+ config.setup_registry()
+ config.add_notfound_view()
+ request = self._makeRequest(config)
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
+ request_iface=IRequest)
+ context = HTTPNotFound()
+ result = view(context, request)
+ self.assertEqual(result, context)
+
def test_add_notfound_view_allows_other_predicates(self):
from pyramid.renderers import null_renderer
config = self._makeOne(autocommit=True)
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index ed41b62ff..48af98f59 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -1,3 +1,4 @@
+from collections import deque
import unittest
from pyramid import testing
@@ -119,13 +120,13 @@ class TestRequest(unittest.TestCase):
def test_add_response_callback(self):
inst = self._makeOne()
- self.assertEqual(inst.response_callbacks, ())
+ self.assertEqual(inst.response_callbacks, None)
def callback(request, response):
""" """
inst.add_response_callback(callback)
- self.assertEqual(inst.response_callbacks, [callback])
+ self.assertEqual(list(inst.response_callbacks), [callback])
inst.add_response_callback(callback)
- self.assertEqual(inst.response_callbacks, [callback, callback])
+ self.assertEqual(list(inst.response_callbacks), [callback, callback])
def test__process_response_callbacks(self):
inst = self._makeOne()
@@ -135,24 +136,48 @@ class TestRequest(unittest.TestCase):
def callback2(request, response):
request.called2 = True
response.called2 = True
- inst.response_callbacks = [callback1, callback2]
+ inst.add_response_callback(callback1)
+ inst.add_response_callback(callback2)
response = DummyResponse()
inst._process_response_callbacks(response)
self.assertEqual(inst.called1, True)
self.assertEqual(inst.called2, True)
self.assertEqual(response.called1, True)
self.assertEqual(response.called2, True)
- self.assertEqual(inst.response_callbacks, [])
+ self.assertEqual(len(inst.response_callbacks), 0)
+
+ def test__process_response_callback_adding_response_callback(self):
+ """
+ When a response callback adds another callback, that new callback should still be called.
+
+ See https://github.com/Pylons/pyramid/pull/1373
+ """
+ inst = self._makeOne()
+ def callback1(request, response):
+ request.called1 = True
+ response.called1 = True
+ request.add_response_callback(callback2)
+ def callback2(request, response):
+ request.called2 = True
+ response.called2 = True
+ inst.add_response_callback(callback1)
+ response = DummyResponse()
+ inst._process_response_callbacks(response)
+ self.assertEqual(inst.called1, True)
+ self.assertEqual(inst.called2, True)
+ self.assertEqual(response.called1, True)
+ self.assertEqual(response.called2, True)
+ self.assertEqual(len(inst.response_callbacks), 0)
def test_add_finished_callback(self):
inst = self._makeOne()
- self.assertEqual(inst.finished_callbacks, ())
+ self.assertEqual(inst.finished_callbacks, None)
def callback(request):
""" """
inst.add_finished_callback(callback)
- self.assertEqual(inst.finished_callbacks, [callback])
+ self.assertEqual(list(inst.finished_callbacks), [callback])
inst.add_finished_callback(callback)
- self.assertEqual(inst.finished_callbacks, [callback, callback])
+ self.assertEqual(list(inst.finished_callbacks), [callback, callback])
def test__process_finished_callbacks(self):
inst = self._makeOne()
@@ -160,11 +185,12 @@ class TestRequest(unittest.TestCase):
request.called1 = True
def callback2(request):
request.called2 = True
- inst.finished_callbacks = [callback1, callback2]
+ inst.add_finished_callback(callback1)
+ inst.add_finished_callback(callback2)
inst._process_finished_callbacks()
self.assertEqual(inst.called1, True)
self.assertEqual(inst.called2, True)
- self.assertEqual(inst.finished_callbacks, [])
+ self.assertEqual(len(inst.finished_callbacks), 0)
def test_resource_url(self):
self._registerResourceURL()
diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py
index a16eb8d33..84ec57757 100644
--- a/pyramid/tests/test_response.py
+++ b/pyramid/tests/test_response.py
@@ -1,4 +1,5 @@
import io
+import mimetypes
import os
import unittest
from pyramid import testing
@@ -51,15 +52,11 @@ class TestFileResponse(unittest.TestCase):
r.app_iter.close()
def test_without_content_type(self):
- for suffix, content_type in (
- ('txt', 'text/plain; charset=UTF-8'),
- ('xml', 'application/xml; charset=UTF-8'),
- ('pdf', 'application/pdf')
- ):
+ for suffix in ('txt', 'xml', 'pdf'):
path = self._getPath(suffix)
r = self._makeOne(path)
- self.assertEqual(r.content_type, content_type.split(';')[0])
- self.assertEqual(r.headers['content-type'], content_type)
+ self.assertEqual(r.headers['content-type'].split(';')[0],
+ mimetypes.guess_type(path, strict=False)[0])
r.app_iter.close()
def test_python_277_bug_15207(self):
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index 838e52db0..c6c6eea1c 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -522,7 +522,7 @@ class TestRouter(unittest.TestCase):
def view(context, request):
def callback(request, response):
response.called_back = True
- request.response_callbacks = [callback]
+ request.add_response_callback(callback)
return response
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
@@ -545,7 +545,7 @@ class TestRouter(unittest.TestCase):
def view(context, request):
def callback(request):
request.environ['called_back'] = True
- request.finished_callbacks = [callback]
+ request.add_finished_callback(callback)
return response
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
@@ -567,7 +567,7 @@ class TestRouter(unittest.TestCase):
def view(context, request):
def callback(request):
request.environ['called_back'] = True
- request.finished_callbacks = [callback]
+ request.add_finished_callback(callback)
raise NotImplementedError
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
diff --git a/pyramid/tests/test_scripts/test_pcreate.py b/pyramid/tests/test_scripts/test_pcreate.py
index 2488e9595..020721ca7 100644
--- a/pyramid/tests/test_scripts/test_pcreate.py
+++ b/pyramid/tests/test_scripts/test_pcreate.py
@@ -73,6 +73,23 @@ class TestPCreateCommand(unittest.TestCase):
{'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
+ def test_scaffold_with_hyphen_in_project_name(self):
+ import os
+ cmd = self._makeOne('-s', 'dummy', 'Distro-')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.1")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.output_dir,
+ os.path.normpath(os.path.join(os.getcwd(), 'Distro-'))
+ )
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Distro-', 'egg': 'Distro_', 'package': 'distro_',
+ 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
+
def test_known_scaffold_absolute_path(self):
import os
path = os.path.abspath('Distro')
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index 35c234e99..b013ffa66 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -521,7 +521,7 @@ class Test_manage_accessed(unittest.TestCase):
result = wrapper(session, 'a')
self.assertEqual(result, 1)
callbacks = request.response_callbacks
- self.assertEqual(len(callbacks), 0)
+ if callbacks is not None: self.assertEqual(len(callbacks), 0)
class Test_manage_changed(unittest.TestCase):
def _makeOne(self, wrapped):
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 2d0548b33..dfcad2a0c 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -217,7 +217,7 @@ class TestDummyRequest(unittest.TestCase):
def test_add_response_callback(self):
request = self._makeOne()
request.add_response_callback(1)
- self.assertEqual(request.response_callbacks, [1])
+ self.assertEqual(list(request.response_callbacks), [1])
def test_registry_is_config_registry_when_setup_is_called_after_ctor(self):
# see https://github.com/Pylons/pyramid/issues/165