1
1
import re
2
2
3
- import semver
4
3
from packaging .version import Version
5
4
6
5
VERSION_REGEX = re .compile (
@@ -45,30 +44,49 @@ def parse_version(version: str) -> dict[str, int | None]:
45
44
}
46
45
47
46
48
- def vdict_to_vinfo ( version_dict : dict [ str , int | None ] ) -> semver . VersionInfo :
47
+ def get_padded_base_version ( version : str | Version ) -> str :
49
48
"""
50
- Coerces version dictionary to a semver.VersionInfo object. If minor or patch
51
- numbers are missing, 0 is substituted in their place.
52
- """
53
- ver = {key : 0 if value is None else value for key , value in version_dict .items ()}
54
- return semver .VersionInfo (** ver )
55
-
49
+ Returns the same PEP440 version padded with zeroes if
50
+ minor or micro are not specified.
56
51
57
- def coerce_to_semver (version : str ) -> str :
58
- """
59
- Coerces a version string to a semantic version.
52
+ >>> get_padded_base_version("0.2.3")
53
+ '0.2.3'
54
+ >>> get_padded_base_version("1")
55
+ '1.0.0'
56
+ >>> get_padded_base_version("1.2")
57
+ '1.2.0'
58
+ >>> get_padded_base_version("1.2.3")
59
+ '1.2.3'
60
+ >>> get_padded_base_version("1.2.3.post1")
61
+ '1.2.3.post1'
62
+ >>> get_padded_base_version("2!1.2.post1")
63
+ '2!1.2.0.post1'
60
64
"""
61
- if semver .VersionInfo .is_valid (version ):
62
- return version
63
-
64
- parsed_version = parse_version (version )
65
- vinfo = vdict_to_vinfo (parsed_version )
66
- return str (vinfo )
67
-
65
+ if not isinstance (version , Version ):
66
+ version = Version (version )
68
67
69
- def get_caret_ceiling (target : str ) -> str :
68
+ # Start with the normalized release
69
+ floor = f"{ version .major } .{ version .minor } .{ version .micro } "
70
+
71
+ # Add other components as they appear
72
+ if version .epoch is not None and version .epoch > 0 :
73
+ floor = f"{ version .epoch } !{ floor } " # Add epoch if present
74
+ if version .pre is not None :
75
+ floor += (
76
+ f"{ version .pre [0 ]} { version .pre [1 ]} " # Add pre-release (e.g., a1, b1, rc1)
77
+ )
78
+ if version .post is not None :
79
+ floor += f".post{ version .post } " # Add post-release (e.g., .post1)
80
+ if version .dev is not None :
81
+ floor += f".dev{ version .dev } " # Add development release (e.g., .dev1)
82
+ if version .local is not None :
83
+ floor += f"+{ version .local } " # Add local metadata (e.g., +local)
84
+ return floor
85
+
86
+
87
+ def get_caret_ceiling (version : str | Version ) -> str :
70
88
"""
71
- Accepts a Poetry caret target and returns the exclusive version ceiling.
89
+ Accepts a Poetry caret version and returns the exclusive version ceiling.
72
90
73
91
Targets that are invalid semver strings (e.g. "1.2", "0") are handled
74
92
according to the Poetry caret requirements specification, which is based on
@@ -97,34 +115,24 @@ def get_caret_ceiling(target: str) -> str:
97
115
'2.0.0'
98
116
>>> get_caret_ceiling("1.2.3")
99
117
'2.0.0'
118
+ >>> get_caret_ceiling("1.2.3.post1")
119
+ '2.0.0'
120
+ >>> get_caret_ceiling("2!1.2.3.post1")
121
+ '2.0.0'
100
122
"""
101
- if not semver .VersionInfo .is_valid (target ):
102
- target_dict = parse_version (target )
103
-
104
- if target_dict ["major" ] == 0 :
105
- if target_dict ["minor" ] is None :
106
- target_dict ["major" ] += 1
107
- elif target_dict ["patch" ] is None :
108
- target_dict ["minor" ] += 1
109
- else :
110
- target_dict ["patch" ] += 1
111
- return str (vdict_to_vinfo (target_dict ))
112
-
113
- vdict_to_vinfo (target_dict )
114
- return str (vdict_to_vinfo (target_dict ).bump_major ())
115
-
116
- target_vinfo = semver .VersionInfo .parse (target )
117
-
118
- if target_vinfo .major == 0 :
119
- if target_vinfo .minor == 0 :
120
- return str (target_vinfo .bump_patch ())
121
- else :
122
- return str (target_vinfo .bump_minor ())
123
+ if not isinstance (version , Version ):
124
+ version = Version (version )
125
+ # Determine the upper bound
126
+ if version .major > 0 or len (version .release ) == 1 :
127
+ ceiling = f"{ version .major + 1 } .0.0"
128
+ elif version .minor > 0 or len (version .release ) == 2 :
129
+ ceiling = f"0.{ version .minor + 1 } .0"
123
130
else :
124
- return str (target_vinfo .bump_major ())
131
+ ceiling = f"0.0.{ version .micro + 1 } "
132
+ return ceiling
125
133
126
134
127
- def get_tilde_ceiling (target : str ) -> str :
135
+ def get_tilde_ceiling (version : str | Version ) -> str :
128
136
"""
129
137
Accepts a Poetry tilde target and returns the exclusive version ceiling.
130
138
@@ -136,11 +144,14 @@ def get_tilde_ceiling(target: str) -> str:
136
144
>>> get_tilde_ceiling("1.2.3")
137
145
'1.3.0'
138
146
"""
139
- target_dict = parse_version (target )
140
- if target_dict ["minor" ]:
141
- return str (vdict_to_vinfo (target_dict ).bump_minor ())
142
-
143
- return str (vdict_to_vinfo (target_dict ).bump_major ())
147
+ if not isinstance (version , Version ):
148
+ version = Version (version )
149
+ # Determine the upper bound based on the specified components
150
+ if len (version .release ) in [2 , 3 ]: # Major, Minor, Micro, or Major, Minor
151
+ tilde_ceiling = f"{ version .major } .{ version .minor + 1 } .0"
152
+ else : # Major, Minor or Only Major
153
+ tilde_ceiling = f"{ version .major + 1 } .0.0"
154
+ return tilde_ceiling
144
155
145
156
146
157
def encode_poetry_version (poetry_specifier : str ) -> str :
@@ -190,6 +201,11 @@ def encode_poetry_version(poetry_specifier: str) -> str:
190
201
>>> encode_poetry_version("^1.2.3")
191
202
'>=1.2.3,<2.0.0'
192
203
204
+ # handle caret operator with a PEP440 version
205
+ # correctly
206
+ >>> encode_poetry_version("^0.8.post1")
207
+ '>=0.8.0.post1,<0.9.0'
208
+
193
209
# handle tilde operator correctly
194
210
# examples from Poetry docs
195
211
>>> encode_poetry_version("~1")
@@ -199,6 +215,11 @@ def encode_poetry_version(poetry_specifier: str) -> str:
199
215
>>> encode_poetry_version("~1.2.3")
200
216
'>=1.2.3,<1.3.0'
201
217
218
+ # # handle tilde operator with a PEP440 version
219
+ # # correctly
220
+ # >>> encode_poetry_version("~0.8.post1")
221
+ # '>=0.8.0.post1,<0.9.0.a0'
222
+
202
223
# handle or operator correctly
203
224
>>> encode_poetry_version("1.2.3|1.2.4")
204
225
'1.2.3|1.2.4'
@@ -221,9 +242,9 @@ def encode_poetry_version(poetry_specifier: str) -> str:
221
242
poetry_clause = poetry_clause .replace (" " , "" )
222
243
if poetry_clause .startswith ("^" ):
223
244
# handle ^ operator
224
- target = poetry_clause [1 :]
225
- floor = coerce_to_semver ( target )
226
- ceiling = get_caret_ceiling (target )
245
+ caret_version = Version ( poetry_clause [1 :])
246
+ floor = get_padded_base_version ( caret_version )
247
+ ceiling = get_caret_ceiling (caret_version )
227
248
conda_clauses .append (">=" + floor )
228
249
conda_clauses .append ("<" + ceiling )
229
250
continue
@@ -236,9 +257,9 @@ def encode_poetry_version(poetry_specifier: str) -> str:
236
257
237
258
if poetry_clause .startswith ("~" ):
238
259
# handle ~ operator
239
- target = poetry_clause [1 :]
240
- floor = coerce_to_semver ( target )
241
- ceiling = get_tilde_ceiling (target )
260
+ tilde_version = poetry_clause [1 :]
261
+ floor = get_padded_base_version ( tilde_version )
262
+ ceiling = get_tilde_ceiling (tilde_version )
242
263
conda_clauses .append (">=" + floor )
243
264
conda_clauses .append ("<" + ceiling )
244
265
continue
0 commit comments