mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
* add smart_deepcopy * uncomment tuple in BUILTIN_COLLECTIONS, fix doc a bit * Fix grammar Co-authored-by: PrettyWood <em.jolibois@gmail.com> * replace map() usage with generator comprehension, fix comment Co-authored-by: PrettyWood <em.jolibois@gmail.com>
This commit is contained in:
+2
-10
@@ -1,6 +1,5 @@
|
||||
import warnings
|
||||
from collections.abc import Iterable as CollectionsIterable
|
||||
from copy import deepcopy
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
@@ -36,7 +35,7 @@ from .typing import (
|
||||
is_new_type,
|
||||
new_type_supertype,
|
||||
)
|
||||
from .utils import PyObjectStr, Representation, lenient_issubclass, sequence_like
|
||||
from .utils import PyObjectStr, Representation, lenient_issubclass, sequence_like, smart_deepcopy
|
||||
from .validators import constant_validator, dict_validator, find_validators, validate_json
|
||||
|
||||
Required: Any = Ellipsis
|
||||
@@ -271,14 +270,7 @@ class ModelField(Representation):
|
||||
self.prepare()
|
||||
|
||||
def get_default(self) -> Any:
|
||||
if self.default_factory is not None:
|
||||
value = self.default_factory()
|
||||
elif self.default is None:
|
||||
# deepcopy is quite slow on None
|
||||
value = None
|
||||
else:
|
||||
value = deepcopy(self.default)
|
||||
return value
|
||||
return smart_deepcopy(self.default) if self.default_factory is None else self.default_factory()
|
||||
|
||||
@classmethod
|
||||
def infer(
|
||||
|
||||
+4
-2
@@ -42,6 +42,7 @@ from .utils import (
|
||||
generate_model_signature,
|
||||
lenient_issubclass,
|
||||
sequence_like,
|
||||
smart_deepcopy,
|
||||
unique_list,
|
||||
validate_field_name,
|
||||
)
|
||||
@@ -219,7 +220,7 @@ class ModelMetaclass(ABCMeta):
|
||||
pre_root_validators, post_root_validators = [], []
|
||||
for base in reversed(bases):
|
||||
if _is_base_model_class_defined and issubclass(base, BaseModel) and base != BaseModel:
|
||||
fields.update(deepcopy(base.__fields__))
|
||||
fields.update(smart_deepcopy(base.__fields__))
|
||||
config = inherit_config(base.__config__, config)
|
||||
validators = inherit_validators(base.__validators__, validators)
|
||||
pre_root_validators += base.__pre_root_validators__
|
||||
@@ -527,7 +528,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
|
||||
Default values are respected, but no other validation is performed.
|
||||
"""
|
||||
m = cls.__new__(cls)
|
||||
object.__setattr__(m, '__dict__', {**deepcopy(cls.__field_defaults__), **values})
|
||||
object.__setattr__(m, '__dict__', {**smart_deepcopy(cls.__field_defaults__), **values})
|
||||
if _fields_set is None:
|
||||
_fields_set = set(values.keys())
|
||||
object.__setattr__(m, '__fields_set__', _fields_set)
|
||||
@@ -558,6 +559,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
|
||||
)
|
||||
|
||||
if deep:
|
||||
# chances of having empty dict here are quite low for using smart_deepcopy
|
||||
v = deepcopy(v)
|
||||
|
||||
cls = self.__class__
|
||||
|
||||
+59
-2
@@ -1,6 +1,9 @@
|
||||
import warnings
|
||||
import weakref
|
||||
from collections import OrderedDict, defaultdict, deque
|
||||
from copy import deepcopy
|
||||
from itertools import islice
|
||||
from types import GeneratorType
|
||||
from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
AbstractSet,
|
||||
@@ -20,7 +23,7 @@ from typing import (
|
||||
no_type_check,
|
||||
)
|
||||
|
||||
from .typing import display_as_type
|
||||
from .typing import NoneType, display_as_type
|
||||
from .version import version_info
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -50,6 +53,41 @@ __all__ = (
|
||||
'ClassAttribute',
|
||||
)
|
||||
|
||||
# these are types that are returned unchanged by deepcopy
|
||||
IMMUTABLE_NON_COLLECTIONS_TYPES: Set[Type[Any]] = {
|
||||
int,
|
||||
float,
|
||||
complex,
|
||||
str,
|
||||
bool,
|
||||
bytes,
|
||||
type,
|
||||
NoneType,
|
||||
FunctionType,
|
||||
BuiltinFunctionType,
|
||||
LambdaType,
|
||||
weakref.ref,
|
||||
CodeType,
|
||||
# note: including ModuleType will differ from behaviour of deepcopy by not producing error.
|
||||
# It might be not a good idea in general, but considering that this function used only internally
|
||||
# against default values of fields, this will allow to actually have a field with module as default value
|
||||
ModuleType,
|
||||
NotImplemented.__class__,
|
||||
Ellipsis.__class__,
|
||||
}
|
||||
|
||||
# these are types that if empty, might be copied with simple copy() instead of deepcopy()
|
||||
BUILTIN_COLLECTIONS: Set[Type[Any]] = {
|
||||
list,
|
||||
set,
|
||||
tuple,
|
||||
frozenset,
|
||||
dict,
|
||||
OrderedDict,
|
||||
defaultdict,
|
||||
deque,
|
||||
}
|
||||
|
||||
|
||||
def import_string(dotted_path: str) -> Any:
|
||||
"""
|
||||
@@ -534,3 +572,22 @@ class ClassAttribute:
|
||||
if instance is None:
|
||||
return self.value
|
||||
raise AttributeError(f'{self.name!r} attribute of {owner.__name__!r} is class-only')
|
||||
|
||||
|
||||
Obj = TypeVar('Obj')
|
||||
|
||||
|
||||
def smart_deepcopy(obj: Obj) -> Obj:
|
||||
"""
|
||||
Return type as is for immutable built-in types
|
||||
Use obj.copy() for built-in empty collections
|
||||
Use copy.deepcopy() for non-empty collections and unknown objects
|
||||
"""
|
||||
|
||||
obj_type = obj.__class__
|
||||
if obj_type in IMMUTABLE_NON_COLLECTIONS_TYPES:
|
||||
return obj # fastest case: obj is immutable and not collection therefore will not be copied anyway
|
||||
elif not obj and obj_type in BUILTIN_COLLECTIONS:
|
||||
# faster way for empty collections, no need to copy its members
|
||||
return obj if obj_type is tuple else obj.copy() # type: ignore # tuple doesn't have copy method
|
||||
return deepcopy(obj) # slowest way when we actually might need a deepcopy
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
from copy import deepcopy
|
||||
from distutils.version import StrictVersion
|
||||
from enum import Enum
|
||||
from typing import NewType, Union
|
||||
@@ -13,12 +14,14 @@ from pydantic.dataclasses import dataclass
|
||||
from pydantic.fields import Undefined
|
||||
from pydantic.typing import Literal, all_literal_values, display_as_type, is_new_type, new_type_supertype
|
||||
from pydantic.utils import (
|
||||
BUILTIN_COLLECTIONS,
|
||||
ClassAttribute,
|
||||
ValueItems,
|
||||
deep_update,
|
||||
get_model,
|
||||
import_string,
|
||||
lenient_issubclass,
|
||||
smart_deepcopy,
|
||||
truncate,
|
||||
unique_list,
|
||||
)
|
||||
@@ -347,3 +350,30 @@ def test_all_literal_values():
|
||||
|
||||
L312 = Literal['3', Literal[L1, L2]]
|
||||
assert sorted(all_literal_values(L312)) == sorted(('1', '2', '3'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'obj',
|
||||
(1, 1.0, '1', b'1', int, None, test_all_literal_values, len, test_all_literal_values.__code__, lambda: ..., ...),
|
||||
)
|
||||
def test_smart_deepcopy_immutable_non_sequence(obj, mocker):
|
||||
# make sure deepcopy is not used
|
||||
# (other option will be to use obj.copy(), but this will produce error as none of given objects have this method)
|
||||
mocker.patch('pydantic.utils.deepcopy', side_effect=RuntimeError)
|
||||
assert smart_deepcopy(obj) is deepcopy(obj) is obj
|
||||
|
||||
|
||||
@pytest.mark.parametrize('empty_collection', (collection() for collection in BUILTIN_COLLECTIONS))
|
||||
def test_smart_deepcopy_empty_collection(empty_collection, mocker):
|
||||
mocker.patch('pydantic.utils.deepcopy', side_effect=RuntimeError) # make sure deepcopy is not used
|
||||
if not isinstance(empty_collection, (tuple, frozenset)): # empty tuple or frozenset are always the same object
|
||||
assert smart_deepcopy(empty_collection) is not empty_collection
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'collection', (c.fromkeys((1,)) if issubclass(c, dict) else c((1,)) for c in BUILTIN_COLLECTIONS)
|
||||
)
|
||||
def test_smart_deepcopy_collection(collection, mocker):
|
||||
expected_value = object()
|
||||
mocker.patch('pydantic.utils.deepcopy', return_value=expected_value)
|
||||
assert smart_deepcopy(collection) is expected_value
|
||||
|
||||
Reference in New Issue
Block a user