@@ -144,6 +144,11 @@ def get_description(self, concise: bool = False) -> str:
144
144
return "" .join (output )
145
145
146
146
147
+ # ====================
148
+ # Core logic
149
+ # ====================
150
+
151
+
147
152
def test_module (module_name : str ) -> Iterator [Error ]:
148
153
"""Tests a given module's stub against introspecting it at runtime.
149
154
@@ -204,7 +209,7 @@ def verify_mypyfile(
204
209
to_check = set (
205
210
m
206
211
for m , o in stub .names .items ()
207
- if not o .module_hidden and (not m . startswith ( "_" ) or hasattr (runtime , m ))
212
+ if not o .module_hidden and (not is_probably_private ( m ) or hasattr (runtime , m ))
208
213
)
209
214
210
215
def _belongs_to_runtime (r : types .ModuleType , attr : str ) -> bool :
@@ -220,15 +225,15 @@ def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
220
225
else [
221
226
m
222
227
for m in dir (runtime )
223
- if not m . startswith ( "_" )
228
+ if not is_probably_private ( m )
224
229
# Ensure that the object's module is `runtime`, since in the absence of __all__ we
225
230
# don't have a good way to detect re-exports at runtime.
226
231
and _belongs_to_runtime (runtime , m )
227
232
]
228
233
)
229
234
# Check all things declared in module's __all__, falling back to our best guess
230
235
to_check .update (runtime_public_contents )
231
- to_check .difference_update ({ "__file__" , "__doc__" , "__name__" , "__builtins__" , "__package__" } )
236
+ to_check .difference_update (IGNORED_MODULE_DUNDERS )
232
237
233
238
for entry in sorted (to_check ):
234
239
stub_entry = stub .names [entry ].node if entry in stub .names else MISSING
@@ -243,60 +248,12 @@ def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
243
248
)
244
249
245
250
246
- IGNORED_DUNDERS = frozenset ({
247
- # Very special attributes
248
- "__weakref__" ,
249
- "__slots__" ,
250
- "__dict__" ,
251
- "__text_signature__" ,
252
- # Pickle methods
253
- "__setstate__" ,
254
- "__getstate__" ,
255
- "__getnewargs__" ,
256
- "__getinitargs__" ,
257
- "__reduce_ex__" ,
258
- "__reduce__" ,
259
- # typing implementation details
260
- "__parameters__" ,
261
- "__origin__" ,
262
- "__args__" ,
263
- "__orig_bases__" ,
264
- "__final__" ,
265
- # isinstance/issubclass hooks that type-checkers don't usually care about
266
- "__instancecheck__" ,
267
- "__subclasshook__" ,
268
- "__subclasscheck__" ,
269
- # Dataclasses implementation details
270
- "__dataclass_fields__" ,
271
- "__dataclass_params__" ,
272
- # ctypes weirdness
273
- "__ctype_be__" ,
274
- "__ctype_le__" ,
275
- "__ctypes_from_outparam__" ,
276
- # These two are basically useless for type checkers
277
- "__hash__" ,
278
- "__getattr__" ,
279
- # For some reason, mypy doesn't infer classes with metaclass=ABCMeta inherit this attribute
280
- "__abstractmethods__" ,
281
- # Ideally we'd include __match_args__ in stubs,
282
- # but this currently has issues
283
- "__match_args__" ,
284
- "__doc__" , # Can only ever be str | None, who cares?
285
- "__del__" , # Only ever called when an object is being deleted, who cares?
286
- "__new_member__" , # If an enum defines __new__, the method is renamed as __new_member__
287
- })
288
-
289
-
290
251
if sys .version_info >= (3 , 7 ):
291
252
_WrapperDescriptorType = types .WrapperDescriptorType
292
253
else :
293
254
_WrapperDescriptorType = type (object .__init__ )
294
255
295
256
296
- def is_private (name : str ) -> bool :
297
- return name .startswith ("_" ) and not is_dunder (name )
298
-
299
-
300
257
@verify .register (nodes .TypeInfo )
301
258
def verify_typeinfo (
302
259
stub : nodes .TypeInfo , runtime : MaybeMissing [Type [Any ]], object_path : List [str ]
@@ -330,7 +287,9 @@ class SubClass(runtime): # type: ignore
330
287
to_check = set (stub .names )
331
288
to_check .update (
332
289
# cast to workaround mypyc complaints
333
- m for m in cast (Any , vars )(runtime ) if not is_private (m ) and m not in IGNORED_DUNDERS
290
+ m
291
+ for m in cast (Any , vars )(runtime )
292
+ if not is_probably_private (m ) and m not in ALLOW_MISSING_CLASS_DUNDERS
334
293
)
335
294
336
295
for entry in sorted (to_check ):
@@ -1009,6 +968,78 @@ def verify_typealias(
1009
968
)
1010
969
1011
970
971
+ # ====================
972
+ # Helpers
973
+ # ====================
974
+
975
+
976
+ IGNORED_MODULE_DUNDERS = frozenset (
977
+ {
978
+ "__file__" ,
979
+ "__doc__" ,
980
+ "__name__" ,
981
+ "__builtins__" ,
982
+ "__package__" ,
983
+ "__cached__" ,
984
+ "__loader__" ,
985
+ "__spec__" ,
986
+ "__path__" , # mypy adds __path__ to packages, but C packages don't have it
987
+ "__getattr__" , # resulting behaviour might be typed explicitly
988
+ # TODO: remove the following from this list
989
+ "__author__" ,
990
+ "__version__" ,
991
+ "__copyright__" ,
992
+ }
993
+ )
994
+
995
+ ALLOW_MISSING_CLASS_DUNDERS = frozenset (
996
+ {
997
+ # Special attributes
998
+ "__dict__" ,
999
+ "__text_signature__" ,
1000
+ "__weakref__" ,
1001
+ "__del__" , # Only ever called when an object is being deleted, who cares?
1002
+ # These two are basically useless for type checkers
1003
+ "__hash__" ,
1004
+ "__getattr__" , # resulting behaviour might be typed explicitly
1005
+ # isinstance/issubclass hooks that type-checkers don't usually care about
1006
+ "__instancecheck__" ,
1007
+ "__subclasshook__" ,
1008
+ "__subclasscheck__" ,
1009
+ # Pickle methods
1010
+ "__setstate__" ,
1011
+ "__getstate__" ,
1012
+ "__getnewargs__" ,
1013
+ "__getinitargs__" ,
1014
+ "__reduce_ex__" ,
1015
+ "__reduce__" ,
1016
+ # ctypes weirdness
1017
+ "__ctype_be__" ,
1018
+ "__ctype_le__" ,
1019
+ "__ctypes_from_outparam__" ,
1020
+ # mypy limitations
1021
+ "__abstractmethods__" , # Classes with metaclass=ABCMeta inherit this attribute
1022
+ "__new_member__" , # If an enum defines __new__, the method is renamed as __new_member__
1023
+ "__dataclass_fields__" , # Generated by dataclasses
1024
+ "__dataclass_params__" , # Generated by dataclasses
1025
+ "__doc__" , # mypy's semanal for namedtuples assumes this is str, not Optional[str]
1026
+ # typing implementation details, consider removing some of these:
1027
+ "__parameters__" ,
1028
+ "__origin__" ,
1029
+ "__args__" ,
1030
+ "__orig_bases__" ,
1031
+ "__final__" ,
1032
+ # Consider removing these:
1033
+ "__match_args__" ,
1034
+ "__slots__" ,
1035
+ }
1036
+ )
1037
+
1038
+
1039
+ def is_probably_private (name : str ) -> bool :
1040
+ return name .startswith ("_" ) and not is_dunder (name )
1041
+
1042
+
1012
1043
def is_probably_a_function (runtime : Any ) -> bool :
1013
1044
return (
1014
1045
isinstance (runtime , (types .FunctionType , types .BuiltinFunctionType ))
@@ -1151,6 +1182,11 @@ def anytype() -> mypy.types.AnyType:
1151
1182
return mypy .types .LiteralType (value = value , fallback = fallback )
1152
1183
1153
1184
1185
+ # ====================
1186
+ # Build and entrypoint
1187
+ # ====================
1188
+
1189
+
1154
1190
_all_stubs : Dict [str , nodes .MypyFile ] = {}
1155
1191
1156
1192
0 commit comments