Repository: xonsh/xonsh

Issue:
`vox deactivate` raises `KeyError` in 0.9.19 if no virtualenv active
Since Xonsh 0.9.19, using `vox deactivate` raises a `KeyError` (instead of printing an error message) if no virtualenv is active.  This is especially annoying if using `autovox`, as that does a `vox.deactivate()` on shell startup/init (in its `load_handler()` function).  In 0.9.18 the error message "No environment currently active. Activate one with "vox activate"." gets shown.

This seems to be due to the `VIRTUAL_ENV` env var now having a default value of `''` if it's not set, which makes the following check in vox's `VoxHandler.cmd_deactivate()` function break:

```!py
        if self.vox.active() is None:
            print(
                'No environment currently active. Activate one with "vox activate".\n',
                file=sys.stderr,
            )
            return None
```

## xonfig

<details>

```
$ xonfig
+------------------+---------------------+
| xonsh            | 0.9.19              |
| Git SHA          | 74450cb5            |
| Commit Date      | Aug 9 13:27:55 2020 |
| Python           | 3.6.9               |
| PLY              | 3.11                |
| have readline    | True                |
| prompt toolkit   | None                |
| shell type       | readline            |
| pygments         | 2.6.1               |
| on posix         | True                |
| on linux         | True                |
| distro           | unknown             |
| on darwin        | False               |
| on windows       | False               |
| on cygwin        | False               |
| on msys2         | False               |
| is superuser     | False               |
| default encoding | utf-8               |
| xonsh encoding   | utf-8               |
| encoding errors  | surrogateescape     |
+------------------+---------------------+
```

</details>

## Expected Behavior
Error message "No environment currently active. Activate one with "vox activate"." to be shown

## Current Behavior
`KeyError` is raised.

### Traceback (if applicable)

<details>

```
Traceback (most recent call last):
  File "/home/me/tmp_virtualenv/lib/python3.6/site-packages/xonsh/__amalgam__.py", line 9661, in _call
    handler(**self._kwargs)
  File "/home/me/tmp_virtualenv/lib/python3.6/site-packages/xontrib/autovox.py", line 106, in load_handler
    check_for_new_venv(Path.cwd(), None)
  File "/home/me/tmp_virtualenv/lib/python3.6/site-packages/xontrib/autovox.py", line 77, in check_for_new_venv
    vox.deactivate()
  File "/home/me/tmp_virtualenv/lib/python3.6/site-packages/xontrib/voxapi.py", line 385, in deactivate
    events.vox_on_deactivate.fire(name=env_name, path=self[env_name].env)
  File "/home/me/tmp_virtualenv/lib/python3.6/site-packages/xontrib/voxapi.py", line 297, in __getitem__
    raise KeyError()
KeyError
```

</details>

## Steps to Reproduce
```sh
$  vox deactivate
$  vox deactivate  # 2nd time, in case a virtualenv was active ;)
```

Alternatively, start a new session with `xontrib load autovox` in your xonshrc (and ensure you're starting this `xonsh` from a session that doesn't already have `$VIRTUAL_ENV` set).

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
Thanks for reporting @sevens-ef. I can confirm the issue.
This is yet another fallout of #3377.
`$VIRTUAL_ENV` now always exists and defaults to an empty string.
Yeah.  Must say I like the improvements and simplifications of #3377 (at least the ones I've seen so far) but it does have some side-effects :)  (had to do some similar fixes in my own code)

Resolving patch:
diff --git a/xontrib/voxapi.py b/xontrib/voxapi.py
--- a/xontrib/voxapi.py
+++ b/xontrib/voxapi.py
@@ -332,9 +332,10 @@ def active(self):
 
         Returns None if no environment is active.
         """
-        if "VIRTUAL_ENV" not in builtins.__xonsh__.env:
+        env = builtins.__xonsh__.env
+        if not env["VIRTUAL_ENV"]:
             return
-        env_path = builtins.__xonsh__.env["VIRTUAL_ENV"]
+        env_path = env["VIRTUAL_ENV"]
         if env_path.startswith(self.venvdir):
             name = env_path[len(self.venvdir) :]
             if name[0] in "/\\":
@@ -354,7 +355,7 @@ def activate(self, name):
         """
         env = builtins.__xonsh__.env
         ve = self[name]
-        if "VIRTUAL_ENV" in env:
+        if env["VIRTUAL_ENV"]:
             self.deactivate()
 
         type(self).oldvars = {"PATH": list(env["PATH"])}
@@ -370,7 +371,7 @@ def deactivate(self):
         Deactivate the active virtual environment. Returns its name.
         """
         env = builtins.__xonsh__.env
-        if "VIRTUAL_ENV" not in env:
+        if not env["VIRTUAL_ENV"]:
             raise NoEnvironmentActive("No environment currently active.")
 
         env_name = self.active()
@@ -380,7 +381,7 @@ def deactivate(self):
                 env[k] = v
             del type(self).oldvars
 
-        env.pop("VIRTUAL_ENV")
+        del env["VIRTUAL_ENV"]
 
         events.vox_on_deactivate.fire(name=env_name, path=self[env_name].env)
         return env_name


Test patch: