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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
michael-cybrid opened this issue Nov 18, 2022 · 13 comments
Labels
bug Something isn't working

Comments

@michael-cybrid
Copy link

michael-cybrid commented Nov 18, 2022

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
@michael-cybrid michael-cybrid added the bug Something isn't working label Nov 18, 2022
@michael-cybrid
Copy link
Author

Sorry, some additional context I forgot to mention.

If you install Pydantic with source only (i.e. without Cython binary), you get an error on startup:

Traceback (most recent call last):
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/_workflow.py", line 113, in __init__
    workflow_runner.prepare_workflow(defn)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_runner.py", line 53, in prepare_workflow
    self.create_instance(
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_runner.py", line 87, in create_instance
    return _Instance(det, self._runner_class, self._restrictions)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_runner.py", line 107, in __init__
    self._create_instance()
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_runner.py", line 118, in _create_instance
    self._run_code(
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_runner.py", line 160, in _run_code
    exec(code, self.globals_and_locals, self.globals_and_locals)
  File "<string>", line 2, in <module>
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 398, in __call__
    return self.current(*args, **kwargs)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 199, in _import
    new_spec.loader.exec_module(new_mod)
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/user/Development/temporal/worker-hang/main.py", line 6, in <module>
    from pydantic import BaseModel
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 398, in __call__
    return self.current(*args, **kwargs)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 201, in _import
    mod = importlib.__import__(name, globals, locals, fromlist, level)
  File "<frozen importlib._bootstrap>", line 1129, in __import__
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/pydantic/__init__.py", line 2, in <module>
    from . import dataclasses
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 398, in __call__
    return self.current(*args, **kwargs)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 201, in _import
    mod = importlib.__import__(name, globals, locals, fromlist, level)
  File "<frozen importlib._bootstrap>", line 1149, in __import__
  File "<frozen importlib._bootstrap>", line 1078, in _handle_fromlist
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/pydantic/dataclasses.py", line 56, in <module>
    from .error_wrappers import ValidationError
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 398, in __call__
    return self.current(*args, **kwargs)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 201, in _import
    mod = importlib.__import__(name, globals, locals, fromlist, level)
  File "<frozen importlib._bootstrap>", line 1133, in __import__
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/pydantic/error_wrappers.py", line 4, in <module>
    from .json import pydantic_encoder
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 398, in __call__
    return self.current(*args, **kwargs)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 201, in _import
    mod = importlib.__import__(name, globals, locals, fromlist, level)
  File "<frozen importlib._bootstrap>", line 1133, in __import__
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/pydantic/json.py", line 14, in <module>
    from .types import SecretBytes, SecretStr
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 398, in __call__
    return self.current(*args, **kwargs)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/workflow_sandbox/_importer.py", line 201, in _import
    mod = importlib.__import__(name, globals, locals, fromlist, level)
  File "<frozen importlib._bootstrap>", line 1133, in __import__
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/pydantic/types.py", line 764, in <module>
    class FilePath(Path):
TypeError: _RestrictedProxy.__init__() missing 1 required positional argument: 'matcher'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/user/Development/temporal/worker-hang/main.py", line 64, in <module>
    asyncio.run(main())
  File "/opt/homebrew/Cellar/[email protected]/3.10.7/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/opt/homebrew/Cellar/[email protected]/3.10.7/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "/Users/user/Development/temporal/worker-hang/main.py", line 47, in main
    async with default_worker(client, task_queue):
  File "/Users/user/Development/temporal/worker-hang/main.py", line 26, in default_worker
    return Worker(client, workflows=[Workflow], activities=[], task_queue=task_queue)
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/_worker.py", line 261, in __init__
    self._workflow_worker = _WorkflowWorker(
  File "/Users/user/.local/share/virtualenvs/worker-hang-grTap8Tq/lib/python3.10/site-packages/temporalio/worker/_workflow.py", line 117, in __init__
    raise RuntimeError(f"Failed validating workflow {defn.name}") from err
RuntimeError: Failed validating workflow Workflow

@cretz
Copy link
Member

cretz commented Nov 18, 2022

I am using the pydantic.json.pydantic_encoder

I do not see where this is used in the reproduction you provided. I don't see any custom data conversion. Can you update the sample code? I will also test it to see why it's choosing one type and not the other. There is a fix we just made in #202 that may have affected type hints on either side of the sandbox.

By default we use Pydantic's dict() which doesn't always return a JSONable dict. Date time is not supported by our JSON encoder natively. We have to cross languages and date time is not a supported type in our other SDKs.

There is discussion of this at #143. But basically Pydantic doesn't provide a way to get a JSON-able dict but they've been actively talking about it lately. It provides a way to get JSON string (which we'd have to convert back at a perf loss) and a way to get a dict Pydantic can convert to JSON (but is not cross-language compatible for Temporal benefit).

I am planning on writing a sample at temporalio/samples-python#25 that will show how to use full Pydantic JSON conversion.

If you install Pydantic with source only (i.e. without Cython binary), you get an error on startup:

This is a known issue at https://github.com/temporalio/sdk-python#extending-restricted-classes and something we hope to fix. In the meantime you can mark pydantic as a pass-through module (we plan on making this easier too).

Also, feel free to join us in #python-sdk at https://temporal.io/slack or https://community.temporal.io too.

@cretz
Copy link
Member

cretz commented Nov 18, 2022

I have replicated with your code and am looking in detail

@michael-cybrid
Copy link
Author

michael-cybrid commented Nov 18, 2022

Thanks for the speedy reply! I was in the middle of typing out a response but see you are looking in detail. Thanks very much.

For context, I was hesitant to mention I am using the pydantic.json.pydantic_encoder because it is just context for how I came across this issue but you can see in the sample code, this behaviour is hit without any conversion. I am happy to show how I've got that bit working separately.

I just tried adding pydantic to the passthrough_modules modules list but it still results in the same erroneous behaviour.

Update sample here:

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, SandboxMatcher

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)


def passthrough_worker(client: Client, task_queue: str) -> Worker:
    restrictions = dataclasses.replace(
        SandboxRestrictions.default,
        passthrough_modules=SandboxRestrictions.passthrough_modules_default | SandboxMatcher(access={"pydantic"}),
    )
    runner = SandboxedWorkflowRunner(restrictions=restrictions)
    return Worker(client, workflows=[Workflow], activities=[], task_queue=task_queue, workflow_runner=runner)


async def run_the_bug(client: Client, task_queue: str, context: str) -> None:
    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 def main():
    task_queue = str(uuid4())
    client = await Client.connect("temporal:7233", namespace="default-namespace")

    async with default_worker(client, task_queue):
        await run_the_bug(client, task_queue, "default sandbox")

    async with relaxed_worker(client, task_queue):
        await run_the_bug(client, task_queue, "relaxed sandbox")

    async with passthrough_worker(client, task_queue):
        await run_the_bug(client, task_queue, "passthrough sandbox")


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

@cretz
Copy link
Member

cretz commented Nov 18, 2022

Here's my small reproduction:

import asyncio
from datetime import datetime
from uuid import uuid4

from pydantic import BaseModel
from temporalio import workflow
from temporalio.exceptions import ApplicationError
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import Worker


class Message(BaseModel):
    content: datetime


@workflow.defn
class Workflow:
    @workflow.run
    async def run(self) -> None:
        message = Message(content=workflow.now())
        if not isinstance(message.content, datetime):
            raise ApplicationError(f"was type {type(message.content)}")


async def main():
    async with await WorkflowEnvironment.start_local() as env:
        task_queue = str(uuid4())
        async with Worker(env.client, workflows=[Workflow], task_queue=task_queue):
            await env.client.execute_workflow(
                Workflow.run, id=str(uuid4()), task_queue=task_queue
            )


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

Debugging now...

@cretz
Copy link
Member

cretz commented Nov 18, 2022

I have hunted this issue down. This is caused by https://github.com/temporalio/sdk-python#is_subclass-of-abc-based-restricted-classes:

Due to https://bugs.python.org/issue44847, classes that are wrapped and then checked to see if they are subclasses of another via is_subclass may fail (see also GrahamDumpleton/wrapt#130).

Also see python/cpython#89010

Basically at https://github.com/pydantic/pydantic/blob/bc74342b39e2b1d0115364cf2aa90d8f874cb9b5/pydantic/validators.py#L751, pydantic is checking is_subclass to determine whether it's datetime before falling through to date. This call fails for proxied objects. I'll look into workarounds, but in the meantime you'd have to remove datetime from being a restricted.

It might look something like (untested):

my_restrictions = dataclasses.replace(
    SandboxRestrictions.default,
    invalid_module_members=SandboxMatcher(
        children={k: v for SandboxRestrictions.invalid_module_members_default.children.items() if k != "datetime"}
    ),
)
my_worker = Worker(..., workflow_runner=SandboxedWorkflowRunner(restrictions=my_restrictions))

But this removes our protections against calling something like datetime.now() which is forbidden in workflows, so be careful.

@michael-cybrid
Copy link
Author

Thanks, @cretz, for chasing this down.

cretz added a commit to cretz/temporal-sdk-python that referenced this issue Dec 2, 2022
@cretz
Copy link
Member

cretz commented Dec 2, 2022

While a fix such as patching issubclass would fix this for non-binary Pydantic, installing the binary form bypasses any stdlib patches (and no I cannot use __subclasscheck__ on my proxy type, because it is datetime.__subclasscheck__ that is being called).

I still can't figure out how best to solve this. Researching...

@cretz cretz changed the title [Bug] SandboxWorkflowRunner changes Pydantic datetime field [Bug] SandboxWorkflowRunner doesn't use correct Pydantic field types in some cases Dec 2, 2022
@cretz
Copy link
Member

cretz commented Dec 2, 2022

For now, I have just marked this a known issue in the README in #219. If this becomes enough of an issue, we'll have to consider patching datetime.__subclasscheck__.

@iAnanich
Copy link

Copilot brought me here...

My error looks slightly different:
RuntimeError: Restriction state not present. Using subclasses of proxied objects is unsupported.
is a cause of
RuntimeError: Failed validating workflow VerifyFaceWorkflowV1

... and then discovered that python-samples contains data converter for Pydantic - that solved the issue for me.

Perhaps this issue could be closed?..

@cretz
Copy link
Member

cretz commented Jan 29, 2024

πŸ‘ Will close.

For others arriving here, this is documented in the README and the supported way to use Pydantic is via a custom payload converter as shown in samples.

@cretz cretz closed this as completed Jan 29, 2024
@jackklika
Copy link

jackklika commented Mar 29, 2024

For future people investigating problems related to this: We ran into an error related to this and disabling sandboxing on the worker or passing through the datetime and pydantic (v1) imports here ultimately fixed it.

Disabling sandboxing on one workflow via the sandboxed=False did not work and we are unclear if this was because of a sandboxed import occurring before importing into that non-sandboxed workflow, a top-level import on the file overriding something imported locally in the activity function, or something else. We need to look more into this ourselves.

Either way, the guidance on the Readme added as a result of this issue is accurate.

Below is the error we got on importing a BaseModel in an activity. The error was specifically around a pydantic Field with a default_factory which instantiated a datetime.now(). I'm reposting it to aid anyone googling in the future and direct them to this issue.

  File "/usr/local/lib/python3.12/site-packages/temporalio/worker/_activity.py", line 438, in _run_activity
    result = await impl.execute_activity(input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/site-packages/temporalio/worker/_activity.py", line 704, in execute_activity
    return await input.fn(*input.args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^

   [...]

  # This call is when we import the pydantic BaseModel which is ultimately calling ModelMetaclass.__new__ here
  File "/usr/local/lib/python3.12/site-packages/pydantic/main.py", line 138, in __new__
    fields.update(smart_deepcopy(base.__fields__))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/site-packages/pydantic/utils.py", line 693, in smart_deepcopy
    return deepcopy(obj)  # slowest way when we actually might need a deepcopy
           ^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 136, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 221, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 162, in deepcopy
    y = _reconstruct(x, memo, *rv)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 259, in _reconstruct
    state = deepcopy(state, memo)
            ^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 136, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 201, in _deepcopy_tuple
    y = [deepcopy(a, memo) for a in x]
         ^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 136, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 221, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.12/copy.py", line 151, in deepcopy
    rv = reductor(4)
         ^^^^^^^^^^^

@spacether
Copy link

Could this be fixed by moving the issubclass for datetime check before the issubclass date check? What is the actual offending line that is causing this to happen?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants