Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Bug] SandboxWorkflowRunner doesn't use correct Pydantic field types in some cases #207

Closed
@michael-cybrid

Description

@michael-cybrid

What are you really trying to do?

I want to be able to pass in Pydantic models with datetime fields.

To encode datetimefields, I am using the pydantic.json.pydantic_encoder but can hit this behaviour without any data conversion occurring.

Describe the bug

The SandboxWorkflowRunner restrictions are resulting in Pydantic models being created with incorrect field types.

Specifically, I am seeing models derived from Pydantic's BaseModel class (let's say MyDatetimeWrapper) that contain a datetime field being instantiated with a date object instead when a MyDatetimeWrapper object is created within a workflow.

Minimal Reproduction

I've created a code sample to illustrate the issue:

import asyncio
import dataclasses
from datetime import datetime
from uuid import uuid4

from pydantic import BaseModel
from temporalio import workflow
from temporalio.client import Client
from temporalio.worker import Worker
from temporalio.worker.workflow_sandbox import SandboxRestrictions, SandboxedWorkflowRunner

class Message(BaseModel):
    content: datetime


@workflow.defn
class Workflow:

    @workflow.run
    async def run(self, context: str) -> None:
        message = Message(content=workflow.now())
        print(f"Message in workflow with {context}: {message} (`content` type is{type(message.content)})\n")


def default_worker(client: Client, task_queue: str) -> Worker:
    return Worker(client, workflows=[Workflow], activities=[], task_queue=task_queue)


def relaxed_worker(client: Client, task_queue: str) -> Worker:
    restrictions = dataclasses.replace(
        SandboxRestrictions.default,
        invalid_module_members=dataclasses.replace(
            SandboxRestrictions.invalid_module_members_default,
            children={
                k: v for k, v in SandboxRestrictions.invalid_module_members_default.children.items() if k != "datetime"
            }
        )
    )
    runner = SandboxedWorkflowRunner(restrictions=restrictions)
    return Worker(client, workflows=[Workflow], activities=[], task_queue=task_queue, workflow_runner=runner)


async def main():
    task_queue = str(uuid4())
    client = await Client.connect("temporal:7233", namespace="default-namespace")

    async with default_worker(client, task_queue):
        context = "default sandbox"
        message = Message(content=datetime.utcnow())
        print(f"Message in main with {context}: {message} (`content` type is{type(message.content)})\n")
        handle = await client.start_workflow(Workflow.run, context, id=str(uuid4()), task_queue=task_queue)
        await handle.result()


    async with relaxed_worker(client, task_queue):
        context = "relaxed sandbox"
        message = Message(content=datetime.utcnow())
        print(f"Message in main with {context}: {message} (`content` type is{type(message.content)})\n")
        handle = await client.start_workflow(Workflow.run, context, id=str(uuid4()), task_queue=task_queue)
        await handle.result()


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

The output from this snippet it:

Message in main with default sandbox: content=datetime.datetime(2022, 11, 18, 14, 13, 25, 326269) (`content` type is<class 'datetime.datetime'>)

Message in workflow with default sandbox: content=datetime.date(2022, 11, 18) (`content` type is<class 'datetime.date'>)

Message in main with relaxed sandbox: content=datetime.datetime(2022, 11, 18, 14, 13, 25, 360676) (`content` type is<class 'datetime.datetime'>)

Message in workflow with relaxed sandbox: content=datetime.datetime(2022, 11, 18, 14, 13, 25, 369645, tzinfo=datetime.timezone.utc) (`content` type is<class 'datetime.datetime'>)

As you can see, the default sandbox runner is resulting in content being converted to just a date despite being instantiated with a datetime.

Environment/Versions

  • OS and processor: M1 Pro Mac
  • Temporal Version: 1.8.4 and SDK version 0.1b3 (Pydantic 1.10.2)
  • Using Docker to run Temporal server
  • Python Version: Python 3.10.7

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