diff options
| author | Chris McDonough <chrism@plope.com> | 2014-11-12 12:23:48 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2014-11-12 12:23:48 -0500 |
| commit | b1fac53cd0c3b930aec90e27f4d19c5f785f52e2 (patch) | |
| tree | e71f8e716ab2d12cb692c5789120cd0984aaa340 | |
| parent | 0337f987466dd47696f4293604d071bf020f165e (diff) | |
| parent | 4d19a6f383c802f9620b7d2fc239e5c6ad6c52f9 (diff) | |
| download | pyramid-b1fac53cd0c3b930aec90e27f4d19c5f785f52e2.tar.gz pyramid-b1fac53cd0c3b930aec90e27f4d19c5f785f52e2.tar.bz2 pyramid-b1fac53cd0c3b930aec90e27f4d19c5f785f52e2.zip | |
Merge branch 'master' of github.com:Pylons/pyramid
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 |
