Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(286)

Side by Side Diff: Tools/Scripts/webkitpy/thirdparty/mechanize/_response.py

Issue 18418010: Check in the thirdparty libs needed for webkitpy. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 """Response classes.
2
3 The seek_wrapper code is not used if you're using UserAgent with
4 .set_seekable_responses(False), or if you're using the urllib2-level interface
5 HTTPEquivProcessor. Class closeable_response is instantiated by some handlers
6 (AbstractHTTPHandler), but the closeable_response interface is only depended
7 upon by Browser-level code. Function upgrade_response is only used if you're
8 using Browser.
9
10
11 Copyright 2006 John J. Lee <jjl@pobox.com>
12
13 This code is free software; you can redistribute it and/or modify it
14 under the terms of the BSD or ZPL 2.1 licenses (see the file COPYING.txt
15 included with the distribution).
16
17 """
18
19 import copy, mimetools, urllib2
20 from cStringIO import StringIO
21
22
23 def len_of_seekable(file_):
24 # this function exists because evaluation of len(file_.getvalue()) on every
25 # .read() from seek_wrapper would be O(N**2) in number of .read()s
26 pos = file_.tell()
27 file_.seek(0, 2) # to end
28 try:
29 return file_.tell()
30 finally:
31 file_.seek(pos)
32
33
34 # XXX Andrew Dalke kindly sent me a similar class in response to my request on
35 # comp.lang.python, which I then proceeded to lose. I wrote this class
36 # instead, but I think he's released his code publicly since, could pinch the
37 # tests from it, at least...
38
39 # For testing seek_wrapper invariant (note that
40 # test_urllib2.HandlerTest.test_seekable is expected to fail when this
41 # invariant checking is turned on). The invariant checking is done by module
42 # ipdc, which is available here:
43 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/436834
44 ## from ipdbc import ContractBase
45 ## class seek_wrapper(ContractBase):
46 class seek_wrapper:
47 """Adds a seek method to a file object.
48
49 This is only designed for seeking on readonly file-like objects.
50
51 Wrapped file-like object must have a read method. The readline method is
52 only supported if that method is present on the wrapped object. The
53 readlines method is always supported. xreadlines and iteration are
54 supported only for Python 2.2 and above.
55
56 Public attributes:
57
58 wrapped: the wrapped file object
59 is_closed: true iff .close() has been called
60
61 WARNING: All other attributes of the wrapped object (ie. those that are not
62 one of wrapped, read, readline, readlines, xreadlines, __iter__ and next)
63 are passed through unaltered, which may or may not make sense for your
64 particular file object.
65
66 """
67 # General strategy is to check that cache is full enough, then delegate to
68 # the cache (self.__cache, which is a cStringIO.StringIO instance). A seek
69 # position (self.__pos) is maintained independently of the cache, in order
70 # that a single cache may be shared between multiple seek_wrapper objects.
71 # Copying using module copy shares the cache in this way.
72
73 def __init__(self, wrapped):
74 self.wrapped = wrapped
75 self.__read_complete_state = [False]
76 self.__is_closed_state = [False]
77 self.__have_readline = hasattr(self.wrapped, "readline")
78 self.__cache = StringIO()
79 self.__pos = 0 # seek position
80
81 def invariant(self):
82 # The end of the cache is always at the same place as the end of the
83 # wrapped file (though the .tell() method is not required to be present
84 # on wrapped file).
85 return self.wrapped.tell() == len(self.__cache.getvalue())
86
87 def close(self):
88 self.wrapped.close()
89 self.is_closed = True
90
91 def __getattr__(self, name):
92 if name == "is_closed":
93 return self.__is_closed_state[0]
94 elif name == "read_complete":
95 return self.__read_complete_state[0]
96
97 wrapped = self.__dict__.get("wrapped")
98 if wrapped:
99 return getattr(wrapped, name)
100
101 return getattr(self.__class__, name)
102
103 def __setattr__(self, name, value):
104 if name == "is_closed":
105 self.__is_closed_state[0] = bool(value)
106 elif name == "read_complete":
107 if not self.is_closed:
108 self.__read_complete_state[0] = bool(value)
109 else:
110 self.__dict__[name] = value
111
112 def seek(self, offset, whence=0):
113 assert whence in [0,1,2]
114
115 # how much data, if any, do we need to read?
116 if whence == 2: # 2: relative to end of *wrapped* file
117 if offset < 0: raise ValueError("negative seek offset")
118 # since we don't know yet where the end of that file is, we must
119 # read everything
120 to_read = None
121 else:
122 if whence == 0: # 0: absolute
123 if offset < 0: raise ValueError("negative seek offset")
124 dest = offset
125 else: # 1: relative to current position
126 pos = self.__pos
127 if pos < offset:
128 raise ValueError("seek to before start of file")
129 dest = pos + offset
130 end = len_of_seekable(self.__cache)
131 to_read = dest - end
132 if to_read < 0:
133 to_read = 0
134
135 if to_read != 0:
136 self.__cache.seek(0, 2)
137 if to_read is None:
138 assert whence == 2
139 self.__cache.write(self.wrapped.read())
140 self.read_complete = True
141 self.__pos = self.__cache.tell() - offset
142 else:
143 data = self.wrapped.read(to_read)
144 if not data:
145 self.read_complete = True
146 else:
147 self.__cache.write(data)
148 # Don't raise an exception even if we've seek()ed past the end
149 # of .wrapped, since fseek() doesn't complain in that case.
150 # Also like fseek(), pretend we have seek()ed past the end,
151 # i.e. not:
152 #self.__pos = self.__cache.tell()
153 # but rather:
154 self.__pos = dest
155 else:
156 self.__pos = dest
157
158 def tell(self):
159 return self.__pos
160
161 def __copy__(self):
162 cpy = self.__class__(self.wrapped)
163 cpy.__cache = self.__cache
164 cpy.__read_complete_state = self.__read_complete_state
165 cpy.__is_closed_state = self.__is_closed_state
166 return cpy
167
168 def get_data(self):
169 pos = self.__pos
170 try:
171 self.seek(0)
172 return self.read(-1)
173 finally:
174 self.__pos = pos
175
176 def read(self, size=-1):
177 pos = self.__pos
178 end = len_of_seekable(self.__cache)
179 available = end - pos
180
181 # enough data already cached?
182 if size <= available and size != -1:
183 self.__cache.seek(pos)
184 self.__pos = pos+size
185 return self.__cache.read(size)
186
187 # no, so read sufficient data from wrapped file and cache it
188 self.__cache.seek(0, 2)
189 if size == -1:
190 self.__cache.write(self.wrapped.read())
191 self.read_complete = True
192 else:
193 to_read = size - available
194 assert to_read > 0
195 data = self.wrapped.read(to_read)
196 if not data:
197 self.read_complete = True
198 else:
199 self.__cache.write(data)
200 self.__cache.seek(pos)
201
202 data = self.__cache.read(size)
203 self.__pos = self.__cache.tell()
204 assert self.__pos == pos + len(data)
205 return data
206
207 def readline(self, size=-1):
208 if not self.__have_readline:
209 raise NotImplementedError("no readline method on wrapped object")
210
211 # line we're about to read might not be complete in the cache, so
212 # read another line first
213 pos = self.__pos
214 self.__cache.seek(0, 2)
215 data = self.wrapped.readline()
216 if not data:
217 self.read_complete = True
218 else:
219 self.__cache.write(data)
220 self.__cache.seek(pos)
221
222 data = self.__cache.readline()
223 if size != -1:
224 r = data[:size]
225 self.__pos = pos+size
226 else:
227 r = data
228 self.__pos = pos+len(data)
229 return r
230
231 def readlines(self, sizehint=-1):
232 pos = self.__pos
233 self.__cache.seek(0, 2)
234 self.__cache.write(self.wrapped.read())
235 self.read_complete = True
236 self.__cache.seek(pos)
237 data = self.__cache.readlines(sizehint)
238 self.__pos = self.__cache.tell()
239 return data
240
241 def __iter__(self): return self
242 def next(self):
243 line = self.readline()
244 if line == "": raise StopIteration
245 return line
246
247 xreadlines = __iter__
248
249 def __repr__(self):
250 return ("<%s at %s whose wrapped object = %r>" %
251 (self.__class__.__name__, hex(abs(id(self))), self.wrapped))
252
253
254 class response_seek_wrapper(seek_wrapper):
255
256 """
257 Supports copying response objects and setting response body data.
258
259 """
260
261 def __init__(self, wrapped):
262 seek_wrapper.__init__(self, wrapped)
263 self._headers = self.wrapped.info()
264
265 def __copy__(self):
266 cpy = seek_wrapper.__copy__(self)
267 # copy headers from delegate
268 cpy._headers = copy.copy(self.info())
269 return cpy
270
271 # Note that .info() and .geturl() (the only two urllib2 response methods
272 # that are not implemented by seek_wrapper) must be here explicitly rather
273 # than by seek_wrapper's __getattr__ delegation) so that the nasty
274 # dynamically-created HTTPError classes in get_seek_wrapper_class() get the
275 # wrapped object's implementation, and not HTTPError's.
276
277 def info(self):
278 return self._headers
279
280 def geturl(self):
281 return self.wrapped.geturl()
282
283 def set_data(self, data):
284 self.seek(0)
285 self.read()
286 self.close()
287 cache = self._seek_wrapper__cache = StringIO()
288 cache.write(data)
289 self.seek(0)
290
291
292 class eoffile:
293 # file-like object that always claims to be at end-of-file...
294 def read(self, size=-1): return ""
295 def readline(self, size=-1): return ""
296 def __iter__(self): return self
297 def next(self): return ""
298 def close(self): pass
299
300 class eofresponse(eoffile):
301 def __init__(self, url, headers, code, msg):
302 self._url = url
303 self._headers = headers
304 self.code = code
305 self.msg = msg
306 def geturl(self): return self._url
307 def info(self): return self._headers
308
309
310 class closeable_response:
311 """Avoids unnecessarily clobbering urllib.addinfourl methods on .close().
312
313 Only supports responses returned by mechanize.HTTPHandler.
314
315 After .close(), the following methods are supported:
316
317 .read()
318 .readline()
319 .info()
320 .geturl()
321 .__iter__()
322 .next()
323 .close()
324
325 and the following attributes are supported:
326
327 .code
328 .msg
329
330 Also supports pickling (but the stdlib currently does something to prevent
331 it: http://python.org/sf/1144636).
332
333 """
334 # presence of this attr indicates is useable after .close()
335 closeable_response = None
336
337 def __init__(self, fp, headers, url, code, msg):
338 self._set_fp(fp)
339 self._headers = headers
340 self._url = url
341 self.code = code
342 self.msg = msg
343
344 def _set_fp(self, fp):
345 self.fp = fp
346 self.read = self.fp.read
347 self.readline = self.fp.readline
348 if hasattr(self.fp, "readlines"): self.readlines = self.fp.readlines
349 if hasattr(self.fp, "fileno"):
350 self.fileno = self.fp.fileno
351 else:
352 self.fileno = lambda: None
353 self.__iter__ = self.fp.__iter__
354 self.next = self.fp.next
355
356 def __repr__(self):
357 return '<%s at %s whose fp = %r>' % (
358 self.__class__.__name__, hex(abs(id(self))), self.fp)
359
360 def info(self):
361 return self._headers
362
363 def geturl(self):
364 return self._url
365
366 def close(self):
367 wrapped = self.fp
368 wrapped.close()
369 new_wrapped = eofresponse(
370 self._url, self._headers, self.code, self.msg)
371 self._set_fp(new_wrapped)
372
373 def __getstate__(self):
374 # There are three obvious options here:
375 # 1. truncate
376 # 2. read to end
377 # 3. close socket, pickle state including read position, then open
378 # again on unpickle and use Range header
379 # XXXX um, 4. refuse to pickle unless .close()d. This is better,
380 # actually ("errors should never pass silently"). Pickling doesn't
381 # work anyway ATM, because of http://python.org/sf/1144636 so fix
382 # this later
383
384 # 2 breaks pickle protocol, because one expects the original object
385 # to be left unscathed by pickling. 3 is too complicated and
386 # surprising (and too much work ;-) to happen in a sane __getstate__.
387 # So we do 1.
388
389 state = self.__dict__.copy()
390 new_wrapped = eofresponse(
391 self._url, self._headers, self.code, self.msg)
392 state["wrapped"] = new_wrapped
393 return state
394
395 def test_response(data='test data', headers=[],
396 url="http://example.com/", code=200, msg="OK"):
397 return make_response(data, headers, url, code, msg)
398
399 def test_html_response(data='test data', headers=[],
400 url="http://example.com/", code=200, msg="OK"):
401 headers += [("Content-type", "text/html")]
402 return make_response(data, headers, url, code, msg)
403
404 def make_response(data, headers, url, code, msg):
405 """Convenient factory for objects implementing response interface.
406
407 data: string containing response body data
408 headers: sequence of (name, value) pairs
409 url: URL of response
410 code: integer response code (e.g. 200)
411 msg: string response code message (e.g. "OK")
412
413 """
414 mime_headers = make_headers(headers)
415 r = closeable_response(StringIO(data), mime_headers, url, code, msg)
416 return response_seek_wrapper(r)
417
418
419 def make_headers(headers):
420 """
421 headers: sequence of (name, value) pairs
422 """
423 hdr_text = []
424 for name_value in headers:
425 hdr_text.append("%s: %s" % name_value)
426 return mimetools.Message(StringIO("\n".join(hdr_text)))
427
428
429 # Rest of this module is especially horrible, but needed, at least until fork
430 # urllib2. Even then, may want to preseve urllib2 compatibility.
431
432 def get_seek_wrapper_class(response):
433 # in order to wrap response objects that are also exceptions, we must
434 # dynamically subclass the exception :-(((
435 if (isinstance(response, urllib2.HTTPError) and
436 not hasattr(response, "seek")):
437 if response.__class__.__module__ == "__builtin__":
438 exc_class_name = response.__class__.__name__
439 else:
440 exc_class_name = "%s.%s" % (
441 response.__class__.__module__, response.__class__.__name__)
442
443 class httperror_seek_wrapper(response_seek_wrapper, response.__class__):
444 # this only derives from HTTPError in order to be a subclass --
445 # the HTTPError behaviour comes from delegation
446
447 _exc_class_name = exc_class_name
448
449 def __init__(self, wrapped):
450 response_seek_wrapper.__init__(self, wrapped)
451 # be compatible with undocumented HTTPError attributes :-(
452 self.hdrs = wrapped.info()
453 self.filename = wrapped.geturl()
454
455 def __repr__(self):
456 return (
457 "<%s (%s instance) at %s "
458 "whose wrapped object = %r>" % (
459 self.__class__.__name__, self._exc_class_name,
460 hex(abs(id(self))), self.wrapped)
461 )
462 wrapper_class = httperror_seek_wrapper
463 else:
464 wrapper_class = response_seek_wrapper
465 return wrapper_class
466
467 def seek_wrapped_response(response):
468 """Return a copy of response that supports seekable response interface.
469
470 Accepts responses from both mechanize and urllib2 handlers.
471
472 Copes with both ordinary response instances and HTTPError instances (which
473 can't be simply wrapped due to the requirement of preserving the exception
474 base class).
475 """
476 if not hasattr(response, "seek"):
477 wrapper_class = get_seek_wrapper_class(response)
478 response = wrapper_class(response)
479 assert hasattr(response, "get_data")
480 return response
481
482 def upgrade_response(response):
483 """Return a copy of response that supports Browser response interface.
484
485 Browser response interface is that of "seekable responses"
486 (response_seek_wrapper), plus the requirement that responses must be
487 useable after .close() (closeable_response).
488
489 Accepts responses from both mechanize and urllib2 handlers.
490
491 Copes with both ordinary response instances and HTTPError instances (which
492 can't be simply wrapped due to the requirement of preserving the exception
493 base class).
494 """
495 wrapper_class = get_seek_wrapper_class(response)
496 if hasattr(response, "closeable_response"):
497 if not hasattr(response, "seek"):
498 response = wrapper_class(response)
499 assert hasattr(response, "get_data")
500 return copy.copy(response)
501
502 # a urllib2 handler constructed the response, i.e. the response is an
503 # urllib.addinfourl or a urllib2.HTTPError, instead of a
504 # _Util.closeable_response as returned by e.g. mechanize.HTTPHandler
505 try:
506 code = response.code
507 except AttributeError:
508 code = None
509 try:
510 msg = response.msg
511 except AttributeError:
512 msg = None
513
514 # may have already-.read() data from .seek() cache
515 data = None
516 get_data = getattr(response, "get_data", None)
517 if get_data:
518 data = get_data()
519
520 response = closeable_response(
521 response.fp, response.info(), response.geturl(), code, msg)
522 response = wrapper_class(response)
523 if data:
524 response.set_data(data)
525 return response
OLDNEW
« no previous file with comments | « Tools/Scripts/webkitpy/thirdparty/mechanize/_request.py ('k') | Tools/Scripts/webkitpy/thirdparty/mechanize/_rfc3986.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698