|
19 | 19 | from functools import lru_cache, reduce |
20 | 20 | from types import GenericAlias |
21 | 21 | from typing import Any, Dict, List, NamedTuple, Optional, Type, cast |
| 22 | +from typing import Literal as TypingLiteral |
22 | 23 |
|
23 | 24 | import msgpack |
24 | 25 | from dataclasses_json import DataClassJsonMixin, dataclass_json |
@@ -1087,6 +1088,85 @@ def assert_type(self, t: Type[enum.Enum], v: T): |
1087 | 1088 | raise TypeTransformerFailedError(f"Value {v} is not in Enum {t}") |
1088 | 1089 |
|
1089 | 1090 |
|
| 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 | + |
1090 | 1170 | def _handle_json_schema_property( |
1091 | 1171 | property_key: str, |
1092 | 1172 | property_val: dict, |
@@ -1173,6 +1253,7 @@ class TypeEngine(typing.Generic[T]): |
1173 | 1253 | _RESTRICTED_TYPES: typing.List[type] = [] |
1174 | 1254 | _DATACLASS_TRANSFORMER: TypeTransformer = DataclassTransformer() # type: ignore |
1175 | 1255 | _ENUM_TRANSFORMER: TypeTransformer = EnumTransformer() # type: ignore |
| 1256 | + _LITERAL_TYPE_TRANSFORMER: TypeTransformer = LiteralTypeTransformer() |
1176 | 1257 | lazy_import_lock = threading.Lock() |
1177 | 1258 |
|
1178 | 1259 | @classmethod |
@@ -1222,6 +1303,9 @@ def _get_transformer(cls, python_type: Type) -> Optional[TypeTransformer[T]]: |
1222 | 1303 | # Special case: prevent that for a type `FooEnum(str, Enum)`, the str transformer is used. |
1223 | 1304 | return cls._ENUM_TRANSFORMER |
1224 | 1305 |
|
| 1306 | + if get_origin(python_type) == TypingLiteral: |
| 1307 | + return cls._LITERAL_TYPE_TRANSFORMER |
| 1308 | + |
1225 | 1309 | if hasattr(python_type, "__origin__"): |
1226 | 1310 | # If the type is a generic type, we should check the origin type. But consider the case like Iterator[JSON] |
1227 | 1311 | # 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]: |
1253 | 1337 | """ |
1254 | 1338 | Implements a recursive search for the transformer. |
1255 | 1339 | """ |
| 1340 | + logger.warning(f"get_transformer: {python_type}") |
1256 | 1341 | v = cls._get_transformer(python_type) |
1257 | 1342 | if v is not None: |
1258 | 1343 | return v |
@@ -2606,6 +2691,7 @@ def _register_default_type_transformers(): |
2606 | 2691 | TypeEngine.register(BinaryIOTransformer()) |
2607 | 2692 | TypeEngine.register(EnumTransformer()) |
2608 | 2693 | TypeEngine.register(ProtobufTransformer()) |
| 2694 | + TypeEngine.register(LiteralTypeTransformer()) |
2609 | 2695 |
|
2610 | 2696 | # inner type is. Also unsupported are typing's Tuples. Even though you can look inside them, Flyte's type system |
2611 | 2697 | # doesn't support these currently. |
|
0 commit comments