Refuse an unknown run input by name before reading its value
`fluksio run --param lr=0.002` died with a bare JSONDecodeError: `--param` is not a `run` flag, so it became an input named `param` whose value `lr=0.002` was json-decoded. The name check ran after the coercion, so the decode error always won. Names are now checked first, and a coercion error is a SyncError naming the input and its type, the way `_ask_params` has always done it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Hra4ndWMCLU5F3KjUuVAc
This commit is contained in:
@@ -439,33 +439,52 @@ def _ask_params(definition: dict[str, Any]) -> dict[str, Any]:
|
||||
def _params(definition: dict[str, Any], rest: list[str]) -> dict[str, Any]:
|
||||
"""Turn `--lr 0.05` into a typed parameter, using the flow's own inputs."""
|
||||
types = _input_types(definition)
|
||||
params: dict[str, Any] = {}
|
||||
# Collected as written and typed afterwards, so a name this flow does not
|
||||
# have is refused by name rather than by whatever its value failed to parse
|
||||
# as: `--param lr=0.002` is a sweep's spelling, and said so it reads as an
|
||||
# input called `param` holding unparseable json.
|
||||
raw: dict[str, str | bool] = {}
|
||||
pending: str | None = None
|
||||
for token in rest:
|
||||
if token.startswith("--"):
|
||||
if pending is not None:
|
||||
# A flag with no value is a flag: `--resume` means true.
|
||||
params[pending] = True
|
||||
raw[pending] = True
|
||||
name, sep, value = token[2:].partition("=")
|
||||
# Only the name is spelled with dashes; a value may hold one, and
|
||||
# `--lr=1e-4` is the case that says so.
|
||||
pending = name.replace("-", "_")
|
||||
if sep:
|
||||
params[pending] = _coerce(value, types.get(pending, "json"))
|
||||
raw[pending] = value
|
||||
pending = None
|
||||
continue
|
||||
if pending is None:
|
||||
raise SyncError(f"unexpected argument '{token}'")
|
||||
params[pending] = _coerce(token, types.get(pending, "json"))
|
||||
raw[pending] = token
|
||||
pending = None
|
||||
if pending is not None:
|
||||
params[pending] = True
|
||||
unknown = sorted(set(params) - set(types))
|
||||
raw[pending] = True
|
||||
unknown = sorted(set(raw) - set(types))
|
||||
if unknown:
|
||||
hint = ""
|
||||
if unknown[0] == "param":
|
||||
hint = (
|
||||
" — one value is `--<name> <value>`; several is a sweep: "
|
||||
"`fluksio sweep --param name=v1,v2`"
|
||||
)
|
||||
raise SyncError(
|
||||
f"'{unknown[0]}' is not an input of this flow (it takes "
|
||||
f"{', '.join(sorted(types)) or 'none'})"
|
||||
f"{', '.join(sorted(types)) or 'none'}){hint}"
|
||||
)
|
||||
params: dict[str, Any] = {}
|
||||
for name, value in raw.items():
|
||||
if value is True:
|
||||
params[name] = True
|
||||
continue
|
||||
try:
|
||||
params[name] = _coerce(value, types[name])
|
||||
except ValueError as exc:
|
||||
raise SyncError(f"'{name}' takes {types[name]}: {exc}") from exc
|
||||
return params
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user