Skip to content

Commit

Permalink
Add support for instance method reassignment when extra='allow' (#7683
Browse files Browse the repository at this point in the history
)

Co-authored-by: David Montague <35119617+dmontagu@users.noreply.github.com>
  • Loading branch information
sydney-runkle and dmontagu committed Oct 2, 2023
1 parent a3095ec commit 8bf4aa0
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 2 deletions.
13 changes: 11 additions & 2 deletions pydantic/main.py
Expand Up @@ -798,8 +798,17 @@ def __setattr__(self, name: str, value: Any) -> None:
# TODO - matching error
raise ValueError(f'"{self.__class__.__name__}" object has no field "{name}"')
elif self.model_config.get('extra') == 'allow' and name not in self.model_fields:
# SAFETY: __pydantic_extra__ is not None when extra = 'allow'
self.__pydantic_extra__[name] = value # type: ignore
if self.model_extra and name in self.model_extra:
self.__pydantic_extra__[name] = value # type: ignore
else:
try:
getattr(self, name)
except AttributeError:
# attribute does not already exist on instance, so put it in extra
self.__pydantic_extra__[name] = value # type: ignore
else:
# attribute _does_ already exist on instance, and was not in extra, so update it
_object_setattr(self, name, value)
else:
self.__dict__[name] = value
self.__pydantic_fields_set__.add(name)
Expand Down
20 changes: 20 additions & 0 deletions tests/test_main.py
Expand Up @@ -7,6 +7,7 @@
from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from functools import partial
from typing import (
Any,
Callable,
Expand Down Expand Up @@ -342,6 +343,25 @@ class Model(BaseModel):
assert model.c == 1


def test_reassign_instance_method_with_extra_allow():
class Model(BaseModel):
model_config = ConfigDict(extra='allow')
name: str

def not_extra_func(self) -> str:
return f'hello {self.name}'

def not_extra_func_replacement(self_sub: Model) -> str:
return f'hi {self_sub.name}'

m = Model(name='james')
assert m.not_extra_func() == 'hello james'

m.not_extra_func = partial(not_extra_func_replacement, m)
assert m.not_extra_func() == 'hi james'
assert 'not_extra_func' in m.__dict__


def test_extra_ignored():
class Model(BaseModel):
model_config = ConfigDict(extra='ignore')
Expand Down

0 comments on commit 8bf4aa0

Please sign in to comment.