@@ -152,8 +152,8 @@ def _source_epoch_or_utc_now() -> datetime:
152152class ScmVersion :
153153 """represents a parsed version from scm"""
154154
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"""
157157 config : _config .Configuration
158158 """the configuration used to parse the version"""
159159 distance : int = 0
@@ -223,9 +223,16 @@ def format_next_version(
223223
224224def _parse_tag (
225225 tag : _VersionT | str , preformatted : bool , config : _config .Configuration
226- ) -> _VersionT | str :
226+ ) -> _VersionT :
227227 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
229236 elif not isinstance (tag , config .version_cls ):
230237 version = tag_to_version (tag , config )
231238 assert version is not None
@@ -246,7 +253,16 @@ def meta(
246253 node_date : date | None = None ,
247254 time : datetime | None = None ,
248255) -> 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+
250266 log .info ("version %s -> %s" , tag , parsed_version )
251267 assert parsed_version is not None , f"Can't parse version { tag } "
252268 scm_version = ScmVersion (
@@ -533,8 +549,7 @@ def format_version(version: ScmVersion) -> str:
533549 log .debug ("scm version %s" , version )
534550 log .debug ("config %s" , version .config )
535551 if version .preformatted :
536- assert isinstance (version .tag , str )
537- return version .tag
552+ return str (version .tag )
538553
539554 # Extract original tag's local data for later combination
540555 original_local = ""
@@ -544,23 +559,10 @@ def format_version(version: ScmVersion) -> str:
544559 # Create a patched ScmVersion with only the base version (no local data) for version schemes
545560 from dataclasses import replace
546561
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 )
564566
565567 main_version = _entrypoints ._call_version_scheme (
566568 version_for_scheme ,
0 commit comments