-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update pydantic samples to use SDK contrib module (#163)
- Loading branch information
1 parent
81b5098
commit 3bd017d
Showing
16 changed files
with
1,787 additions
and
1,501 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Pydantic v1 Converter Sample | ||
|
||
**This sample shows how to use Pydantic v1 with Temporal. This is not recommended: use Pydantic v2 if possible, and use the | ||
main [pydantic_converter](../pydantic_converter/README.md) sample.** | ||
|
||
To install, run: | ||
|
||
poetry install --with pydantic_converter | ||
poetry run pip uninstall pydantic pydantic-core | ||
poetry run pip install pydantic==1.10 | ||
|
||
To run, first see the root [README.md](../README.md) for prerequisites. Then, run the following from this directory to start the | ||
worker: | ||
|
||
poetry run python worker.py | ||
|
||
This will start the worker. Then, in another terminal, run the following to execute the workflow: | ||
|
||
poetry run python starter.py | ||
|
||
In the worker terminal, the workflow and its activity will log that it received the Pydantic models. In the starter | ||
terminal, the Pydantic models in the workflow result will be logged. | ||
|
||
### Notes | ||
|
||
This sample also demonstrates use of `datetime` inside of Pydantic v1 models. Due to a known issue with the Temporal | ||
sandbox, this class is seen by Pydantic v1 as `date` instead of `datetime` upon deserialization. This is due to a | ||
[known Python issue](https://github.com/python/cpython/issues/89010) where, when we proxy the `datetime` class in the | ||
sandbox to prevent non-deterministic calls like `now()`, `issubclass` fails for the proxy type causing Pydantic v1 to think | ||
it's a `date` instead. In `worker.py`, we have shown a workaround of disabling restrictions on `datetime` which solves | ||
this issue but no longer protects against workflow developers making non-deterministic calls in that module. |
Empty file.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import asyncio | ||
import logging | ||
from datetime import datetime | ||
from ipaddress import IPv4Address | ||
|
||
from temporalio.client import Client | ||
|
||
from pydantic_converter_v1.converter import pydantic_data_converter | ||
from pydantic_converter_v1.worker import MyPydanticModel, MyWorkflow | ||
|
||
|
||
async def main(): | ||
logging.basicConfig(level=logging.INFO) | ||
# Connect client using the Pydantic converter | ||
client = await Client.connect( | ||
"localhost:7233", data_converter=pydantic_data_converter | ||
) | ||
|
||
# Run workflow | ||
result = await client.execute_workflow( | ||
MyWorkflow.run, | ||
[ | ||
MyPydanticModel( | ||
some_ip=IPv4Address("127.0.0.1"), | ||
some_date=datetime(2000, 1, 2, 3, 4, 5), | ||
), | ||
MyPydanticModel( | ||
some_ip=IPv4Address("127.0.0.2"), | ||
some_date=datetime(2001, 2, 3, 4, 5, 6), | ||
), | ||
], | ||
id="pydantic_converter-workflow-id", | ||
task_queue="pydantic_converter-task-queue", | ||
) | ||
logging.info("Got models from client: %s" % result) | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import asyncio | ||
import dataclasses | ||
import logging | ||
from datetime import datetime, timedelta | ||
from ipaddress import IPv4Address | ||
from typing import List | ||
|
||
from temporalio import activity, workflow | ||
from temporalio.client import Client | ||
from temporalio.worker import Worker | ||
from temporalio.worker.workflow_sandbox import ( | ||
SandboxedWorkflowRunner, | ||
SandboxRestrictions, | ||
) | ||
|
||
# We always want to pass through external modules to the sandbox that we know | ||
# are safe for workflow use | ||
with workflow.unsafe.imports_passed_through(): | ||
from pydantic import BaseModel | ||
|
||
from pydantic_converter_v1.converter import pydantic_data_converter | ||
|
||
|
||
class MyPydanticModel(BaseModel): | ||
some_ip: IPv4Address | ||
some_date: datetime | ||
|
||
|
||
@activity.defn | ||
async def my_activity(models: List[MyPydanticModel]) -> List[MyPydanticModel]: | ||
activity.logger.info("Got models in activity: %s" % models) | ||
return models | ||
|
||
|
||
@workflow.defn | ||
class MyWorkflow: | ||
@workflow.run | ||
async def run(self, models: List[MyPydanticModel]) -> List[MyPydanticModel]: | ||
workflow.logger.info("Got models in workflow: %s" % models) | ||
return await workflow.execute_activity( | ||
my_activity, models, start_to_close_timeout=timedelta(minutes=1) | ||
) | ||
|
||
|
||
# Due to known issues with Pydantic's use of issubclass and our inability to | ||
# override the check in sandbox, Pydantic will think datetime is actually date | ||
# in the sandbox. At the expense of protecting against datetime.now() use in | ||
# workflows, we're going to remove datetime module restrictions. See sdk-python | ||
# README's discussion of known sandbox issues for more details. | ||
def new_sandbox_runner() -> SandboxedWorkflowRunner: | ||
# TODO(cretz): Use with_child_unrestricted when https://github.com/temporalio/sdk-python/issues/254 | ||
# is fixed and released | ||
invalid_module_member_children = dict( | ||
SandboxRestrictions.invalid_module_members_default.children | ||
) | ||
del invalid_module_member_children["datetime"] | ||
return SandboxedWorkflowRunner( | ||
restrictions=dataclasses.replace( | ||
SandboxRestrictions.default, | ||
invalid_module_members=dataclasses.replace( | ||
SandboxRestrictions.invalid_module_members_default, | ||
children=invalid_module_member_children, | ||
), | ||
) | ||
) | ||
|
||
|
||
interrupt_event = asyncio.Event() | ||
|
||
|
||
async def main(): | ||
logging.basicConfig(level=logging.INFO) | ||
# Connect client using the Pydantic converter | ||
client = await Client.connect( | ||
"localhost:7233", data_converter=pydantic_data_converter | ||
) | ||
|
||
# Run a worker for the workflow | ||
async with Worker( | ||
client, | ||
task_queue="pydantic_converter-task-queue", | ||
workflows=[MyWorkflow], | ||
activities=[my_activity], | ||
workflow_runner=new_sandbox_runner(), | ||
): | ||
# Wait until interrupted | ||
print("Worker started, ctrl+c to exit") | ||
await interrupt_event.wait() | ||
print("Shutting down") | ||
|
||
|
||
if __name__ == "__main__": | ||
loop = asyncio.new_event_loop() | ||
try: | ||
loop.run_until_complete(main()) | ||
except KeyboardInterrupt: | ||
interrupt_event.set() | ||
loop.run_until_complete(loop.shutdown_asyncgens()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.