diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 2cc870619b..bad6660664 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -370,19 +370,22 @@ do so, do things "by hand". First define the view callable. :linenos: import os - from pyramid.response import Response + from pyramid.response import FileResponse def favicon_view(request): here = os.path.dirname(__file__) - icon = open(os.path.join(here, 'static', 'favicon.ico'), 'rb') - return Response(content_type='image/x-icon', app_iter=icon) + icon = os.path.join(here, 'static', 'favicon.ico') + return FileResponse(icon, request=request) The above bit of code within ``favicon_view`` computes "here", which is a path relative to the Python file in which the function is defined. It then uses the Python ``open`` function to obtain a file handle to a file within -"here" named ``static``, and returns a response using the open the file -handle as the response's ``app_iter``. It makes sure to set the right -content_type too. +"here" named ``static``, and returns a :class:`pyramid.response.Fileresponse` +using the file path as the response's ``path`` argument and the request as +the response's ``request`` argument. :class:`pyramid.response.FileResponse` +will serve the file as quickly as possible when it's used this way. It makes +sure to set the right content length and content_type too based on the file +extension of the file you pass. You might register such a view via configuration as a view callable that should be called as the result of a traversal: diff --git a/pyramid/response.py b/pyramid/response.py index d2b6fc8a77..2642d7769a 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -30,12 +30,21 @@ class FileResponse(Response): ``cache_max_age`` if passed, is the number of seconds that should be used to HTTP cache this response. + + ``content_type``, if passed, is the content_type of the response. + + ``content_encoding``, if passed is the content_encoding of the response. + It's generally safe to leave this set to ``None`` if you're serving a + binary file. This argument will be ignored if you don't also pass + ``content-type``. """ - def __init__(self, path, request=None, cache_max_age=None): + def __init__(self, path, request=None, cache_max_age=None, + content_type=None, content_encoding=None): super(FileResponse, self).__init__(conditional_response=True) self.last_modified = getmtime(path) - content_type, content_encoding = mimetypes.guess_type(path, - strict=False) + 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' self.content_type = content_type diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py index af6eb3532b..c5df1fc059 100644 --- a/pyramid/tests/test_response.py +++ b/pyramid/tests/test_response.py @@ -1,4 +1,5 @@ import io +import os import unittest from pyramid import testing @@ -17,6 +18,25 @@ def test_provides_IResponse(self): inst = self._getTargetClass()() self.assertTrue(IResponse.providedBy(inst)) +class TestFileResponse(unittest.TestCase): + def _makeOne(self, file, **kw): + from pyramid.response import FileResponse + return FileResponse(file, **kw) + + def _getPath(self): + here = os.path.dirname(__file__) + return os.path.join(here, 'fixtures', 'minimal.txt') + + def test_with_content_type(self): + path = self._getPath() + r = self._makeOne(path, content_type='image/jpeg') + self.assertEqual(r.content_type, 'image/jpeg') + + def test_without_content_type(self): + path = self._getPath() + r = self._makeOne(path) + self.assertEqual(r.content_type, 'text/plain') + class TestFileIter(unittest.TestCase): def _makeOne(self, file, block_size): from pyramid.response import FileIter