Skip to content

Commit 2630515

Browse files
Barry WuBarryWu0812
authored andcommitted
[Core feature] Add support Literal Transformer
Signed-off-by: Barry Wu <a0987818905@gmail.com>
1 parent eb5a67f commit 2630515

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

flytekit/core/type_engine.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from functools import lru_cache, reduce
2020
from types import GenericAlias
2121
from typing import Any, Dict, List, NamedTuple, Optional, Type, cast
22+
from typing import Literal as TypingLiteral
2223

2324
import msgpack
2425
from dataclasses_json import DataClassJsonMixin, dataclass_json
@@ -1087,6 +1088,85 @@ def assert_type(self, t: Type[enum.Enum], v: T):
10871088
raise TypeTransformerFailedError(f"Value {v} is not in Enum {t}")
10881089

10891090

1091+
class LiteralTypeTransformer(TypeTransformer[TypingLiteral]):
1092+
def __init__(self):
1093+
super().__init__("LiteralTypeTransformer", TypingLiteral)
1094+
1095+
def get_literal_type(self, t: Type) -> LiteralType:
1096+
args = get_args(t)
1097+
if not args:
1098+
raise TypeTransformerFailedError("Literal must have at least one value")
1099+
1100+
base_type = type(args[0])
1101+
if not all(type(a) == base_type for a in args):
1102+
raise TypeTransformerFailedError("All values must be of the same type")
1103+
1104+
if base_type == str:
1105+
return LiteralType(simple=SimpleType.STRING)
1106+
elif base_type == int:
1107+
return LiteralType(simple=SimpleType.INTEGER)
1108+
elif base_type == float:
1109+
return LiteralType(simple=SimpleType.FLOAT)
1110+
elif base_type == bool:
1111+
return LiteralType(simple=SimpleType.BOOLEAN)
1112+
elif base_type == datetime.datetime:
1113+
return LiteralType(simple=SimpleType.DATETIME)
1114+
elif base_type == datetime.timedelta:
1115+
return LiteralType(simple=SimpleType.DURATION)
1116+
else:
1117+
raise TypeTransformerFailedError(f"Unsupported Literal base type: {base_type}")
1118+
1119+
def to_literal(self, ctx: FlyteContext, python_val: T, python_type: Type[T], expected: LiteralType) -> Literal:
1120+
if expected.simple == SimpleType.STRING:
1121+
return Literal(scalar=Scalar(primitive=Primitive(string_value=python_val)))
1122+
elif expected.simple == SimpleType.INTEGER:
1123+
return Literal(scalar=Scalar(primitive=Primitive(integer=python_val)))
1124+
elif expected.simple == SimpleType.FLOAT:
1125+
return Literal(scalar=Scalar(primitive=Primitive(float_value=python_val)))
1126+
elif expected.simple == SimpleType.BOOLEAN:
1127+
return Literal(scalar=Scalar(primitive=Primitive(boolean=python_val)))
1128+
elif expected.simple == SimpleType.DATETIME:
1129+
return Literal(scalar=Scalar(primitive=Primitive(datetime=python_val)))
1130+
elif expected.simple == SimpleType.DURATION:
1131+
return Literal(scalar=Scalar(primitive=Primitive(duration=python_val)))
1132+
else:
1133+
raise TypeError(f"Unsupported LiteralType for LiteralTypeTransformer: {expected.simple}")
1134+
1135+
def to_python_value(self, ctx: FlyteContext, lv: Literal, expected_python_type: Type[T]) -> T:
1136+
if lv.scalar and lv.scalar.binary:
1137+
return self.from_binary_idl(lv.scalar.binary, expected_python_type) # type: ignore
1138+
if lv.scalar.primitive.string_value is not None:
1139+
return lv.scalar.primitive.string_value
1140+
elif lv.scalar.primitive.integer is not None:
1141+
return lv.scalar.primitive.integer
1142+
elif lv.scalar.primitive.float_value is not None:
1143+
return lv.scalar.primitive.float_value
1144+
elif lv.scalar.primitive.boolean is not None:
1145+
return lv.scalar.primitive.boolean
1146+
elif lv.scalar.primitive.datetime is not None:
1147+
return lv.scalar.primitive.datetime
1148+
elif lv.scalar.primitive.duration is not None:
1149+
return lv.scalar.primitive.duration
1150+
else:
1151+
raise TypeTransformerFailedError("Unsupported Literal value")
1152+
1153+
def guess_python_type(self, literal_type: LiteralType):
1154+
if literal_type.simple == SimpleType.STRING:
1155+
return str
1156+
elif literal_type.simple == SimpleType.INTEGER:
1157+
return int
1158+
elif literal_type.simple == SimpleType.FLOAT:
1159+
return float
1160+
elif literal_type.simple == SimpleType.BOOLEAN:
1161+
return bool
1162+
elif literal_type.simple == SimpleType.DATETIME:
1163+
return datetime.datetime
1164+
elif literal_type.simple == SimpleType.DURATION:
1165+
return datetime.timedelta
1166+
else:
1167+
raise TypeTransformerFailedError(f"LiteralTypeTransformer cannot reverse {literal_type}")
1168+
1169+
10901170
def _handle_json_schema_property(
10911171
property_key: str,
10921172
property_val: dict,
@@ -1173,6 +1253,7 @@ class TypeEngine(typing.Generic[T]):
11731253
_RESTRICTED_TYPES: typing.List[type] = []
11741254
_DATACLASS_TRANSFORMER: TypeTransformer = DataclassTransformer() # type: ignore
11751255
_ENUM_TRANSFORMER: TypeTransformer = EnumTransformer() # type: ignore
1256+
_LITERAL_TYPE_TRANSFORMER: TypeTransformer = LiteralTypeTransformer()
11761257
lazy_import_lock = threading.Lock()
11771258

11781259
@classmethod
@@ -1222,6 +1303,9 @@ def _get_transformer(cls, python_type: Type) -> Optional[TypeTransformer[T]]:
12221303
# Special case: prevent that for a type `FooEnum(str, Enum)`, the str transformer is used.
12231304
return cls._ENUM_TRANSFORMER
12241305

1306+
if get_origin(python_type) == TypingLiteral:
1307+
return cls._LITERAL_TYPE_TRANSFORMER
1308+
12251309
if hasattr(python_type, "__origin__"):
12261310
# If the type is a generic type, we should check the origin type. But consider the case like Iterator[JSON]
12271311
# or List[int] has been specifically registered; we should check for the entire type.
@@ -1253,6 +1337,7 @@ def get_transformer(cls, python_type: Type) -> TypeTransformer[T]:
12531337
"""
12541338
Implements a recursive search for the transformer.
12551339
"""
1340+
logger.warning(f"get_transformer: {python_type}")
12561341
v = cls._get_transformer(python_type)
12571342
if v is not None:
12581343
return v
@@ -2606,6 +2691,7 @@ def _register_default_type_transformers():
26062691
TypeEngine.register(BinaryIOTransformer())
26072692
TypeEngine.register(EnumTransformer())
26082693
TypeEngine.register(ProtobufTransformer())
2694+
TypeEngine.register(LiteralTypeTransformer())
26092695

26102696
# inner type is. Also unsupported are typing's Tuples. Even though you can look inside them, Flyte's type system
26112697
# doesn't support these currently.

0 commit comments

Comments
 (0)