Skip to content

Commit d076169

Browse files
authored
Make Server Version Extraction More Flexible (#778)
#250 #261 #771 Just tried to use this library through Ormar->Databases->AsyncPG against a Yugabyte cluster and hit issue 771. Looks like this has been a problem for a while now so going for a complete overhaul of the server version extraction method. Using a groupdict regex against the version string allows for much higher flexibility in extracting what we're looking for and fixes #771 while not breaking any of the existing version patterns.
1 parent da58cd2 commit d076169

File tree

2 files changed

+53
-47
lines changed

2 files changed

+53
-47
lines changed

asyncpg/serverversion.py

+50-47
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,56 @@
55
# the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
66

77

8-
from . import types
8+
import re
9+
10+
from .types import ServerVersion
11+
12+
version_regex = re.compile(
13+
r"(Postgre[^\s]*)?\s*"
14+
r"(?P<major>[0-9]+)\.?"
15+
r"((?P<minor>[0-9]+)\.?)?"
16+
r"(?P<micro>[0-9]+)?"
17+
r"(?P<releaselevel>[a-z]+)?"
18+
r"(?P<serial>[0-9]+)?"
19+
)
920

1021

1122
def split_server_version_string(version_string):
12-
version_string = version_string.strip()
13-
if version_string.startswith('PostgreSQL '):
14-
version_string = version_string[len('PostgreSQL '):]
15-
if version_string.startswith('Postgres-XL'):
16-
version_string = version_string[len('Postgres-XL '):]
17-
18-
# Some distros (e.g Debian) like may inject their branding
19-
# into the numeric version string, so make sure to only look
20-
# at stuff before the first space.
21-
version_string = version_string.split(' ')[0]
22-
parts = version_string.strip().split('.')
23-
if not parts[-1].isdigit():
24-
# release level specified
25-
lastitem = parts[-1]
26-
levelpart = lastitem.rstrip('0123456789').lower()
27-
if levelpart != lastitem:
28-
serial = int(lastitem[len(levelpart):])
29-
else:
30-
serial = 0
31-
32-
level = levelpart.lstrip('0123456789')
33-
if level != levelpart:
34-
parts[-1] = levelpart[:-len(level)]
35-
else:
36-
parts[-1] = 0
37-
else:
38-
level = 'final'
39-
serial = 0
40-
41-
if int(parts[0]) >= 10:
42-
# Since PostgreSQL 10 the versioning scheme has changed.
43-
# 10.x really means 10.0.x. While parsing 10.1
44-
# as (10, 1) may seem less confusing, in practice most
45-
# version checks are written as version[:2], and we
46-
# want to keep that behaviour consistent, i.e not fail
47-
# a major version check due to a bugfix release.
48-
parts.insert(1, 0)
49-
50-
versions = [int(p) for p in parts][:3]
51-
if len(versions) < 3:
52-
versions += [0] * (3 - len(versions))
53-
54-
versions.append(level)
55-
versions.append(serial)
56-
57-
return types.ServerVersion(*versions)
23+
version_match = version_regex.search(version_string)
24+
25+
if version_match is None:
26+
raise ValueError(
27+
"Unable to parse Postgres "
28+
f'version from "{version_string}"'
29+
)
30+
31+
version = version_match.groupdict()
32+
for ver_key, ver_value in version.items():
33+
# Cast all possible versions parts to int
34+
try:
35+
version[ver_key] = int(ver_value)
36+
except (TypeError, ValueError):
37+
pass
38+
39+
if version.get("major") < 10:
40+
return ServerVersion(
41+
version.get("major"),
42+
version.get("minor") or 0,
43+
version.get("micro") or 0,
44+
version.get("releaselevel") or "final",
45+
version.get("serial") or 0,
46+
)
47+
48+
# Since PostgreSQL 10 the versioning scheme has changed.
49+
# 10.x really means 10.0.x. While parsing 10.1
50+
# as (10, 1) may seem less confusing, in practice most
51+
# version checks are written as version[:2], and we
52+
# want to keep that behaviour consistent, i.e not fail
53+
# a major version check due to a bugfix release.
54+
return ServerVersion(
55+
version.get("major"),
56+
0,
57+
version.get("minor") or 0,
58+
version.get("releaselevel") or "final",
59+
version.get("serial") or 0,
60+
)

tests/test_connect.py

+3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ def test_server_version_02(self):
6464
("10.1", (10, 0, 1, 'final', 0),),
6565
("11.1.2", (11, 0, 1, 'final', 0),),
6666
("PostgreSQL 10.1 (Debian 10.1-3)", (10, 0, 1, 'final', 0),),
67+
("PostgreSQL 11.2-YB-2.7.1.1-b0 on x86_64-pc-linux-gnu, "
68+
"compiled by gcc (Homebrew gcc 5.5.0_4) 5.5.0, 64-bit",
69+
(11, 0, 2, "final", 0),),
6770
]
6871
for version, expected in versions:
6972
result = split_server_version_string(version)

0 commit comments

Comments
 (0)