Skip to content

Commit

Permalink
fix: make pydantic errors (un)pickable
Browse files Browse the repository at this point in the history
  • Loading branch information
PrettyWood committed Jun 13, 2020
1 parent 329b1d3 commit 81f58d7
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 0 deletions.
1 change: 1 addition & 0 deletions changes/1616-PrettyWood.md
@@ -0,0 +1 @@
make *pydantic* errors (un)pickable
14 changes: 14 additions & 0 deletions pydantic/errors.py
Expand Up @@ -91,6 +91,17 @@
)


def cls_kwargs(cls, kwargs): # type: ignore
"""
For built-in exceptions like ValueError or TypeError, we need to implement
__reduce__ to override the default behaviour (instead of __getstate__/__setstate__)
By default pickle protocol 2 calls `cls.__new__(cls, *args)`.
Since we only use kwargs, we need a little constructor to change that.
Note: the callable can't be a lambda as pickle looks in the namespace to find it
"""
return cls(**kwargs)


class PydanticErrorMixin:
code: str
msg_template: str
Expand All @@ -101,6 +112,9 @@ def __init__(self, **ctx: Any) -> None:
def __str__(self) -> str:
return self.msg_template.format(**self.__dict__)

def __reduce__(self): # type: ignore
return cls_kwargs, (self.__class__, self.__dict__)


class PydanticTypeError(PydanticErrorMixin, TypeError):
pass
Expand Down
13 changes: 13 additions & 0 deletions tests/test_errors.py
@@ -1,3 +1,4 @@
import pickle
import sys
from typing import Dict, List, Optional, Union
from uuid import UUID, uuid4
Expand All @@ -6,6 +7,7 @@

from pydantic import UUID1, BaseConfig, BaseModel, PydanticTypeError, ValidationError, conint, errors, validator
from pydantic.error_wrappers import flatten_errors, get_exc_type
from pydantic.errors import StrRegexError
from pydantic.typing import Literal


Expand All @@ -22,6 +24,17 @@ def __init__(self, *, test_ctx: int) -> None:
assert str(exc_info.value) == 'test message template "test_value"'


def test_pydantic_error_pickable():
"""
Pydantic errors should be (un)pickable.
(this test does not create a custom local error as we can't pickle local objects)
"""
p = pickle.dumps(StrRegexError(pattern='pika'))
error = pickle.loads(p)
assert isinstance(error, StrRegexError)
assert error.pattern == 'pika'


@pytest.mark.skipif(not Literal, reason='typing_extensions not installed')
def test_interval_validation_error():
class Foo(BaseModel):
Expand Down

0 comments on commit 81f58d7

Please sign in to comment.