Skip to content

Commit 7c2b288

Browse files
authored
Merge pull request #203 from neumond/master
Use importlib to avoid module name clashes for pytest
2 parents c6f4da5 + 642c4d2 commit 7c2b288

File tree

3 files changed

+69
-0
lines changed

3 files changed

+69
-0
lines changed

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
(unreleased)
2+
============
3+
4+
- add ``"importlib"`` pyimport mode for python3.5+, allowing unimportable test suites
5+
to contain identically named modules.
6+
17
1.7.0 (2018-10-11)
28
==================
39

py/_path/local.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ def map_as_list(func, iter):
1818
else:
1919
map_as_list = map
2020

21+
ALLOW_IMPORTLIB_MODE = sys.version_info > (3,5)
22+
if ALLOW_IMPORTLIB_MODE:
23+
import importlib
24+
25+
2126
class Stat(object):
2227
def __getattr__(self, name):
2328
return getattr(self._osstatresult, "st_" + name)
@@ -647,10 +652,35 @@ def pyimport(self, modname=None, ensuresyspath=True):
647652
If ensuresyspath=="append" the root dir will be appended
648653
if it isn't already contained in sys.path.
649654
if ensuresyspath is False no modification of syspath happens.
655+
656+
Special value of ensuresyspath=="importlib" is intended
657+
purely for using in pytest, it is capable only of importing
658+
separate .py files outside packages, e.g. for test suite
659+
without any __init__.py file. It effectively allows having
660+
same-named test modules in different places and offers
661+
mild opt-in via this option. Note that it works only in
662+
recent versions of python.
650663
"""
651664
if not self.check():
652665
raise py.error.ENOENT(self)
653666

667+
if ensuresyspath == 'importlib':
668+
if modname is None:
669+
modname = self.purebasename
670+
if not ALLOW_IMPORTLIB_MODE:
671+
raise ImportError(
672+
"Can't use importlib due to old version of Python")
673+
spec = importlib.util.spec_from_file_location(
674+
modname, str(self))
675+
if spec is None:
676+
raise ImportError(
677+
"Can't find module %s at location %s" %
678+
(modname, str(self))
679+
)
680+
mod = importlib.util.module_from_spec(spec)
681+
spec.loader.exec_module(mod)
682+
return mod
683+
654684
pkgpath = None
655685
if modname is None:
656686
pkgpath = self.pypkgpath()

testing/path/test_local.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,39 @@ def test_ensuresyspath_append(self, tmpdir):
581581
assert str(root1) not in sys.path[:-1]
582582

583583

584+
class TestImportlibImport:
585+
pytestmark = py.test.mark.skipif("sys.version_info < (3, 5)")
586+
587+
OPTS = {'ensuresyspath': 'importlib'}
588+
589+
def test_pyimport(self, path1):
590+
obj = path1.join('execfile.py').pyimport(**self.OPTS)
591+
assert obj.x == 42
592+
assert obj.__name__ == 'execfile'
593+
594+
def test_pyimport_dir_fails(self, tmpdir):
595+
p = tmpdir.join("hello_123")
596+
p.ensure("__init__.py")
597+
with pytest.raises(ImportError):
598+
p.pyimport(**self.OPTS)
599+
600+
def test_pyimport_execfile_different_name(self, path1):
601+
obj = path1.join('execfile.py').pyimport(modname="0x.y.z", **self.OPTS)
602+
assert obj.x == 42
603+
assert obj.__name__ == '0x.y.z'
604+
605+
def test_pyimport_relative_import_fails(self, path1):
606+
otherdir = path1.join('otherdir')
607+
with pytest.raises(ImportError):
608+
otherdir.join('a.py').pyimport(**self.OPTS)
609+
610+
def test_pyimport_doesnt_use_sys_modules(self, tmpdir):
611+
p = tmpdir.ensure('file738jsk.py')
612+
mod = p.pyimport(**self.OPTS)
613+
assert mod.__name__ == 'file738jsk'
614+
assert 'file738jsk' not in sys.modules
615+
616+
584617
def test_pypkgdir(tmpdir):
585618
pkg = tmpdir.ensure('pkg1', dir=1)
586619
pkg.ensure("__init__.py")

0 commit comments

Comments
 (0)