Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/unstract_cli/core/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,18 @@ def _from_schema(
)


def _hidden(node: dict[str, Any]) -> bool:
return bool(node.get("deprecated") or node.get("x-internal"))


def operation_params(product: str, operation_id: str) -> list[Param]:
"""Every parameter one operation accepts: query, then request body.

Path parameters are excluded: they are the route, supplied by the command
from configuration, not by the caller as a flag. So are deprecated ones: a
superseded spelling the client still accepts would otherwise become a second
flag for the same value.
flag for the same value. And so are internal ones (`x-internal`): the server
accepts them and the clients keep them, but they are not offered.
"""
operation = find_operation(product, operation_id)
params = [
Expand All @@ -138,7 +143,7 @@ def operation_params(product: str, operation_id: str) -> list[Param]:
required=bool(p.get("required")),
)
for p in operation.get("parameters", [])
if p.get("in") == "query" and not p.get("deprecated")
if p.get("in") == "query" and not _hidden(p)
]

body = operation.get("requestBody", {}).get("content", {})
Expand All @@ -151,7 +156,7 @@ def operation_params(product: str, operation_id: str) -> list[Param]:
schema = _resolve_ref(product, ref)
mandatory = set(schema.get("required") or ())
for name, prop in (schema.get("properties") or {}).items():
if prop.get("deprecated"):
if _hidden(prop):
continue
params.append(
_from_schema(
Expand Down
60 changes: 60 additions & 0 deletions tests/test_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from __future__ import annotations

import click
import click.testing
import pytest
from unstract.api_deployments.client import APIDeploymentsClient
from unstract.llmwhisperer.client_v2 import LLMWhispererClientV2
Expand Down Expand Up @@ -70,6 +71,65 @@ def test_the_uploaded_document_is_not_a_flag():
assert find_operation("llmwhisperer", "extract")["method"] == "post"


#: A spec carrying the marker on one query parameter and one body property.
MARKED_SPEC = {
"paths": {
"/run": {
"post": {
"operationId": "run",
"parameters": [
{"name": "mode", "in": "query", "schema": {"type": "string"}},
{
"name": "trace",
"in": "query",
"schema": {"type": "boolean"},
"x-internal": True,
},
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"properties": {
"timeout": {"type": "integer"},
"use_file_history": {
"type": "boolean",
"default": False,
"x-internal": True,
},
}
}
}
}
},
}
}
}
}


def test_an_internal_parameter_is_not_offered(monkeypatch):
"""The server accepts it and the generated clients keep it, so it stays in
the spec; the marker is the only thing saying it is not for callers."""
monkeypatch.setattr(params_module, "load_spec", lambda product: MARKED_SPEC)

assert {p.name for p in operation_params("docstudio", "run")} == {"mode", "timeout"}


def test_an_internal_parameter_is_unknown_to_the_parser(monkeypatch):
monkeypatch.setattr(params_module, "load_spec", lambda product: MARKED_SPEC)
command = click.Command(
"run",
params=[click_option(p, {}) for p in operation_params("docstudio", "run")],
callback=lambda **_: None,
)

for flag in ("--trace", "--use-file-history"):
result = click.testing.CliRunner().invoke(command, [flag])
assert result.exit_code == 2, flag
assert "No such option" in result.output, flag


def test_an_unknown_operation_names_itself():
with pytest.raises(KeyError, match="whisper_sideways"):
find_operation("llmwhisperer", "whisper_sideways")
Expand Down
Loading