You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			807 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			807 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
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():
 | 
						|
    # We override dir to not show these members
 | 
						|
    allowlist = {
 | 
						|
        'recarray': 'numpy.rec.recarray',
 | 
						|
    }
 | 
						|
    bad_results = check_dir(np)
 | 
						|
    # pytest gives better error messages with the builtin assert than with
 | 
						|
    # assert_equal
 | 
						|
    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
 | 
						|
 | 
						|
    # Make sure they are still in the __dir__
 | 
						|
    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__)
 | 
						|
    # Make sure an arbitrary NPY_NO_EXPORT function is actually hidden
 | 
						|
    f = getattr(cdll, 'test_not_exported', None)
 | 
						|
    assert f is None, ("'test_not_exported' is mistakenly exported, "
 | 
						|
                      "NPY_NO_EXPORT does not work")
 | 
						|
 | 
						|
 | 
						|
# Historically NumPy has not used leading underscores for private submodules
 | 
						|
# much.  This has resulted in lots of things that look like public modules
 | 
						|
# (i.e. things that can be imported as `import numpy.somesubmodule.somefile`),
 | 
						|
# but were never intended to be public.  The PUBLIC_MODULES list contains
 | 
						|
# modules that are either public because they were meant to be, or because they
 | 
						|
# contain public functions/objects that aren't present in any other namespace
 | 
						|
# for whatever reason and therefore should be treated as public.
 | 
						|
#
 | 
						|
# The PRIVATE_BUT_PRESENT_MODULES list contains modules that look public (lack
 | 
						|
# of underscores) but should not be used.  For many of those modules the
 | 
						|
# current status is fine.  For others it may make sense to work on making them
 | 
						|
# private, to clean up our public API and avoid confusion.
 | 
						|
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",  # note: still needs cleaning, was forgotten for 2.0
 | 
						|
    "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",
 | 
						|
    "fft.helper",
 | 
						|
    "lib.user_array",  # note: not in np.lib, but probably should just be deleted
 | 
						|
    "linalg.lapack_lite",
 | 
						|
    "linalg.linalg",
 | 
						|
    "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:
 | 
						|
            # We have a name that is new.  If that's on purpose, add it to
 | 
						|
            # PUBLIC_MODULES.  We don't expect to have to add anything to
 | 
						|
            # PRIVATE_BUT_PRESENT_MODULES.  Use an underscore in the name!
 | 
						|
            modnames.append(modname)
 | 
						|
 | 
						|
    if modnames:
 | 
						|
        raise AssertionError(f'Found unexpected modules: {modnames}')
 | 
						|
 | 
						|
 | 
						|
# Stuff that clearly shouldn't be in the API and is detected by the next test
 | 
						|
