Michael Merickel
2018-10-26 4149922e64aecf2a213f8efb120cd2d61fed3eb7
commit | author | age
6b3cca 1 import mimetypes
0c29cf 2 from os.path import getmtime, getsize
6b3cca 3
078412 4 import venusian
MH 5
966b5c 6 from webob import Response as _Response
3b7334 7 from zope.interface import implementer
303abc 8 from pyramid.interfaces import IResponse, IResponseFactory
JA 9
966b5c 10
f2f67e 11 def init_mimetypes(mimetypes):
CM 12     # this is a function so it can be unittested
13     if hasattr(mimetypes, 'init'):
14         mimetypes.init()
15         return True
16     return False
17
0c29cf 18
f2f67e 19 # See http://bugs.python.org/issue5853 which is a recursion bug
CM 20 # that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix
21 # has been applied on the Python 2 trunk).
22 init_mimetypes(mimetypes)
23
0c29cf 24 _BLOCK_SIZE = 4096 * 64  # 256K
MM 25
6b3cca 26
3b7334 27 @implementer(IResponse)
99edc5 28 class Response(_Response):
3b7334 29     pass
0c29cf 30
078412 31
6b3cca 32 class FileResponse(Response):
CM 33     """
34     A Response object that can be used to serve a static file from disk
35     simply.
36
37     ``path`` is a file path on disk.
38
092881 39     ``request`` must be a Pyramid :term:`request` object.  Note
6b3cca 40     that a request *must* be passed if the response is meant to attempt to
CM 41     use the ``wsgi.file_wrapper`` feature of the web server that you're using
42     to serve your Pyramid application.
43
092881 44     ``cache_max_age`` is the number of seconds that should be used
6b3cca 45     to HTTP cache this response.
c2e82a 46
092881 47     ``content_type`` is the content_type of the response.
c2e82a 48
092881 49     ``content_encoding`` is the content_encoding of the response.
c2e82a 50     It's generally safe to leave this set to ``None`` if you're serving a
092881 51     binary file.  This argument will be ignored if you also leave
TL 52     ``content-type`` as ``None``.
6b3cca 53     """
0c29cf 54
MM 55     def __init__(
56         self,
57         path,
58         request=None,
59         cache_max_age=None,
60         content_type=None,
61         content_encoding=None,
62     ):
4823d8 63         if content_type is None:
ed06c6 64             content_type, content_encoding = _guess_type(path)
4823d8 65         super(FileResponse, self).__init__(
DV 66             conditional_response=True,
67             content_type=content_type,
0c29cf 68             content_encoding=content_encoding,
4823d8 69         )
6b3cca 70         self.last_modified = getmtime(path)
CM 71         content_length = getsize(path)
72         f = open(path, 'rb')
73         app_iter = None
74         if request is not None:
75             environ = request.environ
76             if 'wsgi.file_wrapper' in environ:
77                 app_iter = environ['wsgi.file_wrapper'](f, _BLOCK_SIZE)
78         if app_iter is None:
79             app_iter = FileIter(f, _BLOCK_SIZE)
80         self.app_iter = app_iter
81         # assignment of content_length must come after assignment of app_iter
82         self.content_length = content_length
83         if cache_max_age is not None:
84             self.cache_expires = cache_max_age
85
0c29cf 86
6b3cca 87 class FileIter(object):
CM 88     """ A fixed-block-size iterator for use as a WSGI app_iter.
89
90     ``file`` is a Python file pointer (or at least an object with a ``read``
91     method that takes a size hint).
92
93     ``block_size`` is an optional block size for iteration.
94     """
0c29cf 95
6b3cca 96     def __init__(self, file, block_size=_BLOCK_SIZE):
CM 97         self.file = file
98         self.block_size = block_size
99
100     def __iter__(self):
101         return self
102
103     def next(self):
104         val = self.file.read(self.block_size)
105         if not val:
106             raise StopIteration
107         return val
108
0c29cf 109     __next__ = next  # py3
6b3cca 110
CM 111     def close(self):
112         self.file.close()
113
114
078412 115 class response_adapter(object):
1f901a 116     """ Decorator activated via a :term:`scan` which treats the function
CM 117     being decorated as a :term:`response adapter` for the set of types or
078412 118     interfaces passed as ``*types_or_ifaces`` to the decorator constructor.
MH 119
1f901a 120     For example, if you scan the following response adapter:
078412 121
MH 122     .. code-block:: python
123
124         from pyramid.response import Response
125         from pyramid.response import response_adapter
126
127         @response_adapter(int)
128         def myadapter(i):
129             return Response(status=i)
130
1f901a 131     You can then return an integer from your view callables, and it will be
CM 132     converted into a response with the integer as the status code.
133
078412 134     More than one type or interface can be passed as a constructor argument.
MH 135     The decorated response adapter will be called for each type or interface.
136
137     .. code-block:: python
138
139         import json
140
141         from pyramid.response import Response
142         from pyramid.response import response_adapter
143
144         @response_adapter(dict, list)
145         def myadapter(ob):
146             return Response(json.dumps(ob))
303abc 147
078412 148     This method will have no effect until a :term:`scan` is performed
MH 149     agains the package or module which contains it, ala:
150
151     .. code-block:: python
152
153         from pyramid.config import Configurator
154         config = Configurator()
155         config.scan('somepackage_containing_adapters')
156
498158 157     Two additional keyword arguments which will be passed to the
MM 158     :term:`venusian` ``attach`` function are ``_depth`` and ``_category``.
159
160     ``_depth`` is provided for people who wish to reuse this class from another
161     decorator. The default value is ``0`` and should be specified relative to
162     the ``response_adapter`` invocation. It will be passed in to the
163     :term:`venusian` ``attach`` function as the depth of the callstack when
164     Venusian checks if the decorator is being used in a class or module
165     context. It's not often used, but it can be useful in this circumstance.
166
167     ``_category`` sets the decorator category name. It can be useful in
168     combination with the ``category`` argument of ``scan`` to control which
169     views should be processed.
170
171     See the :py:func:`venusian.attach` function in Venusian for more
172     information about the ``_depth`` and ``_category`` arguments.
173
174     .. versionchanged:: 1.9.1
175        Added the ``_depth`` and ``_category`` arguments.
176
078412 177     """
0c29cf 178
MM 179     venusian = venusian  # for unit testing
078412 180
498158 181     def __init__(self, *types_or_ifaces, **kwargs):
078412 182         self.types_or_ifaces = types_or_ifaces
498158 183         self.depth = kwargs.pop('_depth', 0)
MM 184         self.category = kwargs.pop('_category', 'pyramid')
185         self.kwargs = kwargs
078412 186
MH 187     def register(self, scanner, name, wrapped):
188         config = scanner.config
189         for type_or_iface in self.types_or_ifaces:
498158 190             config.add_response_adapter(wrapped, type_or_iface, **self.kwargs)
078412 191
MH 192     def __call__(self, wrapped):
0c29cf 193         self.venusian.attach(
MM 194             wrapped,
195             self.register,
196             category=self.category,
197             depth=self.depth + 1,
198         )
078412 199         return wrapped
303abc 200
JA 201
202 def _get_response_factory(registry):
203     """ Obtain a :class: `pyramid.response.Response` using the
204     `pyramid.interfaces.IResponseFactory`.
205     """
206     response_factory = registry.queryUtility(
0c29cf 207         IResponseFactory, default=lambda r: Response()
303abc 208     )
JA 209
210     return response_factory
ed06c6 211
DG 212
213 def _guess_type(path):
0c29cf 214     content_type, content_encoding = mimetypes.guess_type(path, strict=False)
ed06c6 215     if content_type is None:
DG 216         content_type = 'application/octet-stream'
217     # str-ifying content_type is a workaround for a bug in Python 2.7.7
218     # on Windows where mimetypes.guess_type returns unicode for the
219     # content_type.
220     content_type = str(content_type)
221     return content_type, content_encoding