From 3b60605473c20e5a3325e1ae4b9a994c75f07a58 Mon Sep 17 00:00:00 2001 From: yuteng Date: Wed, 5 Nov 2025 13:06:25 +0800 Subject: [PATCH 1/3] support None type in pyflyte run command Signed-off-by: yuteng --- flytekit/clis/sdk_in_container/run.py | 4 ++++ flytekit/interaction/click_types.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/flytekit/clis/sdk_in_container/run.py b/flytekit/clis/sdk_in_container/run.py index eb8164f2b0..874636616e 100644 --- a/flytekit/clis/sdk_in_container/run.py +++ b/flytekit/clis/sdk_in_container/run.py @@ -694,8 +694,12 @@ def _run(*args, **kwargs): ) try: inputs = {} + inputs_type = entity.python_interface.inputs for input_name, v in entity.python_interface.inputs_with_defaults.items(): processed_click_value = kwargs.get(input_name) + input_type = inputs_type[input_name] + if is_optional(input_type) and processed_click_value == "None": + processed_click_value = None optional_v = False skip_default_value_selection = False diff --git a/flytekit/interaction/click_types.py b/flytekit/interaction/click_types.py index 056fa2db61..1f76f825f3 100644 --- a/flytekit/interaction/click_types.py +++ b/flytekit/interaction/click_types.py @@ -572,6 +572,8 @@ def convert( if not self._is_remote: return value + if is_optional(self._python_type) and value == "None": + value = None lit = TypeEngine.to_literal(self._flyte_ctx, value, self._python_type, self._literal_type) return lit except click.BadParameter: @@ -581,3 +583,10 @@ def convert( f"Failed to convert param: {param if param else 'NA'}, value: {value} to type: {self._python_type}." f" Reason {e}" ) from e + + +def is_optional(_type): + """ + Checks if the given type is Optional Type + """ + return typing.get_origin(_type) is typing.Union and type(None) in typing.get_args(_type) From 7818757665c2fc34ca1be0a18c9b6e19c4779ce8 Mon Sep 17 00:00:00 2001 From: yuteng Date: Wed, 5 Nov 2025 21:35:15 +0800 Subject: [PATCH 2/3] update Signed-off-by: yuteng --- flytekit/clis/sdk_in_container/run.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/flytekit/clis/sdk_in_container/run.py b/flytekit/clis/sdk_in_container/run.py index 874636616e..8f58c7a56f 100644 --- a/flytekit/clis/sdk_in_container/run.py +++ b/flytekit/clis/sdk_in_container/run.py @@ -694,15 +694,14 @@ def _run(*args, **kwargs): ) try: inputs = {} - inputs_type = entity.python_interface.inputs for input_name, v in entity.python_interface.inputs_with_defaults.items(): processed_click_value = kwargs.get(input_name) - input_type = inputs_type[input_name] - if is_optional(input_type) and processed_click_value == "None": + skip_default_value_selection = False + if is_optional(v[0]) and processed_click_value == "None": processed_click_value = None + skip_default_value_selection = True optional_v = False - skip_default_value_selection = False if processed_click_value is None and isinstance(v, typing.Tuple): if entity_type == "workflow" and hasattr(v[0], "__args__"): origin_base_type = get_origin(v[0]) @@ -734,6 +733,8 @@ def _run(*args, **kwargs): inputs[input_name] = processed_click_value if processed_click_value is None and v[0] == bool: inputs[input_name] = False + if processed_click_value is None and is_optional(v[0]): + inputs[input_name] = None if not run_level_params.is_remote: with FlyteContextManager.with_context(_update_flyte_context(run_level_params)): From 14523917ae117cf6e3d664f4acb79337451f5acd Mon Sep 17 00:00:00 2001 From: yuteng Date: Thu, 6 Nov 2025 07:59:07 +0800 Subject: [PATCH 3/3] fix nit Signed-off-by: yuteng --- flytekit/clis/sdk_in_container/run.py | 7 ++++++- flytekit/interaction/click_types.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/flytekit/clis/sdk_in_container/run.py b/flytekit/clis/sdk_in_container/run.py index 8f58c7a56f..39d0135f44 100644 --- a/flytekit/clis/sdk_in_container/run.py +++ b/flytekit/clis/sdk_in_container/run.py @@ -17,6 +17,7 @@ from mashumaro.codecs.json import JSONEncoder from rich.progress import Progress, TextColumn, TimeElapsedColumn from typing_extensions import get_origin +from typing_inspect import is_optional_type from flytekit import Annotations, FlyteContext, FlyteContextManager, Labels, LaunchPlan, Literal, WorkflowExecutionPhase from flytekit.clis.sdk_in_container.helpers import ( @@ -697,7 +698,11 @@ def _run(*args, **kwargs): for input_name, v in entity.python_interface.inputs_with_defaults.items(): processed_click_value = kwargs.get(input_name) skip_default_value_selection = False - if is_optional(v[0]) and processed_click_value == "None": + if ( + is_optional_type(v[0]) + and isinstance(processed_click_value, str) + and processed_click_value.lower() == "none" + ): processed_click_value = None skip_default_value_selection = True optional_v = False diff --git a/flytekit/interaction/click_types.py b/flytekit/interaction/click_types.py index 1f76f825f3..0efc821a75 100644 --- a/flytekit/interaction/click_types.py +++ b/flytekit/interaction/click_types.py @@ -19,6 +19,7 @@ from dataclasses_json import DataClassJsonMixin, dataclass_json from packaging.version import Version from pytimeparse import parse +from typing_inspect import is_optional_type from flytekit import BlobType, FlyteContext, Literal, LiteralType, StructuredDataset from flytekit.core.artifact import ArtifactQuery @@ -572,7 +573,7 @@ def convert( if not self._is_remote: return value - if is_optional(self._python_type) and value == "None": + if is_optional_type(self._python_type) and isinstance(value, str) and value.lower() == "none": value = None lit = TypeEngine.to_literal(self._flyte_ctx, value, self._python_type, self._literal_type) return lit