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