Skip to content

Commit c920fc8

Browse files
committed
semver: improve constraint parsing
This change replaces the custom regex used with `packaging.version.VERSION_PATTERN` for consistency with other parts of the code base. Additionally, this fixes previous issues with parsing pre-release dev releases etc.
1 parent ce38eb4 commit c920fc8

File tree

4 files changed

+62
-37
lines changed

4 files changed

+62
-37
lines changed

src/poetry/core/semver/helpers.py

+9-23
Original file line numberDiff line numberDiff line change
@@ -63,26 +63,19 @@ def parse_single_constraint(constraint: str) -> VersionConstraint:
6363
# Tilde range
6464
m = TILDE_CONSTRAINT.match(constraint)
6565
if m:
66-
version = Version.parse(m.group(1))
66+
version = Version.parse(m.group("version"))
6767
high = version.stable.next_minor()
68-
if len(m.group(1).split(".")) == 1:
68+
if version.release.precision == 1:
6969
high = version.stable.next_major()
7070

7171
return VersionRange(version, high, include_min=True)
7272

7373
# PEP 440 Tilde range (~=)
7474
m = TILDE_PEP440_CONSTRAINT.match(constraint)
7575
if m:
76-
precision = 1
77-
if m.group(3):
78-
precision += 1
79-
80-
if m.group(4):
81-
precision += 1
82-
83-
version = Version.parse(m.group(1))
84-
85-
if precision == 2:
76+
version = Version.parse(m.group("version"))
77+
print(version)
78+
if version.release.precision == 2:
8679
high = version.stable.next_major()
8780
else:
8881
high = version.stable.next_minor()
@@ -92,14 +85,14 @@ def parse_single_constraint(constraint: str) -> VersionConstraint:
9285
# Caret range
9386
m = CARET_CONSTRAINT.match(constraint)
9487
if m:
95-
version = Version.parse(m.group(1))
88+
version = Version.parse(m.group("version"))
9689

9790
return VersionRange(version, version.next_breaking(), include_min=True)
9891

9992
# X Range
10093
m = X_CONSTRAINT.match(constraint)
10194
if m:
102-
op = m.group(1)
95+
op = m.group("op")
10396
major = int(m.group(2))
10497
minor = m.group(3)
10598

@@ -124,15 +117,8 @@ def parse_single_constraint(constraint: str) -> VersionConstraint:
124117
# Basic comparator
125118
m = BASIC_CONSTRAINT.match(constraint)
126119
if m:
127-
op = m.group(1)
128-
version_string = m.group(2)
129-
130-
# Technically invalid constraints like `>= 3.*` will appear
131-
# here as `3.`.
132-
# Pip currently supports these and to avoid breaking existing
133-
# users workflows we need to support them as well. To do so,
134-
# we just remove the inconsequential part.
135-
version_string = version_string.rstrip(".")
120+
op = m.group("op")
121+
version_string = m.group("version")
136122

137123
if version_string == "dev":
138124
version_string = "0.0-dev"

src/poetry/core/semver/patterns.py

+20-14
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,27 @@
22

33
import re
44

5+
from packaging.version import VERSION_PATTERN
56

6-
MODIFIERS = (
7-
"[._-]?"
8-
r"((?!post)(?:beta|b|c|pre|RC|alpha|a|patch|pl|p|dev)(?:(?:[.-]?\d+)*)?)?"
9-
r"([+-]?([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?"
10-
)
117

12-
_COMPLETE_VERSION = (
13-
rf"v?(?:\d+!)?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?{MODIFIERS}(?:\+[^\s]+)?"
14-
)
8+
COMPLETE_VERSION = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE)
159

16-
COMPLETE_VERSION = re.compile("(?i)" + _COMPLETE_VERSION)
10+
CARET_CONSTRAINT = re.compile(
11+
rf"^\^(?P<version>{VERSION_PATTERN})$", re.VERBOSE | re.IGNORECASE
12+
)
13+
TILDE_CONSTRAINT = re.compile(
14+
rf"^~(?!=)\s*(?P<version>{VERSION_PATTERN})$", re.VERBOSE | re.IGNORECASE
15+
)
16+
TILDE_PEP440_CONSTRAINT = re.compile(
17+
rf"^~=\s*(?P<version>{VERSION_PATTERN})\s*$", re.VERBOSE | re.IGNORECASE
18+
)
19+
X_CONSTRAINT = re.compile(
20+
r"^(?P<op>!=|==)?\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.[xX*])+$"
21+
)
1722

18-
CARET_CONSTRAINT = re.compile(rf"(?i)^\^({_COMPLETE_VERSION})$")
19-
TILDE_CONSTRAINT = re.compile(rf"(?i)^~(?!=)\s*({_COMPLETE_VERSION})$")
20-
TILDE_PEP440_CONSTRAINT = re.compile(rf"(?i)^~=\s*({_COMPLETE_VERSION})$")
21-
X_CONSTRAINT = re.compile(r"^(!=|==)?\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.[xX*])+$")
22-
BASIC_CONSTRAINT = re.compile(rf"(?i)^(<>|!=|>=?|<=?|==?)?\s*({_COMPLETE_VERSION}|dev)")
23+
# note that we also allow technically incorrect version patterns with astrix (eg: 3.5.*)
24+
# as this is supported by pip and appears in metadata within python packages
25+
BASIC_CONSTRAINT = re.compile(
26+
rf"^(?P<op><>|!=|>=?|<=?|==?)?\s*(?P<version>{VERSION_PATTERN}|dev)(\.\*)?\s*$",
27+
re.VERBOSE | re.IGNORECASE,
28+
)

tests/semver/test_parse_constraint.py

+32
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,28 @@
182182
include_min=True,
183183
),
184184
),
185+
(
186+
"^1.0.0a1.dev0",
187+
VersionRange(
188+
min=Version.from_parts(
189+
1, 0, 0, pre=ReleaseTag("a", 1), dev=ReleaseTag("dev", 0)
190+
),
191+
max=Version.from_parts(2, 0, 0),
192+
include_min=True,
193+
),
194+
),
195+
(
196+
"1.0.0a1.dev0",
197+
VersionRange(
198+
min=Version.from_parts(
199+
1, 0, 0, pre=ReleaseTag("a", 1), dev=ReleaseTag("dev", 0)
200+
),
201+
max=Version.from_parts(
202+
1, 0, 0, pre=ReleaseTag("a", 1), dev=ReleaseTag("dev", 0)
203+
),
204+
include_min=True,
205+
),
206+
),
185207
(
186208
"~1.0.0a1",
187209
VersionRange(
@@ -190,6 +212,16 @@
190212
include_min=True,
191213
),
192214
),
215+
(
216+
"~1.0.0a1.dev0",
217+
VersionRange(
218+
min=Version.from_parts(
219+
1, 0, 0, pre=ReleaseTag("a", 1), dev=ReleaseTag("dev", 0)
220+
),
221+
max=Version.from_parts(1, 1, 0),
222+
include_min=True,
223+
),
224+
),
193225
(
194226
"^0",
195227
VersionRange(

tests/version/test_requirements.py

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def assert_requirement(
4747
("name<3.*", {"name": "name", "constraint": "<3.0"}),
4848
("name>3.5.*", {"name": "name", "constraint": ">3.5"}),
4949
("name==1.0.post1", {"name": "name", "constraint": "==1.0.post1"}),
50+
("name==1.2.0b1.dev0", {"name": "name", "constraint": "==1.2.0b1.dev0"}),
5051
(
5152
"name>=1.2.3;python_version=='2.6'",
5253
{

0 commit comments

Comments
 (0)