Skip to content

Commit 4ccea65

Browse files
committed
Merge branch 'main' into feat-661-uuid
Signed-off-by: ff137 <[email protected]>
2 parents 22ef320 + 7fd1671 commit 4ccea65

File tree

6 files changed

+154
-25
lines changed

6 files changed

+154
-25
lines changed

.github/workflows/test.yml

+9-2
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@ on:
44
push:
55
branches:
66
- main
7+
- "release/**"
8+
- "dev/**"
79
pull_request:
810
branches:
911
- "**"
1012

1113
jobs:
1214
test:
13-
runs-on: ${{ matrix.os }}
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 20
17+
env:
18+
NATS_SERVER_VERSION: ${{ matrix.nats_version }}
1419
strategy:
20+
fail-fast: false
1521
matrix:
16-
os: [ubuntu-latest]
1722
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
23+
nats_version: ["v2.10.26", "v2.11.0", "main"]
1824

1925
steps:
2026
- name: Check out repository
@@ -28,6 +34,7 @@ jobs:
2834
- name: Install dependencies
2935
run: |
3036
pip install pipenv
37+
pip install certifi
3138
pipenv install --dev
3239
bash ./scripts/install_nats.sh
3340

nats/aio/client.py

+55-16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import base64
1919
import ipaddress
2020
import logging
21+
import re
2122
import ssl
2223
import string
2324
import time
@@ -59,7 +60,7 @@
5960
)
6061
from .transport import TcpTransport, Transport, WebSocketTransport
6162

62-
__version__ = "2.9.0"
63+
__version__ = "2.10.0"
6364
__lang__ = "python3"
6465
_logger = logging.getLogger(__name__)
6566
PROTOCOL = 1
@@ -134,39 +135,77 @@ def __init__(self, server_version: str) -> None:
134135
self._major_version: Optional[int] = None
135136
self._minor_version: Optional[int] = None
136137
self._patch_version: Optional[int] = None
138+
self._prerelease_version: Optional[str] = None
139+
self._build_version: Optional[str] = None
137140
self._dev_version: Optional[str] = None
138141

139-
# TODO(@orsinium): use cached_property
140142
def parse_version(self) -> None:
141-
v = (self._server_version).split("-")
142-
if len(v) > 1:
143-
self._dev_version = v[1]
144-
tokens = v[0].split(".")
145-
n = len(tokens)
146-
if n > 1:
147-
self._major_version = int(tokens[0])
148-
if n > 2:
149-
self._minor_version = int(tokens[1])
150-
if n > 3:
151-
self._patch_version = int(tokens[2])
143+
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
144+
_SEMVER_REGEX = r"""
145+
^
146+
(?P<major>0|[1-9]\d*)
147+
\.
148+
(?P<minor>0|[1-9]\d*)
149+
\.
150+
(?P<patch>0|[1-9]\d*)
151+
(?:-(?P<prerelease>
152+
(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
153+
(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
154+
))?
155+
(?:\+(?P<buildmetadata>
156+
[0-9a-zA-Z-]+
157+
(?:\.[0-9a-zA-Z-]+)*
158+
))?
159+
$
160+
"""
161+
_REGEX = re.compile(_SEMVER_REGEX, re.VERBOSE)
162+
match = _REGEX.match(self._server_version)
163+
if match is None:
164+
raise ValueError(
165+
f"{self._server_version} is not a valid Semantic Version"
166+
)
167+
matches = match.groupdict()
168+
self._major_version = int(matches["major"])
169+
self._minor_version = int(matches["minor"])
170+
self._patch_version = int(matches["patch"])
171+
self._prerelease_version = matches["prerelease"] or ""
172+
self._build_version = matches["buildmetadata"] or ""
173+
if self._build_version:
174+
self._dev_version = '+'.join([
175+
self._prerelease_version, self._build_version
176+
])
177+
else:
178+
self._dev_version = self._prerelease_version
152179

153180
@property
154181
def major(self) -> int:
155182
if not self._major_version:
156183
self.parse_version()
157-
return self._major_version or 0
184+
return self._major_version
158185

159186
@property
160187
def minor(self) -> int:
161188
if not self._minor_version:
162189
self.parse_version()
163-
return self._minor_version or 0
190+
return self._minor_version
164191

165192
@property
166193
def patch(self) -> int:
167194
if not self._patch_version:
168195
self.parse_version()
169-
return self._patch_version or 0
196+
return self._patch_version
197+
198+
@property
199+
def prerelease(self) -> int:
200+
if not self._prerelease_version:
201+
self.parse_version()
202+
return self._prerelease_version
203+
204+
@property
205+
def build(self) -> int:
206+
if not self._build_version:
207+
self.parse_version()
208+
return self._build_version
170209

171210
@property
172211
def dev(self) -> str:

scripts/install_nats.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
set -e
44

5-
export DEFAULT_NATS_SERVER_VERSION=v2.10.17
5+
export DEFAULT_NATS_SERVER_VERSION=v2.10.26
66

