Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import mimetypes 

2from os.path import getmtime, getsize 

3 

4import venusian 

5 

6from webob import Response as _Response 

7from zope.interface import implementer 

8from pyramid.interfaces import IResponse, IResponseFactory 

9 

10 

11def init_mimetypes(mimetypes): 

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 

18 

19# See http://bugs.python.org/issue5853 which is a recursion bug 

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). 

22init_mimetypes(mimetypes) 

23 

24_BLOCK_SIZE = 4096 * 64 # 256K 

25 

26 

27@implementer(IResponse) 

28class Response(_Response): 

29 pass 

30 

31 

32class FileResponse(Response): 

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 

39 ``request`` must be a Pyramid :term:`request` object. Note 

40 that a request *must* be passed if the response is meant to attempt to 

41 use the ``wsgi.file_wrapper`` feature of the web server that you're using 

42 to serve your Pyramid application. 

43 

44 ``cache_max_age`` is the number of seconds that should be used 

45 to HTTP cache this response. 

46 

47 ``content_type`` is the content_type of the response. 

48 

49 ``content_encoding`` is the content_encoding of the response. 

50 It's generally safe to leave this set to ``None`` if you're serving a 

51 binary file. This argument will be ignored if you also leave 

52 ``content-type`` as ``None``. 

53 """ 

54 

55 def __init__( 

56 self, 

57 path, 

58 request=None, 

59 cache_max_age=None, 

60 content_type=None, 

61 content_encoding=None, 

62 ): 

63 if content_type is None: 

64 content_type, content_encoding = _guess_type(path) 

65 super(FileResponse, self).__init__( 

66 conditional_response=True, 

67 content_type=content_type, 

68 content_encoding=content_encoding, 

69 ) 

70 self.last_modified = getmtime(path) 

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 

86 

87class FileIter(object): 

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 """ 

95 

96 def __init__(self, file, block_size=_BLOCK_SIZE): 

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 

109 __next__ = next # py3 

110 

111 def close(self): 

112 self.file.close() 

113 

114 

115class response_adapter(object): 

116 """ Decorator activated via a :term:`scan` which treats the function 

117 being decorated as a :term:`response adapter` for the set of types or 

118 interfaces passed as ``*types_or_ifaces`` to the decorator constructor. 

119 

120 For example, if you scan the following response adapter: 

121 

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 

131 You can then return an integer from your view callables, and it will be 

132 converted into a response with the integer as the status code. 

133 

134 More than one type or interface can be passed as a constructor argument. 

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)) 

147 

148 This method will have no effect until a :term:`scan` is performed 

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 

157 Two additional keyword arguments which will be passed to the 

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 

177 """ 

178 

179 venusian = venusian # for unit testing 

180 

181 def __init__(self, *types_or_ifaces, **kwargs): 

182 self.types_or_ifaces = types_or_ifaces 

183 self.depth = kwargs.pop('_depth', 0) 

184 self.category = kwargs.pop('_category', 'pyramid') 

185 self.kwargs = kwargs 

186 

187 def register(self, scanner, name, wrapped): 

188 config = scanner.config 

189 for type_or_iface in self.types_or_ifaces: 

190 config.add_response_adapter(wrapped, type_or_iface, **self.kwargs) 

191 

192 def __call__(self, wrapped): 

193 self.venusian.attach( 

194 wrapped, 

195 self.register, 

196 category=self.category, 

197 depth=self.depth + 1, 

198 ) 

199 return wrapped 

200 

201 

202def _get_response_factory(registry): 

203 """ Obtain a :class: `pyramid.response.Response` using the 

204 `pyramid.interfaces.IResponseFactory`. 

205 """ 

206 response_factory = registry.queryUtility( 

207 IResponseFactory, default=lambda r: Response() 

208 ) 

209 

210 return response_factory 

211 

212 

213def _guess_type(path): 

214 content_type, content_encoding = mimetypes.guess_type(path, strict=False) 

215 if content_type is None: 

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