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/__init__.py +4 -3
- crate/client/blob.py +9 -7
- crate/client/connection.py +58 -52
- crate/client/converter.py +15 -10
- crate/client/cursor.py +55 -51
- crate/client/exceptions.py +5 -3
- crate/client/http.py +192 -160
- crate/testing/__init__.py +0 -1
- crate/testing/layer.py +140 -102
- crate/testing/util.py +80 -5
- {crate-1.0.0.dev1.dist-info → crate-1.0.1.dist-info}/LICENSE +0 -70
- {crate-1.0.0.dev1.dist-info → crate-1.0.1.dist-info}/METADATA +14 -16
- {crate-1.0.0.dev1.dist-info → crate-1.0.1.dist-info}/NOTICE +1 -1
- crate-1.0.1.dist-info/RECORD +17 -0
- {crate-1.0.0.dev1.dist-info → crate-1.0.1.dist-info}/WHEEL +1 -1
- crate/client/test_connection.py +0 -98
- crate/client/test_cursor.py +0 -341
- crate/client/test_exceptions.py +0 -14
- crate/client/test_http.py +0 -678
- crate/client/test_util.py +0 -69
- crate/client/tests.py +0 -340
- crate/testing/settings.py +0 -51
- crate/testing/test_datetime_old.py +0 -90
- crate/testing/test_layer.py +0 -290
- crate/testing/tests.py +0 -34
- crate-1.0.0.dev1-py3.9-nspkg.pth +0 -1
- crate-1.0.0.dev1.dist-info/RECORD +0 -29
- crate-1.0.0.dev1.dist-info/namespace_packages.txt +0 -1
- {crate-1.0.0.dev1.dist-info → crate-1.0.1.dist-info}/top_level.txt +0 -0
crate/client/__init__.py
CHANGED
@@ -23,14 +23,15 @@ from .connection import Connection as connect
|
|
23
23
|
from .exceptions import Error
|
24
24
|
|
25
25
|
__all__ = [
|
26
|
-
connect,
|
27
|
-
Error,
|
26
|
+
"connect",
|
27
|
+
"Error",
|
28
28
|
]
|
29
29
|
|
30
30
|
# version string read from setup.py using a regex. Take care not to break the
|
31
31
|
# regex!
|
32
|
-
__version__ = "1.0.
|
32
|
+
__version__ = "1.0.1"
|
33
33
|
|
34
|
+
# codeql[py/unused-global-variable]
|
34
35
|
apilevel = "2.0"
|
35
36
|
threadsafety = 1
|
36
37
|
paramstyle = "qmark"
|
crate/client/blob.py
CHANGED
@@ -22,8 +22,8 @@
|
|
22
22
|
import hashlib
|
23
23
|
|
24
24
|
|
25
|
-
class BlobContainer
|
26
|
-
"""
|
25
|
+
class BlobContainer:
|
26
|
+
"""class that represents a blob collection in crate.
|
27
27
|
|
28
28
|
can be used to download, upload and delete blobs
|
29
29
|
"""
|
@@ -34,7 +34,7 @@ class BlobContainer(object):
|
|
34
34
|
|
35
35
|
def _compute_digest(self, f):
|
36
36
|
f.seek(0)
|
37
|
-
m = hashlib.sha1()
|
37
|
+
m = hashlib.sha1() # noqa: S324
|
38
38
|
while True:
|
39
39
|
d = f.read(1024 * 32)
|
40
40
|
if not d:
|
@@ -64,8 +64,9 @@ class BlobContainer(object):
|
|
64
64
|
else:
|
65
65
|
actual_digest = self._compute_digest(f)
|
66
66
|
|
67
|
-
created = self.conn.client.blob_put(
|
68
|
-
|
67
|
+
created = self.conn.client.blob_put(
|
68
|
+
self.container_name, actual_digest, f
|
69
|
+
)
|
69
70
|
if digest:
|
70
71
|
return created
|
71
72
|
return actual_digest
|
@@ -78,8 +79,9 @@ class BlobContainer(object):
|
|
78
79
|
:param chunk_size: the size of the chunks returned on each iteration
|
79
80
|
:return: generator returning chunks of data
|
80
81
|
"""
|
81
|
-
return self.conn.client.blob_get(
|
82
|
-
|
82
|
+
return self.conn.client.blob_get(
|
83
|
+
self.container_name, digest, chunk_size
|
84
|
+
)
|
83
85
|
|
84
86
|
def delete(self, digest):
|
85
87
|
"""
|
crate/client/connection.py
CHANGED
@@ -19,37 +19,38 @@
|
|
19
19
|
# with Crate these terms will supersede the license and you may use the
|
20
20
|
# software solely pursuant to the terms of the relevant commercial agreement.
|
21
21
|
|
22
|
+
from verlib2 import Version
|
23
|
+
|
24
|
+
from .blob import BlobContainer
|
22
25
|
from .cursor import Cursor
|
23
|
-
from .exceptions import
|
26
|
+
from .exceptions import ConnectionError, ProgrammingError
|
24
27
|
from .http import Client
|
25
|
-
from .blob import BlobContainer
|
26
|
-
from verlib2 import Version
|
27
28
|
|
28
29
|
|
29
|
-
class Connection
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
30
|
+
class Connection:
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
servers=None,
|
34
|
+
timeout=None,
|
35
|
+
backoff_factor=0,
|
36
|
+
client=None,
|
37
|
+
verify_ssl_cert=True,
|
38
|
+
ca_cert=None,
|
39
|
+
error_trace=False,
|
40
|
+
cert_file=None,
|
41
|
+
key_file=None,
|
42
|
+
ssl_relax_minimum_version=False,
|
43
|
+
username=None,
|
44
|
+
password=None,
|
45
|
+
schema=None,
|
46
|
+
pool_size=None,
|
47
|
+
socket_keepalive=True,
|
48
|
+
socket_tcp_keepidle=None,
|
49
|
+
socket_tcp_keepintvl=None,
|
50
|
+
socket_tcp_keepcnt=None,
|
51
|
+
converter=None,
|
52
|
+
time_zone=None,
|
53
|
+
):
|
53
54
|
"""
|
54
55
|
:param servers:
|
55
56
|
either a string in the form of '<hostname>:<port>'
|
@@ -118,12 +119,16 @@ class Connection(object):
|
|
118
119
|
- ``zoneinfo.ZoneInfo("Australia/Sydney")``
|
119
120
|
- ``+0530`` (UTC offset in string format)
|
120
121
|
|
122
|
+
The driver always returns timezone-"aware" `datetime` objects,
|
123
|
+
with their `tzinfo` attribute set.
|
124
|
+
|
121
125
|
When `time_zone` is `None`, the returned `datetime` objects are
|
122
|
-
|
126
|
+
using Coordinated Universal Time (UTC), because CrateDB is storing
|
127
|
+
timestamp values in this format.
|
123
128
|
|
124
|
-
When `time_zone` is given, the
|
125
|
-
|
126
|
-
"""
|
129
|
+
When `time_zone` is given, the timestamp values will be transparently
|
130
|
+
converted from UTC to use the given time zone.
|
131
|
+
""" # noqa: E501
|
127
132
|
|
128
133
|
self._converter = converter
|
129
134
|
self.time_zone = time_zone
|
@@ -131,24 +136,25 @@ class Connection(object):
|
|
131
136
|
if client:
|
132
137
|
self.client = client
|
133
138
|
else:
|
134
|
-
self.client = Client(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
139
|
+
self.client = Client(
|
140
|
+
servers,
|
141
|
+
timeout=timeout,
|
142
|
+
backoff_factor=backoff_factor,
|
143
|
+
verify_ssl_cert=verify_ssl_cert,
|
144
|
+
ca_cert=ca_cert,
|
145
|
+
error_trace=error_trace,
|
146
|
+
cert_file=cert_file,
|
147
|
+
key_file=key_file,
|
148
|
+
ssl_relax_minimum_version=ssl_relax_minimum_version,
|
149
|
+
username=username,
|
150
|
+
password=password,
|
151
|
+
schema=schema,
|
152
|
+
pool_size=pool_size,
|
153
|
+
socket_keepalive=socket_keepalive,
|
154
|
+
socket_tcp_keepidle=socket_tcp_keepidle,
|
155
|
+
socket_tcp_keepintvl=socket_tcp_keepintvl,
|
156
|
+
socket_tcp_keepcnt=socket_tcp_keepcnt,
|
157
|
+
)
|
152
158
|
self.lowest_server_version = self._lowest_server_version()
|
153
159
|
self._closed = False
|
154
160
|
|
@@ -182,7 +188,7 @@ class Connection(object):
|
|
182
188
|
raise ProgrammingError("Connection closed")
|
183
189
|
|
184
190
|
def get_blob_container(self, container_name):
|
185
|
-
"""
|
191
|
+
"""Retrieve a BlobContainer for `container_name`
|
186
192
|
|
187
193
|
:param container_name: the name of the BLOB container.
|
188
194
|
:returns: a :class:ContainerObject
|
@@ -199,10 +205,10 @@ class Connection(object):
|
|
199
205
|
continue
|
200
206
|
if not lowest or version < lowest:
|
201
207
|
lowest = version
|
202
|
-
return lowest or Version(
|
208
|
+
return lowest or Version("0.0.0")
|
203
209
|
|
204
210
|
def __repr__(self):
|
205
|
-
return
|
211
|
+
return "<Connection {0}>".format(repr(self.client))
|
206
212
|
|
207
213
|
def __enter__(self):
|
208
214
|
return self
|
crate/client/converter.py
CHANGED
@@ -23,9 +23,10 @@ Machinery for converting CrateDB database types to native Python data types.
|
|
23
23
|
|
24
24
|
https://crate.io/docs/crate/reference/en/latest/interfaces/http.html#column-types
|
25
25
|
"""
|
26
|
+
|
27
|
+
import datetime as dt
|
26
28
|
import ipaddress
|
27
29
|
from copy import deepcopy
|
28
|
-
from datetime import datetime
|
29
30
|
from enum import Enum
|
30
31
|
from typing import Any, Callable, Dict, List, Optional, Union
|
31
32
|
|
@@ -33,7 +34,9 @@ ConverterFunction = Callable[[Optional[Any]], Optional[Any]]
|
|
33
34
|
ColTypesDefinition = Union[int, List[Union[int, "ColTypesDefinition"]]]
|
34
35
|
|
35
36
|
|
36
|
-
def _to_ipaddress(
|
37
|
+
def _to_ipaddress(
|
38
|
+
value: Optional[str],
|
39
|
+
) -> Optional[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]:
|
37
40
|
"""
|
38
41
|
https://docs.python.org/3/library/ipaddress.html
|
39
42
|
"""
|
@@ -42,20 +45,20 @@ def _to_ipaddress(value: Optional[str]) -> Optional[Union[ipaddress.IPv4Address,
|
|
42
45
|
return ipaddress.ip_address(value)
|
43
46
|
|
44
47
|
|
45
|
-
def _to_datetime(value: Optional[float]) -> Optional[datetime]:
|
48
|
+
def _to_datetime(value: Optional[float]) -> Optional[dt.datetime]:
|
46
49
|
"""
|
47
50
|
https://docs.python.org/3/library/datetime.html
|
48
51
|
"""
|
49
52
|
if value is None:
|
50
53
|
return None
|
51
|
-
return datetime.
|
54
|
+
return dt.datetime.fromtimestamp(value / 1e3, tz=dt.timezone.utc)
|
52
55
|
|
53
56
|
|
54
57
|
def _to_default(value: Optional[Any]) -> Optional[Any]:
|
55
58
|
return value
|
56
59
|
|
57
60
|
|
58
|
-
#
|
61
|
+
# Data type identifiers defined by the CrateDB HTTP interface.
|
59
62
|
# https://crate.io/docs/crate/reference/en/latest/interfaces/http.html#column-types
|
60
63
|
class DataType(Enum):
|
61
64
|
NULL = 0
|
@@ -112,7 +115,9 @@ class Converter:
|
|
112
115
|
return self._mappings.get(DataType(type_), self._default)
|
113
116
|
type_, inner_type = type_
|
114
117
|
if DataType(type_) is not DataType.ARRAY:
|
115
|
-
raise ValueError(
|
118
|
+
raise ValueError(
|
119
|
+
f"Data type {type_} is not implemented as collection type"
|
120
|
+
)
|
116
121
|
|
117
122
|
inner_convert = self.get(inner_type)
|
118
123
|
|
@@ -128,11 +133,11 @@ class Converter:
|
|
128
133
|
|
129
134
|
|
130
135
|
class DefaultTypeConverter(Converter):
|
131
|
-
def __init__(
|
136
|
+
def __init__(
|
137
|
+
self, more_mappings: Optional[ConverterMapping] = None
|
138
|
+
) -> None:
|
132
139
|
mappings: ConverterMapping = {}
|
133
140
|
mappings.update(deepcopy(_DEFAULT_CONVERTERS))
|
134
141
|
if more_mappings:
|
135
142
|
mappings.update(deepcopy(more_mappings))
|
136
|
-
super().__init__(
|
137
|
-
mappings=mappings, default=_to_default
|
138
|
-
)
|
143
|
+
super().__init__(mappings=mappings, default=_to_default)
|
crate/client/cursor.py
CHANGED
@@ -18,21 +18,20 @@
|
|
18
18
|
# However, if you have executed another commercial license agreement
|
19
19
|
# with Crate these terms will supersede the license and you may use the
|
20
20
|
# software solely pursuant to the terms of the relevant commercial agreement.
|
21
|
-
from datetime import datetime, timedelta, timezone
|
22
|
-
|
23
|
-
from .converter import DataType
|
24
|
-
import warnings
|
25
21
|
import typing as t
|
22
|
+
import warnings
|
23
|
+
from datetime import datetime, timedelta, timezone
|
26
24
|
|
27
|
-
from .converter import Converter
|
25
|
+
from .converter import Converter, DataType
|
28
26
|
from .exceptions import ProgrammingError
|
29
27
|
|
30
28
|
|
31
|
-
class Cursor
|
29
|
+
class Cursor:
|
32
30
|
"""
|
33
31
|
not thread-safe by intention
|
34
32
|
should not be shared between different threads
|
35
33
|
"""
|
34
|
+
|
36
35
|
lastrowid = None # currently not supported
|
37
36
|
|
38
37
|
def __init__(self, connection, converter: Converter, **kwargs):
|
@@ -40,7 +39,7 @@ class Cursor(object):
|
|
40
39
|
self.connection = connection
|
41
40
|
self._converter = converter
|
42
41
|
self._closed = False
|
43
|
-
self._result =
|
42
|
+
self._result: t.Dict[str, t.Any] = {}
|
44
43
|
self.rows = None
|
45
44
|
self._time_zone = None
|
46
45
|
self.time_zone = kwargs.get("time_zone")
|
@@ -55,8 +54,9 @@ class Cursor(object):
|
|
55
54
|
if self._closed:
|
56
55
|
raise ProgrammingError("Cursor closed")
|
57
56
|
|
58
|
-
self._result = self.connection.client.sql(
|
59
|
-
|
57
|
+
self._result = self.connection.client.sql(
|
58
|
+
sql, parameters, bulk_parameters
|
59
|
+
)
|
60
60
|
if "rows" in self._result:
|
61
61
|
if self._converter is None:
|
62
62
|
self.rows = iter(self._result["rows"])
|
@@ -73,9 +73,9 @@ class Cursor(object):
|
|
73
73
|
durations = []
|
74
74
|
self.execute(sql, bulk_parameters=seq_of_parameters)
|
75
75
|
|
76
|
-
for result in self._result.get(
|
77
|
-
if result.get(
|
78
|
-
row_counts.append(result.get(
|
76
|
+
for result in self._result.get("results", []):
|
77
|
+
if result.get("rowcount") > -1:
|
78
|
+
row_counts.append(result.get("rowcount"))
|
79
79
|
if self.duration > -1:
|
80
80
|
durations.append(self.duration)
|
81
81
|
|
@@ -85,7 +85,7 @@ class Cursor(object):
|
|
85
85
|
"rows": [],
|
86
86
|
"cols": self._result.get("cols", []),
|
87
87
|
"col_types": self._result.get("col_types", []),
|
88
|
-
"results": self._result.get("results")
|
88
|
+
"results": self._result.get("results"),
|
89
89
|
}
|
90
90
|
if self._converter is None:
|
91
91
|
self.rows = iter(self._result["rows"])
|
@@ -112,7 +112,7 @@ class Cursor(object):
|
|
112
112
|
This iterator is shared. Advancing this iterator will advance other
|
113
113
|
iterators created from this cursor.
|
114
114
|
"""
|
115
|
-
warnings.warn("DB-API extension cursor.__iter__() used")
|
115
|
+
warnings.warn("DB-API extension cursor.__iter__() used", stacklevel=2)
|
116
116
|
return self
|
117
117
|
|
118
118
|
def fetchmany(self, count=None):
|
@@ -126,7 +126,7 @@ class Cursor(object):
|
|
126
126
|
if count == 0:
|
127
127
|
return self.fetchall()
|
128
128
|
result = []
|
129
|
-
for
|
129
|
+
for _ in range(count):
|
130
130
|
try:
|
131
131
|
result.append(self.next())
|
132
132
|
except StopIteration:
|
@@ -153,7 +153,7 @@ class Cursor(object):
|
|
153
153
|
Close the cursor now
|
154
154
|
"""
|
155
155
|
self._closed = True
|
156
|
-
self._result =
|
156
|
+
self._result = {}
|
157
157
|
|
158
158
|
def setinputsizes(self, sizes):
|
159
159
|
"""
|
@@ -174,7 +174,7 @@ class Cursor(object):
|
|
174
174
|
.execute*() produced (for DQL statements like ``SELECT``) or affected
|
175
175
|
(for DML statements like ``UPDATE`` or ``INSERT``).
|
176
176
|
"""
|
177
|
-
if
|
177
|
+
if self._closed or not self._result or "rows" not in self._result:
|
178
178
|
return -1
|
179
179
|
return self._result.get("rowcount", -1)
|
180
180
|
|
@@ -185,10 +185,10 @@ class Cursor(object):
|
|
185
185
|
"""
|
186
186
|
if self.rows is None:
|
187
187
|
raise ProgrammingError(
|
188
|
-
"No result available. "
|
189
|
-
"execute() or executemany() must be called first."
|
188
|
+
"No result available. "
|
189
|
+
+ "execute() or executemany() must be called first."
|
190
190
|
)
|
191
|
-
|
191
|
+
if not self._closed:
|
192
192
|
return next(self.rows)
|
193
193
|
else:
|
194
194
|
raise ProgrammingError("Cursor closed")
|
@@ -201,17 +201,11 @@ class Cursor(object):
|
|
201
201
|
This read-only attribute is a sequence of 7-item sequences.
|
202
202
|
"""
|
203
203
|
if self._closed:
|
204
|
-
return
|
204
|
+
return None
|
205
205
|
|
206
206
|
description = []
|
207
207
|
for col in self._result["cols"]:
|
208
|
-
description.append((col,
|
209
|
-
None,
|
210
|
-
None,
|
211
|
-
None,
|
212
|
-
None,
|
213
|
-
None,
|
214
|
-
None))
|
208
|
+
description.append((col, None, None, None, None, None, None))
|
215
209
|
return tuple(description)
|
216
210
|
|
217
211
|
@property
|
@@ -220,9 +214,7 @@ class Cursor(object):
|
|
220
214
|
This read-only attribute specifies the server-side duration of a query
|
221
215
|
in milliseconds.
|
222
216
|
"""
|
223
|
-
if self._closed or
|
224
|
-
not self._result or \
|
225
|
-
"duration" not in self._result:
|
217
|
+
if self._closed or not self._result or "duration" not in self._result:
|
226
218
|
return -1
|
227
219
|
return self._result.get("duration", 0)
|
228
220
|
|
@@ -230,22 +222,21 @@ class Cursor(object):
|
|
230
222
|
"""
|
231
223
|
Iterate rows, apply type converters, and generate converted rows.
|
232
224
|
"""
|
233
|
-
|
234
|
-
|
225
|
+
if not ("col_types" in self._result and self._result["col_types"]):
|
226
|
+
raise ValueError(
|
227
|
+
"Unable to apply type conversion "
|
228
|
+
"without `col_types` information"
|
229
|
+
)
|
235
230
|
|
236
|
-
# Resolve `col_types` definition to converter functions. Running
|
237
|
-
# redundantly on each row loop iteration would be a
|
231
|
+
# Resolve `col_types` definition to converter functions. Running
|
232
|
+
# the lookup redundantly on each row loop iteration would be a
|
233
|
+
# huge performance hog.
|
238
234
|
types = self._result["col_types"]
|
239
|
-
converters = [
|
240
|
-
self._converter.get(type) for type in types
|
241
|
-
]
|
235
|
+
converters = [self._converter.get(type_) for type_ in types]
|
242
236
|
|
243
237
|
# Process result rows with conversion.
|
244
238
|
for row in self._result["rows"]:
|
245
|
-
yield [
|
246
|
-
convert(value)
|
247
|
-
for convert, value in zip(converters, row)
|
248
|
-
]
|
239
|
+
yield [convert(value) for convert, value in zip(converters, row)]
|
249
240
|
|
250
241
|
@property
|
251
242
|
def time_zone(self):
|
@@ -267,11 +258,15 @@ class Cursor(object):
|
|
267
258
|
- ``zoneinfo.ZoneInfo("Australia/Sydney")``
|
268
259
|
- ``+0530`` (UTC offset in string format)
|
269
260
|
|
261
|
+
The driver always returns timezone-"aware" `datetime` objects,
|
262
|
+
with their `tzinfo` attribute set.
|
263
|
+
|
270
264
|
When `time_zone` is `None`, the returned `datetime` objects are
|
271
|
-
|
265
|
+
using Coordinated Universal Time (UTC), because CrateDB is storing
|
266
|
+
timestamp values in this format.
|
272
267
|
|
273
|
-
When `time_zone` is given, the
|
274
|
-
|
268
|
+
When `time_zone` is given, the timestamp values will be transparently
|
269
|
+
converted from UTC to use the given time zone.
|
275
270
|
"""
|
276
271
|
|
277
272
|
# Do nothing when time zone is reset.
|
@@ -279,18 +274,22 @@ class Cursor(object):
|
|
279
274
|
self._time_zone = None
|
280
275
|
return
|
281
276
|
|
282
|
-
# Requesting datetime-aware `datetime` objects
|
277
|
+
# Requesting datetime-aware `datetime` objects
|
278
|
+
# needs the data type converter.
|
283
279
|
# Implicitly create one, when needed.
|
284
280
|
if self._converter is None:
|
285
281
|
self._converter = Converter()
|
286
282
|
|
287
|
-
# When the time zone is given as a string,
|
283
|
+
# When the time zone is given as a string,
|
284
|
+
# assume UTC offset format, e.g. `+0530`.
|
288
285
|
if isinstance(tz, str):
|
289
286
|
tz = self._timezone_from_utc_offset(tz)
|
290
287
|
|
291
288
|
self._time_zone = tz
|
292
289
|
|
293
|
-
def _to_datetime_with_tz(
|
290
|
+
def _to_datetime_with_tz(
|
291
|
+
value: t.Optional[float],
|
292
|
+
) -> t.Optional[datetime]:
|
294
293
|
"""
|
295
294
|
Convert CrateDB's `TIMESTAMP` value to a native Python `datetime`
|
296
295
|
object, with timezone-awareness.
|
@@ -306,12 +305,17 @@ class Cursor(object):
|
|
306
305
|
@staticmethod
|
307
306
|
def _timezone_from_utc_offset(tz) -> timezone:
|
308
307
|
"""
|
309
|
-
|
308
|
+
UTC offset in string format (e.g. `+0530`) to `datetime.timezone`.
|
310
309
|
"""
|
311
|
-
|
310
|
+
if len(tz) != 5:
|
311
|
+
raise ValueError(
|
312
|
+
f"Time zone '{tz}' is given in invalid UTC offset format"
|
313
|
+
)
|
312
314
|
try:
|
313
315
|
hours = int(tz[:3])
|
314
316
|
minutes = int(tz[0] + tz[3:])
|
315
317
|
return timezone(timedelta(hours=hours, minutes=minutes), name=tz)
|
316
318
|
except Exception as ex:
|
317
|
-
raise ValueError(
|
319
|
+
raise ValueError(
|
320
|
+
f"Time zone '{tz}' is given in invalid UTC offset format: {ex}"
|
321
|
+
) from ex
|
crate/client/exceptions.py
CHANGED
@@ -21,7 +21,6 @@
|
|
21
21
|
|
22
22
|
|
23
23
|
class Error(Exception):
|
24
|
-
|
25
24
|
def __init__(self, msg=None, error_trace=None):
|
26
25
|
# for compatibility reasons we want to keep the exception message
|
27
26
|
# attribute because clients may depend on it
|
@@ -36,7 +35,8 @@ class Error(Exception):
|
|
36
35
|
return "\n".join([super().__str__(), str(self.error_trace)])
|
37
36
|
|
38
37
|
|
39
|
-
|
38
|
+
# A001 Variable `Warning` is shadowing a Python builtin
|
39
|
+
class Warning(Exception): # noqa: A001
|
40
40
|
pass
|
41
41
|
|
42
42
|
|
@@ -74,7 +74,9 @@ class NotSupportedError(DatabaseError):
|
|
74
74
|
|
75
75
|
# exceptions not in db api
|
76
76
|
|
77
|
-
|
77
|
+
|
78
|
+
# A001 Variable `ConnectionError` is shadowing a Python builtin
|
79
|
+
class ConnectionError(OperationalError): # noqa: A001
|
78
80
|
pass
|
79
81
|
|
80
82
|
|