|
24 | 24 | from .auth import auth |
25 | 25 | from .unixconn import unixconn |
26 | 26 | from .utils import utils |
| 27 | +from . import errors |
27 | 28 |
|
28 | 29 | if not six.PY3: |
29 | 30 | import websocket |
|
33 | 34 | STREAM_HEADER_SIZE_BYTES = 8 |
34 | 35 |
|
35 | 36 |
|
36 | | -class APIError(requests.exceptions.HTTPError): |
37 | | - def __init__(self, message, response, explanation=None): |
38 | | - # requests 1.2 supports response as a keyword argument, but |
39 | | - # requests 1.1 doesn't |
40 | | - super(APIError, self).__init__(message) |
41 | | - self.response = response |
42 | | - |
43 | | - self.explanation = explanation |
44 | | - |
45 | | - if self.explanation is None and response.content: |
46 | | - self.explanation = response.content.strip() |
47 | | - |
48 | | - def __str__(self): |
49 | | - message = super(APIError, self).__str__() |
50 | | - |
51 | | - if self.is_client_error(): |
52 | | - message = '%s Client Error: %s' % ( |
53 | | - self.response.status_code, self.response.reason) |
54 | | - |
55 | | - elif self.is_server_error(): |
56 | | - message = '%s Server Error: %s' % ( |
57 | | - self.response.status_code, self.response.reason) |
58 | | - |
59 | | - if self.explanation: |
60 | | - message = '%s ("%s")' % (message, self.explanation) |
61 | | - |
62 | | - return message |
63 | | - |
64 | | - def is_client_error(self): |
65 | | - return 400 <= self.response.status_code < 500 |
66 | | - |
67 | | - def is_server_error(self): |
68 | | - return 500 <= self.response.status_code < 600 |
69 | | - |
70 | | - |
71 | 37 | class Client(requests.Session): |
72 | 38 | def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION, |
73 | 39 | timeout=DEFAULT_TIMEOUT_SECONDS): |
@@ -112,7 +78,7 @@ def _raise_for_status(self, response, explanation=None): |
112 | 78 | try: |
113 | 79 | response.raise_for_status() |
114 | 80 | except requests.exceptions.HTTPError as e: |
115 | | - raise APIError(e, response, explanation=explanation) |
| 81 | + raise errors.APIError(e, response, explanation=explanation) |
116 | 82 |
|
117 | 83 | def _result(self, response, json=False, binary=False): |
118 | 84 | assert not (json and binary) |
@@ -239,9 +205,23 @@ def _get_raw_response_socket(self, response): |
239 | 205 |
|
240 | 206 | def _stream_helper(self, response): |
241 | 207 | """Generator for data coming from a chunked-encoded HTTP response.""" |
242 | | - for line in response.iter_lines(chunk_size=32): |
243 | | - if line: |
244 | | - yield line |
| 208 | + socket_fp = self._get_raw_response_socket(response) |
| 209 | + socket_fp.setblocking(1) |
| 210 | + socket = socket_fp.makefile() |
| 211 | + while True: |
| 212 | + # Because Docker introduced newlines at the end of chunks in v0.9, |
| 213 | + # and only on some API endpoints, we have to cater for both cases. |
| 214 | + size_line = socket.readline() |
| 215 | + if size_line == '\r\n': |
| 216 | + size_line = socket.readline() |
| 217 | + |
| 218 | + size = int(size_line, 16) |
| 219 | + if size <= 0: |
| 220 | + break |
| 221 | + data = socket.readline() |
| 222 | + if not data: |
| 223 | + break |
| 224 | + yield data |
245 | 225 |
|
246 | 226 | def _multiplexed_buffer_helper(self, response): |
247 | 227 | """A generator of multiplexed data blocks read from a buffered |
@@ -341,7 +321,7 @@ def build(self, path=None, tag=None, quiet=False, fileobj=None, |
341 | 321 | nocache=False, rm=False, stream=False, timeout=None): |
342 | 322 | remote = context = headers = None |
343 | 323 | if path is None and fileobj is None: |
344 | | - raise Exception("Either path or fileobj needs to be provided.") |
| 324 | + raise TypeError("Either path or fileobj needs to be provided.") |
345 | 325 |
|
346 | 326 | if fileobj is not None: |
347 | 327 | context = utils.mkbuildcontext(fileobj) |
@@ -714,8 +694,12 @@ def start(self, container, binds=None, volumes_from=None, port_bindings=None, |
714 | 694 | } |
715 | 695 | if binds: |
716 | 696 | bind_pairs = [ |
717 | | - '{0}:{1}'.format(host, dest) for host, dest in binds.items() |
| 697 | + '%s:%s:%s' % ( |
| 698 | + h, d['bind'], |
| 699 | + 'ro' if 'ro' in d and d['ro'] else 'rw' |
| 700 | + ) for h, d in binds.items() |
718 | 701 | ] |
| 702 | + |
719 | 703 | start_config['Binds'] = bind_pairs |
720 | 704 |
|
721 | 705 | if volumes_from and not isinstance(volumes_from, six.string_types): |
|
0 commit comments