Skip to content

Commit

Permalink
Merge pull request numpy#12850 from mattip/import-multiarray2
Browse files Browse the repository at this point in the history
BUG: fail if old multiarray module detected
  • Loading branch information
charris authored Jan 30, 2019
2 parents 1843549 + 375cbca commit 6f3fca8
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 0 deletions.
13 changes: 13 additions & 0 deletions numpy/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@
del os

from . import umath

# Check that multiarray,umath are pure python modules wrapping
# _multiarray_umath and not either of the old c-extension modules
if not (hasattr(multiarray, '_multiarray_umath') and
hasattr(umath, '_multiarray_umath')):
import sys
path = sys.modules['numpy'].__path__
msg = ("Something is wrong with the numpy installation. "
"While importing we detected an older version of "
"numpy in {}. One method of fixing this is to repeatedly uninstall "
"numpy until none is found, then reinstall this version.")
raise ImportError(msg.format(path))

from . import numerictypes as nt
multiarray.set_typeDict(nt.sctypeDict)
from . import numeric
Expand Down
8 changes: 8 additions & 0 deletions numpy/core/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,14 @@ def generate_umath_c(ext, build_dir):
config.add_extension('_operand_flag_tests',
sources=[join('src', 'umath', '_operand_flag_tests.c.src')])

#######################################################################
# _multiarray_module_test module #
#######################################################################

config.add_extension('_multiarray_module_test',
sources=[join('src', 'multiarray',
'_multiarray_module_test.c')])

config.add_data_dir('tests')
config.add_data_dir('tests/data')

Expand Down
129 changes: 129 additions & 0 deletions numpy/core/src/multiarray/_multiarray_module_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include "Python.h"

/*
* This is a dummy module. It will be used to ruin the import of multiarray
* during testing. It exports two entry points, one to make the build happy,
* and a multiarray one for the actual test. The content of the module is
* irrelevant to the test.
*
* The code is from
* https://docs.python.org/3/howto/cporting.html
* or
* https://github.com/python/cpython/blob/v3.7.0/Doc/howto/cporting.rst
*/

#if defined _WIN32 || defined __CYGWIN__ || defined __MINGW32__
#if defined __GNUC__ || defined __clang__
#define DLL_PUBLIC __attribute__ ((dllexport))
#else
#define DLL_PUBLIC __declspec(dllexport)
#endif
#elif defined __GNUC__ || defined __clang__
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#else
/* Enhancement: error now instead ? */
#define DLL_PUBLIC
#endif

struct module_state {
PyObject *error;
};

#if PY_MAJOR_VERSION >= 3
#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
#else
#define GETSTATE(m) (&_state)
static struct module_state _state;
#endif

static PyObject *
error_out(PyObject *m) {
struct module_state *st = GETSTATE(m);
PyErr_SetString(st->error, "something bad happened");
return NULL;
}

static PyMethodDef multiarray_methods[] = {
{"error_out", (PyCFunction)error_out, METH_NOARGS, NULL},
{NULL, NULL}
};

#if PY_MAJOR_VERSION >= 3

static int multiarray_traverse(PyObject *m, visitproc visit, void *arg) {
Py_VISIT(GETSTATE(m)->error);
return 0;
}

static int multiarray_clear(PyObject *m) {
Py_CLEAR(GETSTATE(m)->error);
return 0;
}


static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"multiarray",
NULL,
sizeof(struct module_state),
multiarray_methods,
NULL,
multiarray_traverse,
multiarray_clear,
NULL
};

#define INITERROR return NULL

DLL_PUBLIC PyObject *
PyInit_multiarray(void)

#else
#define INITERROR return

void
DLL_PUBLIC initmultiarray(void)
#endif
{
#if PY_MAJOR_VERSION >= 3
PyObject *module = PyModule_Create(&moduledef);
#else
PyObject *module = Py_InitModule("multiarray", multiarray_methods);
#endif
struct module_state *st;
if (module == NULL)
INITERROR;
st = GETSTATE(module);

st->error = PyErr_NewException("multiarray.Error", NULL, NULL);
if (st->error == NULL) {
Py_DECREF(module);
INITERROR;
}

#if PY_MAJOR_VERSION >= 3
return module;
#endif
}

/*
* Define a dummy entry point to make MSVC happy
* Python's build system will export this function automatically
*/
#if PY_MAJOR_VERSION >= 3

PyObject *
PyInit__multiarray_module_test(void)
{
return PyInit_multiarray();
}

#else

void
init_multiarray_module_test(void)
{
initmultiarray();
}

#endif
40 changes: 40 additions & 0 deletions numpy/core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -8093,3 +8093,43 @@ def test_getfield():
pytest.raises(ValueError, a.getfield, 'uint8', -1)
pytest.raises(ValueError, a.getfield, 'uint8', 16)
pytest.raises(ValueError, a.getfield, 'uint64', 0)

def test_multiarray_module():
# gh-12736
# numpy 1.16 replaced the multiarray and umath c-extension modules with
# a single _multiarray_umath one. For backward compatibility, it added a
# pure-python multiarray.py and umath.py shim so people can still do
# from numpy.core.multirarray import something-public-api
# It turns out pip can leave old pieces of previous versions of numpy
# around when installing a newer version. If the old c-extension modules
# are found, they will be given precedence over the new pure-python ones.
#
# This test copies a multiarray c-extension in parallel with the pure-
# python one, and starts another python interpreter to load multiarray.
# The expectation is that import will fail.
import subprocess, shutil
core_dir = os.path.dirname(np.core.multiarray.__file__)
cextension = np.core._multiarray_umath.__file__
testfile = cextension.replace('_multiarray_umath', '_multiarray_module_test')
badfile = cextension.replace('_multiarray_umath', 'multiarray')
assert not os.path.exists(badfile), '%s exists, this numpy ' \
'installation is faulty' % badfile
try:
shutil.copy(testfile, badfile)
p = subprocess.Popen([sys.executable, '-c', 'import numpy' ],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=os.environ.copy())
stdout, stderr = p.communicate()
r = p.wait()
#print(stdout.decode())
#print(stderr.decode())
assert r != 0
assert b'ImportError' in stderr
finally:
if os.path.exists(badfile):
try:
# can this fail?
os.remove(badfile)
except:
print("Could not remove %s, remove it by hand" % badfile)
raise

0 comments on commit 6f3fca8

Please sign in to comment.