# below
 | 
						|
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):
 | 
						|
                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.
 | 
						|
    """
 | 
						|
    # For a development install that did not go through meson-python,
 | 
						|
    # the entrypoint will not have been installed. So ensure this test fails
 | 
						|
    # only if numpy is inside site-packages.
 | 
						|
    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':
 | 
						|
        # Looks like the entrypoint for the current numpy build isn't
 | 
						|
        # installed, but an older numpy is also installed and hence the
 | 
						|
        # entrypoint is pointing to the old (no longer existing) location.
 | 
						|
        # This isn't a problem except for when running tests with `spin` or an
 | 
						|
        # in-place build.
 | 
						|
        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"  # included only in __dir__
 | 
						|
        })
 | 
						|
 | 
						|
    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):
 | 
						|
        # Skip private and test members. Also if a module is aliased,
 | 
						|
        # no need to add it to 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)
 | 
						|
 | 
						|
        # np.core is a shim and all submodules of np.core are shims
 | 
						|
        # but we should be able to import everything in those shims
 | 
						|
        # that are available in the "real" modules in np._core, with
 | 
						|
        # the exception of the namespace packages (__spec__.origin is None),
 | 
						|
        # like numpy._core.include, or numpy._core.lib.pkgconfig.
 | 
						|
        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):
 | 
						|
                # ignore dunder names
 | 
						|
                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 often have `__name__` overridden, therefore we need
 | 
						|
    # to keep track of locations where functions have been found.
 | 
						|
    functions_original_paths: dict[Callable[..., Any], str] = {}
 | 
						|
 | 
						|
    # Here we aggregate functions with more than one location.
 | 
						|
    # It must be empty for the test to pass.
 | 
						|
    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)
 | 
						|
 | 
						|
            # first check if we got a module
 | 
						|
            if (
 | 
						|
                inspect.ismodule(member) and  # it's a module
 | 
						|
                "numpy" in member.__name__ and  # inside NumPy
 | 
						|
                not member_name.startswith("_") and  # not private
 | 
						|
                "numpy._core" not in member.__name__ and  # outside _core
 | 
						|
                # not a legacy or testing module
 | 
						|
                member_name not in ["f2py", "ma", "testing", "tests"] and
 | 
						|
                member not in visited_modules  # not visited yet
 | 
						|
            ):
 | 
						|
                modules_queue.append(member)
 | 
						|
                visited_modules.add(member)
 | 
						|
 | 
						|
            # else check if we got a function-like object
 | 
						|
            elif (
 | 
						|
                inspect.isfunction(member) or
 | 
						|
                isinstance(member, (dispatched_function, np.ufunc))
 | 
						|
            ):
 | 
						|
                if member in visited_functions:
 | 
						|
 | 
						|
                    # skip main namespace functions with aliases
 | 
						|
                    if (
 | 
						|
                        member.__name__ in [
 | 
						|
                            "absolute",  # np.abs
 | 
						|
                            "arccos",  # np.acos
 | 
						|
                            "arccosh",  # np.acosh
 | 
						|
                            "arcsin",  # np.asin
 | 
						|
                            "arcsinh",  # np.asinh
 | 
						|
                            "arctan",  # np.atan
 | 
						|
                            "arctan2",  # np.atan2
 | 
						|
                            "arctanh",  # np.atanh
 | 
						|
                            "left_shift",  # np.bitwise_left_shift
 | 
						|
                            "right_shift",  # np.bitwise_right_shift
 | 
						|
                            "conjugate",  # np.conj
 | 
						|
                            "invert",  # np.bitwise_not & np.bitwise_invert
 | 
						|
                            "remainder",  # np.mod
 | 
						|
                            "divide",  # np.true_divide
 | 
						|
                            "concatenate",  # np.concat
 | 
						|
                            "power",  # np.pow
 | 
						|
                            "transpose",  # np.permute_dims
 | 
						|
                        ] and
 | 
						|
                        module.__name__ == "numpy"
 | 
						|
                    ):
 | 
						|
                        continue
 | 
						|
                    # skip trimcoef from numpy.polynomial as it is
 | 
						|
                    # duplicated by design.
 | 
						|
                    if (
 | 
						|
                        member.__name__ == "trimcoef" and
 | 
						|
                        module.__name__.startswith("numpy.polynomial")
 | 
						|
                    ):
 | 
						|
                        continue
 | 
						|
 | 
						|
                    # skip ufuncs that are exported in np.strings as well
 | 
						|
                    if member.__name__ in (
 | 
						|
                        "add",
 | 
						|
                        "equal",
 | 
						|
                        "not_equal",
 | 
						|
                        "greater",
 | 
						|
                        "greater_equal",
 | 
						|
                        "less",
 | 
						|
                        "less_equal",
 | 
						|
                    ) and module.__name__ == "numpy.strings":
 | 
						|
                        continue
 | 
						|
 | 
						|
                    # numpy.char reexports all numpy.strings functions for
 | 
						|
                    # backwards-compatibility
 | 
						|
                    if module.__name__ == "numpy.char":
 | 
						|
                        continue
 | 
						|
 | 
						|
                    # function is present in more than one location!
 | 
						|
                    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)
 | 
						|
            # first check if we got a module
 | 
						|
            if (
 | 
						|
                inspect.ismodule(member) and  # it's a module
 | 
						|
                "numpy" in member.__name__ and  # inside NumPy
 | 
						|
                not member_name.startswith("_") and  # not private
 | 
						|
                "numpy._core" not in member.__name__ and  # outside _core
 | 
						|
                # not in a skip module list
 | 
						|
                member_name not in [
 | 
						|
                    "char", "core", "f2py", "ma", "lapack_lite", "mrecords",
 | 
						|
                    "testing", "tests", "polynomial", "typing", "mtrand",
 | 
						|
                    "bit_generator",
 | 
						|
                ] and
 | 
						|
                member not in visited_modules  # not visited yet
 | 
						|
            ):
 | 
						|
                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
 | 
						|
            ):
 | 
						|
                # skip ufuncs that are exported in np.strings as well
 | 
						|
                if member.__name__ in (
 | 
						|
                    "add", "equal", "not_equal", "greater", "greater_equal",
 | 
						|
                    "less", "less_equal",
 | 
						|
                ) and module.__name__ == "numpy.strings":
 | 
						|
                    continue
 | 
						|
 | 
						|
                # recarray and record are exported in np and np.rec
 | 
						|
                if (
 | 
						|
                    (member.__name__ == "recarray" and module.__name__ == "numpy") or
 | 
						|
                    (member.__name__ == "record" and module.__name__ == "numpy.rec")
 | 
						|
                ):
 | 
						|
                    continue
 | 
						|
 | 
						|
                # ctypeslib exports ctypes c_long/c_longlong
 | 
						|
                if (
 | 
						|
                    member.__name__ in ("c_long", "c_longlong") and
 | 
						|
                    module.__name__ == "numpy.ctypeslib"
 | 
						|
                ):
 | 
						|
                    continue
 | 
						|
 | 
						|
                # skip cdef classes
 | 
						|
                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
 | 
						|
        # `obj` may be a bound method/property of `actual_obj`:
 | 
						|
        (
 | 
						|
            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():
 | 
						|
    # NumPy messes with module and name/qualname attributes, but any object
 | 
						|
    # should be discoverable based on its module and qualname, so test that.
 | 
						|
    # We do this for anything with a name (ensuring qualname is also set).
 | 
						|
    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)
 | 
						|
            # first check if we got a module
 | 
						|
            if (
 | 
						|
                inspect.ismodule(member) and  # it's a module
 | 
						|
                "numpy" in member.__name__ and  # inside NumPy
 | 
						|
                not member_name.startswith("_") and  # not private
 | 
						|
                member_name not in {"tests", "typing"} and  # 2024-12: type names don't match
 | 
						|
                "numpy._core" not in member.__name__ and  # outside _core
 | 
						|
                member not in visited_modules  # not visited yet
 | 
						|
            ):
 | 
						|
                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
 |