crate 1.0.0.dev1__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
crate/client/http.py CHANGED
@@ -30,11 +30,11 @@ import re
30
30
  import socket
31
31
  import ssl
32
32
  import threading
33
- from urllib.parse import urlparse
34
33
  from base64 import b64encode
35
- from time import time
36
- from datetime import datetime, date, timezone
34
+ from datetime import date, datetime, timezone
37
35
  from decimal import Decimal
36
+ from time import time
37
+ from urllib.parse import urlparse
38
38
  from uuid import UUID
39
39
 
40
40
  import urllib3
@@ -52,42 +52,41 @@ from urllib3.util.retry import Retry
52
52
  from verlib2 import Version
53
53
 
54
54
  from crate.client.exceptions import (
55
- ConnectionError,
56
55
  BlobLocationNotFoundException,
56
+ ConnectionError,
57
57
  DigestNotFoundException,
58
- ProgrammingError,
59
58
  IntegrityError,
59
+ ProgrammingError,
60
60
  )
61
61
 
62
-
63
62
  logger = logging.getLogger(__name__)
64
63
 
65
64
 
66
- _HTTP_PAT = pat = re.compile('https?://.+', re.I)
67
- SRV_UNAVAILABLE_STATUSES = set((502, 503, 504, 509))
68
- PRESERVE_ACTIVE_SERVER_EXCEPTIONS = set((ConnectionResetError, BrokenPipeError))
69
- SSL_ONLY_ARGS = set(('ca_certs', 'cert_reqs', 'cert_file', 'key_file'))
65
+ _HTTP_PAT = pat = re.compile("https?://.+", re.I)
66
+ SRV_UNAVAILABLE_STATUSES = {502, 503, 504, 509}
67
+ PRESERVE_ACTIVE_SERVER_EXCEPTIONS = {ConnectionResetError, BrokenPipeError}
68
+ SSL_ONLY_ARGS = {"ca_certs", "cert_reqs", "cert_file", "key_file"}
70
69
 
71
70
 
72
71
  def super_len(o):
73
- if hasattr(o, '__len__'):
72
+ if hasattr(o, "__len__"):
74
73
  return len(o)
75
- if hasattr(o, 'len'):
74
+ if hasattr(o, "len"):
76
75
  return o.len
77
- if hasattr(o, 'fileno'):
76
+ if hasattr(o, "fileno"):
78
77
  try:
79
78
  fileno = o.fileno()
80
79
  except io.UnsupportedOperation:
81
80
  pass
82
81
  else:
83
82
  return os.fstat(fileno).st_size
84
- if hasattr(o, 'getvalue'):
83
+ if hasattr(o, "getvalue"):
85
84
  # e.g. BytesIO, cStringIO.StringI
86
85
  return len(o.getvalue())
86
+ return None
87
87
 
88
88
 
89
89
  class CrateJsonEncoder(json.JSONEncoder):
90
-
91
90
  epoch_aware = datetime(1970, 1, 1, tzinfo=timezone.utc)
92
91
  epoch_naive = datetime(1970, 1, 1)
93
92
 
@@ -99,21 +98,22 @@ class CrateJsonEncoder(json.JSONEncoder):
99
98
  delta = o - self.epoch_aware
100
99
  else:
101
100
  delta = o - self.epoch_naive
102
- return int(delta.microseconds / 1000.0 +
103
- (delta.seconds + delta.days * 24 * 3600) * 1000.0)
101
+ return int(
102
+ delta.microseconds / 1000.0
103
+ + (delta.seconds + delta.days * 24 * 3600) * 1000.0
104
+ )
104
105
  if isinstance(o, date):
105
106
  return calendar.timegm(o.timetuple()) * 1000
106
107
  return json.JSONEncoder.default(self, o)
107
108
 
108
109
 
109
- class Server(object):
110
-
110
+ class Server:
111
111
  def __init__(self, server, **pool_kw):
