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