From 74f87936bd755bbaf698d86893325ebd77384ddb Mon Sep 17 00:00:00 2001 From: Franek Magiera Date: Sat, 11 Sep 2021 23:04:17 +0200 Subject: [PATCH] Add session.invoked_from (#472) * Add original cwd to session properties * Fix test * Rename original_wd to invoked_from * Hoist invoked_from up to the options parser * Add test case for hidden options * Add test case for groupless options Co-authored-by: Thea Flowers --- nox/_option_set.py | 10 ++++++++-- nox/_options.py | 8 ++++++++ nox/sessions.py | 12 ++++++++++++ nox/tasks.py | 3 ++- tests/test__option_set.py | 23 +++++++++++++++++++++++ tests/test_sessions.py | 17 ++++++++++++++++- 6 files changed, 69 insertions(+), 4 deletions(-) diff --git a/nox/_option_set.py b/nox/_option_set.py index 1e81443a..91dcc153 100644 --- a/nox/_option_set.py +++ b/nox/_option_set.py @@ -77,7 +77,7 @@ def __init__( self, name: str, *flags: str, - group: OptionGroup, + group: Optional[OptionGroup], help: Optional[str] = None, noxfile: bool = False, merge_func: Optional[Callable[[Namespace, Namespace], Any]] = None, @@ -230,9 +230,15 @@ def parser(self) -> ArgumentParser: } for option in self.options.values(): - if option.hidden: + if option.hidden is True: continue + # Every option must have a group (except for hidden options) + if option.group is None: + raise ValueError( + f"Option {option.name} must either have a group or be hidden." + ) + argument = groups[option.group.name].add_argument( *option.flags, help=option.help, default=option.default, **option.kwargs ) diff --git a/nox/_options.py b/nox/_options.py index 62515239..353ed647 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -474,6 +474,14 @@ def _session_completer( hidden=True, finalizer_func=_color_finalizer, ), + # Stores the original working directory that Nox was invoked from, + # since it could be different from the Noxfile's directory. + _option_set.Option( + "invoked_from", + group=None, + hidden=True, + default=lambda: os.getcwd(), + ), ) diff --git a/nox/sessions.py b/nox/sessions.py index 93a33dd2..c97c7eb2 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -189,6 +189,18 @@ def interactive(self) -> bool: """Returns True if Nox is being run in an interactive session or False otherwise.""" return not self._runner.global_config.non_interactive and sys.stdin.isatty() + @property + def invoked_from(self) -> str: + """The directory that Nox was originally invoked from. + + Since you can use the ``--noxfile / -f`` command-line + argument to run a Noxfile in a location different from your shell's + current working directory, Nox automatically changes the working directory + to the Noxfile's directory before running any sessions. This gives + you the original working directory that Nox was invoked form. + """ + return self._runner.global_config.invoked_from + def chdir(self, dir: Union[str, os.PathLike]) -> None: """Change the current working directory.""" self.log(f"cd {dir}") diff --git a/nox/tasks.py b/nox/tasks.py index 7e55100d..52263321 100644 --- a/nox/tasks.py +++ b/nox/tasks.py @@ -60,7 +60,8 @@ def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]: # Move to the path where the Noxfile is. # This will ensure that the Noxfile's path is on sys.path, and that # import-time path resolutions work the way the Noxfile author would - # guess. + # guess. The original working directory (the directory that Nox was + # invoked from) gets stored by the .invoke_from "option" in _options. os.chdir(noxfile_parent_dir) return importlib.machinery.SourceFileLoader( "user_nox_module", global_config.noxfile diff --git a/tests/test__option_set.py b/tests/test__option_set.py index 202473ea..3107a54c 100644 --- a/tests/test__option_set.py +++ b/tests/test__option_set.py @@ -55,6 +55,29 @@ def test_namespace_non_existant_options_with_values(self): with pytest.raises(KeyError): optionset.namespace(non_existant_option="meep") + def test_parser_hidden_option(self): + optionset = _option_set.OptionSet() + optionset.add_options( + _option_set.Option( + "oh_boy_i_am_hidden", hidden=True, group=None, default="meep" + ) + ) + + parser = optionset.parser() + namespace = parser.parse_args([]) + optionset._finalize_args(namespace) + + assert namespace.oh_boy_i_am_hidden == "meep" + + def test_parser_groupless_option(self): + optionset = _option_set.OptionSet() + optionset.add_options( + _option_set.Option("oh_no_i_have_no_group", group=None, default="meep") + ) + + with pytest.raises(ValueError): + optionset.parser() + def test_session_completer(self): parsed_args = _options.options.namespace(sessions=(), keywords=(), posargs=[]) all_nox_sessions = _options._session_completer( diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 4408bec3..f12f02c7 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -67,7 +67,10 @@ def make_session_and_runner(self): signatures=["test"], func=func, global_config=_options.options.namespace( - posargs=[], error_on_external_run=False, install_only=False + posargs=[], + error_on_external_run=False, + install_only=False, + invoked_from=os.getcwd(), ), manifest=mock.create_autospec(nox.manifest.Manifest), ) @@ -104,6 +107,7 @@ def test_properties(self): assert session.bin_paths is runner.venv.bin_paths assert session.bin is runner.venv.bin_paths[0] assert session.python is runner.func.python + assert session.invoked_from is runner.global_config.invoked_from def test_no_bin_paths(self): session, runner = self.make_session_and_runner() @@ -155,6 +159,17 @@ def test_chdir(self, tmpdir): assert os.getcwd() == cdto os.chdir(current_cwd) + def test_invoked_from(self, tmpdir): + cdto = str(tmpdir.join("cdbby").ensure(dir=True)) + current_cwd = os.getcwd() + + session, _ = self.make_session_and_runner() + + session.chdir(cdto) + + assert session.invoked_from == current_cwd + os.chdir(current_cwd) + def test_chdir_pathlib(self, tmpdir): cdto = str(tmpdir.join("cdbby").ensure(dir=True)) current_cwd = os.getcwd()