Repository: theacodes/nox
Pull request #249 (2019-09-06 01:05:09)

add types to _option_set.py
Ran `monkeytype apply nox._option_set` then added additional types as needed.

theacodes: SG, thanks for doing this!

On Fri, Sep 6, 2019 at 10:51 AM Chad Smith <notifications@github.com> wrote:

> *@cs01* commented on this pull request.
> ------------------------------
>
> In nox/_options.py
> <https://github.com/theacodes/nox/pull/249#discussion_r321844152>:
>
> >      global_config = parsed_args
>      module = load_nox_module(global_config)
>      manifest = discover_manifest(module, global_config)
>      filtered_manifest = filter_manifest(manifest, global_config)
> +    if isinstance(filtered_manifest, int):  # pragma: no cover
>
> mypy complains that it might be an int, so it's there as a type refinement
> since filter_manifest can return an int. I can look into adding a test
> case to cover this line.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/theacodes/nox/pull/249?email_source=notifications&email_token=AAB5I4Z2WDL2DK3MWQX7DDTQIKKCTA5CNFSM4IUD4UH2YY3PNVWWK3TUL52HS4DFWFIHK3DMKJSXC5LFON2FEZLWNFSXPKTDN5WW2ZLOORPWSZGOCD6OUJY#discussion_r321844152>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAB5I47Z6LPXO7G7MPBVB3LQIKKCTANCNFSM4IUD4UHQ>
> .
>

theacodes: Thank you, @cs01!

Patch:
diff --git a/nox/_option_set.py b/nox/_option_set.py
index a5492af..48307ec 100644
--- a/nox/_option_set.py
+++ b/nox/_option_set.py
@@ -20,12 +20,11 @@ and surfaced in documentation."""
 import argparse
 import collections
 import functools
+from argparse import ArgumentError, ArgumentParser, Namespace, _ArgumentGroup
+from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
 
 import argcomplete  # type: ignore
 
-Namespace = argparse.Namespace
-ArgumentError = argparse.ArgumentError
-
 
 class Option:
     """A single option that can be specified via command-line or configuration
@@ -60,20 +59,20 @@ class Option:
 
     def __init__(
         self,
-        name,
+        name: str,
         *flags,
-        help=None,
-        group=None,
-        noxfile=False,
-        merge_func=None,
-        finalizer_func=None,
-        default=None,
-        hidden=False,
-        completer=None,
+        help: str = None,
+        group: str = None,
+        noxfile: bool = False,
+        merge_func: Callable[[Namespace, Namespace], Any] = None,
+        finalizer_func: Callable[[Any, Namespace], Any] = None,
+        default: Union[Any, Callable[[], Any]] = None,
+        hidden: bool = False,
+        completer: Callable[..., List[str]] = None,
         **kwargs
-    ):
+    ) -> None:
         self.name = name
-        self.flags = flags
+        self.flags = flags  # type: Sequence[str]
         self.help = help
         self.group = group
         self.noxfile = noxfile
@@ -81,17 +80,22 @@ class Option:
         self.finalizer_func = finalizer_func
         self.hidden = hidden
         self.completer = completer
-        self.kwargs = kwargs
+        self.kwargs = kwargs  # type: Dict[str, Any]
         self._default = default
 
     @property
-    def default(self):
+    def default(self) -> Optional[Union[bool, str]]:
         if callable(self._default):
             return self._default()
         return self._default
 
 
-def flag_pair_merge_func(enable_name, disable_name, command_args, noxfile_args):
+def flag_pair_merge_func(
+    enable_name: str,
+    disable_name: str,
+    command_args: Namespace,
+    noxfile_args: Namespace,
+) -> bool:
     """Merge function for flag pairs. If the flag is set in the noxfile or
     the command line params, return ``True`` *unless* the disable flag has been
     specified on the command-line.
@@ -132,7 +136,12 @@ def flag_pair_merge_func(enable_name, disable_name, command_args, noxfile_args):
     return (command_value or noxfile_value) and not disable_value
 
 
-def make_flag_pair(name, enable_flags, disable_flags, **kwargs):
+def make_flag_pair(
+    name: str,
+    enable_flags: Union[Tuple[str, str], Tuple[str]],
+    disable_flags: Tuple[str],
+    **kwargs
+) -> Tuple[Option, Option]:
     """Returns two options - one to enable a behavior and another to disable it.
 
     The positive option is considered to be available to the noxfile, as
@@ -165,13 +174,15 @@ class OptionSet:
     finalization, callable defaults, and strongly typed namespaces for tests.
     """
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args, **kwargs) -> None:
         self.parser_args = args
         self.parser_kwargs = kwargs
-        self.options = collections.OrderedDict()
-        self.groups = collections.OrderedDict()
+        self.options = collections.OrderedDict()  # type: Dict[str, Option]
+        self.groups = (
+            collections.OrderedDict()
+        )  # type: Dict[str, Tuple[Tuple[Any, ...], Dict[str, Any]]]
 
-    def add_options(self, *args):
+    def add_options(self, *args) -> None:
         """Adds a sequence of Options to the OptionSet.
 
         Args:
