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