summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt18
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--docs/api/exceptions.rst12
-rw-r--r--docs/api/httpexceptions.rst94
-rw-r--r--docs/narr/i18n.rst2
-rw-r--r--pyramid/config/views.py21
-rw-r--r--pyramid/scripts/proutes.py66
-rw-r--r--pyramid/tests/test_config/test_views.py37
-rw-r--r--pyramid/tests/test_response.py11
-rw-r--r--pyramid/tests/test_scripts/test_proutes.py10
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)
-