Skip to content

Commit ab91cd8

Browse files
committed
feat: only __replace__
Signed-off-by: Henry Schreiner <[email protected]>
1 parent 326e80b commit ab91cd8

File tree

2 files changed

+70
-60
lines changed

2 files changed

+70
-60
lines changed

src/packaging/version.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
from __future__ import annotations
1111

12-
import copy
1312
import re
1413
import sys
1514
import typing
@@ -334,11 +333,6 @@ def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
334333

335334
return new_version
336335

337-
def replace(self, **kwargs: Unpack[_VersionReplace]) -> Self:
338-
if sys.version_info >= (3, 13):
339-
return copy.replace(self, **kwargs)
340-
return self.__replace__(**kwargs)
341-
342336
@property
343337
def _key(self) -> CmpKey:
344338
if self._key_cache is None:

tests/test_version.py

Lines changed: 70 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,32 @@
66

77
import itertools
88
import operator
9+
import sys
910
import typing
1011

1112
import pretend
1213
import pytest
1314

14-
from packaging.version import InvalidVersion, Version, parse
15+
from packaging.version import InvalidVersion, Version, _VersionReplace, parse
1516

1617
if typing.TYPE_CHECKING:
1718
from collections.abc import Callable
1819

20+
from typing_extensions import Self, Unpack
21+
22+
if sys.version_info >= (3, 13):
23+
from copy import replace
24+
else:
25+
T = typing.TypeVar("T")
26+
27+
class SupportsReplace(typing.Protocol):
28+
def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self: ...
29+
30+
S = typing.TypeVar("S", bound="SupportsReplace")
31+
32+
def replace(item: S, **kwargs: Unpack[_VersionReplace]) -> S:
33+
return item.__replace__(**kwargs)
34+
1935

2036
def test_parse() -> None:
2137
assert isinstance(parse("1.0"), Version)
@@ -780,189 +796,189 @@ def test_micro_version(self) -> None:
780796
def test_replace_no_args(self) -> None:
781797
"""replace() with no arguments should return an equivalent version"""
782798
v = Version("1.2.3a1.post2.dev3+local")
783-
v_replaced = v.replace()
799+
v_replaced = replace(v)
784800
assert v == v_replaced
785801
assert str(v) == str(v_replaced)
786802

787803
def test_replace_epoch(self) -> None:
788804
v = Version("1.2.3")
789-
assert str(v.replace(epoch=2)) == "2!1.2.3"
790-
assert v.replace(epoch=0).epoch == 0
805+
assert str(replace(v, epoch=2)) == "2!1.2.3"
806+
assert replace(v, epoch=0).epoch == 0
791807

792808
v_with_epoch = Version("1!1.2.3")
793-
assert str(v_with_epoch.replace(epoch=2)) == "2!1.2.3"
794-
assert str(v_with_epoch.replace(epoch=None)) == "1.2.3"
809+
assert str(replace(v_with_epoch, epoch=2)) == "2!1.2.3"
810+
assert str(replace(v_with_epoch, epoch=None)) == "1.2.3"
795811

796812
def test_replace_release_tuple(self) -> None:
797813
v = Version("1.2.3")
798-
assert str(v.replace(release=(2, 0, 0))) == "2.0.0"
799-
assert str(v.replace(release=(1,))) == "1"
800-
assert str(v.replace(release=(1, 2, 3, 4, 5))) == "1.2.3.4.5"
814+
assert str(replace(v, release=(2, 0, 0))) == "2.0.0"
815+
assert str(replace(v, release=(1,))) == "1"
816+
assert str(replace(v, release=(1, 2, 3, 4, 5))) == "1.2.3.4.5"
801817

802818
def test_replace_release_none(self) -> None:
803819
v = Version("1.2.3")
804-
assert str(v.replace(release=None)) == "0"
820+
assert str(replace(v, release=None)) == "0"
805821

806822
def test_replace_pre_alpha(self) -> None:
807823
v = Version("1.2.3")
808-
assert str(v.replace(pre=("a", 1))) == "1.2.3a1"
809-
assert str(v.replace(pre=("a", 0))) == "1.2.3a0"
824+
assert str(replace(v, pre=("a", 1))) == "1.2.3a1"
825+
assert str(replace(v, pre=("a", 0))) == "1.2.3a0"
810826

811827
def test_replace_pre_alpha_none(self) -> None:
812828
v = Version("1.2.3a1")
813-
assert str(v.replace(pre=None)) == "1.2.3"
829+
assert str(replace(v, pre=None)) == "1.2.3"
814830

815831
def test_replace_pre_beta(self) -> None:
816832
v = Version("1.2.3")
817-
assert str(v.replace(pre=("b", 1))) == "1.2.3b1"
818-
assert str(v.replace(pre=("b", 0))) == "1.2.3b0"
833+
assert str(replace(v, pre=("b", 1))) == "1.2.3b1"
834+
assert str(replace(v, pre=("b", 0))) == "1.2.3b0"
819835

