@@ -144,6 +144,11 @@ def get_description(self, concise: bool = False) -> str:
144144 return "" .join (output )
145145
146146
147+ # ====================
148+ # Core logic
149+ # ====================
150+
151+
147152def test_module (module_name : str ) -> Iterator [Error ]:
148153 """Tests a given module's stub against introspecting it at runtime.
149154
@@ -204,7 +209,7 @@ def verify_mypyfile(
204209 to_check = set (
205210 m
206211 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 ))
208213 )
209214
210215 def _belongs_to_runtime (r : types .ModuleType , attr : str ) -> bool :
@@ -220,15 +225,15 @@ def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
220225 else [
221226 m
222227 for m in dir (runtime )
223- if not m . startswith ( "_" )
228+ if not is_probably_private ( m )
224229 # Ensure that the object's module is `runtime`, since in the absence of __all__ we
225230 # don't have a good way to detect re-exports at runtime.
226231 and _belongs_to_runtime (runtime , m )
227232 ]
228233 )
229234 # Check all things declared in module's __all__, falling back to our best guess
230235 to_check .update (runtime_public_contents )
231- to_check .difference_update ({ "__file__" , "__doc__" , "__name__" , "__builtins__" , "__package__" } )
236+ to_check .difference_update (IGNORED_MODULE_DUNDERS )
232237
233238 for entry in sorted (to_check ):
234239 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:
243248 )
244249
245250
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-
290251if sys .version_info >= (3 , 7 ):
291252 _WrapperDescriptorType = types .WrapperDescriptorType
292253else :
293254 _WrapperDescriptorType = type (object .__init__ )
294255
295256
296- def is_private (name : str ) -> bool :
297- return name .startswith ("_" ) and not is_dunder (name )
298-
299-
300257@verify .register (nodes .TypeInfo )
301258def verify_typeinfo (
302259 stub : nodes .TypeInfo , runtime : MaybeMissing [Type [Any ]], object_path : List [str ]
@@ -330,7 +287,9 @@ class SubClass(runtime): # type: ignore
330287 to_check = set (stub .names )
331288 to_check .update (
332289 # 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
334293 )
335294
336295 for entry in sorted (to_check ):
@@ -1009,6 +968,78 @@ def verify_typealias(
1009968 )
1010969
1011970
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+
10121043def is_probably_a_function (runtime : Any ) -> bool :
10131044 return (
10141045 isinstance (runtime , (types .FunctionType , types .BuiltinFunctionType ))
@@ -1151,6 +1182,11 @@ def anytype() -> mypy.types.AnyType:
11511182 return mypy .types .LiteralType (value = value , fallback = fallback )
11521183
11531184
1185+ # ====================
1186+ # Build and entrypoint
1187+ # ====================
1188+
1189+
11541190_all_stubs : Dict [str , nodes .MypyFile ] = {}
11551191
11561192
0 commit comments