|
4 | 4 |
|
5 | 5 | from __future__ import annotations |
6 | 6 |
|
| 7 | +import functools |
7 | 8 | import re |
8 | 9 | from typing import NewType, Tuple, Union, cast |
9 | 10 |
|
10 | 11 | from .tags import Tag, parse_tag |
11 | | -from .version import InvalidVersion, Version |
| 12 | +from .version import InvalidVersion, Version, _TrimmedRelease |
12 | 13 |
|
13 | 14 | BuildTag = Union[Tuple[()], Tuple[int, str]] |
14 | 15 | NormalizedName = NewType("NormalizedName", str) |
@@ -54,52 +55,40 @@ def is_normalized_name(name: str) -> bool: |
54 | 55 | return _normalized_regex.match(name) is not None |
55 | 56 |
|
56 | 57 |
|
| 58 | +@functools.singledispatch |
57 | 59 | def canonicalize_version( |
58 | 60 | version: Version | str, *, strip_trailing_zero: bool = True |
59 | 61 | ) -> str: |
60 | 62 | """ |
61 | | - This is very similar to Version.__str__, but has one subtle difference |
62 | | - with the way it handles the release segment. |
63 | | - """ |
64 | | - if isinstance(version, str): |
65 | | - try: |
66 | | - parsed = Version(version) |
67 | | - except InvalidVersion: |
68 | | - # Legacy versions cannot be normalized |
69 | | - return version |
70 | | - else: |
71 | | - parsed = version |
72 | | - |
73 | | - parts = [] |
| 63 | + Return a canonical form of a version as a string. |
74 | 64 |
|
75 | | - # Epoch |
76 | | - if parsed.epoch != 0: |
77 | | - parts.append(f"{parsed.epoch}!") |
| 65 | + >>> canonicalize_version('1.0.1') |
| 66 | + '1.0.1' |
78 | 67 |
|
79 | | - # Release segment |
80 | | - release_segment = ".".join(str(x) for x in parsed.release) |
81 | | - if strip_trailing_zero: |
82 | | - # NB: This strips trailing '.0's to normalize |
83 | | - release_segment = re.sub(r"(\.0)+$", "", release_segment) |
84 | | - parts.append(release_segment) |
| 68 | + Per PEP 625, versions may have multiple canonical forms, differing |
| 69 | + only by trailing zeros. |
85 | 70 |
|
86 | | - # Pre-release |
87 | | - if parsed.pre is not None: |
88 | | - parts.append("".join(str(x) for x in parsed.pre)) |
| 71 | + >>> canonicalize_version('1.0.0') |
| 72 | + '1' |
| 73 | + >>> canonicalize_version('1.0.0', strip_trailing_zero=False) |
| 74 | + '1.0.0' |
89 | 75 |
|
90 | | - # Post-release |
91 | | - if parsed.post is not None: |
92 | | - parts.append(f".post{parsed.post}") |
| 76 | + Invalid versions are returned unaltered. |
93 | 77 |
|
94 | | - # Development release |
95 | | - if parsed.dev is not None: |
96 | | - parts.append(f".dev{parsed.dev}") |
| 78 | + >>> canonicalize_version('foo bar baz') |
| 79 | + 'foo bar baz' |
| 80 | + """ |
| 81 | + return str(_TrimmedRelease(str(version)) if strip_trailing_zero else version) |
97 | 82 |
|
98 | | - # Local version segment |
99 | | - if parsed.local is not None: |
100 | | - parts.append(f"+{parsed.local}") |
101 | 83 |
|
102 | | - return "".join(parts) |
| 84 | +@canonicalize_version.register |
| 85 | +def _(version: str, *, strip_trailing_zero: bool = True) -> str: |
| 86 | + try: |
| 87 | + parsed = Version(version) |
| 88 | + except InvalidVersion: |
| 89 | + # Legacy versions cannot be normalized |
| 90 | + return version |
| 91 | + return canonicalize_version(parsed, strip_trailing_zero=strip_trailing_zero) |
103 | 92 |
|
104 | 93 |
|
105 | 94 | def parse_wheel_filename( |
|
0 commit comments