112
112
  socket_options = _get_socket_opts(
113
- pool_kw.pop('socket_keepalive', False),
114
- pool_kw.pop('socket_tcp_keepidle', None),
115
- pool_kw.pop('socket_tcp_keepintvl', None),
116
- pool_kw.pop('socket_tcp_keepcnt', None),
113
+ pool_kw.pop("socket_keepalive", False),
114
+ pool_kw.pop("socket_tcp_keepidle", None),
115
+ pool_kw.pop("socket_tcp_keepintvl", None),
116
+ pool_kw.pop("socket_tcp_keepcnt", None),
117
117
  )
118
118
  self.pool = connection_from_url(
119
119
  server,
@@ -121,53 +121,57 @@ class Server(object):
121
121
  **pool_kw,
122
122
  )
123
123
 
124
- def request(self,
125
- method,
126
- path,
127
- data=None,
128
- stream=False,
129
- headers=None,
130
- username=None,
131
- password=None,
132
- schema=None,
133
- backoff_factor=0,
134
- **kwargs):
124
+ def request(
125
+ self,
126
+ method,
127
+ path,
128
+ data=None,
129
+ stream=False,
130
+ headers=None,
131
+ username=None,
132
+ password=None,
133
+ schema=None,
134
+ backoff_factor=0,
135
+ **kwargs,
136
+ ):
135
137
  """Send a request
136
138
 
137
139
  Always set the Content-Length and the Content-Type header.
138
140
  """
139
141
  if headers is None:
140
142
  headers = {}
141
- if 'Content-Length' not in headers:
143
+ if "Content-Length" not in headers:
142
144
  length = super_len(data)
143
145
  if length is not None:
144
- headers['Content-Length'] = length
146
+ headers["Content-Length"] = length
145
147
 
146
148
  # Authentication credentials
147
149
  if username is not None:
148
- if 'Authorization' not in headers and username is not None:
149
- credentials = username + ':'
150
+ if "Authorization" not in headers and username is not None:
151
+ credentials = username + ":"
150
152
  if password is not None:
151
153
  credentials += password
152
- headers['Authorization'] = 'Basic %s' % b64encode(credentials.encode('utf-8')).decode('utf-8')
154
+ headers["Authorization"] = "Basic %s" % b64encode(
155
+ credentials.encode("utf-8")
156
+ ).decode("utf-8")
153
157
  # For backwards compatibility with Crate <= 2.2
154
- if 'X-User' not in headers:
155
- headers['X-User'] = username
158
+ if "X-User" not in headers:
159
+ headers["X-User"] = username
156
160
 
157
161
  if schema is not None:
158
- headers['Default-Schema'] = schema
159
- headers['Accept'] = 'application/json'
160
- headers['Content-Type'] = 'application/json'
161
- kwargs['assert_same_host'] = False
162
- kwargs['redirect'] = False
163
- kwargs['retries'] = Retry(read=0, backoff_factor=backoff_factor)
162
+ headers["Default-Schema"] = schema
163
+ headers["Accept"] = "application/json"
164
+ headers["Content-Type"] = "application/json"
165
+ kwargs["assert_same_host"] = False
166
+ kwargs["redirect"] = False
167
+ kwargs["retries"] = Retry(read=0, backoff_factor=backoff_factor)
164
168
  return self.pool.urlopen(
165
169
  method,
166
170
  path,
167
171
  body=data,
168
172
  preload_content=not stream,
169
173
  headers=headers,
170
- **kwargs
174
+ **kwargs,
171
175
  )
172
176
 
173
177
  def close(self):
@@ -176,24 +180,27 @@ class Server(object):
176
180
 
177
181
  def _json_from_response(response):
178
182
  try:
179
- return json.loads(response.data.decode('utf-8'))
180
- except ValueError:
183
+ return json.loads(response.data.decode("utf-8"))
184
+ except ValueError as ex:
181
185
  raise ProgrammingError(
182
- "Invalid server response of content-type '{}':\n{}"
183
- .format(response.headers.get("content-type", "unknown"), response.data.decode('utf-8')))
186
+ "Invalid server response of content-type '{}':\n{}".format(
187
+ response.headers.get("content-type", "unknown"),
188
+ response.data.decode("utf-8"),
189
+ )
190
+ ) from ex
184
191
 
185
192
 
186
193
  def _blob_path(table, digest):
