From cd17624906db3c48d0a73afaa3ab92caf7d242b9 Mon Sep 17 00:00:00 2001 From: Blake Date: Wed, 11 Jun 2025 17:18:25 -0400 Subject: [PATCH 1/3] support passing optional date through CLI Signed-off-by: Blake --- flytekit/interaction/click_types.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/flytekit/interaction/click_types.py b/flytekit/interaction/click_types.py index 7918339da3..02b6b4a7a0 100644 --- a/flytekit/interaction/click_types.py +++ b/flytekit/interaction/click_types.py @@ -558,8 +558,17 @@ def convert( if isinstance(value, ArtifactQuery): return value try: + # Handle datetime.date conversion for union types (e.g., Optional[datetime.date]) + target_type = self._python_type + if hasattr(self._python_type, "__origin__") and self._python_type.__origin__ is typing.Union: + # For union types like Optional[datetime.date], extract the non-None type + union_args = typing.get_args(self._python_type) + non_none_types = [arg for arg in union_args if arg is not type(None)] + if len(non_none_types) == 1: + target_type = non_none_types[0] + # If the expected Python type is datetime.date, adjust the value to date - if self._python_type is datetime.date: + if target_type is datetime.date: # Click produces datetime, so converting to date to avoid type mismatch error value = value.date() # If the input matches the default value in the launch plan, serialization can be skipped. From b2cf45601ab6ddd0191d1d1b14e9ead66955e909 Mon Sep 17 00:00:00 2001 From: Blake Date: Thu, 12 Jun 2025 12:26:33 -0400 Subject: [PATCH 2/3] feedback from review Signed-off-by: Blake --- flytekit/interaction/click_types.py | 39 +++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/flytekit/interaction/click_types.py b/flytekit/interaction/click_types.py index 02b6b4a7a0..c03b75df05 100644 --- a/flytekit/interaction/click_types.py +++ b/flytekit/interaction/click_types.py @@ -264,6 +264,27 @@ def convert( return self._datetime_from_format(value, param, ctx) +class DateType(DateTimeType): + """ + A specialized type for handling date types that extends DateTimeType but returns date objects. + """ + + def convert( + self, value: typing.Any, param: typing.Optional[click.Parameter], ctx: typing.Optional[click.Context] + ) -> typing.Any: + if isinstance(value, ArtifactQuery): + return value + + # First convert using the parent DateTime logic + dt_value = super().convert(value, param, ctx) + + # Then convert to date if it's a datetime object + if isinstance(dt_value, datetime.datetime): + return dt_value.date() + + return dt_value + + class DurationParamType(click.ParamType): name = "[1:24 | :22 | 1 minute | 10 days | ...]" @@ -519,8 +540,13 @@ def literal_type_to_click_type(lt: LiteralType, python_type: typing.Type) -> cli for i in range(len(lt.union_type.variants)): variant = lt.union_type.variants[i] variant_python_type = typing.get_args(python_type)[i] - ct = literal_type_to_click_type(variant, variant_python_type) + # Special handling for datetime.date in union types + if variant_python_type is datetime.date: + ct = DateType() + else: + ct = literal_type_to_click_type(variant, variant_python_type) cts.append(ct) + return UnionParamType(cts) return click.UNPROCESSED @@ -558,17 +584,8 @@ def convert( if isinstance(value, ArtifactQuery): return value try: - # Handle datetime.date conversion for union types (e.g., Optional[datetime.date]) - target_type = self._python_type - if hasattr(self._python_type, "__origin__") and self._python_type.__origin__ is typing.Union: - # For union types like Optional[datetime.date], extract the non-None type - union_args = typing.get_args(self._python_type) - non_none_types = [arg for arg in union_args if arg is not type(None)] - if len(non_none_types) == 1: - target_type = non_none_types[0] - # If the expected Python type is datetime.date, adjust the value to date - if target_type is datetime.date: + if self._python_type is datetime.date and isinstance(value, datetime.datetime): # Click produces datetime, so converting to date to avoid type mismatch error value = value.date() # If the input matches the default value in the launch plan, serialization can be skipped. From 07981f1dcaf5e305fd1150621346e1f8ffa6a040 Mon Sep 17 00:00:00 2001 From: Blake Date: Mon, 4 Aug 2025 11:15:58 -0400 Subject: [PATCH 3/3] feedback from review Signed-off-by: Blake --- flytekit/interaction/click_types.py | 38 ++++++++--------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/flytekit/interaction/click_types.py b/flytekit/interaction/click_types.py index c03b75df05..84ec6f050a 100644 --- a/flytekit/interaction/click_types.py +++ b/flytekit/interaction/click_types.py @@ -8,6 +8,7 @@ import os import pathlib import sys +import types import typing import typing as t from typing import cast, get_args @@ -264,27 +265,6 @@ def convert( return self._datetime_from_format(value, param, ctx) -class DateType(DateTimeType): - """ - A specialized type for handling date types that extends DateTimeType but returns date objects. - """ - - def convert( - self, value: typing.Any, param: typing.Optional[click.Parameter], ctx: typing.Optional[click.Context] - ) -> typing.Any: - if isinstance(value, ArtifactQuery): - return value - - # First convert using the parent DateTime logic - dt_value = super().convert(value, param, ctx) - - # Then convert to date if it's a datetime object - if isinstance(dt_value, datetime.datetime): - return dt_value.date() - - return dt_value - - class DurationParamType(click.ParamType): name = "[1:24 | :22 | 1 minute | 10 days | ...]" @@ -540,13 +520,8 @@ def literal_type_to_click_type(lt: LiteralType, python_type: typing.Type) -> cli for i in range(len(lt.union_type.variants)): variant = lt.union_type.variants[i] variant_python_type = typing.get_args(python_type)[i] - # Special handling for datetime.date in union types - if variant_python_type is datetime.date: - ct = DateType() - else: - ct = literal_type_to_click_type(variant, variant_python_type) + ct = literal_type_to_click_type(variant, variant_python_type) cts.append(ct) - return UnionParamType(cts) return click.UNPROCESSED @@ -585,7 +560,14 @@ def convert( return value try: # If the expected Python type is datetime.date, adjust the value to date - if self._python_type is datetime.date and isinstance(value, datetime.datetime): + is_union = typing.get_origin(self._python_type) in ( + typing.Union, + types.UnionType, + ) + args = typing.get_args(self._python_type) + is_date_type = (is_union and datetime.date in args) or self._python_type is datetime.date + + if is_date_type and isinstance(value, datetime.datetime): # Click produces datetime, so converting to date to avoid type mismatch error value = value.date() # If the input matches the default value in the launch plan, serialization can be skipped.