Files
docker-images/python-tools/app/inputs.py
T
2026-08-08 18:39:09 +00:00

88 lines
3.1 KiB
Python

"""Validation for task-input manifest values."""
from __future__ import annotations
import re
from collections.abc import Mapping, Sequence
from datetime import date, datetime
from .discovery import InputField
def validate_inputs(
fields: Sequence[InputField],
values: Mapping[str, list[str]],
) -> tuple[dict[str, str | int | list[str]], dict[str, str]]:
result: dict[str, str | int | list[str]] = {}
errors: dict[str, str] = {}
for field in fields:
raw_values = values.get(field.name, [])
value, error = validate_field(field, raw_values)
if error:
errors[field.name] = error
elif value is not None:
result[field.name] = value
return result, errors
def validate_field(
field: InputField,
raw_values: list[str],
) -> tuple[str | int | list[str] | None, str | None]:
if field.type == "multi_choice":
return validate_multi_choice(field, raw_values)
if len(raw_values) > 1:
return None, "Only one value is allowed."
raw = raw_values[0] if raw_values else ""
if not raw:
if field.default is not None:
return field.default, None
if field.required:
return None, "This field is required."
return None, None
if field.type == "integer":
try:
value = int(raw)
except ValueError:
return None, "Enter a whole number."
if field.minimum is not None and value < field.minimum:
return None, f"Enter a value of at least {field.minimum}."
if field.maximum is not None and value > field.maximum:
return None, f"Enter a value no greater than {field.maximum}."
if field.step is not None and (value - (field.minimum or 0)) % field.step:
return None, f"Enter a value in increments of {field.step}."
return value, None
if field.type == "date":
try:
date.fromisoformat(raw)
except ValueError:
return None, "Enter a valid date."
elif field.type == "datetime":
try:
datetime.fromisoformat(raw)
except ValueError:
return None, "Enter a valid date and time."
elif field.type == "choice" and raw not in {option.value for option in field.options}:
return None, "Choose one of the available options."
elif field.type == "text" and field.pattern and not re.fullmatch(field.pattern, raw):
return None, "Enter a value in the required format."
return raw, None
def validate_multi_choice(
field: InputField,
raw_values: list[str],
) -> tuple[list[str] | None, str | None]:
if not raw_values:
if field.default is not None:
return list(field.default), None
if field.required:
return None, "Choose at least one option."
return None, None
allowed = {option.value for option in field.options}
if any(value not in allowed for value in raw_values):
return None, "Choose only from the available options."
if len(raw_values) != len(set(raw_values)):
return None, "Each option can be selected only once."
return raw_values, None