Chris McDonough
2013-06-20 56511b0defbc4437a1e1d3b013c504886270d01b
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
99edc5 11 from pyramid.interfaces import IResponse
966b5c 12
f2f67e 13 def init_mimetypes(mimetypes):
CM 14     # this is a function so it can be unittested
15     if hasattr(mimetypes, 'init'):
16         mimetypes.init()
17         return True
18     return False
19
20 # See http://bugs.python.org/issue5853 which is a recursion bug
21 # that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix
22 # has been applied on the Python 2 trunk).
23 init_mimetypes(mimetypes)
24
6b3cca 25 _BLOCK_SIZE = 4096 * 64 # 256K
CM 26
3b7334 27 @implementer(IResponse)
99edc5 28 class Response(_Response):
3b7334 29     pass
078412 30
6b3cca 31 class FileResponse(Response):
CM 32     """
33     A Response object that can be used to serve a static file from disk
34     simply.
35
36     ``path`` is a file path on disk.
37
092881 38     ``request`` must be a Pyramid :term:`request` object.  Note
6b3cca 39     that a request *must* be passed if the response is meant to attempt to
CM 40     use the ``wsgi.file_wrapper`` feature of the web server that you're using
41     to serve your Pyramid application.
42
092881 43     ``cache_max_age`` is the number of seconds that should be used
6b3cca 44     to HTTP cache this response.
c2e82a 45
092881 46     ``content_type`` is the content_type of the response.
c2e82a 47
092881 48     ``content_encoding`` is the content_encoding of the response.
c2e82a 49     It's generally safe to leave this set to ``None`` if you're serving a
092881 50     binary file.  This argument will be ignored if you also leave
TL 51     ``content-type`` as ``None``.
6b3cca 52     """
c2e82a 53     def __init__(self, path, request=None, cache_max_age=None,
CM 54                  content_type=None, content_encoding=None):
6b3cca 55         super(FileResponse, self).__init__(conditional_response=True)
CM 56         self.last_modified = getmtime(path)
c2e82a 57         if content_type is None:
CM 58             content_type, content_encoding = mimetypes.guess_type(path,
59                                                                   strict=False)
6b3cca 60         if content_type is None:
CM 61             content_type = 'application/octet-stream'
62         self.content_type = content_type
63         self.content_encoding = content_encoding
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))
138         
139     This method will have no effect until a :term:`scan` is performed
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
148     """
149     venusian = venusian # for unit testing
150
151     def __init__(self, *types_or_ifaces):
152         self.types_or_ifaces = types_or_ifaces
153
154     def register(self, scanner, name, wrapped):
155         config = scanner.config
156         for type_or_iface in self.types_or_ifaces:
157             config.add_response_adapter(wrapped, type_or_iface)
158
159     def __call__(self, wrapped):
160         self.venusian.attach(wrapped, self.register, category='pyramid')
161         return wrapped