|
6 | 6 |
|
7 | 7 | import itertools |
8 | 8 | import operator |
| 9 | +import sys |
9 | 10 | import typing |
10 | 11 |
|
11 | 12 | import pretend |
12 | 13 | import pytest |
13 | 14 |
|
14 | | -from packaging.version import InvalidVersion, Version, parse |
| 15 | +from packaging.version import InvalidVersion, Version, _VersionReplace, parse |
15 | 16 |
|
16 | 17 | if typing.TYPE_CHECKING: |
17 | 18 | from collections.abc import Callable |
18 | 19 |
|
| 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 | + |
19 | 35 |
|
20 | 36 | def test_parse() -> None: |
21 | 37 | assert isinstance(parse("1.0"), Version) |
@@ -780,189 +796,189 @@ def test_micro_version(self) -> None: |
780 | 796 | def test_replace_no_args(self) -> None: |
781 | 797 | """replace() with no arguments should return an equivalent version""" |
782 | 798 | v = Version("1.2.3a1.post2.dev3+local") |
783 | | - v_replaced = v.replace() |
| 799 | + v_replaced = replace(v) |
784 | 800 | assert v == v_replaced |
785 | 801 | assert str(v) == str(v_replaced) |
786 | 802 |
|
787 | 803 | def test_replace_epoch(self) -> None: |
788 | 804 | 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 |
791 | 807 |
|
792 | 808 | 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" |
795 | 811 |
|
796 | 812 | def test_replace_release_tuple(self) -> None: |
797 | 813 | 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" |
801 | 817 |
|
802 | 818 | def test_replace_release_none(self) -> None: |
803 | 819 | v = Version("1.2.3") |
804 | | - assert str(v.replace(release=None)) == "0" |
| 820 | + assert str(replace(v, release=None)) == "0" |
805 | 821 |
|
806 | 822 | def test_replace_pre_alpha(self) -> None: |
807 | 823 | 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" |
810 | 826 |
|
811 | 827 | def test_replace_pre_alpha_none(self) -> None: |
812 | 828 | 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" |
814 | 830 |
|
815 | 831 | def test_replace_pre_beta(self) -> None: |
816 | 832 | 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" |
819 | 835 |
|
820 | 836 | def test_replace_pre_beta_none(self) -> None: |
821 | 837 | 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" |
823 | 839 |
|
824 | 840 | def test_replace_pre_rc(self) -> None: |
825 | 841 | 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" |
828 | 844 |
|
829 | 845 | def test_replace_pre_rc_none(self) -> None: |
830 | 846 | 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" |
832 | 848 |
|
833 | 849 | def test_replace_post(self) -> None: |
834 | 850 | 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" |
837 | 853 |
|
838 | 854 | def test_replace_post_none(self) -> None: |
839 | 855 | 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" |
841 | 857 |
|
842 | 858 | def test_replace_dev(self) -> None: |
843 | 859 | 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" |
846 | 862 |
|
847 | 863 | def test_replace_dev_none(self) -> None: |
848 | 864 | 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" |
850 | 866 |
|
851 | 867 | def test_replace_local_string(self) -> None: |
852 | 868 | 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" |
856 | 872 |
|
857 | 873 | def test_replace_local_none(self) -> None: |
858 | 874 | 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" |
860 | 876 |
|
861 | 877 | def test_replace_multiple_components(self) -> None: |
862 | 878 | 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" |
866 | 882 |
|
867 | 883 | def test_replace_clear_all_optional(self) -> None: |
868 | 884 | 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) |
870 | 886 | assert str(cleared) == "1.2.3" |
871 | 887 |
|
872 | 888 | def test_replace_preserves_comparison(self) -> None: |
873 | 889 | v1 = Version("1.2.3") |
874 | 890 | v2 = Version("1.2.4") |
875 | 891 |
|
876 | | - v1_new = v1.replace(release=(1, 2, 4)) |
| 892 | + v1_new = replace(v1, release=(1, 2, 4)) |
877 | 893 | assert v1_new == v2 |
878 | 894 | assert v1 < v2 |
879 | 895 | assert v1_new >= v2 |
880 | 896 |
|
881 | 897 | def test_replace_preserves_hash(self) -> None: |
882 | 898 | v1 = Version("1.2.3") |
883 | | - v2 = v1.replace(release=(1, 2, 3)) |
| 899 | + v2 = replace(v1, release=(1, 2, 3)) |
884 | 900 | assert hash(v1) == hash(v2) |
885 | 901 |
|
886 | | - v3 = v1.replace(release=(2, 0, 0)) |
| 902 | + v3 = replace(v1, release=(2, 0, 0)) |
887 | 903 | assert hash(v1) != hash(v3) |
888 | 904 |
|
889 | 905 | def test_replace_change_pre_type(self) -> None: |
890 | 906 | """Can change from one pre-release type to another""" |
891 | 907 | 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" |
894 | 910 |
|
895 | 911 | 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" |
897 | 913 |
|
898 | 914 | def test_replace_invalid_epoch_type(self) -> None: |
899 | 915 | v = Version("1.2.3") |
900 | 916 | 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] |
902 | 918 |
|
903 | 919 | def test_replace_invalid_post_type(self) -> None: |
904 | 920 | v = Version("1.2.3") |
905 | 921 | 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] |
907 | 923 |
|
908 | 924 | def test_replace_invalid_dev_type(self) -> None: |
909 | 925 | v = Version("1.2.3") |
910 | 926 | 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] |
912 | 928 |
|
913 | 929 | def test_replace_invalid_epoch_negative(self) -> None: |
914 | 930 | v = Version("1.2.3") |
915 | 931 | with pytest.raises(InvalidVersion, match="epoch must be non-negative"): |
916 | | - v.replace(epoch=-1) |
| 932 | + replace(v, epoch=-1) |
917 | 933 |
|
918 | 934 | def test_replace_invalid_release_empty(self) -> None: |
919 | 935 | v = Version("1.2.3") |
920 | 936 | with pytest.raises(InvalidVersion, match="release must be a non-empty tuple"): |
921 | | - v.replace(release=()) |
| 937 | + replace(v, release=()) |
922 | 938 |
|
923 | 939 | def test_replace_invalid_release_tuple_content(self) -> None: |
924 | 940 | v = Version("1.2.3") |
925 | 941 | with pytest.raises( |
926 | 942 | InvalidVersion, match="release must be a non-empty tuple of non-negative" |
927 | 943 | ): |
928 | | - v.replace(release=(1, -2, 3)) |
| 944 | + replace(v, release=(1, -2, 3)) |
929 | 945 |
|
930 | 946 | def test_replace_invalid_pre_negative(self) -> None: |
931 | 947 | v = Version("1.2.3") |
932 | 948 | with pytest.raises(InvalidVersion, match="pre must be a tuple"): |
933 | | - v.replace(pre=("a", -1)) |
| 949 | + replace(v, pre=("a", -1)) |
934 | 950 |
|
935 | 951 | def test_replace_invalid_pre_type(self) -> None: |
936 | 952 | v = Version("1.2.3") |
937 | 953 | 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] |
939 | 955 |
|
940 | 956 | def test_replace_invalid_pre_format(self) -> None: |
941 | 957 | v = Version("1.2.3") |
942 | 958 | 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] |
944 | 960 | 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] |
946 | 962 | 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] |
948 | 964 |
|
949 | 965 | def test_replace_invalid_post_negative(self) -> None: |
950 | 966 | v = Version("1.2.3") |
951 | 967 | with pytest.raises(InvalidVersion, match="post must be non-negative"): |
952 | | - v.replace(post=-1) |
| 968 | + replace(v, post=-1) |
953 | 969 |
|
954 | 970 | def test_replace_invalid_dev_negative(self) -> None: |
955 | 971 | v = Version("1.2.3") |
956 | 972 | with pytest.raises(InvalidVersion, match="dev must be non-negative"): |
957 | | - v.replace(dev=-1) |
| 973 | + replace(v, dev=-1) |
958 | 974 |
|
959 | 975 | def test_replace_invalid_local_string(self) -> None: |
960 | 976 | v = Version("1.2.3") |
961 | 977 | with pytest.raises( |
962 | 978 | InvalidVersion, match="local must be a valid version string" |
963 | 979 | ): |
964 | | - v.replace(local="abc+123") |
| 980 | + replace(v, local="abc+123") |
965 | 981 | with pytest.raises( |
966 | 982 | InvalidVersion, match="local must be a valid version string" |
967 | 983 | ): |
968 | | - v.replace(local="+abc") |
| 984 | + replace(v, local="+abc") |
0 commit comments