crate 0.29.0__py2.py3-none-any.whl → 0.30.1__py2.py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. crate/client/__init__.py +1 -1
  2. crate/client/_pep440.py +501 -0
  3. crate/client/connection.py +3 -3
  4. crate/client/sqlalchemy/__init__.py +24 -0
  5. crate/client/sqlalchemy/compat/__init__.py +0 -0
  6. crate/client/sqlalchemy/compat/api13.py +156 -0
  7. crate/client/sqlalchemy/compat/core10.py +264 -0
  8. crate/client/sqlalchemy/compat/core14.py +359 -0
  9. crate/client/sqlalchemy/compat/core20.py +447 -0
  10. crate/client/sqlalchemy/compiler.py +1 -481
  11. crate/client/sqlalchemy/dialect.py +32 -17
  12. crate/client/sqlalchemy/sa_version.py +4 -3
  13. crate/client/sqlalchemy/tests/__init__.py +17 -6
  14. crate/client/sqlalchemy/tests/array_test.py +6 -3
  15. crate/client/sqlalchemy/tests/bulk_test.py +7 -4
  16. crate/client/sqlalchemy/tests/compiler_test.py +10 -9
  17. crate/client/sqlalchemy/tests/connection_test.py +25 -11
  18. crate/client/sqlalchemy/tests/create_table_test.py +19 -16
  19. crate/client/sqlalchemy/tests/datetime_test.py +6 -3
  20. crate/client/sqlalchemy/tests/dialect_test.py +42 -13
  21. crate/client/sqlalchemy/tests/dict_test.py +17 -13
  22. crate/client/sqlalchemy/tests/function_test.py +6 -3
  23. crate/client/sqlalchemy/tests/insert_from_select_test.py +9 -6
  24. crate/client/sqlalchemy/tests/match_test.py +6 -3
  25. crate/client/sqlalchemy/tests/update_test.py +6 -3
  26. crate/client/sqlalchemy/tests/warnings_test.py +33 -0
  27. crate/client/test_connection.py +25 -0
  28. crate/client/tests.py +98 -119
  29. crate/testing/layer.py +1 -1
  30. crate/testing/settings.py +51 -0
  31. crate/testing/test_layer.py +188 -2
  32. crate/testing/tests.py +2 -38
  33. crate/testing/util.py +20 -0
  34. {crate-0.29.0.dist-info → crate-0.30.1.dist-info}/METADATA +10 -8
  35. crate-0.30.1.dist-info/RECORD +53 -0
  36. crate-0.29.0.dist-info/RECORD +0 -44
  37. /crate-0.29.0-py3.9-nspkg.pth → /crate-0.30.1-py3.9-nspkg.pth +0 -0
  38. {crate-0.29.0.dist-info → crate-0.30.1.dist-info}/LICENSE +0 -0
  39. {crate-0.29.0.dist-info → crate-0.30.1.dist-info}/NOTICE +0 -0
  40. {crate-0.29.0.dist-info → crate-0.30.1.dist-info}/WHEEL +0 -0
  41. {crate-0.29.0.dist-info → crate-0.30.1.dist-info}/entry_points.txt +0 -0
  42. {crate-0.29.0.dist-info → crate-0.30.1.dist-info}/namespace_packages.txt +0 -0
  43. {crate-0.29.0.dist-info → crate-0.30.1.dist-info}/top_level.txt +0 -0
