@@ -152,8 +152,8 @@ def _source_epoch_or_utc_now() -> datetime:
152
152
class ScmVersion :
153
153
"""represents a parsed version from scm"""
154
154
155
- tag : _v .Version | _v .NonNormalizedVersion | str
156
- """the related tag or preformatted version string """
155
+ tag : _v .Version | _v .NonNormalizedVersion
156
+ """the related tag or preformatted version"""
157
157
config : _config .Configuration
158
158
"""the configuration used to parse the version"""
159
159
distance : int = 0
@@ -223,9 +223,16 @@ def format_next_version(
223
223
224
224
def _parse_tag (
225
225
tag : _VersionT | str , preformatted : bool , config : _config .Configuration
226
- ) -> _VersionT | str :
226
+ ) -> _VersionT :
227
227
if preformatted :
228
- return tag
228
+ # For preformatted versions, tag should already be validated as a version object
229
+ # String validation is handled in meta function before calling this
230
+ if isinstance (tag , str ):
231
+ # This should not happen with enhanced meta, but kept for safety
232
+ return _v .NonNormalizedVersion (tag )
233
+ else :
234
+ # Already a version object (including test mocks), return as-is
235
+ return tag
229
236
elif not isinstance (tag , config .version_cls ):
230
237
version = tag_to_version (tag , config )
231
238
assert version is not None
@@ -246,7 +253,16 @@ def meta(
246
253
node_date : date | None = None ,
247
254
time : datetime | None = None ,
248
255
) -> ScmVersion :
249
- parsed_version = _parse_tag (tag , preformatted , config )
256
+ parsed_version : _VersionT
257
+ # Enhanced string validation for preformatted versions
258
+ if preformatted and isinstance (tag , str ):
259
+ # Validate PEP 440 compliance using NonNormalizedVersion
260
+ # Let validation errors bubble up to the caller
261
+ parsed_version = _v .NonNormalizedVersion (tag )
262
+ else :
263
+ # Use existing _parse_tag logic for non-preformatted or already validated inputs
264
+ parsed_version = _parse_tag (tag , preformatted , config )
265
+
250
266
log .info ("version %s -> %s" , tag , parsed_version )
251
267
assert parsed_version is not None , f"Can't parse version { tag } "
252
268
scm_version = ScmVersion (
@@ -533,8 +549,7 @@ def format_version(version: ScmVersion) -> str:
533
549
log .debug ("scm version %s" , version )
534
550
log .debug ("config %s" , version .config )
535
551
if version .preformatted :
536
- assert isinstance (version .tag , str )
537
- return version .tag
552
+ return str (version .tag )
538
553
539
554
# Extract original tag's local data for later combination
540
555
original_local = ""
@@ -544,23 +559,10 @@ def format_version(version: ScmVersion) -> str:
544
559
# Create a patched ScmVersion with only the base version (no local data) for version schemes
545
560
from dataclasses import replace
546
561
547
- if version .tag :
548
- # Extract the base version (public part) from the tag using config's version_cls
549
- if hasattr (version .tag , "public" ):
550
- # It's a Version object with a public attribute
551
- base_version_str = str (version .tag .public )
552
- elif isinstance (version .tag , str ):
553
- # It's a string - strip any local part
554
- base_version_str = version .tag .split ("+" )[0 ]
555
- else :
556
- # It's some other type - use string representation and strip local part
557
- base_version_str = str (version .tag ).split ("+" )[0 ]
558
-
559
- # Create the base tag using the config's version class
560
- base_tag = version .config .version_cls (base_version_str )
561
- version_for_scheme = replace (version , tag = base_tag )
562
- else :
563
- version_for_scheme = version
562
+ # Extract the base version (public part) from the tag using config's version_cls
563
+ base_version_str = str (version .tag .public )
564
+ base_tag = version .config .version_cls (base_version_str )
565
+ version_for_scheme = replace (version , tag = base_tag )
564
566
565
567
main_version = _entrypoints ._call_version_scheme (
566
568
version_for_scheme ,
0 commit comments