Files
wehub-resource-sync 09e9f3545f
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
dependency-audit / pip-audit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:30:13 +08:00

221 lines
7.6 KiB
Python

"""Public CLI contract introspection (the ``cli_contract`` baseline derive).
``build_cli_contract()`` walks the Click command tree and returns the
deterministic public CLI inventory (command tree, options, defaults, help,
aliases) that is frozen as the ``cli_contract`` regenerable baseline (ADR-0022,
``tests/fixtures/cli_contract_baseline.json``).
This pure-introspection core was extracted out of
``tests/unit/cli/test_cli_contract.py`` into this ``_``-prefixed non-test module
so the baseline registry (``tests/_baselines/registry.py``) can import
``build_cli_contract`` without one ``test_*`` module importing from another
(forbidden by ``tests/_guardrails/test_no_cross_test_imports.py``). The behavioural
CLI-contract tests still live in ``test_cli_contract.py`` and import these names
from here.
"""
from __future__ import annotations
import click
from notebooklm.notebooklm_cli import cli
ROOT_COMMAND = "notebooklm"
TRACKED_GROUPS = (
"download",
"source",
"generate",
"artifact",
"session",
"profile",
"notebook",
"chat",
"note",
"label",
"share",
"research",
)
CLICK_GROUPS = (
"agent",
"download",
"source",
"generate",
"artifact",
"language",
"profile",
"note",
"label",
"share",
"research",
"skill",
)
TOP_LEVEL_SURFACES = {
"session": ("login", "auth", "use", "status", "clear"),
"notebook": ("list", "create", "delete", "rename", "metadata", "summary"),
"chat": ("ask", "suggest-prompts", "configure", "history"),
}
EXTRA_TOP_LEVEL_COMMANDS = ("completion", "doctor")
def _command_for(path: str) -> click.Command:
cmd: click.Command = cli
if not path or path == ROOT_COMMAND:
return cmd
for part in path.split():
if not isinstance(cmd, click.Group):
raise AssertionError(f"{path!r} traversed through non-group {cmd!r}")
next_cmd = cmd.get_command(click.Context(cmd), part)
if next_cmd is None:
raise AssertionError(f"missing command path: {path!r}")
cmd = next_cmd
return cmd
def _json_default(value):
if value is None or isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, (tuple, list)):
return [_json_default(v) for v in value]
return str(value)
def _type_contract(param_type: click.ParamType) -> dict[str, object]:
data: dict[str, object] = {"name": param_type.name}
if isinstance(param_type, click.Choice):
data["choices"] = list(param_type.choices)
data["case_sensitive"] = param_type.case_sensitive
if isinstance(param_type, click.IntRange):
data["min"] = param_type.min
data["max"] = param_type.max
data["clamp"] = param_type.clamp
if isinstance(param_type, click.Path):
data["exists"] = param_type.exists
data["file_okay"] = param_type.file_okay
data["dir_okay"] = param_type.dir_okay
data["writable"] = param_type.writable
data["readable"] = param_type.readable
data["executable"] = param_type.executable
data["resolve_path"] = param_type.resolve_path
data["allow_dash"] = param_type.allow_dash
return data
def _has_custom_shell_complete(param: click.Option) -> bool:
return getattr(param, "_custom_shell_complete", None) is not None
def _visible_command_names(group: click.Group) -> list[str]:
ctx = click.Context(group)
names = group.list_commands(ctx)
return [name for name in names if not getattr(group.get_command(ctx, name), "hidden", False)]
def _param_contract(param: click.Parameter) -> dict[str, object]:
base: dict[str, object] = {
"name": param.name,
"required": param.required,
"type": _type_contract(param.type),
}
if isinstance(param, click.Option):
base.update(
{
"kind": "option",
"opts": list(param.opts),
"secondary_opts": list(param.secondary_opts),
"default": _json_default(param.default),
"envvar": _json_default(param.envvar),
"is_flag": param.is_flag,
"multiple": param.multiple,
"help": param.help,
"has_custom_shell_complete": _has_custom_shell_complete(param),
}
)
else:
base.update({"kind": "argument", "nargs": param.nargs})
return base
def _command_contract(path: str) -> dict[str, object]:
cmd = _command_for(path)
data: dict[str, object] = {
"class": type(cmd).__name__,
"params": [_param_contract(param) for param in cmd.params],
"short_help": cmd.get_short_help_str(),
}
if isinstance(cmd, click.Group):
data["commands"] = _visible_command_names(cmd)
return data
def _option_by_name(path: str, name: str) -> click.Option:
for param in _command_for(path).params:
if isinstance(param, click.Option) and param.name == name:
return param
raise AssertionError(f"{path!r} has no option named {name!r}")
def _iter_command_paths(path: str) -> list[str]:
cmd = _command_for(path)
paths = [path]
if isinstance(cmd, click.Group):
for child in _visible_command_names(cmd):
child_path = f"{path} {child}" if path else child
paths.extend(_iter_command_paths(child_path))
return paths
def _tracked_command_paths() -> list[str]:
paths: list[str] = [ROOT_COMMAND]
for group in CLICK_GROUPS:
paths.extend(_iter_command_paths(group))
for commands in TOP_LEVEL_SURFACES.values():
for name in commands:
paths.extend(_iter_command_paths(name))
for name in EXTRA_TOP_LEVEL_COMMANDS:
paths.extend(_iter_command_paths(name))
return sorted(set(paths))
def _same_params(left: click.Command, right: click.Command) -> bool:
return [_param_contract(p) for p in left.params] == [_param_contract(p) for p in right.params]
def build_cli_contract() -> dict[str, object]:
"""Return the deterministic public CLI inventory used by the baseline."""
download_cinematic_video = _command_for("download cinematic-video")
download_video = _command_for("download video")
generate_cinematic_video = _command_for("generate cinematic-video")
generate_video = _command_for("generate video")
return {
"schema_version": 1,
"tracked_surfaces": list(TRACKED_GROUPS),
"root_commands": _visible_command_names(cli),
"top_level_surfaces": {key: list(value) for key, value in TOP_LEVEL_SURFACES.items()},
"click_groups": {
group: _visible_command_names(_command_for(group)) for group in CLICK_GROUPS
},
"aliases": {
"download cinematic-video": {
"canonical": "download video",
"same_callback": download_cinematic_video.callback is download_video.callback,
"same_params": _same_params(download_cinematic_video, download_video),
},
"generate cinematic-video": {
"canonical": "generate video --format cinematic",
"same_callback": generate_cinematic_video.callback is generate_video.callback,
"same_params": _same_params(generate_cinematic_video, generate_video),
},
},
"completion_callbacks": {
"notebook": _has_custom_shell_complete(_option_by_name("source list", "notebook_id")),
"download_artifact": _has_custom_shell_complete(
_option_by_name("download audio", "artifact_id")
),
},
"commands": {path: _command_contract(path) for path in _tracked_command_paths()},
}