crate 0.29.0__tar.gz → 0.30.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. {crate-0.29.0 → crate-0.30.1}/DEVELOP.rst +30 -1
  2. {crate-0.29.0 → crate-0.30.1}/PKG-INFO +1 -1
  3. {crate-0.29.0 → crate-0.30.1}/docs/_extra/robots.txt +1 -3
  4. {crate-0.29.0 → crate-0.30.1}/setup.cfg +1 -1
  5. {crate-0.29.0 → crate-0.30.1}/setup.py +11 -7
  6. {crate-0.29.0 → crate-0.30.1}/src/crate/client/__init__.py +1 -1
  7. crate-0.30.1/src/crate/client/_pep440.py +501 -0
  8. {crate-0.29.0 → crate-0.30.1}/src/crate/client/connection.py +3 -3
  9. crate-0.30.1/src/crate/client/pki/readme.rst +91 -0
  10. crate-0.30.1/src/crate/client/sqlalchemy/__init__.py +50 -0
  11. crate-0.30.1/src/crate/client/sqlalchemy/compat/__init__.py +0 -0
  12. crate-0.30.1/src/crate/client/sqlalchemy/compat/api13.py +156 -0
  13. crate-0.30.1/src/crate/client/sqlalchemy/compat/core10.py +264 -0
  14. crate-0.30.1/src/crate/client/sqlalchemy/compat/core14.py +359 -0
  15. crate-0.30.1/src/crate/client/sqlalchemy/compat/core20.py +447 -0
  16. crate-0.30.1/src/crate/client/sqlalchemy/compiler.py +228 -0
  17. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/dialect.py +32 -17
  18. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/sa_version.py +4 -3
  19. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/__init__.py +17 -6
  20. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/array_test.py +6 -3
  21. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/bulk_test.py +7 -4
  22. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/compiler_test.py +10 -9
  23. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/connection_test.py +25 -11
  24. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/create_table_test.py +19 -16
  25. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/datetime_test.py +6 -3
  26. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/dialect_test.py +42 -13
  27. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/dict_test.py +17 -13
  28. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/function_test.py +6 -3
  29. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/insert_from_select_test.py +9 -6
  30. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/match_test.py +6 -3
  31. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/update_test.py +6 -3
  32. crate-0.30.1/src/crate/client/sqlalchemy/tests/warnings_test.py +33 -0
  33. {crate-0.29.0 → crate-0.30.1}/src/crate/client/test_connection.py +25 -0
  34. crate-0.29.0/src/crate/client/sqlalchemy/__init__.py → crate-0.30.1/src/crate/testing/settings.py +29 -4
  35. crate-0.30.1/src/crate/testing/test_layer.py +290 -0
  36. crate-0.30.1/src/crate/testing/util.py +20 -0
  37. {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/PKG-INFO +1 -1
  38. {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/SOURCES.txt +9 -12
  39. {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/requires.txt +9 -7
  40. crate-0.29.0/src/crate/client/doctests/blob.txt +0 -97
  41. crate-0.29.0/src/crate/client/doctests/client.txt +0 -278
  42. crate-0.29.0/src/crate/client/doctests/connection.txt +0 -53
  43. crate-0.29.0/src/crate/client/doctests/cursor.txt +0 -442
  44. crate-0.29.0/src/crate/client/doctests/http.txt +0 -206
  45. crate-0.29.0/src/crate/client/doctests/https.txt +0 -104
  46. crate-0.29.0/src/crate/client/doctests/mocking.txt +0 -20
  47. crate-0.29.0/src/crate/client/doctests/sqlalchemy.txt +0 -435
  48. crate-0.29.0/src/crate/client/pki/readme.rst +0 -33
  49. crate-0.29.0/src/crate/client/sqlalchemy/compiler.py +0 -708
  50. crate-0.29.0/src/crate/client/sqlalchemy/doctests/dialect.txt +0 -27
  51. crate-0.29.0/src/crate/client/sqlalchemy/doctests/itests.txt +0 -229
  52. crate-0.29.0/src/crate/client/sqlalchemy/doctests/reflection.txt +0 -42
  53. crate-0.29.0/src/crate/testing/doctests/layer.txt +0 -233
  54. crate-0.29.0/src/crate/testing/test_layer.py +0 -104
  55. {crate-0.29.0 → crate-0.30.1}/CONTRIBUTING.rst +0 -0
  56. {crate-0.29.0 → crate-0.30.1}/LICENSE +0 -0
  57. {crate-0.29.0 → crate-0.30.1}/MANIFEST.in +0 -0
  58. {crate-0.29.0 → crate-0.30.1}/NOTICE +0 -0
  59. {crate-0.29.0 → crate-0.30.1}/README.rst +0 -0
  60. {crate-0.29.0 → crate-0.30.1}/docs/requirements.txt +0 -0
  61. {crate-0.29.0 → crate-0.30.1}/pyproject.toml +0 -0
  62. {crate-0.29.0 → crate-0.30.1}/src/crate/__init__.py +0 -0
  63. {crate-0.29.0 → crate-0.30.1}/src/crate/client/blob.py +0 -0
  64. {crate-0.29.0 → crate-0.30.1}/src/crate/client/converter.py +0 -0
  65. {crate-0.29.0 → crate-0.30.1}/src/crate/client/cursor.py +0 -0
  66. {crate-0.29.0 → crate-0.30.1}/src/crate/client/exceptions.py +0 -0
  67. {crate-0.29.0 → crate-0.30.1}/src/crate/client/http.py +0 -0
  68. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/predicates/__init__.py +0 -0
  69. {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/types.py +0 -0
  70. {crate-0.29.0 → crate-0.30.1}/src/crate/client/test_cursor.py +0 -0
  71. {crate-0.29.0 → crate-0.30.1}/src/crate/client/test_http.py +0 -0
  72. {crate-0.29.0 → crate-0.30.1}/src/crate/client/test_util.py +0 -0
  73. {crate-0.29.0 → crate-0.30.1}/src/crate/testing/__init__.py +0 -0
  74. {crate-0.29.0 → crate-0.30.1}/src/crate/testing/layer.py +1 -1
  75. {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/dependency_links.txt +0 -0
  76. {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/entry_points.txt +0 -0
  77. {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/namespace_packages.txt +0 -0
  78. {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/top_level.txt +0 -0
@@ -24,7 +24,7 @@ see, for example, `useful command-line options for zope-testrunner`_.
24
24
 
25
25
  Run all tests::
26
26
 
27
- ./bin/test
27
+ ./bin/test -vvvv
28
28
 
29
29
  Run specific tests::
30
30
 
@@ -36,6 +36,25 @@ Ignore specific test directories::
36
36
 
37
37
  ./bin/test -vvvv --ignore_dir=testing
38
38
 
39
+ The ``LayerTest`` test cases have quite some overhead. Omitting them will save
40
+ a few cycles (~70 seconds runtime)::
41
+
42
+ ./bin/test -t '!LayerTest'
43
+
44
+ Invoke all tests without integration tests (~15 seconds runtime)::
45
+
46
+ ./bin/test --layer '!crate.testing.layer.crate' --test '!LayerTest'
47
+
48
+ Yet ~130 test cases, but only ~5 seconds runtime::
49
+
50
+ ./bin/test --layer '!crate.testing.layer.crate' --test '!LayerTest' \
51
+ -t '!test_client_threaded' -t '!test_no_retry_on_read_timeout' \
52
+ -t '!test_wait_for_http' -t '!test_table_clustered_by'
53
+
54
+ To inspect the whole list of test cases, run::
55
+
56
+ ./bin/test --list-tests
57
+
39
58
  You can run the tests against multiple Python interpreters with `tox`_::
40
59
 
41
60
  tox
@@ -51,6 +70,15 @@ To run against a single interpreter, you can also invoke::
51
70
  are listening on the default CrateDB transport port to avoid side effects with
52
71
  the test layer.
53
72
 
73
+
74
+ Renew certificates
75
+ ==================
76
+
77
+ For conducting TLS connectivity tests, there are a few X.509 certificates at
78
+ `src/crate/client/pki/*.pem`_. In order to renew them, follow the instructions
79
+ within the README file in this folder.
80
+
81
+
54
82
  Preparing a release
55
83
  ===================
56
84
 
@@ -114,6 +142,7 @@ nothing special you need to do to get the live docs to update.
114
142
  .. _Read the Docs: http://readthedocs.org
115
143
  .. _ReStructuredText: http://docutils.sourceforge.net/rst.html
116
144
  .. _Sphinx: http://sphinx-doc.org/
145
+ .. _src/crate/client/pki/*.pem: https://github.com/crate/crate-python/tree/master/src/crate/client/pki
117
146
  .. _tox: http://testrun.org/tox/latest/
118
147
  .. _twine: https://pypi.python.org/pypi/twine
119
148
  .. _useful command-line options for zope-testrunner: https://pypi.org/project/zope.testrunner/#some-useful-command-line-options-to-get-you-started
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crate
3
- Version: 0.29.0
3
+ Version: 0.30.1
4
4
  Summary: CrateDB Python Client
5
5
  Home-page: https://github.com/crate/crate-python
6
6
  Author: Crate.io
@@ -1,4 +1,2 @@
1
- User-agent: *
2
- Disallow: /
3
-
4
1
  Sitemap: https://crate.io/docs/python/en/latest/site.xml
2
+ User-agent: *
@@ -2,7 +2,7 @@
2
2
  universal = 1
3
3
 
4
4
  [flake8]
5
- ignore = E501, C901, W504
5
+ ignore = E501, C901, W503, W504
6
6
 
7
7
  [egg_info]
8
8
  tag_build =
@@ -60,18 +60,22 @@ setup(
60
60
  },
61
61
  install_requires=['urllib3>=1.9,<2'],
62
62
  extras_require=dict(
63
- sqlalchemy=['sqlalchemy>=1.0,<1.5',
64
- 'geojson>=2.5.0,<3',
63
+ sqlalchemy=['sqlalchemy>=1.0,<2.1',
64
+ 'geojson>=2.5.0,<4',
65
65
  'backports.zoneinfo<1; python_version<"3.9"'],
66
- test=['tox>=3,<4',
67
- 'zope.testing>=4,<5',
66
+ test=['tox>=3,<5',
67
+ 'zope.testing>=4,<6',
68
68
  'zope.testrunner>=5,<6',
69
69
  'zc.customdoctests>=1.0.1,<2',
70
70
  'createcoverage>=1,<2',
71
71
  'stopit>=1.1.2,<2',
72
- 'flake8>=4,<5'],
73
- doc=['sphinx>=3.5,<6',
74
- 'crate-docs-theme'],
72
+ 'flake8>=4,<7',
73
+ 'pytz',
74
+ # `test_http.py` needs `setuptools.ssl_support`
75
+ 'setuptools<57',
76
+ ],
77
+ doc=['sphinx>=3.5,<7',
78
+ 'crate-docs-theme>=0.26.5'],
75
79
  ),
76
80
  python_requires='>=3.4',
77
81
  package_data={'': ['*.txt']},
@@ -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))