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