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.
- {crate-0.29.0 → crate-0.30.1}/DEVELOP.rst +30 -1
- {crate-0.29.0 → crate-0.30.1}/PKG-INFO +1 -1
- {crate-0.29.0 → crate-0.30.1}/docs/_extra/robots.txt +1 -3
- {crate-0.29.0 → crate-0.30.1}/setup.cfg +1 -1
- {crate-0.29.0 → crate-0.30.1}/setup.py +11 -7
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/__init__.py +1 -1
- crate-0.30.1/src/crate/client/_pep440.py +501 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/connection.py +3 -3
- crate-0.30.1/src/crate/client/pki/readme.rst +91 -0
- crate-0.30.1/src/crate/client/sqlalchemy/__init__.py +50 -0
- crate-0.30.1/src/crate/client/sqlalchemy/compat/__init__.py +0 -0
- crate-0.30.1/src/crate/client/sqlalchemy/compat/api13.py +156 -0
- crate-0.30.1/src/crate/client/sqlalchemy/compat/core10.py +264 -0
- crate-0.30.1/src/crate/client/sqlalchemy/compat/core14.py +359 -0
- crate-0.30.1/src/crate/client/sqlalchemy/compat/core20.py +447 -0
- crate-0.30.1/src/crate/client/sqlalchemy/compiler.py +228 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/dialect.py +32 -17
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/sa_version.py +4 -3
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/__init__.py +17 -6
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/array_test.py +6 -3
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/bulk_test.py +7 -4
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/compiler_test.py +10 -9
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/connection_test.py +25 -11
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/create_table_test.py +19 -16
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/datetime_test.py +6 -3
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/dialect_test.py +42 -13
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/dict_test.py +17 -13
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/function_test.py +6 -3
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/insert_from_select_test.py +9 -6
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/match_test.py +6 -3
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/tests/update_test.py +6 -3
- crate-0.30.1/src/crate/client/sqlalchemy/tests/warnings_test.py +33 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/test_connection.py +25 -0
- crate-0.29.0/src/crate/client/sqlalchemy/__init__.py → crate-0.30.1/src/crate/testing/settings.py +29 -4
- crate-0.30.1/src/crate/testing/test_layer.py +290 -0
- crate-0.30.1/src/crate/testing/util.py +20 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/PKG-INFO +1 -1
- {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/SOURCES.txt +9 -12
- {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/requires.txt +9 -7
- crate-0.29.0/src/crate/client/doctests/blob.txt +0 -97
- crate-0.29.0/src/crate/client/doctests/client.txt +0 -278
- crate-0.29.0/src/crate/client/doctests/connection.txt +0 -53
- crate-0.29.0/src/crate/client/doctests/cursor.txt +0 -442
- crate-0.29.0/src/crate/client/doctests/http.txt +0 -206
- crate-0.29.0/src/crate/client/doctests/https.txt +0 -104
- crate-0.29.0/src/crate/client/doctests/mocking.txt +0 -20
- crate-0.29.0/src/crate/client/doctests/sqlalchemy.txt +0 -435
- crate-0.29.0/src/crate/client/pki/readme.rst +0 -33
- crate-0.29.0/src/crate/client/sqlalchemy/compiler.py +0 -708
- crate-0.29.0/src/crate/client/sqlalchemy/doctests/dialect.txt +0 -27
- crate-0.29.0/src/crate/client/sqlalchemy/doctests/itests.txt +0 -229
- crate-0.29.0/src/crate/client/sqlalchemy/doctests/reflection.txt +0 -42
- crate-0.29.0/src/crate/testing/doctests/layer.txt +0 -233
- crate-0.29.0/src/crate/testing/test_layer.py +0 -104
- {crate-0.29.0 → crate-0.30.1}/CONTRIBUTING.rst +0 -0
- {crate-0.29.0 → crate-0.30.1}/LICENSE +0 -0
- {crate-0.29.0 → crate-0.30.1}/MANIFEST.in +0 -0
- {crate-0.29.0 → crate-0.30.1}/NOTICE +0 -0
- {crate-0.29.0 → crate-0.30.1}/README.rst +0 -0
- {crate-0.29.0 → crate-0.30.1}/docs/requirements.txt +0 -0
- {crate-0.29.0 → crate-0.30.1}/pyproject.toml +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/__init__.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/blob.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/converter.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/cursor.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/exceptions.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/http.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/predicates/__init__.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/sqlalchemy/types.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/test_cursor.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/test_http.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/client/test_util.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/testing/__init__.py +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate/testing/layer.py +1 -1
- {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/dependency_links.txt +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/entry_points.txt +0 -0
- {crate-0.29.0 → crate-0.30.1}/src/crate.egg-info/namespace_packages.txt +0 -0
- {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
|
@@ -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
|
64
|
-
'geojson>=2.5.0,<
|
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,<
|
67
|
-
'zope.testing>=4,<
|
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,<
|
73
|
-
|
74
|
-
|
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']},
|
@@ -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
|
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 =
|
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
|
200
|
+
return lowest or Version('0.0.0')
|
201
201
|
|
202
202
|
def __repr__(self):
|
203
203
|
return '<Connection {0}>'.format(repr(self.client))
|