Repository: theacodes/nox
Pull request #377 (2021-01-01 21:46:30)

Define the location attribute on base class ProcessEnv
Fixes errors when running mypy on a project's noxfile.py:

    noxfile.py:42: error: "ProcessEnv" has no attribute "location"

Discovered while adding types pip's noxfile.py:
pypa/pip#9411

jdufresne: @stsewd The syntanx used here is an instance variable type declaration. It does not not assign the attribute to the class (or even the instance). A true assignment is still required for that. Observe:

```py
class MyClass:
    a: str

obj = MyClass()
obj.a
```

```
$ python blah.py
Traceback (most recent call last):
  File "/home/jon/blah.py", line 5, in <module>
    obj.a
AttributeError: 'MyClass' object has no attribute 'a'
```

The syntax is use for type declaration only so it makes mypy happy. Please let me know if I can provide additional detail.

Patch:
diff --git a/nox/sessions.py b/nox/sessions.py
index d44f10b..96d80eb 100644
--- a/nox/sessions.py
+++ b/nox/sessions.py
@@ -167,10 +167,12 @@ class Session:
         return self.virtualenv.bin_paths
 
     @property
-    def bin(self) -> Optional[str]:
+    def bin(self) -> str:
         """The first bin directory for the virtualenv."""
         paths = self.bin_paths
-        return paths[0] if paths is not None else None
+        if paths is None:
+            raise ValueError("The environment does not have a bin directory.")
+        return paths[0]
 
     def create_tmp(self) -> str:
         """Create, and return, a temporary directory."""
diff --git a/nox/virtualenv.py b/nox/virtualenv.py
index c32efaf..0e3bbca 100644
--- a/nox/virtualenv.py
+++ b/nox/virtualenv.py
@@ -43,6 +43,8 @@ class InterpreterNotFound(OSError):
 class ProcessEnv:
     """A environment with a 'bin' directory and a set of 'env' vars."""
 
+    location: str
+
     # Does this environment provide any process isolation?
     is_sandboxed = False
 
@@ -69,10 +71,12 @@ class ProcessEnv:
         return self._bin_paths
 
     @property
-    def bin(self) -> Optional[str]:
+    def bin(self) -> str:
         """The first bin directory for the virtualenv."""
         paths = self.bin_paths
-        return paths[0] if paths is not None else None
+        if paths is None:
+            raise ValueError("The environment does not have a bin directory.")
+        return paths[0]
 
     def create(self) -> bool:
         raise NotImplementedError("ProcessEnv.create should be overwritten in subclass")


Test patch:
diff --git a/tests/test_sessions.py b/tests/test_sessions.py
index 326133e..2d4cf11 100644
--- a/tests/test_sessions.py
+++ b/tests/test_sessions.py
@@ -109,7 +109,10 @@ class TestSession:
         session, runner = self.make_session_and_runner()
 
         runner.venv.bin_paths = None
-        assert session.bin is None
+        with pytest.raises(
+            ValueError, match=r"^The environment does not have a bin directory\.$"
+        ):
+            session.bin
         assert session.bin_paths is None
 
     def test_virtualenv_as_none(self):
diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py
index eb67694..7200d2d 100644
--- a/tests/test_virtualenv.py
+++ b/tests/test_virtualenv.py
@@ -121,10 +121,17 @@ def patch_sysfind(make_mocked_interpreter_path):
 def test_process_env_constructor():
     penv = nox.virtualenv.ProcessEnv()
     assert not penv.bin_paths
+    with pytest.raises(
+        ValueError, match=r"^The environment does not have a bin directory\.$"
+    ):
+        penv.bin
 
     penv = nox.virtualenv.ProcessEnv(env={"SIGIL": "123"})
     assert penv.env["SIGIL"] == "123"
 
+    penv = nox.virtualenv.ProcessEnv(bin_paths=["/bin"])
+    assert penv.bin == "/bin"
+
 
 def test_process_env_create():
     penv = nox.virtualenv.ProcessEnv()