Skip to content

Commit 119fd00

Browse files
authored
feat: --json --list-sessions (#665)
Closes #658
1 parent befe771 commit 119fd00

File tree

4 files changed

+98
-2
lines changed

4 files changed

+98
-2
lines changed

docs/usage.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ To list all available sessions, including parametrized sessions:
3030
nox --list
3131
nox --list-sessions
3232
33+
If you'd like to use the output in later processing, you can add ``--json`` to
34+
get json output for the selected session. Fields include ``session`` (pretty
35+
name), ``name``, ``description``, ``python`` (null if not specified), ``tags``,
36+
and ``call_spec`` (for parametrized sessions).
37+
3338

3439
.. _session_execution_order:
3540

nox/_options.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,13 @@ def _session_completer(
264264
action="store_true",
265265
help="List all available sessions and exit.",
266266
),
267+
_option_set.Option(
268+
"json",
269+
"--json",
270+
group=options.groups["sessions"],
271+
action="store_true",
272+
help="JSON output formatting. Requires list-sessions currently.",
273+
),
267274
_option_set.Option(
268275
"sessions",
269276
"-s",

nox/tasks.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ def filter_manifest(manifest: Manifest, global_config: Namespace) -> Manifest |
179179
logger.error("Error while collecting sessions.")
180180
logger.error(exc.args[0])
181181
return 3
182+
182183
if not manifest and not global_config.list_sessions:
183184
print("No sessions selected. Please select a session with -s <session name>.\n")
184185
_produce_listing(manifest, global_config)
@@ -264,6 +265,23 @@ def _produce_listing(manifest: Manifest, global_config: Namespace) -> None:
264265
)
265266

266267

268+
def _produce_json_listing(manifest: Manifest, global_config: Namespace) -> None:
269+
report = []
270+
for session, selected in manifest.list_all_sessions():
271+
if selected:
272+
report.append(
273+
{
274+
"session": session.friendly_name,
275+
"name": session.name,
276+
"description": session.description or "",
277+
"python": session.func.python,
278+
"tags": session.tags,
279+
"call_spec": getattr(session.func, "call_spec", {}),
280+
}
281+
)
282+
print(json.dumps(report))
283+
284+
267285
def honor_list_request(manifest: Manifest, global_config: Namespace) -> Manifest | int:
268286
"""If --list was passed, simply list the manifest and exit cleanly.
269287
@@ -275,10 +293,18 @@ def honor_list_request(manifest: Manifest, global_config: Namespace) -> Manifest
275293
Union[~.Manifest,int]: ``0`` if a listing is all that is requested,
276294
the manifest otherwise (to be sent to the next task).
277295
"""
278-
if not global_config.list_sessions:
296+
if not (global_config.list_sessions or global_config.json):
279297
return manifest
280298

281-
_produce_listing(manifest, global_config)
299+
# JSON output requires list sessions also be specified
300+
if global_config.json and not global_config.list_sessions:
301+
logger.error("Must specify --list-sessions with --json")
302+
return 3
303+
304+
if global_config.json:
305+
_produce_json_listing(manifest, global_config)
306+
else:
307+
_produce_listing(manifest, global_config)
282308

283309
return 0
284310

tests/test_tasks.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,64 @@ def test_honor_list_request_doesnt_print_docstring_if_not_present(capsys):
401401
assert "Hello I'm a docstring" not in out
402402

403403

404+
def test_honor_list_json_request(capsys):
405+
config = _options.options.namespace(
406+
list_sessions=True, noxfile="noxfile.py", json=True
407+
)
408+
manifest = mock.create_autospec(Manifest)
409+
manifest.list_all_sessions.return_value = [
410+
(
411+
argparse.Namespace(
412+
name="bar",
413+
friendly_name="foo",
414+
description="simple",
415+
func=argparse.Namespace(python="123"),
416+
tags=[],
417+
),
418+
True,
419+
),
420+
(
421+
argparse.Namespace(),
422+
False,
423+
),
424+
]
425+
return_value = tasks.honor_list_request(manifest, global_config=config)
426+
assert return_value == 0
427+
assert json.loads(capsys.readouterr().out) == [
428+
{
429+
"session": "foo",
430+
"name": "bar",
431+
"description": "simple",
432+
"python": "123",
433+
"tags": [],
434+
"call_spec": {},
435+
}
436+
]
437+
438+
439+
def test_refuse_json_nolist_request(caplog):
440+
config = _options.options.namespace(
441+
list_sessions=False, noxfile="noxfile.py", json=True
442+
)
443+
manifest = mock.create_autospec(Manifest)
444+
manifest.list_all_sessions.return_value = [
445+
(
446+
argparse.Namespace(
447+
name="bar",
448+
friendly_name="foo",
449+
description="simple",
450+
func=argparse.Namespace(python="123"),
451+
tags=[],
452+
),
453+
True,
454+
)
455+
]
456+
return_value = tasks.honor_list_request(manifest, global_config=config)
457+
assert return_value == 3
458+
(record,) = caplog.records
459+
assert record.message == "Must specify --list-sessions with --json"
460+
461+
404462
def test_empty_session_list_in_noxfile(capsys):
405463
config = _options.options.namespace(noxfile="noxfile.py", sessions=(), posargs=[])
406464
manifest = Manifest({"session": session_func}, config)

0 commit comments

Comments
 (0)