187
- return '/_blobs/{table}/{digest}'.format(table=table, digest=digest)
194
+ return "/_blobs/{table}/{digest}".format(table=table, digest=digest)
188
195
 
189
196
 
190
197
  def _ex_to_message(ex):
191
- return getattr(ex, 'message', None) or str(ex) or repr(ex)
198
+ return getattr(ex, "message", None) or str(ex) or repr(ex)
192
199
 
193
200
 
194
201
  def _raise_for_status(response):
195
202
  """
196
- Properly raise `IntegrityError` exceptions for CrateDB's `DuplicateKeyException` errors.
203
+ Raise `IntegrityError` exceptions for `DuplicateKeyException` errors.
197
204
  """
198
205
  try:
199
206
  return _raise_for_status_real(response)
@@ -204,29 +211,33 @@ def _raise_for_status(response):
204
211
 
205
212
 
206
213
  def _raise_for_status_real(response):
207
- """ make sure that only crate.exceptions are raised that are defined in
208
- the DB-API specification """
209
- message = ''
214
+ """make sure that only crate.exceptions are raised that are defined in
215
+ the DB-API specification"""
216
+ message = ""
210
217
  if 400 <= response.status < 500:
211
- message = '%s Client Error: %s' % (response.status, response.reason)
218
+ message = "%s Client Error: %s" % (response.status, response.reason)
212
219
  elif 500 <= response.status < 600:
213
- message = '%s Server Error: %s' % (response.status, response.reason)
220
+ message = "%s Server Error: %s" % (response.status, response.reason)
214
221
  else:
215
222
  return
216
223
  if response.status == 503:
217
224
  raise ConnectionError(message)
218
225
  if response.headers.get("content-type", "").startswith("application/json"):
219
- data = json.loads(response.data.decode('utf-8'))
220
- error = data.get('error', {})
221
- error_trace = data.get('error_trace', None)
226
+ data = json.loads(response.data.decode("utf-8"))
227
+ error = data.get("error", {})
228
+ error_trace = data.get("error_trace", None)
222
229
  if "results" in data:
223
- errors = [res["error_message"] for res in data["results"]
224
- if res.get("error_message")]
230
+ errors = [
231
+ res["error_message"]
232
+ for res in data["results"]
233
+ if res.get("error_message")
234
+ ]
225
235
  if errors:
226
236
  raise ProgrammingError("\n".join(errors))
227
237
  if isinstance(error, dict):
228
- raise ProgrammingError(error.get('message', ''),
229
- error_trace=error_trace)
238
+ raise ProgrammingError(
239
+ error.get("message", ""), error_trace=error_trace
240
+ )
230
241
  raise ProgrammingError(error, error_trace=error_trace)
231
242
  raise ProgrammingError(message)
232
243
 
@@ -247,9 +258,9 @@ def _server_url(server):
247
258
  http://demo.crate.io