crate/client/__init__.py CHANGED
@@ -29,7 +29,7 @@ __all__ = [
29
29
 
30
30
  # version string read from setup.py using a regex. Take care not to break the
31
31
  # regex!
32
- __version__ = "0.29.0"
32
+ __version__ = "0.30.1"
33
33
 
34
34
  apilevel = "2.0"
35
35
  threadsafety = 2
@@ -0,0 +1,501 @@
1
+ """Utility to compare pep440 compatible version strings.
2
+
3
+ The LooseVersion and StrictVersion classes that distutils provides don't
4
+ work; they don't recognize anything like alpha/beta/rc/dev versions.
5
+
6
+ This specific file has been vendored from NumPy on 2023-02-10 [1].
7
+ Its reference location is in `packaging` [2,3].
8
+
9
+ [1] https://github.com/numpy/numpy/blob/v1.25.0.dev0/numpy/compat/_pep440.py
10
+ [2] https://github.com/pypa/packaging/blob/23.0/src/packaging/_structures.py
11
+ [3] https://github.com/pypa/packaging/blob/23.0/src/packaging/version.py
12
+ """
13
+
14
+ # Copyright (c) Donald Stufft and individual contributors.
15
+ # All rights reserved.
16
+
17
+ # Redistribution and use in source and binary forms, with or without
18
+ # modification, are permitted provided that the following conditions are met:
19
+
20
+ # 1. Redistributions of source code must retain the above copyright notice,
21
+ # this list of conditions and the following disclaimer.
22
+
23
+ # 2. Redistributions in binary form must reproduce the above copyright
24
+ # notice, this list of conditions and the following disclaimer in the
25
+ # documentation and/or other materials provided with the distribution.
26
+
27
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37
+ # POSSIBILITY OF SUCH DAMAGE.
38
+
39
+ import collections
40
+ import itertools
41
+ import re
42
+
43
+
44
+ __all__ = [
45
+ "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN",
46
+ ]
47
+
48
+
49
+ # BEGIN packaging/_structures.py
50
+
51
+
52
+ class Infinity:
53
+ def __repr__(self):
54
+ return "Infinity"
55
+
56
+ def __hash__(self):
57
+ return hash(repr(self))
58
+
59
+ def __lt__(self, other):
60
+ return False
61
+
62
+ def __le__(self, other):
63
+ return False
64
+
65
+ def __eq__(self, other):
66
+ return isinstance(other, self.__class__)
67
+
68
+ def __ne__(self, other):
69
+ return not isinstance(other, self.__class__)
70
+
71
+ def __gt__(self, other):
72
+ return True
73
+
74
+ def __ge__(self, other):
75
+ return True
76
+
77
+ def __neg__(self):
78
+ return NegativeInfinity
79
+
80
+
81
+ Infinity = Infinity()
82
+
83
+
84
+ class NegativeInfinity:
85
+ def __repr__(self):
86
+ return "-Infinity"
87
+
88
+ def __hash__(self):
89
+ return hash(repr(self))
90
+
91
+ def __lt__(self, other):
92
+ return True
93
+
94
+ def __le__(self, other):
95
+ return True
96
+
97
+ def __eq__(self, other):
98
+ return isinstance(other, self.__class__)
99
+
100
+ def __ne__(self, other):
101
+ return not isinstance(other, self.__class__)
102
+
103
+ def __gt__(self, other):
104
+ return False
105
+
106
+ def __ge__(self, other):
107
+ return False
108
+
109
+ def __neg__(self):
110
+ return Infinity
111
+
112
+
113
+ # BEGIN packaging/version.py
114
+
115
+
116
+ NegativeInfinity = NegativeInfinity()
117
+
118
+ _Version = collections.namedtuple(
119
+ "_Version",
120
+ ["epoch", "release", "dev", "pre", "post", "local"],
121
+ )
122
+
123
+
124
+ def parse(version):
125
+ """
126
+ Parse the given version string and return either a :class:`Version` object
127
+ or a :class:`LegacyVersion` object depending on if the given version is
128
+ a valid PEP 440 version or a legacy version.
129
+ """
130
+ try:
131
+ return Version(version)
132
+ except InvalidVersion:
133
+ return LegacyVersion(version)
134
+
135
+
136
+ class InvalidVersion(ValueError):
137
+ """
138
+ An invalid version was found, users should refer to PEP 440.
139
+ """
140
+
141
+
142
+ class _BaseVersion:
143
+
144
+ def __hash__(self):
145
+ return hash(self._key)
146
+
147
+ def __lt__(self, other):
148
+ return self._compare(other, lambda s, o: s < o)
149
+
150
+ def __le__(self, other):
151
+ return self._compare(other, lambda s, o: s <= o)
152
+
153
+ def __eq__(self, other):
154
+ return self._compare(other, lambda s, o: s == o)
155
+
156
+ def __ge__(self, other):
157
+ return self._compare(other, lambda s, o: s >= o)
158
+
159
+ def __gt__(self, other):
160
+ return self._compare(other, lambda s, o: s > o)
161
+
162
+ def __ne__(self, other):
163
+ return self._compare(other, lambda s, o: s != o)
164
+
165
+ def _compare(self, other, method):
166
+ if not isinstance(other, _BaseVersion):
167
+ return NotImplemented
168
+
169
+ return method(self._key, other._key)
170
+
171
+
172
+ class LegacyVersion(_BaseVersion):
173
+
174
+ def __init__(self, version):
175
+ self._version = str(version)
176
+ self._key = _legacy_cmpkey(self._version)
177
+
178
+ def __str__(self):
179
+ return self._version
180
+
181
+ def __repr__(self):
182
+ return "<LegacyVersion({0})>".format(repr(str(self)))
183
+
184
+ @property
185
+ def public(self):
186
+ return self._version
187
+
188
+ @property
189
+ def base_version(self):
190
+ return self._version
191
+
192
+ @property
193
+ def local(self):
194
+ return None
195
+
196
+ @property
197
+ def is_prerelease(self):
198
+ return False
199
+
200
+ @property
201
+ def is_postrelease(self):
202
+ return False
203
+
204
+
205
+ _legacy_version_component_re = re.compile(
206
+ r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
207
+ )
208
+
209
+ _legacy_version_replacement_map = {
210
+ "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
211
+ }
212
+
213
+
214
+ def _parse_version_parts(s):
215
+ for part in _legacy_version_component_re.split(s):
216
+ part = _legacy_version_replacement_map.get(part, part)
217
+
218
+ if not part or part == ".":
219
+ continue
220
+
221
+ if part[:1] in "0123456789":
222
+ # pad for numeric comparison
223
+ yield part.zfill(8)
224
+ else:
225
+ yield "*" + part
226
+
227
+ # ensure that alpha/beta/candidate are before final
228
+ yield "*final"
229
+
230
+
231
+ def _legacy_cmpkey(version):
232
+ # We hardcode an epoch of -1 here. A PEP 440 version can only have an epoch
233
+ # greater than or equal to 0. This will effectively put the LegacyVersion,
234
+ # which uses the defacto standard originally implemented by setuptools,
235
+ # as before all PEP 440 versions.
236
+ epoch = -1
237
+
238
+ # This scheme is taken from pkg_resources.parse_version setuptools prior to
239
+ # its adoption of the packaging library.
240
+ parts = []
241
+ for part in _parse_version_parts(version.lower()):
242
+ if part.startswith("*"):
243
+ # remove "-" before a prerelease tag
244
+ if part < "*final":
245
+ while parts and parts[-1] == "*final-":
246
+ parts.pop()
247
+
248
+ # remove trailing zeros from each series of numeric parts
249
+ while parts and parts[-1] == "00000000":
250
+ parts.pop()
251
+
252
+ parts.append(part)
253
+ parts = tuple(parts)
254
+
255
+ return epoch, parts
256
+
257
+
258
+ # Deliberately not anchored to the start and end of the string, to make it
259
+ # easier for 3rd party code to reuse
260
+ VERSION_PATTERN = r"""
261
+ v?
262
+ (?:
263
+ (?:(?P<epoch>[0-9]+)!)? # epoch
264
+ (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
265
+ (?P<pre> # pre-release
266
+ [-_\.]?
267
+ (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
268
+ [-_\.]?
269
+ (?P<pre_n>[0-9]+)?
270
+ )?
271
+ (?P<post> # post release
272
+ (?:-(?P<post_n1>[0-9]+))
273
+ |
274
+ (?:
275
+ [-_\.]?
276
+ (?P<post_l>post|rev|r)
277
+ [-_\.]?
278
+ (?P<post_n2>[0-9]+)?
279
+ )
280
+ )?
281
+ (?P<dev> # dev release
282
+ [-_\.]?
283
+ (?P<dev_l>dev)
284
+ [-_\.]?
285
+ (?P<dev_n>[0-9]+)?
286
+ )?
287
+ )
288
+ (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
289
+ """
290
+
291
+
292
+ class Version(_BaseVersion):
293
+
294
+ _regex = re.compile(
295
+ r"^\s*" + VERSION_PATTERN + r"\s*$",
296
+ re.VERBOSE | re.IGNORECASE,
297
+ )
298
+
299
+ def __init__(self, version):
300
+ # Validate the version and parse it into pieces
301
+ match = self._regex.search(version)
302
+ if not match:
303
+ raise InvalidVersion("Invalid version: '{0}'".format(version))
304
+
305
+ # Store the parsed out pieces of the version
306
+ self._version = _Version(
307
+ epoch=int(match.group("epoch")) if match.group("epoch") else 0,
308
+ release=tuple(int(i) for i in match.group("release").split(".")),
309
+ pre=_parse_letter_version(
310
+ match.group("pre_l"),
311
+ match.group("pre_n"),
312
+ ),
313
+ post=_parse_letter_version(
314
+ match.group("post_l"),
315
+ match.group("post_n1") or match.group("post_n2"),
316
+ ),
317
+ dev=_parse_letter_version(
318
+ match.group("dev_l"),
319
+ match.group("dev_n"),
320
+ ),
321
+ local=_parse_local_version(match.group("local")),
322
+ )
323
+
324
+ # Generate a key which will be used for sorting
325
+ self._key = _cmpkey(
326
+ self._version.epoch,
327
+ self._version.release,
328
+ self._version.pre,
329
+ self._version.post,
330
+ self._version.dev,
331
+ self._version.local,
332
+ )
333
+
334
+ def __repr__(self):
335
+ return "<Version({0})>".format(repr(str(self)))
336
+
337
+ def __str__(self):
338
+ parts = []
339
+
340
+ # Epoch
341
+ if self._version.epoch != 0:
342
+ parts.append("{0}!".format(self._version.epoch))
343
+
344
+ # Release segment
345
+ parts.append(".".join(str(x) for x in self._version.release))
346
+
347
+ # Pre-release
348
+ if self._version.pre is not None:
349
+ parts.append("".join(str(x) for x in self._version.pre))
350
+
351
+ # Post-release
352
+ if self._version.post is not None:
353
+ parts.append(".post{0}".format(self._version.post[1]))
354
+
355
+ # Development release
356
+ if self._version.dev is not None:
357
+ parts.append(".dev{0}".format(self._version.dev[1]))
358
+
359
+ # Local version segment
360
+ if self._version.local is not None:
361
+ parts.append(
362
+ "+{0}".format(".".join(str(x) for x in self._version.local))
363
+ )
364
+
365
+ return "".join(parts)
366
+
367
+ @property
368
+ def public(self):
369
+ return str(self).split("+", 1)[0]
370
+
371
+ @property
372
+ def base_version(self):
373
+ parts = []
374
+
375
+ # Epoch
376
+ if self._version.epoch != 0:
377
+ parts.append("{0}!".format(self._version.epoch))
378
+
379
+ # Release segment
380
+ parts.append(".".join(str(x) for x in self._version.release))
381
+
382
+ return "".join(parts)
383
+
384
+ @property
385
+ def local(self):
386
+ version_string = str(self)
387
+ if "+" in version_string:
388
+ return version_string.split("+", 1)[1]
389
+
390
+ @property
391
+ def is_prerelease(self):
392
+ return bool(self._version.dev or self._version.pre)
393
+
394
+ @property
395
+ def is_postrelease(self):
396
+ return bool(self._version.post)
397
+
398
+ @property
399
+ def version(self) -> tuple:
400
+ """
401
+ PATCH: Return version tuple for backward-compatibility.
402
+ """
403
+ return self._version.release
404
+
405
+
406
+ def _parse_letter_version(letter, number):
407
+ if letter:
408
+ # We assume there is an implicit 0 in a pre-release if there is
409
+ # no numeral associated with it.
410
+ if number is None:
411
+ number = 0
412
+
413
+ # We normalize any letters to their lower-case form
414
+ letter = letter.lower()
415
+
416
+ # We consider some words to be alternate spellings of other words and
417
+ # in those cases we want to normalize the spellings to our preferred
418
+ # spelling.
419
+ if letter == "alpha":
420
+ letter = "a"
421
+ elif letter == "beta":
422
+ letter = "b"
423
+ elif letter in ["c", "pre", "preview"]:
424
+ letter = "rc"
425
+ elif letter in ["rev", "r"]:
426
+ letter = "post"
427
+
428
+ return letter, int(number)
429
+ if not letter and number:
430
+ # We assume that if we are given a number but not given a letter,
431
+ # then this is using the implicit post release syntax (e.g., 1.0-1)
432
+ letter = "post"
433
+
434
+ return letter, int(number)
435
+
436
+
437
+ _local_version_seperators = re.compile(r"[\._-]")
438
+
439
+
440
+ def _parse_local_version(local):
441
+ """
442
+ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
443
+ """
444
+ if local is not None:
445
+ return tuple(
446
+ part.lower() if not part.isdigit() else int(part)
447
+ for part in _local_version_seperators.split(local)
448
+ )
449
+
450
+
451
+ def _cmpkey(epoch, release, pre, post, dev, local):
452
+ # When we compare a release version, we want to compare it with all of the
453
+ # trailing zeros removed. So we'll use a reverse the list, drop all the now
454
+ # leading zeros until we come to something non-zero, then take the rest,
455
+ # re-reverse it back into the correct order, and make it a tuple and use
456
+ # that for our sorting key.
457
+ release = tuple(
458
+ reversed(list(
459
+ itertools.dropwhile(
460
+ lambda x: x == 0,
461
+ reversed(release),
462
+ )
463
+ ))
464
+ )
465
+
466
+ # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
467
+ # We'll do this by abusing the pre-segment, but we _only_ want to do this
468
+ # if there is no pre- or a post-segment. If we have one of those, then
469
+ # the normal sorting rules will handle this case correctly.
470
+ if pre is None and post is None and dev is not None:
471
+ pre = -Infinity
472
+ # Versions without a pre-release (except as noted above) should sort after
473
+ # those with one.
474
+ elif pre is None:
475
+ pre = Infinity
476
+
477
+ # Versions without a post-segment should sort before those with one.
478
+ if post is None:
479
+ post = -Infinity
480
+
481
+ # Versions without a development segment should sort after those with one.
482
+ if dev is None:
483
+ dev = Infinity
484
+
485
+ if local is None:
486
+ # Versions without a local segment should sort before those with one.
487
+ local = -Infinity
488
+ else:
489
+ # Versions with a local segment need that segment parsed to implement
490
+ # the sorting rules in PEP440.
491
+ # - Alphanumeric segments sort before numeric segments
492
+ # - Alphanumeric segments sort lexicographically
493
+ # - Numeric segments sort numerically
494
+ # - Shorter versions sort before longer versions when the prefixes
495
+ # match exactly
496
+ local = tuple(
497
+ (i, "") if isinstance(i, int) else (-Infinity, i)
498
+ for i in local
499
+ )
500
+
501
+ return epoch, release, pre, post, dev, local
@@ -23,7 +23,7 @@ from .cursor import Cursor
23
23
  from .exceptions import ProgrammingError, ConnectionError
24
24
  from .http import Client
25
25
  from .blob import BlobContainer
26
- from distutils.version import StrictVersion
26
+ from ._pep440 import Version
27
27
 
28
28
 
29
29
  class Connection(object):
@@ -192,12 +192,12 @@ class Connection(object):
192
192
  for server in self.client.active_servers:
193
193
  try:
194
194
  _, _, version = self.client.server_infos(server)
195
- version = StrictVersion(version)
195
+ version = Version(version)
196
196
  except (ValueError, ConnectionError):
197
197
  continue
198
198
  if not lowest or version < lowest:
199
199
  lowest = version
200
- return lowest or StrictVersion('0.0.0')
200
+ return lowest or Version('0.0.0')
201
201
 
202
202
  def __repr__(self):
203
203
  return '<Connection {0}>'.format(repr(self.client))
@@ -19,7 +19,31 @@
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 .compat.api13 import monkeypatch_add_exec_driver_sql
22
23
  from .dialect import CrateDialect
24
+ from .sa_version import SA_1_4, SA_VERSION
25
+
26
+
27
+ if SA_VERSION < SA_1_4:
28
+ import textwrap
29
+ import warnings
30
+
31
+ # SQLAlchemy 1.3 is effectively EOL.
32
+ SA13_DEPRECATION_WARNING = textwrap.dedent("""
33
+ WARNING: SQLAlchemy 1.3 is effectively EOL.
34
+
35
+ SQLAlchemy 1.3 is EOL since 2023-01-27.
36
+ Future versions of the CrateDB SQLAlchemy dialect will drop support for SQLAlchemy 1.3.
37
+ It is recommended that you transition to using SQLAlchemy 1.4 or 2.0:
38
+
39
+ - https://docs.sqlalchemy.org/en/14/changelog/migration_14.html
40
+ - https://docs.sqlalchemy.org/en/20/changelog/migration_20.html
41
+ """.lstrip("\n"))
42
+ warnings.warn(message=SA13_DEPRECATION_WARNING, category=DeprecationWarning)
43
+
44
+ # SQLAlchemy 1.3 does not have the `exec_driver_sql` method, so add it.
45
+ monkeypatch_add_exec_driver_sql()
46
+
23
47
 
24
48
  __all__ = [
25
49
  CrateDialect,
File without changes