|
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