From 119fd00cbb7584127e6deae5c1e4c1c9811f850e Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sun, 5 Mar 2023 07:33:25 -0800 Subject: [PATCH] feat: --json --list-sessions (#665) Closes https://github.com/wntrblm/nox/issues/658 --- docs/usage.rst | 5 ++++ nox/_options.py | 7 ++++++ nox/tasks.py | 30 +++++++++++++++++++++-- tests/test_tasks.py | 58 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 6bcb0a08..9dbf72e2 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -30,6 +30,11 @@ To list all available sessions, including parametrized sessions: nox --list nox --list-sessions +If you'd like to use the output in later processing, you can add ``--json`` to +get json output for the selected session. Fields include ``session`` (pretty +name), ``name``, ``description``, ``python`` (null if not specified), ``tags``, +and ``call_spec`` (for parametrized sessions). + .. _session_execution_order: diff --git a/nox/_options.py b/nox/_options.py index d6ad0715..c2506bc6 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -264,6 +264,13 @@ def _session_completer( action="store_true", help="List all available sessions and exit.", ), + _option_set.Option( + "json", + "--json", + group=options.groups["sessions"], + action="store_true", + help="JSON output formatting. Requires list-sessions currently.", + ), _option_set.Option( "sessions", "-s", diff --git a/nox/tasks.py b/nox/tasks.py index b92c7c44..a72d570e 100644 --- a/nox/tasks.py +++ b/nox/tasks.py @@ -179,6 +179,7 @@ def filter_manifest(manifest: Manifest, global_config: Namespace) -> Manifest | logger.error("Error while collecting sessions.") logger.error(exc.args[0]) return 3 + if not manifest and not global_config.list_sessions: print("No sessions selected. Please select a session with -s .\n") _produce_listing(manifest, global_config) @@ -264,6 +265,23 @@ def _produce_listing(manifest: Manifest, global_config: Namespace) -> None: ) +def _produce_json_listing(manifest: Manifest, global_config: Namespace) -> None: + report = [] + for session, selected in manifest.list_all_sessions(): + if selected: + report.append( + { + "session": session.friendly_name, + "name": session.name, + "description": session.description or "", + "python": session.func.python, + "tags": session.tags, + "call_spec": getattr(session.func, "call_spec", {}), + } + ) + print(json.dumps(report)) + + def honor_list_request(manifest: Manifest, global_config: Namespace) -> Manifest | int: """If --list was passed, simply list the manifest and exit cleanly. @@ -275,10 +293,18 @@ def honor_list_request(manifest: Manifest, global_config: Namespace) -> Manifest Union[~.Manifest,int]: ``0`` if a listing is all that is requested, the manifest otherwise (to be sent to the next task). """ - if not global_config.list_sessions: + if not (global_config.list_sessions or global_config.json): return manifest - _produce_listing(manifest, global_config) + # JSON output requires list sessions also be specified + if global_config.json and not global_config.list_sessions: + logger.error("Must specify --list-sessions with --json") + return 3 + + if global_config.json: + _produce_json_listing(manifest, global_config) + else: + _produce_listing(manifest, global_config) return 0 diff --git a/tests/test_tasks.py b/tests/test_tasks.py index c6c88db8..c6bdbfe8 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -401,6 +401,64 @@ def test_honor_list_request_doesnt_print_docstring_if_not_present(capsys): assert "Hello I'm a docstring" not in out +def test_honor_list_json_request(capsys): + config = _options.options.namespace( + list_sessions=True, noxfile="noxfile.py", json=True + ) + manifest = mock.create_autospec(Manifest) + manifest.list_all_sessions.return_value = [ + ( + argparse.Namespace( + name="bar", + friendly_name="foo", + description="simple", + func=argparse.Namespace(python="123"), + tags=[], + ), + True, + ), + ( + argparse.Namespace(), + False, + ), + ] + return_value = tasks.honor_list_request(manifest, global_config=config) + assert return_value == 0 + assert json.loads(capsys.readouterr().out) == [ + { + "session": "foo", + "name": "bar", + "description": "simple", + "python": "123", + "tags": [], + "call_spec": {}, + } + ] + + +def test_refuse_json_nolist_request(caplog): + config = _options.options.namespace( + list_sessions=False, noxfile="noxfile.py", json=True + ) + manifest = mock.create_autospec(Manifest) + manifest.list_all_sessions.return_value = [ + ( + argparse.Namespace( + name="bar", + friendly_name="foo", + description="simple", + func=argparse.Namespace(python="123"), + tags=[], + ), + True, + ) + ] + return_value = tasks.honor_list_request(manifest, global_config=config) + assert return_value == 3 + (record,) = caplog.records + assert record.message == "Must specify --list-sessions with --json" + + def test_empty_session_list_in_noxfile(capsys): config = _options.options.namespace(noxfile="noxfile.py", sessions=(), posargs=[]) manifest = Manifest({"session": session_func}, config)