From 055bcb1aa8bf9b0b6c7edfb496dcd9989cf1886d Mon Sep 17 00:00:00 2001 From: neumond Date: Thu, 1 Nov 2018 23:51:02 +0800 Subject: [PATCH 1/2] Specific "importlib" mode for pyimport intended for pytest improvement regarding loading test modules with identical names --- py/_path/local.py | 30 ++++++++++++++++++++++++++++++ testing/path/test_local.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/py/_path/local.py b/py/_path/local.py index 41271106..3ac31945 100644 --- a/py/_path/local.py +++ b/py/_path/local.py @@ -18,6 +18,11 @@ def map_as_list(func, iter): else: map_as_list = map +ALLOW_IMPORTLIB_MODE = sys.version_info > (3,5) +if ALLOW_IMPORTLIB_MODE: + import importlib + + class Stat(object): def __getattr__(self, name): return getattr(self._osstatresult, "st_" + name) @@ -647,10 +652,35 @@ def pyimport(self, modname=None, ensuresyspath=True): If ensuresyspath=="append" the root dir will be appended if it isn't already contained in sys.path. if ensuresyspath is False no modification of syspath happens. + + Special value of ensuresyspath=="importlib" is intended + purely for using in pytest, it is capable only of importing + separate .py files outside packages, e.g. for test suite + without any __init__.py file. It effectively allows having + same-named test modules in different places and offers + mild opt-in via this option. Note that it works only in + recent versions of python. """ if not self.check(): raise py.error.ENOENT(self) + if ensuresyspath == 'importlib': + if modname is None: + modname = self.purebasename + if not ALLOW_IMPORTLIB_MODE: + raise ImportError( + "Can't use importlib due to old version of Python") + spec = importlib.util.spec_from_file_location( + modname, str(self)) + if spec is None: + raise ImportError( + "Can't find module %s at location %s" % + (modname, str(self)) + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + pkgpath = None if modname is None: pkgpath = self.pypkgpath() diff --git a/testing/path/test_local.py b/testing/path/test_local.py index af64086d..2b8519c5 100644 --- a/testing/path/test_local.py +++ b/testing/path/test_local.py @@ -581,6 +581,39 @@ def test_ensuresyspath_append(self, tmpdir): assert str(root1) not in sys.path[:-1] +class TestImportlibImport: + pytestmark = py.test.mark.skipif("sys.version_info < (3, 5)") + + OPTS = {'ensuresyspath': 'importlib'} + + def test_pyimport(self, path1): + obj = path1.join('execfile.py').pyimport(**self.OPTS) + assert obj.x == 42 + assert obj.__name__ == 'execfile' + + def test_pyimport_dir_fails(self, tmpdir): + p = tmpdir.join("hello_123") + p.ensure("__init__.py") + with pytest.raises(ImportError): + p.pyimport(**self.OPTS) + + def test_pyimport_execfile_different_name(self, path1): + obj = path1.join('execfile.py').pyimport(modname="0x.y.z", **self.OPTS) + assert obj.x == 42 + assert obj.__name__ == '0x.y.z' + + def test_pyimport_relative_import_fails(self, path1): + otherdir = path1.join('otherdir') + with pytest.raises(ImportError): + otherdir.join('a.py').pyimport(**self.OPTS) + + def test_pyimport_doesnt_use_sys_modules(self, tmpdir): + p = tmpdir.ensure('file738jsk.py') + mod = p.pyimport(**self.OPTS) + assert mod.__name__ == 'file738jsk' + assert 'file738jsk' not in sys.modules + + def test_pypkgdir(tmpdir): pkg = tmpdir.ensure('pkg1', dir=1) pkg.ensure("__init__.py") From 642c4d26f9b4b6065dcfe2c135e57e9d37a6a6dd Mon Sep 17 00:00:00 2001 From: neumond Date: Tue, 19 Feb 2019 11:54:05 +0300 Subject: [PATCH 2/2] Changelog entry --- CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 7d2e7f37..94c06345 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +(unreleased) +============ + +- add ``"importlib"`` pyimport mode for python3.5+, allowing unimportable test suites + to contain identically named modules. + 1.7.0 (2018-10-11) ==================