248
259
  """
249
260
  if not _HTTP_PAT.match(server):
250
- server = 'http://%s' % server
261
+ server = "http://%s" % server
251
262
  parsed = urlparse(server)
252
- url = '%s://%s' % (parsed.scheme, parsed.netloc)
263
+ url = "%s://%s" % (parsed.scheme, parsed.netloc)
253
264
  return url
254
265
 
255
266
 
@@ -259,30 +270,36 @@ def _to_server_list(servers):
259
270
  return [_server_url(s) for s in servers]
260
271
 
261
272
 
262
- def _pool_kw_args(verify_ssl_cert, ca_cert, client_cert, client_key,
263
- timeout=None, pool_size=None):
264
- ca_cert = ca_cert or os.environ.get('REQUESTS_CA_BUNDLE', None)
273
+ def _pool_kw_args(
274
+ verify_ssl_cert,
275
+ ca_cert,
276
+ client_cert,
277
+ client_key,
278
+ timeout=None,
279
+ pool_size=None,
280
+ ):
281
+ ca_cert = ca_cert or os.environ.get("REQUESTS_CA_BUNDLE", None)
265
282
  if ca_cert and not os.path.exists(ca_cert):
266
283
  # Sanity check
267
284
  raise IOError('CA bundle file "{}" does not exist.'.format(ca_cert))
268
285
 
269
286
  kw = {
270
- 'ca_certs': ca_cert,
271
- 'cert_reqs': ssl.CERT_REQUIRED if verify_ssl_cert else ssl.CERT_NONE,
272
- 'cert_file': client_cert,
273
- 'key_file': client_key,
287
+ "ca_certs": ca_cert,
288
+ "cert_reqs": ssl.CERT_REQUIRED if verify_ssl_cert else ssl.CERT_NONE,
289
+ "cert_file": client_cert,
290
+ "key_file": client_key,
274
291
  }
275
292
  if timeout is not None:
276
293
  if isinstance(timeout, str):
277
294
  timeout = float(timeout)
278
- kw['timeout'] = timeout
295
+ kw["timeout"] = timeout
279
296
  if pool_size is not None:
280
- kw['maxsize'] = int(pool_size)
297
+ kw["maxsize"] = int(pool_size)
281
298
  return kw
282
299
 
283
300
 
284
301
  def _remove_certs_for_non_https(server, kwargs):
285
- if server.lower().startswith('https'):
302
+ if server.lower().startswith("https"):
286
303
  return kwargs
287
304
  used_ssl_args = SSL_ONLY_ARGS & set(kwargs.keys())
288
305
  if used_ssl_args:
@@ -300,6 +317,7 @@ def _update_pool_kwargs_for_ssl_minimum_version(server, kwargs):
300
317
  """
301
318
  if Version(urllib3.__version__) >= Version("2"):
302
319
  from urllib3.util import parse_url
320
+
303
321
  scheme, _, host, port, *_ = parse_url(server)
304
322
  if scheme == "https":
305
323
  kwargs["ssl_minimum_version"] = ssl.TLSVersion.MINIMUM_SUPPORTED
@@ -307,24 +325,21 @@ def _update_pool_kwargs_for_ssl_minimum_version(server, kwargs):
307
325
 
308
326
  def _create_sql_payload(stmt, args, bulk_args):
309
327
  if not isinstance(stmt, str):
310
- raise ValueError('stmt is not a string')
328
+ raise ValueError("stmt is not a string")
311
329
  if args and bulk_args:
312
- raise ValueError('Cannot provide both: args and bulk_args')
330
+ raise ValueError("Cannot provide both: args and bulk_args")
313
331
 
314
- data = {
315
- 'stmt': stmt
316
- }
332
+ data = {"stmt": stmt}
317
333
  if args:
318
- data['args'] = args
334
+ data["args"] = args
319
335
  if bulk_args:
320
- data['bulk_args'] = bulk_args
336
+ data["bulk_args"] = bulk_args
321
337
  return json.dumps(data, cls=CrateJsonEncoder)
322
338
 
323
339
 
324
- def _get_socket_opts(keepalive=True,
325
- tcp_keepidle=None,
326
- tcp_keepintvl=None,
327
- tcp_keepcnt=None):
340
+ def _get_socket_opts(
341
+ keepalive=True, tcp_keepidle=None, tcp_keepintvl=None, tcp_keepcnt=None
342
+ ):
328
343
  """
329
344
  Return an optional list of socket options for urllib3's HTTPConnection
330
345
  constructor.
@@ -337,23 +352,23 @@ def _get_socket_opts(keepalive=True,
337
352
 
338
353
  # hasattr check because some options depend on system capabilities
339
354
  # see https://docs.python.org/3/library/socket.html#socket.SOMAXCONN
340
- if hasattr(socket, 'TCP_KEEPIDLE') and tcp_keepidle is not None:
355
+ if hasattr(socket, "TCP_KEEPIDLE") and tcp_keepidle is not None:
341
356
  opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, tcp_keepidle))
342
- if hasattr(socket, 'TCP_KEEPINTVL') and tcp_keepintvl is not None:
357
+ if hasattr(socket, "TCP_KEEPINTVL") and tcp_keepintvl is not None:
343
358
  opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, tcp_keepintvl))
344
- if hasattr(socket, 'TCP_KEEPCNT') and tcp_keepcnt is not None:
359
+ if hasattr(socket, "TCP_KEEPCNT") and tcp_keepcnt is not None:
345
360
  opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, tcp_keepcnt))
346
361
 
347
362
  # additionally use urllib3's default socket options
348
- return HTTPConnection.default_socket_options + opts
363
+ return list(HTTPConnection.default_socket_options) + opts
349
364
 
350
365
 
351
- class Client(object):
366
+ class Client:
352
367
  """
