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
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,15 @@ def validate_params(self) -> None:
"""
Validate the `params` attribute of the instance.

Note: Value validation (e.g., required fields, schema) is intentionally skipped here
because HITLOperator params represent form fields that are filled by a human at runtime.
Values do not exist at Dag parse time, so validating them in ``__init__`` would cause
a ``ParamValidationError`` for any param without a default. Value validation happens
in ``validate_params_input`` after the human submits the form.

Raises:
ValueError: If `"_options"` key is present in `params`, which is not allowed.
ValueError: If ``"_options"`` key is present in ``params``, which is not allowed.
"""
self.params.validate()
if "_options" in self.params:
raise ValueError('"_options" is not allowed in params')

Expand Down
64 changes: 42 additions & 22 deletions providers/standard/tests/unit/standard/operators/test_hitl.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,37 +139,57 @@ def test_validate_options_with_empty_options(self) -> None:
params=ParamsDict({"input_1": 1}),
)

@pytest.mark.parametrize(
("params", "exc", "error_msg"),
(
(ParamsDict({"_options": 1}), ValueError, '"_options" is not allowed in params'),
(
ParamsDict({"param": Param("", type="integer")}),
ParamValidationError,
(
"Invalid input for param param: '' is not of type 'integer'\n\n"
"Failed validating 'type' in schema:\n"
" {'type': 'integer'}\n\n"
"On instance:\n ''"
),
),
),
)
def test_validate_params(
self, params: ParamsDict, exc: type[ValueError | ParamValidationError], error_msg: str
) -> None:
# validate_params is called during initialization
with pytest.raises(exc, match=error_msg):
def test_validate_params_rejects_options_key(self) -> None:
"""_options is a reserved key and must not be allowed in params."""
with pytest.raises(ValueError, match='"_options" is not allowed in params'):
HITLOperator(
task_id="hitl_test",
subject="This is subject",
options=["1", "2"],
body="This is body",
defaults=["1"],
multiple=False,
params=params,
params=ParamsDict({"_options": 1}),
)

@pytest.mark.parametrize(
("params", "expected_key"),
[
pytest.param(
{"my_param": Param(type="string")},
"my_param",
id="no_default",
),
pytest.param(
{"my_param": Param("hello", type="string")},
"my_param",
id="with_default",
),
pytest.param(
{"param": Param("", type="integer")},
"param",
id="wrong_value_type",
),
],
)
def test_param_value_validation_deferred_to_runtime(self, params: dict, expected_key: str) -> None:
"""Regression test for #59551.
HITLOperator params are form fields filled by a human at runtime.
Value validation (required, schema) must NOT happen in __init__ — it is
deferred to ``validate_params_input`` after the human submits the form.
"""
op = HITLOperator(
task_id="hitl_test",
subject="This is subject",
options=["1", "2"],
body="This is body",
defaults=["1"],
multiple=False,
params=params,
)
assert expected_key in op.params

def test_validate_defaults(self) -> None:
hitl_op = HITLOperator(
task_id="hitl_test",
Expand Down
Loading