Update Python tools

This commit is contained in:
ajp_anton
2026-08-09 02:31:54 +00:00
parent b0c707d624
commit 6d28257b54
10 changed files with 362 additions and 12 deletions
+61 -4
View File
@@ -11,7 +11,7 @@ from typing import Any
FIELD_NAME = re.compile(r"[a-z][a-z0-9_]*\Z")
FIELD_TYPES = frozenset({"text", "integer", "date", "datetime", "choice", "multi_choice"})
FIELD_TYPES = frozenset({"text", "integer", "date", "datetime", "choice", "multi_choice", "file"})
def display_name(value: str) -> str:
@@ -28,6 +28,7 @@ class Task:
description: str | None
inputs: tuple["InputField", ...]
wait_for_result: bool = False
download_artifacts: bool = False
@dataclass(frozen=True)
@@ -58,6 +59,8 @@ class InputField:
step: int | None
pattern: str | None
options: tuple[InputOption, ...]
accept: tuple[str, ...]
maximum_bytes: int | None
@dataclass(frozen=True)
@@ -154,6 +157,7 @@ class TaskCatalog:
description=read_description(path),
inputs=declaration.inputs if declaration else (),
wait_for_result=declaration.wait_for_result if declaration else False,
download_artifacts=declaration.download_artifacts if declaration else False,
)
)
return tasks
@@ -275,6 +279,7 @@ def load_input_manifest(
class TaskDeclaration:
inputs: tuple[InputField, ...]
wait_for_result: bool
download_artifacts: bool
def parse_task_declaration(
@@ -282,7 +287,11 @@ def parse_task_declaration(
filename: str,
declaration: Any,
) -> TaskDeclaration:
if not isinstance(declaration, dict) or not {"inputs"} <= set(declaration) or set(declaration) - {"inputs", "wait_for_result"}:
if (
not isinstance(declaration, dict)
or not {"inputs"} <= set(declaration)
or set(declaration) - {"inputs", "wait_for_result", "download_artifacts"}
):
raise ValueError(f"{manifest_path} task {filename} has an invalid declaration.")
raw_inputs = declaration["inputs"]
if not isinstance(raw_inputs, list):
@@ -294,7 +303,16 @@ def parse_task_declaration(
wait_for_result = declaration.get("wait_for_result", False)
if not isinstance(wait_for_result, bool):
raise ValueError(f"{manifest_path} task {filename} wait_for_result must be true or false.")
return TaskDeclaration(inputs=fields, wait_for_result=wait_for_result)
download_artifacts = declaration.get("download_artifacts", False)
if not isinstance(download_artifacts, bool):
raise ValueError(f"{manifest_path} task {filename} download_artifacts must be true or false.")
if download_artifacts and not wait_for_result:
raise ValueError(f"{manifest_path} task {filename} download_artifacts requires wait_for_result.")
return TaskDeclaration(
inputs=fields,
wait_for_result=wait_for_result,
download_artifacts=download_artifacts,
)
def parse_input_field(manifest_path: Path, filename: str, raw: Any) -> InputField:
@@ -302,7 +320,7 @@ def parse_input_field(manifest_path: Path, filename: str, raw: Any) -> InputFiel
raise ValueError(f"{manifest_path} task {filename} has a non-object input field.")
allowed = {
"name", "label", "type", "required", "default", "minimum", "maximum",
"step", "pattern", "options",
"step", "pattern", "options", "accept", "maximum_bytes",
}
unknown = set(raw) - allowed
if unknown:
@@ -344,6 +362,20 @@ def parse_input_field(manifest_path: Path, filename: str, raw: Any) -> InputFiel
except re.error as error:
raise ValueError(f"{manifest_path} task {filename} input {name} has an invalid pattern: {error}.") from error
accept = parse_file_accept(manifest_path, filename, name, field_type, raw.get("accept"))
maximum_bytes = raw.get("maximum_bytes")
if field_type == "file":
if pattern is not None or raw.get("default") is not None:
raise ValueError(f"{manifest_path} task {filename} file input {name} cannot define a pattern or default.")
if maximum_bytes is not None and (
not isinstance(maximum_bytes, int)
or isinstance(maximum_bytes, bool)
or maximum_bytes < 1
):
raise ValueError(f"{manifest_path} task {filename} file input {name} maximum_bytes must be positive.")
elif maximum_bytes is not None:
raise ValueError(f"{manifest_path} task {filename} input {name} only file fields accept maximum_bytes.")
options = parse_options(manifest_path, filename, name, field_type, raw.get("options"))
default = parse_default(manifest_path, filename, name, field_type, options, raw.get("default"))
validate_default(
@@ -368,9 +400,34 @@ def parse_input_field(manifest_path: Path, filename: str, raw: Any) -> InputFiel
step=numeric_values["step"],
pattern=pattern,
options=options,
accept=accept,
maximum_bytes=maximum_bytes,
)
def parse_file_accept(
manifest_path: Path,
filename: str,
name: str,
field_type: str,
raw_accept: Any,
) -> tuple[str, ...]:
if field_type != "file":
if raw_accept is not None:
raise ValueError(f"{manifest_path} task {filename} input {name} only file fields accept accept.")
return ()
if raw_accept is None:
return ()
if (
not isinstance(raw_accept, list)
or not raw_accept
or not all(isinstance(value, str) and re.fullmatch(r"\.[a-z0-9]{1,10}", value) for value in raw_accept)
or len(set(raw_accept)) != len(raw_accept)
):
raise ValueError(f"{manifest_path} task {filename} file input {name} has invalid accept values.")
return tuple(raw_accept)
def parse_options(
manifest_path: Path,
filename: str,