Repository: xonsh/xonsh

Issue:
Crash when setting $ENABLE_ASYNC_PROMPT=True
Seems $ASYNC_PROMPT_THREAD_WORKERS has an invalid default value.

## Repro
 ```
$ xonsh --no-rc
 . . snip . .
(x1)  $ $XONSH_SHOW_TRACEBACK = True
(x1)  $ $ENABLE_ASYNC_PROMPT=True
```
### Expect
Should work.

### Observe
Crash as noted below.  Reference to `ASYNC_PROMPT_THREAD_WORKERS` is significant.  I can avoid the crash by doing this sequence: 
```
(x1)  $ $XONSH_SHOW_TRACEBACK = True
(x1)  $ $ASYNC_PROMPT_THREAD_WORKERS = 5
(x1)  $ $ENABLE_ASYNC_PROMPT=True
```

### Traceback (if applicable)

<details>

```
Traceback (most recent call last):
  File "c:\users\bobhy\src\xonsh\xonsh\ptk_shell\shell.py", line 276, in _get_prompt_tokens
    p = self.prompt_formatter(
  File "c:\users\bobhy\src\xonsh\xonsh\ptk_shell\formatter.py", line 31, in __call__
    self.updator = PromptUpdator(self.session)
  File "c:\users\bobhy\src\xonsh\xonsh\ptk_shell\updator.py", line 144, in __init__
    self.executor = Executor()
  File "c:\users\bobhy\src\xonsh\xonsh\ptk_shell\updator.py", line 18, in __init__
    max_workers=builtins.__xonsh__.env["ASYNC_PROMPT_THREAD_WORKERS"]
  File "c:\users\bobhy\src\xonsh\xonsh\environ.py", line 1992, in __getitem__
    val = converter(val)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
Traceback (most recent call last):
  File "C:\Users\bobhy\venv\x1\Scripts\xonsh-script.py", line 33, in <module>
    sys.exit(load_entry_point('xonsh', 'console_scripts', 'xonsh')())
  File "c:\users\bobhy\src\xonsh\xonsh\main.py", line 422, in main
    _failback_to_other_shells(args, err)
  File "c:\users\bobhy\src\xonsh\xonsh\main.py", line 391, in _failback_to_other_shells
    raise err
  File "c:\users\bobhy\src\xonsh\xonsh\main.py", line 420, in main
    sys.exit(main_xonsh(args))
  File "c:\users\bobhy\src\xonsh\xonsh\main.py", line 458, in main_xonsh
    shell.shell.cmdloop()
  File "c:\users\bobhy\src\xonsh\xonsh\ptk_shell\shell.py", line 254, in cmdloop
    line = self.singleline(auto_suggest=auto_suggest)
  File "c:\users\bobhy\src\xonsh\xonsh\ptk_shell\shell.py", line 222, in singleline
    line = self.prompter.prompt(**prompt_args)
  File "c:\users\bobhy\venv\x1\lib\site-packages\prompt_toolkit\shortcuts\prompt.py", line 1013, in prompt
    return self.app.run(set_exception_handler=set_exception_handler)
  File "c:\users\bobhy\venv\x1\lib\site-packages\prompt_toolkit\application\application.py", line 814, in run
    return loop.run_until_complete(
  File "C:\Users\bobhy\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "c:\users\bobhy\venv\x1\lib\site-packages\prompt_toolkit\application\application.py", line 781, in run_async
    return await _run_async2()
  File "c:\users\bobhy\venv\x1\lib\site-packages\prompt_toolkit\application\application.py", line 763, in _run_async2
    result = await _run_async()
  File "c:\users\bobhy\venv\x1\lib\site-packages\prompt_toolkit\application\application.py", line 644, in _run_async
    self._pre_run(pre_run)
  File "c:\users\bobhy\venv\x1\lib\site-packages\prompt_toolkit\application\application.py", line 602, in _pre_run
    c()
  File "c:\users\bobhy\venv\x1\lib\site-packages\prompt_toolkit\shortcuts\prompt.py", line 1202, in pre_run2
    pre_run()
  File "c:\users\bobhy\src\xonsh\xonsh\ptk_shell\formatter.py", line 63, in start_update
    self.updator.start()
AttributeError: 'PTKPromptFormatter' object has no attribute 'updator'
```
</details>

## xonfig

<details>

```
+------------------+---------------------+
| xonsh            | 0.9.22              |
| Git SHA          | 7a55a805            |
| Commit Date      | Oct 7 14:06:16 2020 |
| Python           | 3.8.6               |
| PLY              | 3.11                |
| have readline    | False               |
| prompt toolkit   | 3.0.7               |
| shell type       | prompt_toolkit      |
| pygments         | 2.7.1               |
| on posix         | False               |
| on linux         | False               |
| on darwin        | False               |
| on windows       | True                |
| on cygwin        | False               |
| on msys2         | False               |
| is superuser     | False               |
| default encoding | utf-8               |
| xonsh encoding   | utf-8               |
| encoding errors  | surrogateescape     |
| on jupyter       | False               |
| jupyter kernel   | None                |
+------------------+---------------------+
```

</details>

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
Thanks! PRs welcome!
I would think that this is a one line change
@jnoortheen, could you take a look?
My intention to leave this as `None` is to retain the [`ThreadPool`'s](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor)  behaviour. Here the converter, needs a way to set None value. Is there anyway to set Optional integer ? @scopatz 

Resolving patch:
diff --git a/xonsh/environ.py b/xonsh/environ.py
--- a/xonsh/environ.py
+++ b/xonsh/environ.py
@@ -87,6 +87,7 @@
     is_str_str_dict,
     to_str_str_dict,
     dict_to_str,
+    to_int_or_none,
 )
 from xonsh.ansi_colors import (
     ansi_color_escape_code_to_name,
@@ -740,11 +741,11 @@ def DEFAULT_VARS():
         ),
         "ASYNC_PROMPT_THREAD_WORKERS": Var(
             is_int,
-            int,
+            to_int_or_none,
             str,
             None,
             "Define the number of workers used by the ASYC_PROPMT's pool. "
-            "By default it is defined by Python's concurrent.futures.ThreadPoolExecutor",
+            "By default it is the same as defined by Python's concurrent.futures.ThreadPoolExecutor class.",
         ),
         "BASH_COMPLETIONS": Var(
             is_env_path,
diff --git a/xonsh/tools.py b/xonsh/tools.py
--- a/xonsh/tools.py
+++ b/xonsh/tools.py
@@ -1359,6 +1359,14 @@ def to_itself(x):
     return x
 
 
+def to_int_or_none(x) -> tp.Optional[int]:
+    """Convert the given value to integer if possible. Otherwise return None"""
+    if isinstance(x, str) and x.lower() == "none":
+        return None
+    else:
+        return int(x)
+
+
 def bool_to_str(x):
     """Converts a bool to an empty string if False and the string '1' if
     True.


Test patch:
diff --git a/tests/test_tools.py b/tests/test_tools.py
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -52,6 +52,7 @@
     to_bool,
     to_bool_or_int,
     to_bool_or_none,
+    to_int_or_none,
     to_dynamic_cwd_tuple,
     to_logfile_opt,
     pathsep_to_set,
@@ -712,6 +713,11 @@ def test_always_true(inp):
     assert always_true(inp)
 
 
+@pytest.mark.parametrize("inp,exp", [(42, 42), ("42", 42), ("None", None)])
+def test_to_optional_int(inp, exp):
+    assert to_int_or_none(inp) == exp
+
+
 @pytest.mark.parametrize("inp", [42, "42"])
 def test_always_false(inp):
     assert not always_false(inp)