353
368
  Crate connection client using CrateDB's HTTP API.
354
369
  """
355
370
 
356
- SQL_PATH = '/_sql?types=true'
371
+ SQL_PATH = "/_sql?types=true"
357
372
  """Crate URI path for issuing SQL statements."""
358
373
 
359
374
  retry_interval = 30
@@ -362,25 +377,26 @@ class Client(object):
362
377
  default_server = "http://127.0.0.1:4200"
363
378
  """Default server to use if no servers are given on instantiation."""
364
379
 
365
- def __init__(self,
366
- servers=None,
367
- timeout=None,
368
- backoff_factor=0,
369
- verify_ssl_cert=True,
370
- ca_cert=None,
371
- error_trace=False,
372
- cert_file=None,
373
- key_file=None,
374
- ssl_relax_minimum_version=False,
375
- username=None,
376
- password=None,
377
- schema=None,
378
- pool_size=None,
379
- socket_keepalive=True,
380
- socket_tcp_keepidle=None,
381
- socket_tcp_keepintvl=None,
382
- socket_tcp_keepcnt=None,
383
- ):
380
+ def __init__(
381
+ self,
382
+ servers=None,
383
+ timeout=None,
384
+ backoff_factor=0,
385
+ verify_ssl_cert=True,
386
+ ca_cert=None,
387
+ error_trace=False,
388
+ cert_file=None,
389
+ key_file=None,
390
+ ssl_relax_minimum_version=False,
391
+ username=None,
392
+ password=None,
393
+ schema=None,
394
+ pool_size=None,
395
+ socket_keepalive=True,
396
+ socket_tcp_keepidle=None,
397
+ socket_tcp_keepintvl=None,
398
+ socket_tcp_keepcnt=None,
399
+ ):
384
400
  if not servers:
385
401
  servers = [self.default_server]
386
402
  else:
@@ -396,22 +412,30 @@ class Client(object):
396
412
  if url.password is not None:
397
413
  password = url.password
398
414
  except Exception as ex:
399
- logger.warning("Unable to decode credentials from database "
400
- "URI, so connecting to CrateDB without "
401
- "authentication: {ex}"
402
- .format(ex=ex))
415
+ logger.warning(
416
+ "Unable to decode credentials from database "
417
+ "URI, so connecting to CrateDB without "
418
+ "authentication: {ex}".format(ex=ex)
419
+ )
403
420
 
404
421
  self._active_servers = servers
405
422
  self._inactive_servers = []
406
423
  pool_kw = _pool_kw_args(
407
- verify_ssl_cert, ca_cert, cert_file, key_file, timeout, pool_size,
424
+ verify_ssl_cert,
425
+ ca_cert,
426
+ cert_file,
427
+ key_file,
428
+ timeout,
429
+ pool_size,
430
+ )
431
+ pool_kw.update(
432
+ {
433
+ "socket_keepalive": socket_keepalive,
434
+ "socket_tcp_keepidle": socket_tcp_keepidle,
435
+ "socket_tcp_keepintvl": socket_tcp_keepintvl,
436
+ "socket_tcp_keepcnt": socket_tcp_keepcnt,
437
+ }
408
438
  )
409
- pool_kw.update({
410
- 'socket_keepalive': socket_keepalive,
411
- 'socket_tcp_keepidle': socket_tcp_keepidle,
412
- 'socket_tcp_keepintvl': socket_tcp_keepintvl,
413
- 'socket_tcp_keepcnt': socket_tcp_keepcnt,
414
- })
415
439
  self.ssl_relax_minimum_version = ssl_relax_minimum_version
416
440
  self.backoff_factor = backoff_factor
417
441
  self.server_pool = {}
@@ -425,7 +449,7 @@ class Client(object):
425
449
 
426
450
  self.path = self.SQL_PATH
427
451
  if error_trace:
428
- self.path += '&error_trace=true'
452
+ self.path += "&error_trace=true"
429
453
 
430
454
  def close(self):
431
455
  for server in self.server_pool.values():
@@ -433,8 +457,9 @@ class Client(object):
433
457
 
434
458
  def _create_server(self, server, **pool_kw):
435
459
  kwargs = _remove_certs_for_non_https(server, pool_kw)
436
- # After updating to urllib3 v2, optionally retain support for TLS 1.0 and TLS 1.1,
437
- # in order to support connectivity to older versions of CrateDB.
460
+ # After updating to urllib3 v2, optionally retain support
461
+ # for TLS 1.0 and TLS 1.1, in order to support connectivity
462
+ # to older versions of CrateDB.
438
463
  if self.ssl_relax_minimum_version:
439
464
  _update_pool_kwargs_for_ssl_minimum_version(server, kwargs)
440
465
  self.server_pool[server] = Server(server, **kwargs)
@@ -451,28 +476,26 @@ class Client(object):
451
476
  return None
452
477
 
453
478
  data = _create_sql_payload(stmt, parameters, bulk_parameters)
454
- logger.debug(
455
- 'Sending request to %s with payload: %s', self.path, data)
456
- content = self._json_request('POST', self.path, data=data)
479
+ logger.debug("Sending request to %s with payload: %s", self.path, data)
480
+ content = self._json_request("POST", self.path, data=data)
457
481
  logger.debug("JSON response for stmt(%s): %s", stmt, content)
458
482
 
459
483
  return content
460
484
 
461
485
  def server_infos(self, server):
462
- response = self._request('GET', '/', server=server)
486
+ response = self._request("GET", "/", server=server)
463
487
  _raise_for_status(response)
464
488
  content = _json_from_response(response)
465
489
  node_name = content.get("name")
466
- node_version = content.get('version', {}).get('number', '0.0.0')
490
+ node_version = content.get("version", {}).get("number", "0.0.0")
467
491
  return server, node_name, node_version
468
492
 
469
- def blob_put(self, table, digest, data):
493
+ def blob_put(self, table, digest, data) -> bool:
470
494
  """