820836
def test_replace_pre_beta_none(self) -> None:
821837
v = Version("1.2.3b1")
822-
assert str(v.replace(pre=None)) == "1.2.3"
838+
assert str(replace(v, pre=None)) == "1.2.3"
823839

824840
def test_replace_pre_rc(self) -> None:
825841
v = Version("1.2.3")
826-
assert str(v.replace(pre=("rc", 1))) == "1.2.3rc1"
827-
assert str(v.replace(pre=("rc", 0))) == "1.2.3rc0"
842+
assert str(replace(v, pre=("rc", 1))) == "1.2.3rc1"
843+
assert str(replace(v, pre=("rc", 0))) == "1.2.3rc0"
828844

829845
def test_replace_pre_rc_none(self) -> None:
830846
v = Version("1.2.3rc1")
831-
assert str(v.replace(pre=None)) == "1.2.3"
847+
assert str(replace(v, pre=None)) == "1.2.3"
832848

833849
def test_replace_post(self) -> None:
834850
v = Version("1.2.3")
835-
assert str(v.replace(post=1)) == "1.2.3.post1"
836-
assert str(v.replace(post=0)) == "1.2.3.post0"
851+
assert str(replace(v, post=1)) == "1.2.3.post1"
852+
assert str(replace(v, post=0)) == "1.2.3.post0"
837853

838854
def test_replace_post_none(self) -> None:
839855
v = Version("1.2.3.post1")
840-
assert str(v.replace(post=None)) == "1.2.3"
856+
assert str(replace(v, post=None)) == "1.2.3"
841857

842858
def test_replace_dev(self) -> None:
843859
v = Version("1.2.3")
844-
assert str(v.replace(dev=1)) == "1.2.3.dev1"
845-
assert str(v.replace(dev=0)) == "1.2.3.dev0"
860+
assert str(replace(v, dev=1)) == "1.2.3.dev1"
861+
assert str(replace(v, dev=0)) == "1.2.3.dev0"
846862

847863
def test_replace_dev_none(self) -> None:
848864
v = Version("1.2.3.dev1")
849-
assert str(v.replace(dev=None)) == "1.2.3"
865+
assert str(replace(v, dev=None)) == "1.2.3"
850866

851867
def test_replace_local_string(self) -> None:
852868
v = Version("1.2.3")
853-
assert str(v.replace(local="abc")) == "1.2.3+abc"
854-
assert str(v.replace(local="abc.123")) == "1.2.3+abc.123"
855-
assert str(v.replace(local="abc-123")) == "1.2.3+abc.123"
869+
assert str(replace(v, local="abc")) == "1.2.3+abc"
870+
assert str(replace(v, local="abc.123")) == "1.2.3+abc.123"
871+
assert str(replace(v, local="abc-123")) == "1.2.3+abc.123"
856872

857873
def test_replace_local_none(self) -> None:
858874
v = Version("1.2.3+local")
859-
assert str(v.replace(local=None)) == "1.2.3"
875+
assert str(replace(v, local=None)) == "1.2.3"
860876

861877
def test_replace_multiple_components(self) -> None:
862878
v = Version("1.2.3")
863-
assert str(v.replace(pre=("a", 1), post=1)) == "1.2.3a1.post1"
864-
assert str(v.replace(release=(2, 0, 0), pre=("b", 2), dev=1)) == "2.0.0b2.dev1"
865-
assert str(v.replace(epoch=1, release=(3, 0), local="abc")) == "1!3.0+abc"
879+
assert str(replace(v, pre=("a", 1), post=1)) == "1.2.3a1.post1"
880+
assert str(replace(v, release=(2, 0, 0), pre=("b", 2), dev=1)) == "2.0.0b2.dev1"
881+
assert str(replace(v, epoch=1, release=(3, 0), local="abc")) == "1!3.0+abc"
866882

867883
def test_replace_clear_all_optional(self) -> None:
868884
v = Version("1!1.2.3a1.post2.dev3+local")
869-
cleared = v.replace(epoch=None, pre=None, post=None, dev=None, local=None)
885+
cleared = replace(v, epoch=None, pre=None, post=None, dev=None, local=None)
870886
assert str(cleared) == "1.2.3"
871887

872888
def test_replace_preserves_comparison(self) -> None:
873889
v1 = Version("1.2.3")
874890
v2 = Version("1.2.4")
875891

876-
v1_new = v1.replace(release=(1, 2, 4))
892+
v1_new = replace(v1, release=(1, 2, 4))
877893
assert v1_new == v2
878894
assert v1 < v2
879895
assert v1_new >= v2
880896

881897
def test_replace_preserves_hash(self) -> None:
882898
v1 = Version("1.2.3")
883-
v2 = v1.replace(release=(1, 2, 3))
899+
v2 = replace(v1, release=(1, 2, 3))
884900
assert hash(v1) == hash(v2)
885901

886-
v3 = v1.replace(release=(2, 0, 0))
902+
v3 = replace(v1, release=(2, 0, 0))
887903
assert hash(v1) != hash(v3)
888904

889905
def test_replace_change_pre_type(self) -> None:
890906
"""Can change from one pre-release type to another"""
891907
v = Version("1.2.3a1")
892-
assert str(v.replace(pre=("b", 2))) == "1.2.3b2"
893-
assert str(v.replace(pre=("rc", 1))) == "1.2.3rc1"
908+
assert str(replace(v, pre=("b", 2))) == "1.2.3b2"
909+
assert str(replace(v, pre=("rc", 1))) == "1.2.3rc1"
894910

895911
v2 = Version("1.2.3rc5")
896-
assert str(v2.replace(pre=("a", 0))) == "1.2.3a0"
912+
assert str(replace(v2, pre=("a", 0))) == "1.2.3a0"
897913

898914
def test_replace_invalid_epoch_type(self) -> None:
899915
v = Version("1.2.3")
900916
with pytest.raises(InvalidVersion, match="epoch must be non-negative"):
901-
v.replace(epoch="1") # type: ignore[arg-type]
917+
replace(v, epoch="1") # type: ignore[arg-type]
902918

903919
def test_replace_invalid_post_type(self) -> None:
904920
v = Version("1.2.3")
905921
with pytest.raises(InvalidVersion, match="post must be non-negative"):
906-
v.replace(post="1") # type: ignore[arg-type]
922+
replace(v, post="1") # type: ignore[arg-type]
907923

908924
def test_replace_invalid_dev_type(self) -> None:
909925
v = Version("1.2.3")
910926
with pytest.raises(InvalidVersion, match="dev must be non-negative"):
911-
v.replace(dev="1") # type: ignore[arg-type]
927+
replace(v, dev="1") # type: ignore[arg-type]
912928

913929
def test_replace_invalid_epoch_negative(self) -> None:
914930
v = Version("1.2.3")
915931
with pytest.raises(InvalidVersion, match="epoch must be non-negative"):
916-
v.replace(epoch=-1)
932+
replace(v, epoch=-1)
917933

918934
def test_replace_invalid_release_empty(self) -> None:
919935
v = Version("1.2.3")
920936
with pytest.raises(InvalidVersion, match="release must be a non-empty tuple"):
921-
v.replace(release=())
937+
replace(v, release=())
922938

923939
def test_replace_invalid_release_tuple_content(self) -> None:
924940
v = Version("1.2.3")
925941
with pytest.raises(
926942
InvalidVersion, match="release must be a non-empty tuple of non-negative"
927943
):
928-
v.replace(release=(1, -2, 3))
944+
replace(v, release=(1, -2, 3))
929945

930946
def test_replace_invalid_pre_negative(self) -> None:
931947
v = Version("1.2.3")
932948
with pytest.raises(InvalidVersion, match="pre must be a tuple"):
933-
v.replace(pre=("a", -1))
949+
replace(v, pre=("a", -1))
934950

935951
def test_replace_invalid_pre_type(self) -> None:
936952
v = Version("1.2.3")
937953
with pytest.raises(InvalidVersion, match="pre must be a tuple"):
938-
v.replace(pre=("x", 1)) # type: ignore[arg-type]
954+
replace(v, pre=("x", 1)) # type: ignore[arg-type]
939955

940956
def test_replace_invalid_pre_format(self) -> None:
941957
v = Version("1.2.3")
942958
with pytest.raises(InvalidVersion, match="pre must be a tuple"):
943-
v.replace(pre="a1") # type: ignore[arg-type]
959+
replace(v, pre="a1") # type: ignore[arg-type]
944960
with pytest.raises(InvalidVersion, match="pre must be a tuple"):
945-
v.replace(pre=("a",)) # type: ignore[arg-type]
961+
replace(v, pre=("a",)) # type: ignore[arg-type]
946962
with pytest.raises(InvalidVersion, match="pre must be a tuple"):
947-
v.replace(pre=("a", 1, 2)) # type: ignore[arg-type]
963+
replace(v, pre=("a", 1, 2)) # type: ignore[arg-type]
948964

949965
def test_replace_invalid_post_negative(self) -> None:
950966
v = Version("1.2.3")
951967
with pytest.raises(InvalidVersion, match="post must be non-negative"):
952-
v.replace(post=-1)
968+
replace(v, post=-1)
953969

954970
def test_replace_invalid_dev_negative(self) -> None:
955971
v = Version("1.2.3")
956972
with pytest.raises(InvalidVersion, match="dev must be non-negative"):
957-
v.replace(dev=-1)
973+
replace(v, dev=-1)
958974

959975
def test_replace_invalid_local_string(self) -> None:
960976
v = Version("1.2.3")
961977
with pytest.raises(
962978
InvalidVersion, match="local must be a valid version string"
963979
):
964-
v.replace(local="abc+123")
980+
replace(v, local="abc+123")
965981
with pytest.raises(
966982
InvalidVersion, match="local must be a valid version string"
967983
):
968-
v.replace(local="+abc")
984+
replace(v, local="+abc")

0 commit comments

Comments
 (0)