diff options
| -rw-r--r-- | CHANGES.txt | 15 | ||||
| -rw-r--r-- | TODO.txt | 4 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 24 | ||||
| -rw-r--r-- | docs/narr/viewconfig.rst | 8 | ||||
| -rw-r--r-- | docs/quick_tour.rst | 14 | ||||
| -rw-r--r-- | pyramid/config/views.py | 8 | ||||
| -rw-r--r-- | pyramid/response.py | 25 | ||||
| -rw-r--r-- | pyramid/session.py | 6 | ||||
| -rw-r--r-- | pyramid/static.py | 17 | ||||
| -rw-r--r-- | pyramid/tests/test_static.py | 12 | ||||
| -rw-r--r-- | pyramid/url.py | 6 | ||||
| -rw-r--r-- | setup.cfg | 48 | ||||
| -rw-r--r-- | tox.ini | 8 |
13 files changed, 133 insertions, 62 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 434557f89..1939ad125 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,12 +16,25 @@ Backward Incompatibilities See https://github.com/Pylons/pyramid/pull/2615 -- ``pcreate`` is now interactive by default. You will be prompted if it +- ``pcreate`` is now interactive by default. You will be prompted if a file already exists with different content. Previously if there were similar files it would silently skip them unless you specified ``--interactive`` or ``--overwrite``. See https://github.com/Pylons/pyramid/pull/2775 +- Removed undocumented argument ``cachebust_match`` from + ``pyramid.static.static_view``. This argument was shipped accidentally + in Pyramid 1.6. See https://github.com/Pylons/pyramid/pull/2681 + +- Change static view to avoid setting the ``Content-Encoding`` response header + to an encoding guessed using Python's ``mimetypes`` module. This was causing + clients to decode the content of gzipped files when downloading them. The + client would end up with a ``foo.txt.gz`` file on disk that was already + decoded, thus should really be ``foo.txt``. Also, the ``Content-Encoding`` + should only have been used if the client itself broadcast support for the + encoding via ``Accept-Encoding`` request headers. + See https://github.com/Pylons/pyramid/pull/2810 + Features -------- @@ -120,9 +120,7 @@ Future - 1.6: Remove IContextURL and TraversalContextURL. -- 1.8: Remove set_request_property. -- 1.8: Drop Python 3.3 support. - +- 1.9: Remove set_request_property. - 1.9: Remove extra code enabling ``pyramid.security.remember(principal=...)`` and force use of ``userid``. diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index b22b31bf9..d21edc7b4 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -116,14 +116,6 @@ callable: .. note:: - Both :meth:`pyramid.config.Configurator.add_notfound_view` and - :class:`pyramid.view.notfound_view_config` are new as of Pyramid 1.3. - Older Pyramid documentation instructed users to use ``add_view`` instead, - with a ``context`` of ``HTTPNotFound``. This still works; the convenience - method and decorator are just wrappers around this functionality. - -.. warning:: - When a Not Found View callable accepts an argument list as described in :ref:`request_and_context_view_definitions`, the ``context`` passed as the first argument to the view callable will be the @@ -131,6 +123,13 @@ callable: available, the resource context will still be available as ``request.context``. +.. warning:: + + The :term:`Not Found View` callables are only invoked when a + :exc:`~pyramid.httpexceptions.HTTPNotFound` exception is raised. If the + exception is returned from a view then it will be treated as a regular + response object and it will not trigger the custom view. + .. index:: single: forbidden view @@ -210,6 +209,13 @@ Here's some sample code that implements a minimal forbidden view: whether the ``pyramid.debug_authorization`` environment setting is true or false. +.. warning:: + + The :term:`forbidden view` callables are only invoked when a + :exc:`~pyramid.httpexceptions.HTTPForbidden` exception is raised. If the + exception is returned from a view then it will be treated as a regular + response object and it will not trigger the custom view. + .. index:: single: request factory @@ -744,7 +750,9 @@ The API that must be implemented by a class that provides """ Accept the resource and request and set self.physical_path and self.virtual_path """ self.virtual_path = some_function_of(resource, request) + self.virtual_path_tuple = some_function_of(resource, request) self.physical_path = some_other_function_of(resource, request) + self.physical_path_tuple = some_function_of(resource, request) The default context URL generator is available for perusal as the class :class:`pyramid.traversal.ResourceURL` in the `traversal module diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 7cb8e0306..3b683ff79 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -252,7 +252,7 @@ Non-Predicate Arguments def myview(request): ... - Is similar to doing:: + Is similar to decorating the view callable directly:: @view_config(...) @decorator2 @@ -260,8 +260,10 @@ Non-Predicate Arguments def myview(request): ... - All view callables in the decorator chain must return a response object - implementing :class:`pyramid.interfaces.IResponse` or raise an exception: + An important distinction is that each decorator will receive a response + object implementing :class:`pyramid.interfaces.IResponse` instead of the + raw value returned from the view callable. All decorators in the chain must + return a response object or raise an exception: .. code-block:: python diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 39b4cafb3..45c706b0d 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -504,7 +504,7 @@ Pyramid's ``pcreate`` command can list the available scaffolds: .. code-block:: bash - $ pcreate --list + $ $VENV/bin/pcreate --list Available scaffolds: alchemy: Pyramid project using SQLAlchemy, SQLite, URL dispatch, and Jinja2 pyramid_jinja2_starter: Pyramid Jinja2 starter project @@ -517,7 +517,7 @@ that scaffold to make our project: .. code-block:: bash - $ pcreate --scaffold pyramid_jinja2_starter hello_world + $ $VENV/bin/pcreate --scaffold pyramid_jinja2_starter hello_world We next use the normal Python command to set up our package for development: @@ -678,10 +678,10 @@ egregious, as Pyramid has had a deep commitment to full test coverage since before its release. Our ``pyramid_jinja2_starter`` scaffold generated a ``tests.py`` module with -one unit test in it. To run it, let's install the handy ``pytest`` test runner -by editing ``setup.py``. While we're at it, we'll throw in the ``pytest-cov`` -tool which yells at us for code that isn't tested. Insert and edit the -following lines as shown: +one unit test in it. It also configured ``setup.py`` with test requirements: +``py.test`` as the test runner, ``WebTest`` for running view tests, and the +``pytest-cov`` tool which yells at us for code that isn't tested. The +highlighted lines show this: .. code-block:: python :linenos: @@ -711,7 +711,7 @@ following lines as shown: 'testing': tests_require, }, -We changed ``setup.py`` which means we need to rerun ``$VENV/bin/pip install -e +To install the test requirements, run ``$VENV/bin/pip install -e ".[testing]"``. We can now run all our tests: .. code-block:: bash diff --git a/pyramid/config/views.py b/pyramid/config/views.py index acdc00704..6082d8b48 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -444,9 +444,11 @@ class ViewsConfiguratorMixin(object): think about preserving function attributes such as ``__name__`` and ``__module__`` within decorator logic). - All view callables in the decorator chain must return a response - object implementing :class:`pyramid.interfaces.IResponse` or raise - an exception: + An important distinction is that each decorator will receive a + response object implementing :class:`pyramid.interfaces.IResponse` + instead of the raw value returned from the view callable. All + decorators in the chain must return a response object or raise an + exception: .. code-block:: python diff --git a/pyramid/response.py b/pyramid/response.py index 892e5dfff..1d9daae7d 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -54,16 +54,7 @@ class FileResponse(Response): def __init__(self, path, request=None, cache_max_age=None, content_type=None, content_encoding=None): if content_type is None: - content_type, content_encoding = mimetypes.guess_type( - path, - strict=False - ) - if content_type is None: - content_type = 'application/octet-stream' - # str-ifying content_type is a workaround for a bug in Python 2.7.7 - # on Windows where mimetypes.guess_type returns unicode for the - # content_type. - content_type = str(content_type) + content_type, content_encoding = _guess_type(path) super(FileResponse, self).__init__( conditional_response=True, content_type=content_type, @@ -180,3 +171,17 @@ def _get_response_factory(registry): ) return response_factory + + +def _guess_type(path): + content_type, content_encoding = mimetypes.guess_type( + path, + strict=False + ) + if content_type is None: + content_type = 'application/octet-stream' + # str-ifying content_type is a workaround for a bug in Python 2.7.7 + # on Windows where mimetypes.guess_type returns unicode for the + # content_type. + content_type = str(content_type) + return content_type, content_encoding diff --git a/pyramid/session.py b/pyramid/session.py index a3cbe5172..47b80f617 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -215,9 +215,9 @@ def check_csrf_token(request, supplied by ``request.session.get_csrf_token()``, and ``raises`` is ``True``, this function will raise an :exc:`pyramid.exceptions.BadCSRFToken` exception. - If the check does succeed and ``raises`` is ``False``, this - function will return ``False``. If the CSRF check is successful, this - function will return ``True`` unconditionally. + If the values differ and ``raises`` is ``False``, this function will + return ``False``. If the CSRF check is successful, this function will + return ``True`` unconditionally. Note that using this function requires that a :term:`session factory` is configured. diff --git a/pyramid/static.py b/pyramid/static.py index 0965be95c..a8088129e 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -32,7 +32,12 @@ from pyramid.httpexceptions import ( ) from pyramid.path import caller_package -from pyramid.response import FileResponse + +from pyramid.response import ( + _guess_type, + FileResponse, +) + from pyramid.traversal import traversal_path_info slash = text_('/') @@ -83,7 +88,7 @@ class static_view(object): """ def __init__(self, root_dir, cache_max_age=3600, package_name=None, - use_subpath=False, index='index.html', cachebust_match=None): + use_subpath=False, index='index.html'): # package_name is for bw compat; it is preferred to pass in a # package-relative path as root_dir # (e.g. ``anotherpackage:foo/static``). @@ -96,15 +101,12 @@ class static_view(object): self.docroot = docroot self.norm_docroot = normcase(normpath(docroot)) self.index = index - self.cachebust_match = cachebust_match def __call__(self, context, request): if self.use_subpath: path_tuple = request.subpath else: path_tuple = traversal_path_info(request.environ['PATH_INFO']) - if self.cachebust_match: - path_tuple = self.cachebust_match(path_tuple) path = _secure_path(path_tuple) if path is None: @@ -134,7 +136,10 @@ class static_view(object): if not exists(filepath): raise HTTPNotFound(request.url) - return FileResponse(filepath, request, self.cache_max_age) + content_type, content_encoding = _guess_type(filepath) + return FileResponse( + filepath, request, self.cache_max_age, + content_type, content_encoding=None) def add_slash_redirect(self, request): url = request.path_url + '/' diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index 2ca86bc44..f76cc5067 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -113,14 +113,6 @@ class Test_static_view_use_subpath_False(unittest.TestCase): response = inst(context, request) self.assertTrue(b'<html>static</html>' in response.body) - def test_cachebust_match(self): - inst = self._makeOne('pyramid.tests:fixtures/static') - inst.cachebust_match = lambda subpath: subpath[1:] - request = self._makeRequest({'PATH_INFO':'/foo/index.html'}) - context = DummyContext() - response = inst(context, request) - self.assertTrue(b'<html>static</html>' in response.body) - def test_resource_is_file_with_wsgi_file_wrapper(self): from pyramid.response import _BLOCK_SIZE inst = self._makeOne('pyramid.tests:fixtures/static') @@ -186,14 +178,14 @@ class Test_static_view_use_subpath_False(unittest.TestCase): from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) - def test_resource_with_content_encoding(self): + def test_gz_resource_no_content_encoding(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/arcs.svg.tgz'}) context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/x-tar') - self.assertEqual(response.content_encoding, 'gzip') + self.assertEqual(response.content_encoding, None) response.app_iter.close() def test_resource_no_content_encoding(self): diff --git a/pyramid/url.py b/pyramid/url.py index 0214d35ad..d6587e783 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -365,7 +365,7 @@ class URLMethodsMixin(object): of ``query`` may be a sequence of two-tuples *or* a data structure with an ``.items()`` method that returns a sequence of two-tuples (presumably a dictionary). This data structure will be turned into a - query string per the documentation of :func:``pyramid.url.urlencode`` + query string per the documentation of :func:`pyramid.url.urlencode` function. This will produce a query string in the ``x-www-form-urlencoded`` encoding. A non-``x-www-form-urlencoded`` query string may be used by passing a *string* value as ``query`` in @@ -455,7 +455,7 @@ class URLMethodsMixin(object): ``resource_url(someresource, 'element1', 'element2', query={'a':1}, route_name='blogentry')`` is roughly equivalent to doing:: - remainder_path = request.resource_path(someobject) + traversal_path = request.resource_path(someobject) url = request.route_url( 'blogentry', 'element1', @@ -487,7 +487,7 @@ class URLMethodsMixin(object): 'element2', route_name='blogentry', route_kw={'id':'4'}, _query={'a':'1'})`` is roughly equivalent to:: - remainder_path = request.resource_path_tuple(someobject) + traversal_path = request.resource_path_tuple(someobject) kw = {'id':'4', '_query':{'a':'1'}, 'traverse':traversal_path} url = request.route_url( 'blogentry', @@ -14,6 +14,52 @@ docs = develop easy_install pyramid[docs] universal = 1 [flake8] -ignore = E301,E302,E731,E261,E123,E121,E128,E129,E125,W291,E501,W293,E303,W391,E266,E231,E201,E202,E127,E262,E265 +ignore = + # E121: continuation line under-indented for hanging indent + E121, + # E123: closing bracket does not match indentation of opening bracket's line + E123, + # E125: continuation line with same indent as next logical line + E125, + # E127: continuation line over-indented for visual indent + E127, + # E128: continuation line under-indented for visual indent + E128, + # E129: visually indented line with same indent as next logical line + E129, + # E201: whitespace after '(' + E201, + # E202: whitespace before ')' + E202, + # E231: missing whitespace after ',', ';', or ':' + E231, + # E261: at least two spaces before inline comment + E261, + # E262: inline comment should start with '# ' + E262, + # E265: block comment should start with '# ' + E265, + # E266: too many leading '#' for block comment + E266, + # E301: expected 1 blank line, found 0 + E301, + # E302: expected 2 blank lines, found 0 + E302, + # E303: too many blank lines (3) + E303, + # E305: expected 2 blank lines after class or function definition, found 1 + E305, + # E306: expected 1 blank line before a nested definition, found 0 + E306, + # E501: line too long (82 > 79 characters) + E501, + # E731: do not assign a lambda expression, use a def + E731, + # W291: trailing whitespace + W291, + # W293: blank line contains whitespace + W293, + # W391: blank line at end of file + W391 exclude = pyramid/tests/,pyramid/compat.py,pyramid/resource.py show-source = True @@ -3,7 +3,7 @@ envlist = py27,py34,py35,py36,py37,pypy, docs,pep8, {py2,py3}-cover,coverage, -skip-missing-interpreters = True +skip_missing_interpreters = True [testenv] # Most of these are defaults but if you specify any you can't fall back @@ -19,7 +19,7 @@ basepython = py3: python3.5 commands = - pip install pyramid[testing] + pip install -q pyramid[testing] nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:} [testenv:py27-scaffolds] @@ -72,7 +72,7 @@ commands = # combination of versions of coverage and nosexcover that i can find. [testenv:py2-cover] commands = - pip install pyramid[testing] + pip install -q pyramid[testing] coverage run --source=pyramid {envbindir}/nosetests coverage xml -o coverage-py2.xml setenv = @@ -80,7 +80,7 @@ setenv = [testenv:py3-cover] commands = - pip install pyramid[testing] + pip install -q pyramid[testing] coverage run --source=pyramid {envbindir}/nosetests coverage xml -o coverage-py3.xml setenv = |
