|
28 | 28 | transforms,
|
29 | 29 | util,
|
30 | 30 | )
|
31 |
| -from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS, Context |
| 31 | +from astroid.const import IS_PYPY, PY310_PLUS, PY311_PLUS, PY312_PLUS, Context |
32 | 32 | from astroid.context import InferenceContext
|
33 | 33 | from astroid.exceptions import (
|
34 | 34 | AstroidBuildingError,
|
@@ -929,67 +929,274 @@ def test(self):
|
929 | 929 |
|
930 | 930 |
|
931 | 931 | class BoundMethodNodeTest(unittest.TestCase):
|
932 |
| - def test_is_property(self) -> None: |
| 932 | + def _is_property(self, ast: nodes.Module, prop: str) -> None: |
| 933 | + inferred = next(ast[prop].infer()) |
| 934 | + self.assertIsInstance(inferred, nodes.Const, prop) |
| 935 | + self.assertEqual(inferred.value, 42, prop) |
| 936 | + |
| 937 | + def test_is_standard_property(self) -> None: |
| 938 | + # Test to make sure the Python-provided property decorators |
| 939 | + # are properly interpreted as properties |
933 | 940 | ast = builder.parse(
|
934 | 941 | """
|
935 | 942 | import abc
|
| 943 | + import functools |
936 | 944 |
|
937 |
| - def cached_property(): |
938 |
| - # Not a real decorator, but we don't care |
939 |
| - pass |
940 |
| - def reify(): |
941 |
| - # Same as cached_property |
942 |
| - pass |
943 |
| - def lazy_property(): |
944 |
| - pass |
945 |
| - def lazyproperty(): |
946 |
| - pass |
947 |
| - def lazy(): pass |
948 | 945 | class A(object):
|
949 | 946 | @property
|
950 |
| - def builtin_property(self): |
951 |
| - return 42 |
| 947 | + def builtin_property(self): return 42 |
| 948 | +
|
952 | 949 | @abc.abstractproperty
|
953 |
| - def abc_property(self): |
954 |
| - return 42 |
| 950 | + def abc_property(self): return 42 |
| 951 | +
|
| 952 | + @property |
| 953 | + @abc.abstractmethod |
| 954 | + def abstractmethod_property(self): return 42 |
| 955 | +
|
| 956 | + @functools.cached_property |
| 957 | + def functools_property(self): return 42 |
| 958 | +
|
| 959 | + cls = A() |
| 960 | + builtin_p = cls.builtin_property |
| 961 | + abc_p = cls.abc_property |
| 962 | + abstractmethod_p = cls.abstractmethod_property |
| 963 | + functools_p = cls.functools_property |
| 964 | + """ |
| 965 | + ) |
| 966 | + for prop in ( |
| 967 | + "builtin_p", |
| 968 | + "abc_p", |
| 969 | + "abstractmethod_p", |
| 970 | + "functools_p", |
| 971 | + ): |
| 972 | + self._is_property(ast, prop) |
| 973 | + |
| 974 | + @pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11") |
| 975 | + def test_is_standard_property_py311(self) -> None: |
| 976 | + # Test to make sure the Python-provided property decorators |
| 977 | + # are properly interpreted as properties |
| 978 | + ast = builder.parse( |
| 979 | + """ |
| 980 | + import enum |
| 981 | +
|
| 982 | + class A(object): |
| 983 | + @enum.property |
| 984 | + def enum_property(self): return 42 |
| 985 | +
|
| 986 | + cls = A() |
| 987 | + enum_p = cls.enum_property |
| 988 | + """ |
| 989 | + ) |
| 990 | + self._is_property(ast, "enum_p") |
| 991 | + |
| 992 | + def test_is_possible_property(self) -> None: |
| 993 | + # Test to make sure that decorators with POSSIBLE_PROPERTIES names |
| 994 | + # are properly interpreted as properties |
| 995 | + ast = builder.parse( |
| 996 | + """ |
| 997 | + # Not real decorators, but we don't care |
| 998 | + def cachedproperty(): pass |
| 999 | + def cached_property(): pass |
| 1000 | + def reify(): pass |
| 1001 | + def lazy_property(): pass |
| 1002 | + def lazyproperty(): pass |
| 1003 | + def lazy(): pass |
| 1004 | + def lazyattribute(): pass |
| 1005 | + def lazy_attribute(): pass |
| 1006 | + def LazyProperty(): pass |
| 1007 | + def DynamicClassAttribute(): pass |
| 1008 | +
|
| 1009 | + class A(object): |
| 1010 | + @cachedproperty |
| 1011 | + def cachedproperty(self): return 42 |
| 1012 | +
|
955 | 1013 | @cached_property
|
956 | 1014 | def cached_property(self): return 42
|
| 1015 | +
|
957 | 1016 | @reify
|
958 | 1017 | def reified(self): return 42
|
| 1018 | +
|
959 | 1019 | @lazy_property
|
960 | 1020 | def lazy_prop(self): return 42
|
| 1021 | +
|
961 | 1022 | @lazyproperty
|
962 | 1023 | def lazyprop(self): return 42
|
963 |
| - def not_prop(self): pass |
| 1024 | +
|
964 | 1025 | @lazy
|
965 | 1026 | def decorated_with_lazy(self): return 42
|
966 | 1027 |
|
| 1028 | + @lazyattribute |
| 1029 | + def lazyattribute(self): return 42 |
| 1030 | +
|
| 1031 | + @lazy_attribute |
| 1032 | + def lazy_attribute(self): return 42 |
| 1033 | +
|
| 1034 | + @LazyProperty |
| 1035 | + def LazyProperty(self): return 42 |
| 1036 | +
|
| 1037 | + @DynamicClassAttribute |
| 1038 | + def DynamicClassAttribute(self): return 42 |
| 1039 | +
|
967 | 1040 | cls = A()
|
968 |
| - builtin_property = cls.builtin_property |
969 |
| - abc_property = cls.abc_property |
| 1041 | + cachedp = cls.cachedproperty |
970 | 1042 | cached_p = cls.cached_property
|
971 | 1043 | reified = cls.reified
|
972 |
| - not_prop = cls.not_prop |
973 | 1044 | lazy_prop = cls.lazy_prop
|
974 | 1045 | lazyprop = cls.lazyprop
|
975 | 1046 | decorated_with_lazy = cls.decorated_with_lazy
|
| 1047 | + lazya = cls.lazyattribute |
| 1048 | + lazy_a = cls.lazy_attribute |
| 1049 | + LazyP = cls.LazyProperty |
| 1050 | + DynamicClassA = cls.DynamicClassAttribute |
976 | 1051 | """
|
977 | 1052 | )
|
978 | 1053 | for prop in (
|
979 |
| - "builtin_property", |
980 |
| - "abc_property", |
| 1054 | + "cachedp", |
981 | 1055 | "cached_p",
|
982 | 1056 | "reified",
|
983 | 1057 | "lazy_prop",
|
984 | 1058 | "lazyprop",
|
985 | 1059 | "decorated_with_lazy",
|
| 1060 | + "lazya", |
| 1061 | + "lazy_a", |
| 1062 | + "LazyP", |
| 1063 | + "DynamicClassA", |
986 | 1064 | ):
|
987 |
| - inferred = next(ast[prop].infer()) |
988 |
| - self.assertIsInstance(inferred, nodes.Const, prop) |
989 |
| - self.assertEqual(inferred.value, 42, prop) |
| 1065 | + self._is_property(ast, prop) |
| 1066 | + |
| 1067 | + def test_is_standard_property_subclass(self) -> None: |
| 1068 | + # Test to make sure that subclasses of the Python-provided property decorators |
| 1069 | + # are properly interpreted as properties |
| 1070 | + ast = builder.parse( |
| 1071 | + """ |
| 1072 | + import abc |
| 1073 | + import functools |
| 1074 | + from typing import Generic, TypeVar |
| 1075 | +
|
| 1076 | + class user_property(property): pass |
| 1077 | + class user_abc_property(abc.abstractproperty): pass |
| 1078 | + class user_functools_property(functools.cached_property): pass |
| 1079 | + T = TypeVar('T') |
| 1080 | + class annotated_user_functools_property(functools.cached_property[T], Generic[T]): pass |
| 1081 | +
|
| 1082 | + class A(object): |
| 1083 | + @user_property |
| 1084 | + def user_property(self): return 42 |
990 | 1085 |
|
991 |
| - inferred = next(ast["not_prop"].infer()) |
992 |
| - self.assertIsInstance(inferred, bases.BoundMethod) |
| 1086 | + @user_abc_property |
| 1087 | + def user_abc_property(self): return 42 |
| 1088 | +
|
| 1089 | + @user_functools_property |
| 1090 | + def user_functools_property(self): return 42 |
| 1091 | +
|
| 1092 | + @annotated_user_functools_property |
| 1093 | + def annotated_user_functools_property(self): return 42 |
| 1094 | +
|
| 1095 | + cls = A() |
| 1096 | + user_p = cls.user_property |
| 1097 | + user_abc_p = cls.user_abc_property |
| 1098 | + user_functools_p = cls.user_functools_property |
| 1099 | + annotated_user_functools_p = cls.annotated_user_functools_property |
| 1100 | + """ |
| 1101 | + ) |
| 1102 | + for prop in ( |
| 1103 | + "user_p", |
| 1104 | + "user_abc_p", |
| 1105 | + "user_functools_p", |
| 1106 | + "annotated_user_functools_p", |
| 1107 | + ): |
| 1108 | + self._is_property(ast, prop) |
| 1109 | + |
| 1110 | + @pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11") |
| 1111 | + def test_is_standard_property_subclass_py311(self) -> None: |
| 1112 | + # Test to make sure that subclasses of the Python-provided property decorators |
| 1113 | + # are properly interpreted as properties |
| 1114 | + ast = builder.parse( |
| 1115 | + """ |
| 1116 | + import enum |
| 1117 | +
|
| 1118 | + class user_enum_property(enum.property): pass |
| 1119 | +
|
| 1120 | + class A(object): |
| 1121 | + @user_enum_property |
| 1122 | + def user_enum_property(self): return 42 |
| 1123 | +
|
| 1124 | + cls = A() |
| 1125 | + user_enum_p = cls.user_enum_property |
| 1126 | + """ |
| 1127 | + ) |
| 1128 | + self._is_property(ast, "user_enum_p") |
| 1129 | + |
| 1130 | + @pytest.mark.skipif(not PY312_PLUS, reason="Uses 3.12 generic typing syntax") |
| 1131 | + def test_is_standard_property_subclass_py312(self) -> None: |
| 1132 | + ast = builder.parse( |
| 1133 | + """ |
| 1134 | + from functools import cached_property |
| 1135 | +
|
| 1136 | + class annotated_user_cached_property[T](cached_property[T]): |
| 1137 | + pass |
| 1138 | +
|
| 1139 | + class A(object): |
| 1140 | + @annotated_user_cached_property |
| 1141 | + def annotated_user_cached_property(self): return 42 |
| 1142 | +
|
| 1143 | + cls = A() |
| 1144 | + annotated_user_cached_p = cls.annotated_user_cached_property |
| 1145 | + """ |
| 1146 | + ) |
| 1147 | + self._is_property(ast, "annotated_user_cached_p") |
| 1148 | + |
| 1149 | + def test_is_not_property(self) -> None: |
| 1150 | + ast = builder.parse( |
| 1151 | + """ |
| 1152 | + from collections.abc import Iterator |
| 1153 | +
|
| 1154 | + class cached_property: pass |
| 1155 | + # If a decorator is named cached_property, we will accept it as a property, |
| 1156 | + # even if it isn't functools.cached_property. |
| 1157 | + # However, do not extend the same leniency to superclasses of decorators. |
| 1158 | + class wrong_superclass_type1(cached_property): pass |
| 1159 | + class wrong_superclass_type2(cached_property[float]): pass |
| 1160 | + cachedproperty = { float: int } |
| 1161 | + class wrong_superclass_type3(cachedproperty[float]): pass |
| 1162 | + class wrong_superclass_type4(Iterator[float]): pass |
| 1163 | +
|
| 1164 | + class A(object): |
| 1165 | + def no_decorator(self): return 42 |
| 1166 | +
|
| 1167 | + def property(self): return 42 |
| 1168 | +
|
| 1169 | + @wrong_superclass_type1 |
| 1170 | + def wrong_superclass_type1(self): return 42 |
| 1171 | +
|
| 1172 | + @wrong_superclass_type2 |
| 1173 | + def wrong_superclass_type2(self): return 42 |
| 1174 | +
|
| 1175 | + @wrong_superclass_type3 |
| 1176 | + def wrong_superclass_type3(self): return 42 |
| 1177 | +
|
| 1178 | + @wrong_superclass_type4 |
| 1179 | + def wrong_superclass_type4(self): return 42 |
| 1180 | +
|
| 1181 | + cls = A() |
| 1182 | + no_decorator = cls.no_decorator |
| 1183 | + not_prop = cls.property |
| 1184 | + bad_superclass1 = cls.wrong_superclass_type1 |
| 1185 | + bad_superclass2 = cls.wrong_superclass_type2 |
| 1186 | + bad_superclass3 = cls.wrong_superclass_type3 |
| 1187 | + bad_superclass4 = cls.wrong_superclass_type4 |
| 1188 | + """ |
| 1189 | + ) |
| 1190 | + for prop in ( |
| 1191 | + "no_decorator", |
| 1192 | + "not_prop", |
| 1193 | + "bad_superclass1", |
| 1194 | + "bad_superclass2", |
| 1195 | + "bad_superclass3", |
| 1196 | + "bad_superclass4", |
| 1197 | + ): |
| 1198 | + inferred = next(ast[prop].infer()) |
| 1199 | + self.assertIsInstance(inferred, bases.BoundMethod) |
993 | 1200 |
|
994 | 1201 |
|
995 | 1202 | class AliasesTest(unittest.TestCase):
|
|
0 commit comments