@@ -924,67 +924,274 @@ def test(self):
924924
925925
926926class 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
928935 ast = builder .parse (
929936 """
930937 import abc
938+ import functools
931939
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
943940 class A(object):
944941 @property
945- def builtin_property(self):
946- return 42
942+ def builtin_property(self): return 42
943+
947944 @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+
9501008 @cached_property
9511009 def cached_property(self): return 42
1010+
9521011 @reify
9531012 def reified(self): return 42
1013+
9541014 @lazy_property
9551015 def lazy_prop(self): return 42
1016+
9561017 @lazyproperty
9571018 def lazyprop(self): return 42
958- def not_prop(self): pass
1019+
9591020 @lazy
9601021 def decorated_with_lazy(self): return 42
9611022
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+
9621035 cls = A()
963- builtin_property = cls.builtin_property
964- abc_property = cls.abc_property
1036+ cachedp = cls.cachedproperty
9651037 cached_p = cls.cached_property
9661038 reified = cls.reified
967- not_prop = cls.not_prop
9681039 lazy_prop = cls.lazy_prop
9691040 lazyprop = cls.lazyprop
9701041 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
9711046 """
9721047 )
9731048 for prop in (
974- "builtin_property" ,
975- "abc_property" ,
1049+ "cachedp" ,
9761050 "cached_p" ,
9771051 "reified" ,
9781052 "lazy_prop" ,
9791053 "lazyprop" ,
9801054 "decorated_with_lazy" ,
1055+ "lazya" ,
1056+ "lazy_a" ,
1057+ "LazyP" ,
1058+ "DynamicClassA" ,
9811059 ):
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
9851080
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 )
9881195
9891196
9901197class AliasesTest (unittest .TestCase ):
0 commit comments