Skip to content

[Bug] Generic Nested Dataclasses not desrialized correctly #234

@ferntheplant

Description

@ferntheplant

What are you really trying to do?

Create activities corresponding to hitting different API endpoints. The endpoint responses are paginated and I want a separate iterator that controls pagination on behalf of the endpoint caller. So each activity would accept the params for the endpoint provided by the user + a cursor provided by the iterator. So an activity might look like:

P = TypeVar("P")
@dataclass
class ApiParams(Generic[P]):
    args: P
    cursor: str

@dataclass 
class EndpointFooParams:
    foo: str
    bar: str

@activity.defn
async def hit_endpoint_foo(param: ApiParams[EndpointFooParams]) -> list:
    args = params.args
    cursor = params.cursor
    response = await api.foo(args.foo, args.bar, cursor)
    return response.data

Describe the bug

When running an activity defined like above I get a runtime error

    a = param.args
    ^^^^^^^^^^^
AttributeError: 'dict' object has no attribute 'args'

This indicates that the activity params dataclass was deserialized into a plain dictionary instead of a proper dataclass.

Minimal Reproduction

import asyncio

from dataclasses import dataclass
from typing import Generic, TypeVar
from datetime import timedelta

from temporalio import activity, workflow
from temporalio.client import Client
from temporalio.worker import Worker

P = TypeVar("P")

@dataclass
class A:
    foo: str

@dataclass
class B(Generic[P]):
    bar: str
    nested_a: P

@activity.defn
async def demo_activity(param: B[A]) -> str:
    a = param.nested_a
    return a.foo + param.bar

@workflow.defn()
class BasicWorkflow:
    @workflow.run
    async def run(self, foo: str) -> str:
        return await workflow.execute_activity(
            demo_activity,
            B(bar=foo, nested_a=A(foo=foo)),
            start_to_close_timeout=timedelta(seconds=10)
        )

async def main():
    client = await Client.connect("localhost:7233")

    async with Worker(
        client,
        task_queue="basic",
        workflows=[BasicWorkflow],
        activities=[demo_activity],
    ):
        await client.execute_workflow(
            BasicWorkflow.run,
            "hello",
            id="basic",
            task_queue="basic",
        )


if __name__ == "__main__":
    asyncio.run(main())

Environment/Versions

  • OS and processor: M1 Mac running macOS 12.5.1
  • Temporal Version: python sdk 0.1b4
  • Are you using Docker or Kubernetes or building Temporal from source? Docker compose

Additional context

As per slack discussion resolving dataclass generics for deserialization is not expected of temporal data converters. Resolution is to update readme to warn future users

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions