Skip to content

Commit 89cdc1f

Browse files
veeceeyclaude
andcommitted
Fix CI failures: update doctest, add changelog, fix coverage
- Update extending.md doctest to reflect that field_transformer now receives pre-resolved aliases (use alias == name.lstrip("_") instead of `not field.alias` to detect auto-generated aliases) - Add changelog entry for #1479 - Add test_hook_new_field_without_alias to cover the post-transformer alias resolution path (line 496 of _make.py) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3c39e5f commit 89cdc1f

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

changelog.d/1479.change

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Field aliases are now resolved *before* calling `field_transformer`, so transformers receive fully populated `Attribute` objects with usable `alias` values instead of `None`.

docs/extending.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,13 @@ Data(a=3, b='spam', c=datetime.datetime(2020, 5, 4, 13, 37))
248248
```
249249

250250
Or, perhaps you would prefer to generate dataclass-compatible `__init__` signatures via a default field *alias*.
251-
Note, *field_transformer* operates on {class}`attrs.Attribute` instances before the default private-attribute handling is applied so explicit user-provided aliases can be detected.
251+
Note, *field_transformer* receives {class}`attrs.Attribute` instances with default aliases already resolved (e.g., leading-underscore stripping has been applied), so you can compare against the default to detect explicit user-provided aliases.
252252

253253
```{doctest}
254254
>>> def dataclass_names(cls, fields):
255255
... return [
256256
... field.evolve(alias=field.name)
257-
... if not field.alias
257+
... if field.alias == field.name.lstrip("_")
258258
... else field
259259
... for field in fields
260260
... ]

tests/test_hooks.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,44 @@ class C:
292292

293293
assert "my_alias" == attr.fields(C).renamed.alias
294294

295+
def test_hook_new_field_without_alias(self):
296+
"""
297+
When a field_transformer adds a brand-new field without setting an
298+
alias, the post-transformer alias resolution fills it in.
299+
300+
Regression test for #1479.
301+
"""
302+
303+
def hook(cls, attribs):
304+
return list(attribs) + [
305+
attr.Attribute(
306+
name="_extra",
307+
default=0,
308+
validator=None,
309+
repr=True,
310+
cmp=None,
311+
hash=None,
312+
init=True,
313+
metadata={},
314+
type=int,
315+
converter=None,
316+
kw_only=False,
317+
eq=True,
318+
eq_key=None,
319+
order=True,
320+
order_key=None,
321+
on_setattr=None,
322+
alias=None,
323+
inherited=False,
324+
)
325+
]
326+
327+
@attr.s(auto_attribs=True, field_transformer=hook)
328+
class C:
329+
x: int
330+
331+
assert "extra" == attr.fields(C)._extra.alias
332+
295333

296334
class TestAsDictHook:
297335
def test_asdict(self):

0 commit comments

Comments
 (0)