Description
What are you really trying to do?
I want to be able to pass in Pydantic models with datetime
fields.
To encode datetime
fields, 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