diff options
| author | Michael Merickel <michael@merickel.org> | 2014-11-17 01:27:41 -0600 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2014-11-17 01:27:41 -0600 |
| commit | 405be483f50c37edef694e81aadcaa326311e51d (patch) | |
| tree | 3f34467bd232d0dd1da6fe4e2cf6c225fbbbe08d | |
| parent | 3ffd40c295a1e37ec94b6123fff8fb4dd5f5abf5 (diff) | |
| parent | af0290407b50a18664c9ae28a4c01d4cfb27920b (diff) | |
| download | pyramid-405be483f50c37edef694e81aadcaa326311e51d.tar.gz pyramid-405be483f50c37edef694e81aadcaa326311e51d.tar.bz2 pyramid-405be483f50c37edef694e81aadcaa326311e51d.zip | |
Merge branch 'master' into feature.security-docs-enhancements
| -rw-r--r-- | CHANGES.txt | 18 | ||||
| -rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
| -rw-r--r-- | docs/api/exceptions.rst | 12 | ||||
| -rw-r--r-- | docs/api/httpexceptions.rst | 94 | ||||
| -rw-r--r-- | docs/narr/i18n.rst | 2 | ||||
| -rw-r--r-- | pyramid/config/views.py | 21 | ||||
| -rw-r--r-- | pyramid/scripts/proutes.py | 66 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_views.py | 37 | ||||
| -rw-r--r-- | pyramid/tests/test_response.py | 11 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/test_proutes.py | 10 |
10 files changed, 195 insertions, 78 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 8083113ef..4dd92b46f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,6 +11,9 @@ Features - 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 may be natural for a developer to define + imports or assets relative to the top-level package. See https://github.com/Pylons/pyramid/pull/1337 - Added line numbers to the log formatters in the scaffolds to assist with @@ -21,6 +24,15 @@ Features ``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 + +- Greatly improve the readability of the ``pcreate`` shell script output. + See https://github.com/Pylons/pyramid/pull/1453 + Bug Fixes --------- @@ -55,6 +67,12 @@ Bug Fixes 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 + +- Fix route generation for static view asset specifications having no path. + See https://github.com/Pylons/pyramid/pull/1377 + Deprecations ------------ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index c77d3e92c..66f029cb7 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -232,3 +232,5 @@ Contributors - Amit Mane, 2014/01/23 - Fenton Travers, 2014/05/06 + +- Randall Leeds, 2014/11/11 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/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/pyramid/config/views.py b/pyramid/config/views.py index 5ca696069..c01b72e12 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 ( @@ -1185,10 +1186,6 @@ class ViewsConfiguratorMixin(object): predlist = self.get_predlist('view') def register(permission=permission, renderer=renderer): - # the discrim_func above is guaranteed to have been called already - order = view_intr['order'] - preds = view_intr['predicates'] - phash = view_intr['phash'] request_iface = IRequest if route_name is not None: request_iface = self.registry.queryUtility(IRouteRequest, @@ -1591,9 +1588,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 +1609,9 @@ class ViewsConfiguratorMixin(object): % arg ) + if view is None: + view = default_exceptionresponse_view + settings = dict( view=view, context=HTTPForbidden, @@ -1671,6 +1674,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 +1703,9 @@ class ViewsConfiguratorMixin(object): % arg ) + if view is None: + view = default_exceptionresponse_view + settings = dict( view=view, context=HTTPNotFound, @@ -1942,7 +1951,7 @@ class StaticURLInfo(object): sep = os.sep else: sep = '/' - if not spec.endswith(sep): + if not spec.endswith(sep) and not spec.endswith(':'): spec = spec + sep # we also make sure the name ends with a slash, purely as a diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 5784026bb..792030a74 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -4,11 +4,17 @@ import textwrap from pyramid.paster import bootstrap from pyramid.scripts.common import parse_vars +from pyramid.config.views import MultiView + + +PAD = 3 + def main(argv=sys.argv, quiet=False): command = PRoutesCommand(argv, quiet) return command.run() + class PRoutesCommand(object): description = """\ Print all URL dispatch routes used by a Pyramid application in the @@ -43,7 +49,7 @@ class PRoutesCommand(object): def out(self, msg): # pragma: no cover if not self.quiet: print(msg) - + def run(self, quiet=False): if not self.args: self.out('requires a config file argument') @@ -59,13 +65,22 @@ class PRoutesCommand(object): registry = env['registry'] mapper = self._get_mapper(registry) if mapper is not None: + mapped_routes = [('Name', 'Pattern', 'View')] + + max_name = len('Name') + max_pattern = len('Pattern') + max_view = len('View') + routes = mapper.get_routes() - fmt = '%-15s %-30s %-25s' if not routes: return 0 - self.out(fmt % ('Name', 'Pattern', 'View')) - self.out( - fmt % ('-'*len('Name'), '-'*len('Pattern'), '-'*len('View'))) + + mapped_routes.append(( + '-' * max_name, + '-' * max_pattern, + '-' * max_view, + )) + for route in routes: pattern = route.pattern if not pattern.startswith('/'): @@ -73,13 +88,50 @@ class PRoutesCommand(object): request_iface = registry.queryUtility(IRouteRequest, name=route.name) view_callable = None + if (request_iface is None) or (route.factory is not None): - self.out(fmt % (route.name, pattern, '<unknown>')) + view_callable = '<unknown>' else: view_callable = registry.adapters.lookup( (IViewClassifier, request_iface, Interface), IView, name='', default=None) - self.out(fmt % (route.name, pattern, view_callable)) + + if view_callable is not None: + if isinstance(view_callable, MultiView): + view_callables = [ + x[1] for x in view_callable.views + ] + else: + view_callables = [view_callable] + + for view_func in view_callables: + view_callable = '%s.%s' % ( + view_func.__module__, + view_func.__name__, + ) + else: + view_callable = str(None) + + if len(route.name) > max_name: + max_name = len(route.name) + + if len(pattern) > max_pattern: + max_pattern = len(pattern) + + if len(view_callable) > max_view: + max_view = len(view_callable) + + mapped_routes.append((route.name, pattern, view_callable)) + + fmt = '%-{0}s %-{1}s %-{2}s'.format( + max_name + PAD, + max_pattern + PAD, + max_view + PAD, + ) + + for route_data in mapped_routes: + self.out(fmt % route_data) + return 0 if __name__ == '__main__': # pragma: no cover diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index a0d9ee0c3..b0d03fb72 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) @@ -3868,6 +3898,13 @@ class TestStaticURLInfo(unittest.TestCase): ('http://example.com/', 'anotherpackage:path/', None, None)] self._assertRegistrations(config, expected) + def test_add_package_root(self): + inst = self._makeOne() + config = self._makeConfig() + inst.add(config, 'http://example.com', 'package:') + expected = [('http://example.com/', 'package:', None, None)] + self._assertRegistrations(config, expected) + def test_add_url_withendslash(self): inst = self._makeOne() config = self._makeConfig() 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_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 25a3cd2e3..45ab57d3a 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -123,8 +123,11 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split()[:3] - self.assertEqual(compare_to, ['a', '/a', '<function']) - + self.assertEqual( + compare_to, + ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view'] + ) + def test_single_route_one_view_registered_with_factory(self): from zope.interface import Interface from pyramid.registry import Registry @@ -161,7 +164,7 @@ class TestPRoutesCommand(unittest.TestCase): registry = Registry() result = command._get_mapper(registry) self.assertEqual(result.__class__, RoutesMapper) - + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main @@ -170,4 +173,3 @@ class Test_main(unittest.TestCase): def test_it(self): result = self._callFUT(['proutes']) self.assertEqual(result, 2) - |