471
495
  Stores the contents of the file like @data object in a blob under the
472
496
  given table and digest.
473
497
  """
474
- response = self._request('PUT', _blob_path(table, digest),
475
- data=data)
498
+ response = self._request("PUT", _blob_path(table, digest), data=data)
476
499
  if response.status == 201:
477
500
  # blob created
478
501
  return True
@@ -482,40 +505,43 @@ class Client(object):
482
505
  if response.status in (400, 404):
483
506
  raise BlobLocationNotFoundException(table, digest)
484
507
  _raise_for_status(response)
508
+ return False
485
509
 
486
- def blob_del(self, table, digest):
510
+ def blob_del(self, table, digest) -> bool:
487
511
  """
488
512
  Deletes the blob with given digest under the given table.
489
513
  """
490
- response = self._request('DELETE', _blob_path(table, digest))
514
+ response = self._request("DELETE", _blob_path(table, digest))
491
515
  if response.status == 204:
492
516
  return True
493
517
  if response.status == 404:
494
518
  return False
495
519
  _raise_for_status(response)
520
+ return False
496
521
 
497
522
  def blob_get(self, table, digest, chunk_size=1024 * 128):
498
523
  """
499
524
  Returns a file like object representing the contents of the blob
500
525
  with the given digest.
501
526
  """
502
- response = self._request('GET', _blob_path(table, digest), stream=True)
527
+ response = self._request("GET", _blob_path(table, digest), stream=True)
503
528
  if response.status == 404:
504
529
  raise DigestNotFoundException(table, digest)
505
530
  _raise_for_status(response)
506
531
  return response.stream(amt=chunk_size)
507
532
 
508
- def blob_exists(self, table, digest):
533
+ def blob_exists(self, table, digest) -> bool:
509
534
  """
510
535
  Returns true if the blob with the given digest exists
511
536
  under the given table.
