8000 Fix :: Signature generation with `extra: allow` never uses a field na… · ag-python/pydantic@b287146 · GitHub
[go: up one dir, main page]

Skip to content

Commit b287146

Browse files
Fix :: Signature generation with extra: allow never uses a field name (pydantic#1420)
* fix :: signature generation does not use field names fix pydantic#1418 * fix :: use field.outer_type_ when generating signature `extra_data: Dict[str, str]` would generate `extra_data: str` in the signature * tweak to keep signature kwargs name Co-authored-by: Samuel Colvin <s@muelcolvin.com>
1 parent 3cd8b1e commit b287146

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

changes/1418-prettywood.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Signature generation with `extra: allow` never uses a field name

pydantic/utils.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,31 @@ def generate_model_signature(
174174

175175
# TODO: replace annotation with actual expected types once #1055 solved
176176
kwargs = {'default': field.default} if not field.required else {}
177-
merged_params[param_name] = Parameter(param_name, Parameter.KEYWORD_ONLY, annotation=field.type_, **kwargs)
177+
merged_params[param_name] = Parameter(
178+
param_name, Parameter.KEYWORD_ONLY, annotation=field.outer_type_, **kwargs
179+
)
178180

179181
if config.extra is config.extra.allow:
180182
use_var_kw = True
181183

182184
if var_kw and use_var_kw:
183-
merged_params[var_kw.name] = var_kw
185+
# Make sure the parameter for extra kwargs
186+
# does not have the same name as a field
187+
default_model_signature = [
188+
('__pydantic_self__', Parameter.POSITIONAL_OR_KEYWORD),
189+
('data', Parameter.VAR_KEYWORD),
190+
]
191+
if [(p.name, p.kind) for p in present_params] == default_model_signature:
192+
# if this is the standard model signature, use extra_data as the extra args name
193+
var_kw_name = 'extra_data'
194+
else:
195+
# else start from var_kw
196+
var_kw_name = var_kw.name
197+
198+
# generate a name that's definitely unique
199+
while var_kw_name in fields:
200+
var_kw_name += '_'
201+
merged_params[var_kw_name] = var_kw.replace(name=var_kw_name)
184202

185203
return Signature(parameters=list(merged_params.values()), return_annotation=None)
186204

tests/test_model_signature.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def test_invalid_identifiers_signature():
7171
)
7272
assert _equals(str(signature(model)), '(*, valid_identifier: int = 123, yeah: int = 0) -> None')
7373
model = create_model('Model', **{'123 invalid identifier!': 123, '!': Field(0, alias='yeah')})
74-
assert _equals(str(signature(model)), '(*, yeah: int = 0, **data: Any) -> None')
74+
assert _equals(str(signature(model)), '(*, yeah: int = 0, **extra_data: Any) -> None')
7575

7676

7777
def test_use_field_name():
@@ -82,3 +82,47 @@ class Config:
8282
allow_population_by_field_name = True
8383

8484
assert _equals(str(signature(Foo)), '(*, foo: str) -> None')
85+
86+
87+
def test_extra_allow_no_conflict():
88+
class Model(BaseModel):
89+
spam: str
90+
91+
class Config:
92+
extra = Extra.allow
93+
94+
assert _equals(str(signature(Model)), '(*, spam: str, **extra_data: Any) -> None')
95+
96+
97+
def test_extra_allow_conflict():
98+
class Model(BaseModel):
99+
extra_data: str
100+
101+
class Config:
102+
extra = Extra.allow
103+
104+
assert _equals(str(signature(Model)), '(*, extra_data: str, **extra_data_: Any) -> None')
105+
106+
107+
def test_extra_allow_conflict_twice():
108+
class Model(BaseModel):
109+
extra_data: str
110+
extra_data_: str
111+
112+
class Config:
113+
extra = Extra.allow
114+
115+
assert _equals(str(signature(Model)), '(*, extra_data: str, extra_data_: str, **extra_data__: Any) -> None')
116+
117+
118+
def test_extra_allow_conflict_custom_signature():
119+
class Model(BaseModel):
120+
extra_data: int
121+
122+
def __init__(self, extra_data: int = 1, **foobar: Any):
123+
super().__init__(extra_data=extra_data, **foobar)
124+
125+
class Config:
126+
extra = Extra.allow
127+
128+
assert _equals(str(signature(Model)), '(extra_data: int = 1, **foobar: Any) -> None')

0 commit comments

Comments
 (0)
0