Skip to content

Commit

Permalink
Fix duplicate calls to __set_name__ for non-private attributes in `…
Browse files Browse the repository at this point in the history
…BaseModel.__new__` (#4410)

* fix: double call for public attrs, extend test

* add changes

* remove change file, update 4407 file
  • Loading branch information
tlambert03 committed Aug 22, 2022
1 parent d501c39 commit beb3e4c
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 7 deletions.
2 changes: 1 addition & 1 deletion changes/4407-tlambert03.md
@@ -1 +1 @@
Fix PEP487 protocol in `BaseModel`: call `__set_name__` on namespace values that implement the method.
Fix PEP487 `__set_name__` protocol in `BaseModel` for PrivateAttrs.
8 changes: 5 additions & 3 deletions pydantic/main.py
Expand Up @@ -287,10 +287,12 @@ def is_untouched(v: Any) -> bool:
cls.__try_update_forward_refs__()

# preserve `__set_name__` protocol defined in https://peps.python.org/pep-0487
# for attributes not in `new_namespace` (e.g. private attributes)
for name, obj in namespace.items():
set_name = getattr(obj, '__set_name__', None)
if callable(set_name):
set_name(cls, name)
if name not in new_namespace:
set_name = getattr(obj, '__set_name__', None)
if callable(set_name):
set_name(cls, name)

return cls

Expand Down
20 changes: 17 additions & 3 deletions tests/test_create_model.py
Expand Up @@ -225,20 +225,34 @@ class TestGenericModel(GenericModel):
assert result.__config__.orm_mode is True


def test_set_name():
@pytest.mark.parametrize('base', [ModelPrivateAttr, object])
def test_set_name(base):
calls = []

class class_deco(ModelPrivateAttr):
class class_deco(base):
def __init__(self, fn):
super().__init__()
self.fn = fn

def __set_name__(self, owner, name):
calls.append((owner, name))

def __get__(self, obj, type=None):
return self.fn(obj) if obj else self

class A(BaseModel):
x: int

@class_deco
def _some_func(self):
return self
return self.x

assert calls == [(A, '_some_func')]
a = A(x=2)

# we don't test whether calling the method on a PrivateAttr works:
# attribute access on privateAttributes is more complicated, it doesn't
# get added to the class namespace (and will also get set on the instance
# with _init_private_attributes), so the descriptor protocol won't work.
if base is object:
assert a._some_func == 2

0 comments on commit beb3e4c

Please sign in to comment.