Skip to content
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

allow for shallow copies #4093

Merged
merged 13 commits into from Aug 10, 2022
2 changes: 2 additions & 0 deletions changes/4093-timkpaine.md
@@ -0,0 +1,2 @@
Allow for shallow copies of attributes, adjusting the behavior of #3642
`Config.copy_on_model_validation` is now a str enum of `["", "deep", "shallow"]` corresponding to reference, deep copy, shallow copy.
12 changes: 10 additions & 2 deletions pydantic/config.py
Expand Up @@ -36,6 +36,12 @@ class Extra(str, Enum):
forbid = 'forbid'


class Copy(str, Enum):
timkpaine marked this conversation as resolved.
Show resolved Hide resolved
none = ''
deep = 'deep'
shallow = 'shallow'


class BaseConfig:
title: Optional[str] = None
anystr_lower: bool = False
Expand All @@ -62,8 +68,10 @@ class BaseConfig:
json_encoders: Dict[Union[Type[Any], str, ForwardRef], AnyCallable] = {}
underscore_attrs_are_private: bool = False

# whether inherited models as fields should be reconstructed as base model
copy_on_model_validation: bool = True
# whether inherited models as fields should be reconstructed as base model,
# and whether such a copy should be shallow or deep
copy_on_model_validation: Copy = Copy.deep

# whether `Union` should check all allowed types before even trying to coerce
smart_union: bool = False

Expand Down
6 changes: 4 additions & 2 deletions pydantic/main.py
Expand Up @@ -25,7 +25,7 @@
)

from .class_validators import ValidatorGroup, extract_root_validators, extract_validators, inherit_validators
from .config import BaseConfig, Extra, inherit_config, prepare_config
from .config import BaseConfig, Copy, Extra, inherit_config, prepare_config
from .error_wrappers import ErrorWrapper, ValidationError
from .errors import ConfigError, DictError, ExtraError, MissingError
from .fields import MAPPING_LIKE_SHAPES, Field, FieldInfo, ModelField, ModelPrivateAttr, PrivateAttr, Undefined
Expand Down Expand Up @@ -675,7 +675,9 @@ def __get_validators__(cls) -> 'CallableGenerator':
@classmethod
def validate(cls: Type['Model'], value: Any) -> 'Model':
if isinstance(value, cls):
if cls.__config__.copy_on_model_validation:
if cls.__config__.copy_on_model_validation == Copy.shallow:
timkpaine marked this conversation as resolved.
Show resolved Hide resolved
return value._copy_and_set_values(value.__dict__, value.__fields_set__, deep=False)
elif cls.__config__.copy_on_model_validation:
return value._copy_and_set_values(value.__dict__, value.__fields_set__, deep=True)
else:
return value
Expand Down
22 changes: 22 additions & 0 deletions tests/test_main.py
Expand Up @@ -1567,6 +1567,28 @@ class Config:
assert t.dict() == {'id': '1234567890', 'user': {'id': 42, 'hobbies': ['scuba diving']}}


def test_model_exclude_copy_on_model_validation_shallow():
"""When `Config.copy_on_model_validation` is set and `Config.copy_on_model_validation_shallow` is set,
do the same as the previous test but perform a shallow copy"""

class User(BaseModel):
class Config:
copy_on_model_validation = 'shallow'

hobbies: List[str]

my_user = User(hobbies=['scuba diving'])

class Transaction(BaseModel):
user: User = Field(...)

t = Transaction(
user=my_user,
)

assert t.user.hobbies is my_user.hobbies # unlike above, this should be a shallow copy


def test_validation_deep_copy():
"""By default, Config.copy_on_model_validation should do a deep copy"""

Expand Down