512
537
  """
513
- response = self._request('HEAD', _blob_path(table, digest))
538
+ response = self._request("HEAD", _blob_path(table, digest))
514
539
  if response.status == 200:
515
540
  return True
516
541
  elif response.status == 404:
517
542
  return False
518
543
  _raise_for_status(response)
544
+ return False
519
545
 
520
546
  def _add_server(self, server):
521
547
  with self._lock:
@@ -537,42 +563,45 @@ class Client(object):
537
563
  password=self.password,
538
564
  backoff_factor=self.backoff_factor,
539
565
  schema=self.schema,
540
- **kwargs
566
+ **kwargs,
541
567
  )
542
568
  redirect_location = response.get_redirect_location()
543
569
  if redirect_location and 300 <= response.status <= 308:
544
570
  redirect_server = _server_url(redirect_location)
545
571
  self._add_server(redirect_server)
546
572
  return self._request(
547
- method, path, server=redirect_server, **kwargs)
573
+ method, path, server=redirect_server, **kwargs
574
+ )
548
575
  if not server and response.status in SRV_UNAVAILABLE_STATUSES:
549
576
  with self._lock:
550
577
  # drop server from active ones
551
578
  self._drop_server(next_server, response.reason)
552
579
  else:
553
580
  return response
554
- except (MaxRetryError,
555
- ReadTimeoutError,
556
- SSLError,
557
- HTTPError,
558
- ProxyError,) as ex:
581
+ except (
582
+ MaxRetryError,
583
+ ReadTimeoutError,
584
+ SSLError,
585
+ HTTPError,
586
+ ProxyError,
587
+ ) as ex:
559
588
  ex_message = _ex_to_message(ex)
560
589
  if server:
561
590
  raise ConnectionError(
562
591
  "Server not available, exception: %s" % ex_message
563
- )
592
+ ) from ex
564
593
  preserve_server = False
565
594
  if isinstance(ex, ProtocolError):
566
595
  preserve_server = any(
567
596
  t in [type(arg) for arg in ex.args]
568
597
  for t in PRESERVE_ACTIVE_SERVER_EXCEPTIONS
569
598
  )
570
- if (not preserve_server):
599
+ if not preserve_server:
571
600
  with self._lock:
572
601
  # drop server from active ones
573
602
  self._drop_server(next_server, ex_message)
574
603
  except Exception as e:
575
- raise ProgrammingError(_ex_to_message(e))
604
+ raise ProgrammingError(_ex_to_message(e)) from e
576
605
 
577
606
  def _json_request(self, method, path, data):
578
607
  """
@@ -592,7 +621,7 @@ class Client(object):
592
621
  """
593
622
  with self._lock:
594
623
  inactive_server_count = len(self._inactive_servers)
595
- for i in range(inactive_server_count):
624
+ for _ in range(inactive_server_count):
596
625
  try:
597
626
  ts, server, message = heapq.heappop(self._inactive_servers)
598
627
  except IndexError:
@@ -600,12 +629,14 @@ class Client(object):
600
629
  else:
601
630
  if (ts + self.retry_interval) > time():
602
631
  # Not yet, put it back
603
- heapq.heappush(self._inactive_servers,
604
- (ts, server, message))
632
+ heapq.heappush(
633
+ self._inactive_servers, (ts, server, message)
634
+ )
605
635
  else:
606
636
  self._active_servers.append(server)
607
- logger.warning("Restored server %s into active pool",
608
- server)
637
+ logger.warning(
638
+ "Restored server %s into active pool", server
639
+ )
609
640
 
610
641
  # if none is old enough, use oldest
611
642
  if not self._active_servers:
@@ -639,8 +670,9 @@ class Client(object):
639
670
  # if this is the last server raise exception, otherwise try next
640
671
  if not self._active_servers:
641
672
  raise ConnectionError(
642
- ("No more Servers available, "
643
- "exception from last server: %s") % message)
673
+ ("No more Servers available, " "exception from last server: %s")
674
+ % message
675
+ )
644
676
 
645
677
  def _roundrobin(self):
646
678
  """
@@ -649,4 +681,4 @@ class Client(object):
649
681
  self._active_servers.append(self._active_servers.pop(0))
650
682
 
651
683
  def __repr__(self):
652
- return '<Client {0}>'.format(str(self._active_servers))
684
+ return "<Client {0}>".format(str(self._active_servers))