<@ULVA73B9P> I have a pydantic class that uses a g...
# ask-marvin
m
@Marvin I have a pydantic class that uses a generic and serialization fails:
Copy code
Task run 'google_download_file-c9c' - Encountered an error while serializing result for transaction 'ccda71d380b8bf741ee2e6fa41c9005a': Failed to serialize object of type 'APIResult[TypeVar]' with serializer 'json'
Is there a way to handle this?
m
thought for 243.4 seconds
n
can you show your type definition?
m
Copy code
class CustomErrorTypeModel(BaseModel):
    file: FileStoreResource
    error_message: str
    error_type: str

class APIResult[T](BaseModel):
    data: T | None = None
    error: CustomErrorTypeModel | None = None
m
Short answer: yes. This error happens because the JSON result serializer doesn’t play nicely with Pydantic generics that aren’t fully concrete/importable. You’ve got a few good options depending on whether you really need JSON. What’s happening - With Prefect 3.x, the JSON serializer tries to encode the class and then decode it later. For Pydantic generics, if the instance’s class looks like
APIResult[TypeVar]
(or even
APIResult[User]
as a dynamic generic alias), JSON encoding/decoding can fail. - Pickle handles these cases fine because it doesn’t need to reconstruct the class from an importable name. Ways to fix it 1) Switch to pickle (easiest) - This is the default in Prefect 3.x, so if you set
result_serializer="json"
somewhere, just remove it or set it to pickle explicitly.
Copy code
from prefect import task

@task(result_serializer="pickle")  # or omit; pickle is default in Prefect 3.x
def my_task():
    ...
- For large results, consider:
Copy code
@task(result_serializer="compressed/pickle")
2) Keep JSON, return a JSON-able dict - Dump the model to a dict so the serializer stores plain JSON data.
Copy code
from prefect import task

@task(result_serializer="json")
def my_task():
    result = APIResult<data=User(name="alice", age=30|User>)
    return result.model_dump(mode="json")  # or result.model_dump()
- If you prefer a JSON string:
Copy code
return result.model_dump_json()
3) Keep JSON, but use a named concrete subclass (importable) - Avoid returning a dynamic generic like
APIResult[User]
and instead define a concrete, top-level subclass so Prefect can re-import it on load.
Copy code
from pydantic import BaseModel
from typing import Generic, TypeVar

T = TypeVar("T")

class APIResult(BaseModel, Generic[T]):
    data: T
    status: str = "success"

class User(BaseModel):
    name: str
    age: int

# Define at module top-level so it's importable
class UserResult(APIResult[User]):
    pass

from prefect import task

@task(result_serializer="json")
def my_task() -> UserResult:
    return UserResult(data=User(name="alice", age=30))
4) Don’t persist results - If you don’t need result persistence, you can disable it:
Copy code
@task(persist_result=False)
def my_task():
    return APIResult<...|User>
Helpful docs - Results and serializers in Prefect 3.x: Prefect results and serializers A couple of clarifying questions so I can suggest the best fit: - Do you need JSON-persisted results for downstream systems, or is pickle acceptable? - Where is
result_serializer="json"
set (on the task/flow, or via
PREFECT_RESULTS_DEFAULT_SERIALIZER
)? - Are your models Pydantic v2 (Prefect 3.x assumes v2)?
m
@Marvin if the serialization fails will this impact caching?
m
thought for 140.4 seconds
Short answer: it can, but only for persistent cache. Your task will still run and return a result; the failure just prevents the cache from being written to storage. Details: - Task caching in Prefect 3.x has two layers: - In-memory cache (per run): does not require serialization. A serialization failure does not affect this. - Persistent cache (across runs): requires persisting the result, which triggers serialization. If serialization fails, Prefect logs a warning and skips committing the transaction; no persistent cache entry is written. Implications: - If you rely on cache across separate runs, a serialization failure means there won’t be a cache hit next time. - Within the same run, repeated calls can still use the in-memory cache (unless you’ve disabled it). If you want persistent caching to work with your generic Pydantic result, use one of: - Switch the task’s
result_serializer
to
pickle
(or
compressed/pickle
). - Keep JSON but return
model_dump()
instead of the model instance. - Use a concrete, importable subclass of your generic model. If you don’t need persistence, you can set `persist_result=False`; Prefect won’t attempt to serialize on commit, but the in-memory cache will still work during the run.
n