Skip to content
Open
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
5 changes: 4 additions & 1 deletion metaflow/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ def _check_type(self, val, deploy_time):

if isinstance(self.parameter_type, list):
if not any(isinstance(val, x) for x in self.parameter_type):
msg += "Expected one of the following %s." % TYPES[self.parameter_type]
msg += "Expected one of the following %s." % ", ".join(
TYPES.get(t, getattr(t, "__name__", str(t)))
for t in self.parameter_type
)
raise ParameterFieldTypeMismatch(msg)
return str(val) if self.return_str else val
elif self.parameter_type in TYPES:
Expand Down
80 changes: 80 additions & 0 deletions test/unit/test_deploy_time_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Tests for DeployTimeField._check_type.

@trigger / @trigger_on_finish wrap a callable attribute in a DeployTimeField
whose parameter_type is a *list* of acceptable types, e.g. [str, dict] for
`event=` and [list, dict, tuple] for `event={"parameters": ...}`. When the
user's function returns something outside that set, the mismatch message has
to be buildable -- otherwise the user never sees why their function was
rejected.
"""

import pytest

import metaflow.parameters as parameters
from metaflow.exception import ParameterFieldTypeMismatch
from metaflow.parameters import DeployTimeField, ParameterContext


@pytest.fixture(autouse=True)
def parameter_context(monkeypatch):
"""DeployTimeField.__call__ curries the module-level context onto the
user function; the CLI normally installs it via set_parameter_context()."""
monkeypatch.setattr(
parameters,
"context_proto",
ParameterContext(
flow_name="TriggerFlow",
user_name="tester",
parameter_name=None,
logger=lambda *a, **kw: None,
ds_type="local",
configs=None,
),
)


def _field(parameter_type, value):
return DeployTimeField(
"event", parameter_type, None, lambda ctx, deploy_time: value, False
)


@pytest.mark.parametrize(
"parameter_type, expected_names",
[
([str, dict], ["str", "dict"]),
([list, dict, tuple], ["list", "dict", "tuple"]),
],
ids=["trigger_event", "trigger_event_parameters"],
)
def test_list_of_types_reports_the_mismatch(parameter_type, expected_names):
field = _field(parameter_type, 42)
with pytest.raises(ParameterFieldTypeMismatch) as excinfo:
field(deploy_time=True)
message = str(excinfo.value)
for name in expected_names:
assert name in message


@pytest.mark.parametrize(
"parameter_type, value",
[
([str, dict], "an-event-name"),
([str, dict], {"name": "an-event-name"}),
([list, dict, tuple], ["a", "b"]),
([list, dict, tuple], ("a", "b")),
],
)
def test_list_of_types_accepts_any_listed_type(parameter_type, value):
assert _field(parameter_type, value)(deploy_time=True) == value


def test_single_type_mismatch_still_reports(parameter_type=int):
field = _field(parameter_type, "not-an-int")
with pytest.raises(ParameterFieldTypeMismatch) as excinfo:
field(deploy_time=True)
assert "Expected a int." in str(excinfo.value)


def test_single_type_match_still_passes():
assert _field(int, 7)(deploy_time=True) == 7