| | import functools |
| | import importlib |
| | import inspect |
| | import pkgutil |
| | import subprocess |
| | import sys |
| | import sysconfig |
| | import types |
| | import warnings |
| |
|
| | import pytest |
| |
|
| | import numpy |
| | import numpy as np |
| | from numpy.testing import IS_WASM |
| |
|
| | try: |
| | import ctypes |
| | except ImportError: |
| | ctypes = None |
| |
|
| |
|
| | def check_dir(module, module_name=None): |
| | """Returns a mapping of all objects with the wrong __module__ attribute.""" |
| | if module_name is None: |
| | module_name = module.__name__ |
| | results = {} |
| | for name in dir(module): |
| | if name == "core": |
| | continue |
| | item = getattr(module, name) |
| | if (hasattr(item, '__module__') and hasattr(item, '__name__') |
| | and item.__module__ != module_name): |
| | results[name] = item.__module__ + '.' + item.__name__ |
| | return results |
| |
|
| |
|
| | def test_numpy_namespace(): |
| | |
| | allowlist = { |
| | 'recarray': 'numpy.rec.recarray', |
| | } |
| | bad_results = check_dir(np) |
| | |
| | |
| | assert bad_results == allowlist |
| |
|
| |
|
| | @pytest.mark.skipif(IS_WASM, reason="can't start subprocess") |
| | @pytest.mark.parametrize('name', ['testing']) |
| | def test_import_lazy_import(name): |
| | """Make sure we can actually use the modules we lazy load. |
| | |
| | While not exported as part of the public API, it was accessible. With the |
| | use of __getattr__ and __dir__, this isn't always true It can happen that |
| | an infinite recursion may happen. |
| | |
| | This is the only way I found that would force the failure to appear on the |
| | badly implemented code. |
| | |
| | We also test for the presence of the lazily imported modules in dir |
| | |
| | """ |
| | exe = (sys.executable, '-c', "import numpy; numpy." + name) |
| | result = subprocess.check_output(exe) |
| | assert not result |
| |
|
| | |
| | assert name in dir(np) |
| |
|
| |
|
| | def test_dir_testing(): |
| | """Assert that output of dir has only one "testing/tester" |
| | attribute without duplicate""" |
| | assert len(dir(np)) == len(set(dir(np))) |
| |
|
| |
|
| | def test_numpy_linalg(): |
| | bad_results = check_dir(np.linalg) |
| | assert bad_results == {} |
| |
|
| |
|
| | def test_numpy_fft(): |
| | bad_results = check_dir(np.fft) |
| | assert bad_results == {} |
| |
|
| |
|
| | @pytest.mark.skipif(ctypes is None, |
| | reason="ctypes not available in this python") |
| | def test_NPY_NO_EXPORT(): |
| | cdll = ctypes.CDLL(np._core._multiarray_tests.__file__) |
| | |
| | f = getattr(cdll, 'test_not_exported', None) |
| | assert f is None, ("'test_not_exported' is mistakenly exported, " |
| | "NPY_NO_EXPORT does not work") |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | PUBLIC_MODULES = ['numpy.' + s for s in [ |
| | "ctypeslib", |
| | "dtypes", |
| | "exceptions", |
| | "f2py", |
| | "fft", |
| | "lib", |
| | "lib.array_utils", |
| | "lib.format", |
| | "lib.introspect", |
| | "lib.mixins", |
| | "lib.npyio", |
| | "lib.recfunctions", |
| | "lib.scimath", |
| | "lib.stride_tricks", |
| | "linalg", |
| | "ma", |
| | "ma.extras", |
| | "ma.mrecords", |
| | "polynomial", |
| | "polynomial.chebyshev", |
| | "polynomial.hermite", |
| | "polynomial.hermite_e", |
| | "polynomial.laguerre", |
| | "polynomial.legendre", |
| | "polynomial.polynomial", |
| | "random", |
| | "strings", |
| | "testing", |
| | "testing.overrides", |
| | "typing", |
| | "typing.mypy_plugin", |
| | "version", |
| | ]] |
| | if sys.version_info < (3, 12): |
| | PUBLIC_MODULES += [ |
| | 'numpy.' + s for s in [ |
| | "distutils", |
| | "distutils.cpuinfo", |
| | "distutils.exec_command", |
| | "distutils.misc_util", |
| | "distutils.log", |
| | "distutils.system_info", |
| | ] |
| | ] |
| |
|
| |
|
| | PUBLIC_ALIASED_MODULES = [ |
| | "numpy.char", |
| | "numpy.emath", |
| | "numpy.rec", |
| | ] |
| |
|
| |
|
| | PRIVATE_BUT_PRESENT_MODULES = ['numpy.' + s for s in [ |
| | "conftest", |
| | "core", |
| | "core.multiarray", |
| | "core.numeric", |
| | "core.umath", |
| | "core.arrayprint", |
| | "core.defchararray", |
| | "core.einsumfunc", |
| | "core.fromnumeric", |
| | "core.function_base", |
| | "core.getlimits", |
| | "core.numerictypes", |
| | "core.overrides", |
| | "core.records", |
| | "core.shape_base", |
| | "f2py.auxfuncs", |
| | "f2py.capi_maps", |
| | "f2py.cb_rules", |
| | "f2py.cfuncs", |
| | "f2py.common_rules", |
| | "f2py.crackfortran", |
| | "f2py.diagnose", |
| | "f2py.f2py2e", |
| | "f2py.f90mod_rules", |
| | "f2py.func2subr", |
| | "f2py.rules", |
| | "f2py.symbolic", |
| | "f2py.use_rules", |
| | "lib.user_array", |
| | "linalg.lapack_lite", |
| | "ma.core", |
| | "ma.testutils", |
| | "matlib", |
| | "matrixlib", |
| | "matrixlib.defmatrix", |
| | "polynomial.polyutils", |
| | "random.mtrand", |
| | "random.bit_generator", |
| | "testing.print_coercion_tables", |
| | ]] |
| | if sys.version_info < (3, 12): |
| | PRIVATE_BUT_PRESENT_MODULES += [ |
| | 'numpy.' + s for s in [ |
| | "distutils.armccompiler", |
| | "distutils.fujitsuccompiler", |
| | "distutils.ccompiler", |
| | 'distutils.ccompiler_opt', |
| | "distutils.command", |
| | "distutils.command.autodist", |
| | "distutils.command.bdist_rpm", |
| | "distutils.command.build", |
| | "distutils.command.build_clib", |
| | "distutils.command.build_ext", |
| | "distutils.command.build_py", |
| | "distutils.command.build_scripts", |
| | "distutils.command.build_src", |
| | "distutils.command.config", |
| | "distutils.command.config_compiler", |
| | "distutils.command.develop", |
| | "distutils.command.egg_info", |
| | "distutils.command.install", |
| | "distutils.command.install_clib", |
| | "distutils.command.install_data", |
| | "distutils.command.install_headers", |
| | "distutils.command.sdist", |
| | "distutils.conv_template", |
| | "distutils.core", |
| | "distutils.extension", |
| | "distutils.fcompiler", |
| | "distutils.fcompiler.absoft", |
| | "distutils.fcompiler.arm", |
| | "distutils.fcompiler.compaq", |
| | "distutils.fcompiler.environment", |
| | "distutils.fcompiler.g95", |
| | "distutils.fcompiler.gnu", |
| | "distutils.fcompiler.hpux", |
| | "distutils.fcompiler.ibm", |
| | "distutils.fcompiler.intel", |
| | "distutils.fcompiler.lahey", |
| | "distutils.fcompiler.mips", |
| | "distutils.fcompiler.nag", |
| | "distutils.fcompiler.none", |
| | "distutils.fcompiler.pathf95", |
| | "distutils.fcompiler.pg", |
| | "distutils.fcompiler.nv", |
| | "distutils.fcompiler.sun", |
| | "distutils.fcompiler.vast", |
| | "distutils.fcompiler.fujitsu", |
| | "distutils.from_template", |
| | "distutils.intelccompiler", |
| | "distutils.lib2def", |
| | "distutils.line_endings", |
| | "distutils.mingw32ccompiler", |
| | "distutils.msvccompiler", |
| | "distutils.npy_pkg_config", |
| | "distutils.numpy_distribution", |
| | "distutils.pathccompiler", |
| | "distutils.unixccompiler", |
| | ] |
| | ] |
| |
|
| |
|
| | def is_unexpected(name): |
| | """Check if this needs to be considered.""" |
| | return ( |
| | '._' not in name and '.tests' not in name and '.setup' not in name |
| | and name not in PUBLIC_MODULES |
| | and name not in PUBLIC_ALIASED_MODULES |
| | and name not in PRIVATE_BUT_PRESENT_MODULES |
| | ) |
| |
|
| |
|
| | if sys.version_info >= (3, 12): |
| | SKIP_LIST = [] |
| | else: |
| | SKIP_LIST = ["numpy.distutils.msvc9compiler"] |
| |
|
| |
|
| | def test_all_modules_are_expected(): |
| | """ |
| | Test that we don't add anything that looks like a new public module by |
| | accident. Check is based on filenames. |
| | """ |
| |
|
| | modnames = [] |
| | for _, modname, ispkg in pkgutil.walk_packages(path=np.__path__, |
| | prefix=np.__name__ + '.', |
| | onerror=None): |
| | if is_unexpected(modname) and modname not in SKIP_LIST: |
| | |
| | |
| | |
| | modnames.append(modname) |
| |
|
| | if modnames: |
| | raise AssertionError(f'Found unexpected modules: {modnames}') |
| |
|
| |
|
| | |
| | |
| | SKIP_LIST_2 = [ |
| | 'numpy.lib.math', |
| | 'numpy.matlib.char', |
| | 'numpy.matlib.rec', |
| | 'numpy.matlib.emath', |
| | 'numpy.matlib.exceptions', |
| | 'numpy.matlib.math', |
| | 'numpy.matlib.linalg', |
| | 'numpy.matlib.fft', |
| | 'numpy.matlib.random', |
| | 'numpy.matlib.ctypeslib', |
| | 'numpy.matlib.ma', |
| | ] |
| | if sys.version_info < (3, 12): |
| | SKIP_LIST_2 += [ |
| | 'numpy.distutils.log.sys', |
| | 'numpy.distutils.log.logging', |
| | 'numpy.distutils.log.warnings', |
| | ] |
| |
|
| |
|
| | def test_all_modules_are_expected_2(): |
| | """ |
| | Method checking all objects. The pkgutil-based method in |
| | `test_all_modules_are_expected` does not catch imports into a namespace, |
| | only filenames. So this test is more thorough, and checks this like: |
| | |
| | import .lib.scimath as emath |
| | |
| | To check if something in a module is (effectively) public, one can check if |
| | there's anything in that namespace that's a public function/object but is |
| | not exposed in a higher-level namespace. For example for a `numpy.lib` |
| | submodule:: |
| | |
| | mod = np.lib.mixins |
| | for obj in mod.__all__: |
| | if obj in np.__all__: |
| | continue |
| | elif obj in np.lib.__all__: |
| | continue |
| | |
| | else: |
| | print(obj) |
| | |
| | """ |
| |
|
| | def find_unexpected_members(mod_name): |
| | members = [] |
| | module = importlib.import_module(mod_name) |
| | if hasattr(module, '__all__'): |
| | objnames = module.__all__ |
| | else: |
| | objnames = dir(module) |
| |
|
| | for objname in objnames: |
| | if not objname.startswith('_'): |
| | fullobjname = mod_name + '.' + objname |
| | if isinstance(getattr(module, objname), types.ModuleType): |
| | if is_unexpected(fullobjname): |
| | if fullobjname not in SKIP_LIST_2: |
| | members.append(fullobjname) |
| |
|
| | return members |
| |
|
| | unexpected_members = find_unexpected_members("numpy") |
| | for modname in PUBLIC_MODULES: |
| | unexpected_members.extend(find_unexpected_members(modname)) |
| |
|
| | if unexpected_members: |
| | raise AssertionError("Found unexpected object(s) that look like " |
| | f"modules: {unexpected_members}") |
| |
|
| |
|
| | def test_api_importable(): |
| | """ |
| | Check that all submodules listed higher up in this file can be imported |
| | |
| | Note that if a PRIVATE_BUT_PRESENT_MODULES entry goes missing, it may |
| | simply need to be removed from the list (deprecation may or may not be |
| | needed - apply common sense). |
| | """ |
| | def check_importable(module_name): |
| | try: |
| | importlib.import_module(module_name) |
| | except (ImportError, AttributeError): |
| | return False |
| |
|
| | return True |
| |
|
| | module_names = [] |
| | for module_name in PUBLIC_MODULES: |
| | if not check_importable(module_name): |
| | module_names.append(module_name) |
| |
|
| | if module_names: |
| | raise AssertionError("Modules in the public API that cannot be " |
| | f"imported: {module_names}") |
| |
|
| | for module_name in PUBLIC_ALIASED_MODULES: |
| | try: |
| | eval(module_name) |
| | except AttributeError: |
| | module_names.append(module_name) |
| |
|
| | if module_names: |
| | raise AssertionError("Modules in the public API that were not " |
| | f"found: {module_names}") |
| |
|
| | with warnings.catch_warnings(record=True) as w: |
| | warnings.filterwarnings('always', category=DeprecationWarning) |
| | warnings.filterwarnings('always', category=ImportWarning) |
| | for module_name in PRIVATE_BUT_PRESENT_MODULES: |
| | if not check_importable(module_name): |
| | |
| | |
| | if not module_name == 'numpy.distutils.msvccompiler': |
| | module_names.append(module_name) |
| |
|
| | if module_names: |
| | raise AssertionError("Modules that are not really public but looked " |
| | "public and can not be imported: " |
| | f"{module_names}") |
| |
|
| |
|
| | @pytest.mark.xfail( |
| | sysconfig.get_config_var("Py_DEBUG") not in (None, 0, "0"), |
| | reason=( |
| | "NumPy possibly built with `USE_DEBUG=True ./tools/travis-test.sh`, " |
| | "which does not expose the `array_api` entry point. " |
| | "See https://github.com/numpy/numpy/pull/19800" |
| | ), |
| | ) |
| | def test_array_api_entry_point(): |
| | """ |
| | Entry point for Array API implementation can be found with importlib and |
| | returns the main numpy namespace. |
| | """ |
| | |
| | |
| | |
| | numpy_in_sitepackages = sysconfig.get_path('platlib') in np.__file__ |
| |
|
| | eps = importlib.metadata.entry_points() |
| | xp_eps = eps.select(group="array_api") |
| | if len(xp_eps) == 0: |
| | if numpy_in_sitepackages: |
| | msg = "No entry points for 'array_api' found" |
| | raise AssertionError(msg) from None |
| | return |
| |
|
| | try: |
| | ep = next(ep for ep in xp_eps if ep.name == "numpy") |
| | except StopIteration: |
| | if numpy_in_sitepackages: |
| | msg = "'numpy' not in array_api entry points" |
| | raise AssertionError(msg) from None |
| | return |
| |
|
| | if ep.value == 'numpy.array_api': |
| | |
| | |
| | |
| | |
| | |
| | return |
| |
|
| | xp = ep.load() |
| | msg = ( |
| | f"numpy entry point value '{ep.value}' " |
| | "does not point to our Array API implementation" |
| | ) |
| | assert xp is numpy, msg |
| |
|
| |
|
| | def test_main_namespace_all_dir_coherence(): |
| | """ |
| | Checks if `dir(np)` and `np.__all__` are consistent and return |
| | the same content, excluding exceptions and private members. |
| | """ |
| | def _remove_private_members(member_set): |
| | return {m for m in member_set if not m.startswith('_')} |
| |
|
| | def _remove_exceptions(member_set): |
| | return member_set.difference({ |
| | "bool" |
| | }) |
| |
|
| | all_members = _remove_private_members(np.__all__) |
| | all_members = _remove_exceptions(all_members) |
| |
|
| | dir_members = _remove_private_members(np.__dir__()) |
| | dir_members = _remove_exceptions(dir_members) |
| |
|
| | assert all_members == dir_members, ( |
| | "Members that break symmetry: " |
| | f"{all_members.symmetric_difference(dir_members)}" |
| | ) |
| |
|
| |
|
| | @pytest.mark.filterwarnings( |
| | r"ignore:numpy.core(\.\w+)? is deprecated:DeprecationWarning" |
| | ) |
| | def test_core_shims_coherence(): |
| | """ |
| | Check that all "semi-public" members of `numpy._core` are also accessible |
| | from `numpy.core` shims. |
| | """ |
| | import numpy.core as core |
| |
|
| | for member_name in dir(np._core): |
| | |
| | |
| | if ( |
| | member_name.startswith("_") |
| | or member_name in ["tests", "strings"] |
| | or f"numpy.{member_name}" in PUBLIC_ALIASED_MODULES |
| | ): |
| | continue |
| |
|
| | member = getattr(np._core, member_name) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if ( |
| | inspect.ismodule(member) |
| | and member.__spec__ and member.__spec__.origin is not None |
| | ): |
| | submodule = member |
| | submodule_name = member_name |
| | for submodule_member_name in dir(submodule): |
| | |
| | if submodule_member_name.startswith("__"): |
| | continue |
| | submodule_member = getattr(submodule, submodule_member_name) |
| |
|
| | core_submodule = __import__( |
| | f"numpy.core.{submodule_name}", |
| | fromlist=[submodule_member_name] |
| | ) |
| |
|
| | assert submodule_member is getattr( |
| | core_submodule, submodule_member_name |
| | ) |
| |
|
| | else: |
| | assert member is getattr(core, member_name) |
| |
|
| |
|
| | def test_functions_single_location(): |
| | """ |
| | Check that each public function is available from one location only. |
| | |
| | Test performs BFS search traversing NumPy's public API. It flags |
| | any function-like object that is accessible from more that one place. |
| | """ |
| | from collections.abc import Callable |
| | from typing import Any |
| |
|
| | from numpy._core._multiarray_umath import ( |
| | _ArrayFunctionDispatcher as dispatched_function, |
| | ) |
| |
|
| | visited_modules: set[types.ModuleType] = {np} |
| | visited_functions: set[Callable[..., Any]] = set() |
| | |
| | |
| | functions_original_paths: dict[Callable[..., Any], str] = {} |
| |
|
| | |
| | |
| | duplicated_functions: list[tuple] = [] |
| |
|
| | modules_queue = [np] |
| |
|
| | while len(modules_queue) > 0: |
| |
|
| | module = modules_queue.pop() |
| |
|
| | for member_name in dir(module): |
| | member = getattr(module, member_name) |
| |
|
| | |
| | if ( |
| | inspect.ismodule(member) and |
| | "numpy" in member.__name__ and |
| | not member_name.startswith("_") and |
| | "numpy._core" not in member.__name__ and |
| | |
| | member_name not in ["f2py", "ma", "testing", "tests"] and |
| | member not in visited_modules |
| | ): |
| | modules_queue.append(member) |
| | visited_modules.add(member) |
| |
|
| | |
| | elif ( |
| | inspect.isfunction(member) or |
| | isinstance(member, (dispatched_function, np.ufunc)) |
| | ): |
| | if member in visited_functions: |
| |
|
| | |
| | if ( |
| | member.__name__ in [ |
| | "absolute", |
| | "arccos", |
| | "arccosh", |
| | "arcsin", |
| | "arcsinh", |
| | "arctan", |
| | "arctan2", |
| | "arctanh", |
| | "left_shift", |
| | "right_shift", |
| | "conjugate", |
| | "invert", |
| | "remainder", |
| | "divide", |
| | "concatenate", |
| | "power", |
| | "transpose", |
| | ] and |
| | module.__name__ == "numpy" |
| | ): |
| | continue |
| | |
| | |
| | if ( |
| | member.__name__ == "trimcoef" and |
| | module.__name__.startswith("numpy.polynomial") |
| | ): |
| | continue |
| |
|
| | |
| | if member.__name__ in ( |
| | "add", |
| | "equal", |
| | "not_equal", |
| | "greater", |
| | "greater_equal", |
| | "less", |
| | "less_equal", |
| | ) and module.__name__ == "numpy.strings": |
| | continue |
| |
|
| | |
| | |
| | if module.__name__ == "numpy.char": |
| | continue |
| |
|
| | |
| | duplicated_functions.append( |
| | (member.__name__, |
| | module.__name__, |
| | functions_original_paths[member]) |
| | ) |
| | else: |
| | visited_functions.add(member) |
| | functions_original_paths[member] = module.__name__ |
| |
|
| | del visited_functions, visited_modules, functions_original_paths |
| |
|
| | assert len(duplicated_functions) == 0, duplicated_functions |
| |
|
| |
|
| | def test___module___attribute(): |
| | modules_queue = [np] |
| | visited_modules = {np} |
| | visited_functions = set() |
| | incorrect_entries = [] |
| |
|
| | while len(modules_queue) > 0: |
| | module = modules_queue.pop() |
| | for member_name in dir(module): |
| | member = getattr(module, member_name) |
| | |
| | if ( |
| | inspect.ismodule(member) and |
| | "numpy" in member.__name__ and |
| | not member_name.startswith("_") and |
| | "numpy._core" not in member.__name__ and |
| | |
| | member_name not in [ |
| | "char", "core", "f2py", "ma", "lapack_lite", "mrecords", |
| | "testing", "tests", "polynomial", "typing", "mtrand", |
| | "bit_generator", |
| | ] and |
| | member not in visited_modules |
| | ): |
| | modules_queue.append(member) |
| | visited_modules.add(member) |
| | elif ( |
| | not inspect.ismodule(member) and |
| | hasattr(member, "__name__") and |
| | not member.__name__.startswith("_") and |
| | member.__module__ != module.__name__ and |
| | member not in visited_functions |
| | ): |
| | |
| | if member.__name__ in ( |
| | "add", "equal", "not_equal", "greater", "greater_equal", |
| | "less", "less_equal", |
| | ) and module.__name__ == "numpy.strings": |
| | continue |
| |
|
| | |
| | if ( |
| | (member.__name__ == "recarray" and module.__name__ == "numpy") or |
| | (member.__name__ == "record" and module.__name__ == "numpy.rec") |
| | ): |
| | continue |
| |
|
| | |
| | if ( |
| | member.__name__ in ("c_long", "c_longlong") and |
| | module.__name__ == "numpy.ctypeslib" |
| | ): |
| | continue |
| |
|
| | |
| | if member.__name__ in ( |
| | "BitGenerator", "Generator", "MT19937", "PCG64", "PCG64DXSM", |
| | "Philox", "RandomState", "SFC64", "SeedSequence", |
| | ): |
| | continue |
| |
|
| | incorrect_entries.append( |
| | { |
| | "Func": member.__name__, |
| | "actual": member.__module__, |
| | "expected": module.__name__, |
| | } |
| | ) |
| | visited_functions.add(member) |
| |
|
| | if incorrect_entries: |
| | assert len(incorrect_entries) == 0, incorrect_entries |
| |
|
| |
|
| | def _check_correct_qualname_and_module(obj) -> bool: |
| | qualname = obj.__qualname__ |
| | name = obj.__name__ |
| | module_name = obj.__module__ |
| | assert name == qualname.split(".")[-1] |
| |
|
| | module = sys.modules[module_name] |
| | actual_obj = functools.reduce(getattr, qualname.split("."), module) |
| | return ( |
| | actual_obj is obj or |
| | |
| | ( |
| | hasattr(actual_obj, "__get__") and hasattr(obj, "__self__") and |
| | actual_obj.__module__ == obj.__module__ and |
| | actual_obj.__qualname__ == qualname |
| | ) |
| | ) |
| |
|
| |
|
| | def test___qualname___and___module___attribute(): |
| | |
| | |
| | |
| | modules_queue = [np] |
| | visited_modules = {np} |
| | visited_functions = set() |
| | incorrect_entries = [] |
| |
|
| | while len(modules_queue) > 0: |
| | module = modules_queue.pop() |
| | for member_name in dir(module): |
| | member = getattr(module, member_name) |
| | |
| | if ( |
| | inspect.ismodule(member) and |
| | "numpy" in member.__name__ and |
| | not member_name.startswith("_") and |
| | member_name not in {"tests", "typing"} and |
| | "numpy._core" not in member.__name__ and |
| | member not in visited_modules |
| | ): |
| | modules_queue.append(member) |
| | visited_modules.add(member) |
| | elif ( |
| | not inspect.ismodule(member) and |
| | hasattr(member, "__name__") and |
| | not member.__name__.startswith("_") and |
| | not member_name.startswith("_") and |
| | not _check_correct_qualname_and_module(member) and |
| | member not in visited_functions |
| | ): |
| | incorrect_entries.append( |
| | { |
| | "found_at": f"{module.__name__}:{member_name}", |
| | "advertises": f"{member.__module__}:{member.__qualname__}", |
| | } |
| | ) |
| | visited_functions.add(member) |
| |
|
| | if incorrect_entries: |
| | assert len(incorrect_entries) == 0, incorrect_entries |
| |
|