From 04a6c4e45c6ccc867e10fdbc299b50e968623e01 Mon Sep 17 00:00:00 2001 From: Samuel Lotz Date: Tue, 13 May 2025 17:33:46 -0400 Subject: [PATCH 1/3] fixes faulty dataclass type introspection The `dataclasses.fields` function returns type annotations as strings, which then is not handled properly during optional detection. By using the `typing.get_type_hints` function the actual `typing` objects are fetched. This function then correctly detects optional types. For instance when writing out a `FlyteFile` struct the `metadata` field should be optional, however it was not being detected as such. Signed-off-by: Samuel Lotz --- flytekit/core/type_engine.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/flytekit/core/type_engine.py b/flytekit/core/type_engine.py index 24a78f184b..bf6823ffc7 100644 --- a/flytekit/core/type_engine.py +++ b/flytekit/core/type_engine.py @@ -531,10 +531,7 @@ def assert_type(self, expected_type: Type[DataClassJsonMixin], v: T): # However, FooSchema is created by flytekit and it's not equal to the user-defined dataclass (Foo). # Therefore, we should iterate all attributes in the dataclass and check the type of value in dataclass matches the expected_type. - expected_fields_dict = {} - - for f in dataclasses.fields(expected_type): - expected_fields_dict[f.name] = f.type + expected_fields_dict = typing.get_type_hints(expected_type) if isinstance(v, dict): original_dict = v From ccd7de952e20cb35221d104d805f1d94dc87a17c Mon Sep 17 00:00:00 2001 From: Samuel Lotz Date: Fri, 16 May 2025 12:35:23 -0400 Subject: [PATCH 2/3] fixes handling of union types In the dataclasses conversion code type that is a member of a union was not properly checked for if it was a member and so there would always be an error. For instance `FlyteFile.path` is `Union[str,Pathlike]` and so `str != Union[str,Pathlike]`. This patch adds support for checking that a type is part of a union and a satisfactory type. Signed-off-by: Samuel Lotz --- flytekit/core/type_engine.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/flytekit/core/type_engine.py b/flytekit/core/type_engine.py index bf6823ffc7..e0dfe11439 100644 --- a/flytekit/core/type_engine.py +++ b/flytekit/core/type_engine.py @@ -13,6 +13,7 @@ import sys import textwrap import threading +import types import typing from abc import ABC, abstractmethod from collections import OrderedDict @@ -566,7 +567,13 @@ def assert_type(self, expected_type: Type[DataClassJsonMixin], v: T): original_type = type(v) if UnionTransformer.is_optional_type(expected_type): expected_type = UnionTransformer.get_sub_type_in_optional(expected_type) - if original_type != expected_type: + + if UnionTransformer.is_union(expected_type) and UnionTransformer.in_union( + original_type, expected_type + ): + pass + + elif original_type != expected_type: raise TypeTransformerFailedError( f"Type of Val '{original_type}' is not an instance of {expected_type}" ) @@ -1946,6 +1953,14 @@ class UnionTransformer(AsyncTypeTransformer[T]): def __init__(self): super().__init__("Typed Union", typing.Union) + @staticmethod + def is_union(t: Type[Any] | types.UnionType) -> bool: + return _is_union_type(t) + + @staticmethod + def in_union(t: Type[Any], union: types.UnionType) -> bool: + return t in typing.get_args(union) + @staticmethod def is_optional_type(t: Type) -> bool: return _is_union_type(t) and type(None) in get_args(t) From 7bdc03a35fb9a019783d8b697455b514ddb3cb80 Mon Sep 17 00:00:00 2001 From: Samuel Lotz Date: Sat, 10 Jan 2026 00:12:48 -0500 Subject: [PATCH 3/3] handle optional unions with None values in type_engine.py --- flytekit/core/type_engine.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flytekit/core/type_engine.py b/flytekit/core/type_engine.py index e0dfe11439..3b6ac4c88e 100644 --- a/flytekit/core/type_engine.py +++ b/flytekit/core/type_engine.py @@ -565,10 +565,14 @@ def assert_type(self, expected_type: Type[DataClassJsonMixin], v: T): else: expected_type = expected_fields_dict[k] original_type = type(v) + is_optional = False if UnionTransformer.is_optional_type(expected_type): + is_optional = True expected_type = UnionTransformer.get_sub_type_in_optional(expected_type) - if UnionTransformer.is_union(expected_type) and UnionTransformer.in_union( + if is_optional and original_type is type(None): + pass + elif UnionTransformer.is_union(expected_type) and UnionTransformer.in_union( original_type, expected_type ): pass