77
export NATS_SERVER_VERSION="${NATS_SERVER_VERSION:=$DEFAULT_NATS_SERVER_VERSION}"
88

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# These are here for GitHub's dependency graph and help with setuptools support in some environments.
55
setup(
66
name="nats-py",
7-
version="2.9.0",
7+
version="2.10.0",
88
license="Apache 2 License",
99
extras_require={
1010
"nkeys": ["nkeys"],

tests/test_client.py

+83-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import nats
1111
import nats.errors
1212
import pytest
13-
from nats.aio.client import Client as NATS, __version__
13+
from nats.aio.client import Client as NATS, ServerVersion, __version__
1414
from nats.json_util import JsonUtil as json
1515
from tests.utils import (
1616
ClusteringDiscoveryAuthTestCase,
@@ -67,6 +67,79 @@ def test_default_connect_command_with_name(self):
6767

6868
self.assertEqual(expected.encode(), got)
6969

70+
def test_semver_parsing(self):
71+
# Test common versions for the NATS Server.
72+
v = ServerVersion("2.10.26")
73+
self.assertEqual(2, v.major)
74+
self.assertEqual(10, v.minor)
75+
self.assertEqual(26, v.patch)
76+
77+
v = ServerVersion("2.10.26+foo")
78+
self.assertEqual(2, v.major)
79+
self.assertEqual(10, v.minor)
80+
self.assertEqual(26, v.patch)
81+
self.assertEqual("foo", v.build)
82+
83+
v = ServerVersion("2.11.1-RC.1")
84+
self.assertEqual(2, v.major)
85+
self.assertEqual(11, v.minor)
86+
self.assertEqual(1, v.patch)
87+
self.assertEqual("RC.1", v.prerelease)
88+
89+
v = ServerVersion("2.11.1-RC.1+syn1")
90+
self.assertEqual(2, v.major)
91+
self.assertEqual(11, v.minor)
92+
self.assertEqual(1, v.patch)
93+
self.assertEqual("RC.1", v.prerelease)
94+
self.assertEqual("syn1", v.build)
95+
96+
v = ServerVersion("2.11.1-beta+syn2")
97+
self.assertEqual(2, v.major)
98+
self.assertEqual(11, v.minor)
99+
self.assertEqual(1, v.patch)
100+
self.assertEqual("beta", v.prerelease)
101+
self.assertEqual("syn2", v.build)
102+
self.assertEqual("beta+syn2", v.dev)
103+
104+
v = ServerVersion("2.11.1-dev")
105+
self.assertEqual(2, v.major)
106+
self.assertEqual(11, v.minor)
107+
self.assertEqual(1, v.patch)
108+
self.assertEqual("dev", v.prerelease)
109+
self.assertEqual("", v.build)
110+
self.assertEqual("dev", v.dev)
111+
112+
with self.assertRaises(ValueError):
113+
v = ServerVersion("aaaaaaaaa")
114+
v.major
115+
116+
with self.assertRaises(ValueError):
117+
v = ServerVersion("2.11.1!dev")
118+
v.major
119+
120+
with self.assertRaises(ValueError):
121+
v = ServerVersion("")
122+
v.major
123+
124+
# Check that some common server versions do not panic.
125+
versions = [
126+
"2.2.2", "2.2.2", "2.2.2", "2.2.2-prerelease+meta", "2.2.2+meta",
127+
"2.2.2+meta-valid", "2.2.2-alpha", "2.2.2-beta",
128+
"2.2.2-alpha.beta", "2.2.2-alpha.beta.1", "2.2.2-alpha.1",
129+
"2.2.2-alpha0.valid", "2.2.2-alpha.0valid",
130+
"2.2.2-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay",
131+
"2.2.2-rc.1+build.1", "2.2.2-rc.1+build.123", "2.2.2-RC.1+build.1",
132+
"2.2.2-RC.1+build.123", "2.2.2-rc.1", "2.2.2-RC.1",
133+
"2.2.2-RC.1+foo", "2.2.2-beta", "2.2.2-DEV-SNAPSHOT",
134+
"2.2.2-SNAPSHOT-123", "2.2.2", "2.2.2", "2.2.2",
135+
"2.2.2+build.1848", "2.2.2-alpha.1227", "2.2.2-alpha+beta"
136+
]
137+
for version in versions:
138+
v = ServerVersion(version)
139+
self.assertEqual(v.major, 2)
140+
self.assertEqual(v.minor, 2)
141+
self.assertTrue(v.patch, 2)
142+
70143

71144
class ClientTest(SingleServerTestCase):
72145

@@ -87,6 +160,15 @@ async def test_default_connect(self):
87160
self.assertTrue(nc.is_closed)
88161
self.assertFalse(nc.is_connected)
89162

163+
@async_test
164+
async def test_connected_server_version(self):
165+
nc = await nats.connect()
166+
version = nc.connected_server_version
167+
self.assertEqual(version.major, 2)
168+
self.assertTrue(version.minor is not None)
169+
self.assertTrue(version.patch is not None)
170+
await nc.close()
171+
90172
@async_test
91173
async def test_default_module_connect(self):
92174
nc = await nats.connect()

tests/test_js.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -2622,8 +2622,9 @@ async def error_handler(e):
26222622
# Check remaining messages in the stream state.
26232623
status = await kv.status()
26242624
# NOTE: Behavior changed here from v2.10.9 => v2.10.10
2625-
# assert status.values == 2
2626-
assert status.values == 1
2625+
# then changed again around v2.10.26.
2626+
assert status.values == 2
2627+
# assert status.values == 1
26272628

26282629
entry = await kv.get("hello")
26292630
assert "hello" == entry.key
@@ -2638,13 +2639,13 @@ async def error_handler(e):
26382639
assert 1 == entry.revision
26392640

26402641
status = await kv.status()
2641-
assert status.values == 1
2642+
assert status.values == 2
26422643

26432644
for i in range(100, 200):
26442645
await kv.put(f"hello.{i}", b"Hello JS KV!")
26452646

26462647
status = await kv.status()
2647-
assert status.values == 101
2648+
assert status.values == 102
26482649

26492650
with pytest.raises(NotFoundError):
26502651
await kv.get("hello.5")

0 commit comments

Comments
 (0)