How to Create a Custom Type Transformer
Flyte uses a sophisticated type system to bridge the gap between Python's dynamic typing and the strongly-typed Flyte IDL. When you need to support a custom Python type that isn't natively handled by flyte-sdk, you must implement a TypeTransformer and register it with the TypeEngine.
Implement a Simple Transformer
For basic types that map directly to Flyte primitives (like int, str, or bool), use the SimpleTransformer class. This avoids the boilerplate of a full class implementation by using lambdas for conversion.
The following example shows how flyte-sdk implements the IntTransformer in types/_type_engine.py:
from flyte.models import types as types_pb2
from flyte.models.literals import Literal, Scalar, Primitive
from flyte.types._type_engine import SimpleTransformer, TypeEngine
# 1. Define the transformer
IntTransformer = SimpleTransformer(
"int",
int,
types_pb2.LiteralType(simple=types_pb2.SimpleType.INTEGER),
to_literal_transformer=lambda x: Literal(scalar=Scalar(primitive=Primitive(integer=x))),
from_literal_transformer=lambda x: x.scalar.primitive.integer,
)
# 2. Register it with the TypeEngine
TypeEngine.register(IntTransformer)
Implement a Custom Type Transformer
For complex objects, inherit from TypeTransformer[T]. You must implement three core methods:
get_literal_type: Defines how the Python type maps to a FlyteLiteralType.to_literal: Converts a Python object instance into a FlyteLiteral.to_python_value: Converts a FlyteLiteralback into a Python object.
Consider a custom MyUser class that you want to pass between tasks:
This example could not be verified against this version of the codebase and may not work as shown. Validator finding: 'MyUser' has no member 'name' 'MyUser' has no member 'age'
import typing
import json
from flyte.models import types as types_pb2
from flyte.models.literals import Literal, Scalar, Binary
from flyte.types._type_engine import TypeTransformer, TypeEngine
class MyUser:
name: str
age: int
def __init__(self, name: str, age: int):
self.name = name
self.age = age
class MyUserTransformer(TypeTransformer[MyUser]):
def __init__(self):
super().__init__(name="MyUserTransformer", t=MyUser)
def get_literal_type(self, t: typing.Type[MyUser]) -> types_pb2.LiteralType:
# Map this to a binary type for custom serialization
return types_pb2.LiteralType(simple=types_pb2.SimpleType.BINARY, tag="my-user-json")
async def to_literal(self, python_val: MyUser, python_type: typing.Type[MyUser], expected: types_pb2.LiteralType) -> Literal:
# Serialize the object to bytes
data = json.dumps({"name": python_val.name, "age": python_val.age}).encode("utf-8")
return Literal(scalar=Scalar(binary=Binary(value=data, tag="my-user-json")))
async def to_python_value(self, lv: Literal, expected_python_type: typing.Type[MyUser]) -> MyUser:
# Deserialize bytes back to the Python object
data = json.loads(lv.scalar.binary.value.decode("utf-8"))
return MyUser(name=data["name"], age=data["age"])
def guess_python_type(self, literal_type: types_pb2.LiteralType) -> typing.Type[MyUser]:
if literal_type.simple == types_pb2.SimpleType.BINARY and literal_type.tag == "my-user-json":
return MyUser
raise ValueError("Cannot reverse literal type")
# Register the transformer
TypeEngine.register(MyUserTransformer())
Registering Additional Types
If a single transformer can handle multiple related Python types (e.g., a base class and its subclasses), use the additional_types parameter during registration. The TypeEngine uses these to populate its internal _REGISTRY.
# Registering a transformer for a type and its alias
TypeEngine.register(MyUserTransformer(), additional_types=[typing.NewType("SpecialUser", MyUser)])
Handling Dataframe Types
If you are implementing support for a new dataframe library, do not register directly with TypeEngine. Instead, flyte-sdk uses a specialized DataFrameTransformerEngine found in io/_dataframe/dataframe.py. You should register your handler with that engine to ensure it integrates with Flyte's structured dataset features.
Troubleshooting Type Mismatches
The TypeEngine performs validation during conversion. If type_assertions_enabled is True (the default in TypeTransformer), the engine calls assert_type before to_literal.
If you encounter a TypeTransformerFailedError, verify:
- The
python_typepassed toto_literalmatches the type your transformer is registered for. - Your
guess_python_typeimplementation correctly identifies theLiteralTypetags you defined inget_literal_type. - For
SimpleTransformer, ensure the input value exactly matches the type specified in the constructor, as it performs a stricttype(python_val) is not self._typecheck.