Repository: tox-dev/tox (Python)

Issue:
TypeError: Item in ``from list'' must be str, not NoneType
I'm trying to add PEP 517 support to a packaging tool I've written ([pyo3-pack](https://github.com/PyO3/pyo3-pack)) and I when I tried to test sdist support with tox, I got a TypeError inside tox code. 

pyproject.toml

```toml
[build-system]
requires = ["/path/to/current/dev/build/pyo3_pack-0.6.1-py2.py3-none-manylinux1_x86_64.whl", "toml"]
build-backend = "pyo3_pack"

[tool.pyo3-pack]
bindings = "cffi"
```

tox.ini

```
[tox]
envlist = py35,py36,py37
isolated_build = True

[testenv]
deps = pytest cffi
commands = pytest
```

Running leads to the following output:

```
$ tox -rvv
using tox.ini: /home/konsti/pyo3-pack/test-crates/cffi-pure/tox.ini (pid 9495)
using tox-3.12.1 from /home/konsti/pyo3-pack/venv/lib/python3.6/site-packages/tox/__init__.py (pid 9495)
.package start: getenv /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package
.package cannot reuse: -r flag
.package create: /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package
/home/konsti/pyo3-pack/venv/bin/python (/home/konsti/pyo3-pack/venv/bin/python) is {'executable': '/home/konsti/pyo3-pack/venv/bin/python', 'name': 'python', 'version_info': [3, 6, 8, 'final', 0], 'version': '3.6.8 (default, Jan 14 2019, 11:02:34) \n[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]]', 'is_64': True, 'sysplatform': 'linux'}
.package uses /home/konsti/pyo3-pack/venv/bin/python
setting PATH=/home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/bin:/home/konsti/pyo3-pack/venv/bin:/home/konsti/.pyenv/bin:/home/konsti/.pyenv/plugins/pyenv-virtualenv/shims:/home/konsti/.pyenv/shims:/home/konsti/.nvm/versions/node/v10.5.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/konsti/.cargo/bin:/home/konsti/.local/bin
[9502] /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox$ /home/konsti/pyo3-pack/venv/bin/python -m virtualenv --no-download --python /home/konsti/pyo3-pack/venv/bin/python .package
Already using interpreter /home/konsti/pyo3-pack/venv/bin/python
Using base prefix '/usr'
New python executable in /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/bin/python
Installing setuptools, pip, wheel...
done.
.package installdeps: /home/konsti/pyo3-pack/target/wheels/pyo3_pack-0.6.1-py2.py3-none-manylinux1_x86_64.whl, toml
setting PATH=/home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/bin:/home/konsti/pyo3-pack/venv/bin:/home/konsti/.pyenv/bin:/home/konsti/.pyenv/plugins/pyenv-virtualenv/shims:/home/konsti/.pyenv/shims:/home/konsti/.nvm/versions/node/v10.5.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/konsti/.cargo/bin:/home/konsti/.local/bin
[9511] /home/konsti/pyo3-pack/test-crates/cffi-pure$ /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/bin/python -m pip install /home/konsti/pyo3-pack/target/wheels/pyo3_pack-0.6.1-py2.py3-none-manylinux1_x86_64.whl toml
Processing /home/konsti/pyo3-pack/target/wheels/pyo3_pack-0.6.1-py2.py3-none-manylinux1_x86_64.whl
Collecting toml
  Using cached https://files.pythonhosted.org/packages/a2/12/ced7105d2de62fa7c8fb5fce92cc4ce66b57c95fb875e9318dba7f8c5db0/toml-0.10.0-py2.py3-none-any.whl
Installing collected packages: pyo3-pack, toml
Successfully installed pyo3-pack-0.6.1 toml-0.10.0
.package finish: getenv /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package after 4.66 seconds
.package start: finishvenv 
write config to /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/.tox-config1 as '94aaba361509f3eb875f759baaad9c10 /home/konsti/pyo3-pack/venv/bin/python\n3.12.1 0 0 0\n9a98df95e2f78ca59a589cb2f3e1099f /home/konsti/pyo3-pack/target/wheels/pyo3_pack-0.6.1-py2.py3-none-manylinux1_x86_64.whl\n00000000000000000000000000000000 toml'
.package finish: finishvenv  after 0.03 seconds
.package start: get-build-requires /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package
setting PATH=/home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/bin:/home/konsti/pyo3-pack/venv/bin:/home/konsti/.pyenv/bin:/home/konsti/.pyenv/plugins/pyenv-virtualenv/shims:/home/konsti/.pyenv/shims:/home/konsti/.nvm/versions/node/v10.5.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/konsti/.cargo/bin:/home/konsti/.local/bin
[9518] /home/konsti/pyo3-pack/test-crates/cffi-pure$ /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/bin/python /home/konsti/pyo3-pack/venv/lib/python3.6/site-packages/tox/helper/build_requires.py pyo3_pack '' >.tox/.package/log/.package-0.log
ERROR: invocation failed (exit code 1), logfile: /home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/log/.package-0.log
================================================================================= log start =================================================================================
Traceback (most recent call last):
  File "/home/konsti/pyo3-pack/venv/lib/python3.6/site-packages/tox/helper/build_requires.py", line 7, in <module>
    backend = __import__(backend_spec, fromlist=[None])
  File "<frozen importlib._bootstrap>", line 1014, in _handle_fromlist
TypeError: Item in ``from list'' must be str, not NoneType

================================================================================== log end ==================================================================================
ERROR: FAIL could not package project - v = InvocationError("/home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/bin/python /home/konsti/pyo3-pack/venv/lib/python3.6/site-packages/tox/helper/build_requires.py pyo3_pack ''", 1)
```

The module that code tries to import is importable (`/home/konsti/pyo3-pack/test-crates/cffi-pure/.tox/.package/bin/python -c "import pyo3_pack"` passes). If I replace the failing line with `backend = __import__(backend_spec, fromlist=[])` or  `backend = __import__(backend_spec)`, the error disappears. The current WIP PEP 517 implementation is at [`pyo3_pack/__init__.py`](https://github.com/PyO3/pyo3-pack/blob/671fcdb5dd00c5a71fa513c4b7ed39ce601f5a55/pyo3_pack/__init__.py), the failing code was introduce in #1145.


Discussion:
I created a minimal example at https://github.com/konstin/tox-1344:

```
git clone https://github.com/konstin/tox-1344
cd tox-1344
python3 -m venv venv
source venv/bin/activate
pip install tox
tox
```

This prints:

```
.package create: /home/konsti/tox-1344/.tox/.package
.package installdeps: pyo3-pack==0.7.0-beta.4, toml
ERROR: invocation failed (exit code 1), logfile: /home/konsti/tox-1344/.tox/.package/log/.package-2.log
================================================================================= log start =================================================================================
Traceback (most recent call last):
  File "venv/lib/python3.6/site-packages/tox/helper/build_requires.py", line 7, in <module>
    backend = __import__(backend_spec, fromlist=[None])
  File "<frozen importlib._bootstrap>", line 1014, in _handle_fromlist
TypeError: Item in ``from list'' must be str, not NoneType

================================================================================== log end ==================================================================================
ERROR: FAIL could not package project - v = InvocationError("/home/konsti/tox-1344/.tox/.package/bin/python venv/lib/python3.6/site-packages/tox/helper/build_requires.py pyo3_pack ''", 1)
```

`tox --version`: `3.13.2 imported from /home/konsti/tox-1344/venv/lib/python3.6/site-packages/tox/__init__.py`


<blockquote><img src="https://avatars3.githubusercontent.com/u/6826232?s=400&v=4" width="48" align="right"><div><img src="https://github.githubassets.com/favicon.ico" height="14"> GitHub</div><div><strong><a href="https://github.com/konstin/tox-1344">konstin/tox-1344</a></strong></div><div>Contribute to konstin/tox-1344 development by creating an account on GitHub.</div></blockquote>
I think we left out this part from pep 517: It's also legal to leave out the :object part, e.g.
Feel free to open a PR against master and fix it. My available efforts at the moment are aimed at fixing this as part of https://github.com/tox-dev/tox/issues/1394, but that probably will take a while (ETA September).



Patch:
diff --git a/docs/changelog/1629.bugfix.rst b/docs/changelog/1629.bugfix.rst
new file mode 100644
index 000000000..cbb3fb6ab
--- /dev/null
+++ b/docs/changelog/1629.bugfix.rst
@@ -0,0 +1,1 @@
+Fix ``TypeError`` when using isolated_build with backends that are not submodules (e.g. ``maturin``)
diff --git a/src/tox/helper/build_isolated.py b/src/tox/helper/build_isolated.py
index 59680ad6d..55ea41b8d 100644
--- a/src/tox/helper/build_isolated.py
+++ b/src/tox/helper/build_isolated.py
@@ -4,7 +4,7 @@
 backend_spec = sys.argv[2]
 backend_obj = sys.argv[3] if len(sys.argv) >= 4 else None
 
-backend = __import__(backend_spec, fromlist=[None])
+backend = __import__(backend_spec, fromlist=["_trash"])
 if backend_obj:
     backend = getattr(backend, backend_obj)
 
diff --git a/src/tox/helper/build_requires.py b/src/tox/helper/build_requires.py
index 08bf94126..cd074f97f 100644
--- a/src/tox/helper/build_requires.py
+++ b/src/tox/helper/build_requires.py
@@ -4,7 +4,7 @@
 backend_spec = sys.argv[1]
 backend_obj = sys.argv[2] if len(sys.argv) >= 3 else None
 
-backend = __import__(backend_spec, fromlist=[None])
+backend = __import__(backend_spec, fromlist=["_trash"])
 if backend_obj:
     backend = getattr(backend, backend_obj)
 


Test patch:
diff --git a/tests/unit/package/test_package.py b/tests/unit/package/test_package.py
index fc8da8bc6..d546ecc03 100644
--- a/tests/unit/package/test_package.py
+++ b/tests/unit/package/test_package.py
@@ -1,4 +1,5 @@
 import re
+import sys
 
 from tox.config import parseconfig
 from tox.package import get_package
@@ -106,6 +107,48 @@ def test_make_sdist(initproj):
     assert sdist_new.stat().size > 10
 
 
+def test_build_backend_without_submodule(initproj, cmd):
+    # The important part of this test is that the build backend
+    # "inline_backend" is just a base package without a submodule.
+    # (Regression test for #1344)
+    initproj(
+        "magic-0.1",
+        filedefs={
+            "tox.ini": """\
+                [tox]
+                isolated_build = true
+                [testenv:.package]
+                basepython = {}
+                [testenv]
+                setenv = PYTHONPATH = {{toxinidir}}
+            """.format(
+                sys.executable,
+            ),
+            "pyproject.toml": """\
+                [build-system]
+                requires = []
+                build-backend = "inline_backend"
+            """,
+            # To trigger original bug, must be package with __init__.py
+            "inline_backend": {
+                "__init__.py": """\
+                    def get_requires_for_build_sdist(*args, **kwargs):
+                        return ["pathlib2"]
+
+                    def build_sdist(sdist_directory, config_settings=None):
+                        import pathlib2
+                        (pathlib2.Path(sdist_directory) / "magic-0.1.0.tar.gz").touch()
+                        return "magic-0.1.0.tar.gz"
+                """,
+            },
+            ".gitignore": ".tox",
+        },
+        add_missing_setup_py=False,
+    )
+    result = cmd("--sdistonly", "-e", "py", "-v", "-v")
+    result.assert_success(is_run_test_env=False)
+
+
 def test_package_inject(initproj, cmd, monkeypatch, tmp_path):
     monkeypatch.delenv(str("PYTHONPATH"), raising=False)
     initproj(