@@ -180,7 +191,7 @@ class OptionSet:
         for option in args:
             self.options[option.name] = option
 
-    def add_group(self, name, *args, **kwargs):
+    def add_group(self, name: str, *args, **kwargs) -> None:
         """Adds a new argument group.
 
         When :func:`parser` is invoked, the OptionSet will turn all distinct
@@ -189,14 +200,16 @@ class OptionSet:
         """
         self.groups[name] = (args, kwargs)
 
-    def _add_to_parser(self, parser, option):
+    def _add_to_parser(
+        self, parser: Union[_ArgumentGroup, ArgumentParser], option: Option
+    ) -> None:
         argument = parser.add_argument(
             *option.flags, help=option.help, default=option.default, **option.kwargs
         )
-        if option.completer:
-            argument.completer = option.completer
+        if getattr(option, "completer"):
+            setattr(argument, "completer", option.completer)
 
-    def parser(self):
+    def parser(self) -> ArgumentParser:
         """Returns an ``ArgumentParser`` for this option set.
 
         Generally, you won't use this directly. Instead, use
@@ -223,7 +236,7 @@ class OptionSet:
     def print_help(self):
         return self.parser().print_help()
 
-    def _finalize_args(self, args):
+    def _finalize_args(self, args: Namespace) -> None:
         """Does any necessary post-processing on arguments."""
         for option in self.options.values():
             # Handle hidden items.
@@ -236,7 +249,7 @@ class OptionSet:
             if option.finalizer_func:
                 setattr(args, option.name, option.finalizer_func(value, args))
 
-    def parse_args(self):
+    def parse_args(self) -> Namespace:
         parser = self.parser()
         argcomplete.autocomplete(parser)
         args = parser.parse_args()
@@ -267,7 +280,7 @@ class OptionSet:
 
         return argparse.Namespace(**args)
 
-    def noxfile_namespace(self):
+    def noxfile_namespace(self) -> Namespace:
         """Returns a namespace of options that can be set in the configuration
         file."""
         return argparse.Namespace(
@@ -278,7 +291,9 @@ class OptionSet:
             }
         )
 
-    def merge_namespaces(self, command_args, noxfile_args):
+    def merge_namespaces(
+        self, command_args: Namespace, noxfile_args: Namespace
+    ) -> None:
         """Merges the command-line options with the noxfile options."""
         for name, option in self.options.items():
             if option.merge_func:
diff --git a/nox/_options.py b/nox/_options.py
index a27d5e4..e8acca6 100644
--- a/nox/_options.py
+++ b/nox/_options.py
@@ -16,6 +16,7 @@ import argparse
 import functools
 import os
 import sys
+from typing import List
 
 from nox import _option_set
 from nox.tasks import discover_manifest, filter_manifest, load_nox_module
@@ -122,11 +123,15 @@ def _posargs_finalizer(value, args):
     return posargs[dash_index + 1 :]
 
 
-def _session_completer(prefix, parsed_args, **kwargs):
+def _session_completer(
+    prefix: str, parsed_args: argparse.Namespace, **kwargs
+) -> List[str]:
     global_config = parsed_args
     module = load_nox_module(global_config)
     manifest = discover_manifest(module, global_config)
     filtered_manifest = filter_manifest(manifest, global_config)
+    if isinstance(filtered_manifest, int):
+        return []
     return [
         session.friendly_name for session, _ in filtered_manifest.list_all_sessions()
     ]


Test patch:
diff --git a/tests/test__option_set.py b/tests/test__option_set.py
index 07bf1cd..3bcf4f2 100644
--- a/tests/test__option_set.py
+++ b/tests/test__option_set.py
@@ -12,9 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import sys
-from unittest import mock
-
 import pytest
 from nox import _option_set, _options
 
@@ -48,12 +45,18 @@ class TestOptionSet:
             optionset.namespace(non_existant_option="meep")
 
     def test_session_completer(self):
-        with mock.patch("sys.argv", [sys.executable]):
-            parsed_args = _options.options.parse_args()
-            all_nox_sessions = _options._session_completer(
-                prefix=None, parsed_args=parsed_args
-            )
-            # if noxfile.py changes, this will have to change as well since these are
-            # some of the actual sessions found in noxfile.py
-            some_expected_sessions = ["cover", "blacken", "lint", "docs"]
-            assert len(set(some_expected_sessions) - set(all_nox_sessions)) == 0
+        parsed_args = _options.options.namespace(sessions=(), keywords=())
+        all_nox_sessions = _options._session_completer(
+            prefix=None, parsed_args=parsed_args
+        )
+        # if noxfile.py changes, this will have to change as well since these are
+        # some of the actual sessions found in noxfile.py
+        some_expected_sessions = ["cover", "blacken", "lint", "docs"]
+        assert len(set(some_expected_sessions) - set(all_nox_sessions)) == 0
+
+    def test_session_completer_invalid_sessions(self):
+        parsed_args = _options.options.namespace(sessions=("baz",), keywords=())
+        all_nox_sessions = _options._session_completer(
+            prefix=None, parsed_args=parsed_args
+        )
+        assert len(all_nox_sessions) == 0