Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Structure hook on a PEP 695 alias is ignored inside a passthrough union

Aperta
#797 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@Eryx-Z ci sta già lavorando.

Dal 16/10/2026.

  • #799 di @Eryx-Z — aperta

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
62/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
data

Direzione di ricerca

Start with the reproduction script in the issue, then read get_passthrough_base in src/cattrs/strategies/_unions.py (around lines 180-183 and 202-207), where a PEP 695 alias is unwrapped to its value before the union hook is built. The preconf converters pick this up through src/cattrs/preconf/json.py. Done means structure(' x ', Trimmed | int) returns 'x' again, matching 26.1.0, with a regression test that registers a hook on an alias and structures a union containing it.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Since 26.2.0, a structure hook registered for a PEP 695 type alias is skipped when the alias is a member of a union handled by the union passthrough strategy, which every preconf converter configures, so the raw value comes back with no error.

Minimal example

import cattrs
from cattrs.preconf.json import make_converter
from cattrs.strategies import configure_union_passthrough

type Trimmed = str


def build_plain():
    c = cattrs.Converter()
    configure_union_passthrough(str | int, c)
    return c


for name, c in (("Converter + passthrough", build_plain()), ("preconf.json", make_converter())):
    c.register_structure_hook_func(lambda t: t is Trimmed, lambda v, _: v.strip())
    print(f"[{name}]")
    print("  structure('  x  ', Trimmed)       observed:", repr(c.structure("  x  ", Trimmed)))
    print("  structure('  x  ', Trimmed | int) observed:", repr(c.structure("  x  ", Trimmed | int)))
    print("  structure('  x  ', Trimmed | int) expected:", repr("x"))

Observed output

[Converter + passthrough]
  structure('  x  ', Trimmed)       observed: 'x'
  structure('  x  ', Trimmed | int) observed: '  x  '
  structure('  x  ', Trimmed | int) expected: 'x'
[preconf.json]
  structure('  x  ', Trimmed)       observed: 'x'
  structure('  x  ', Trimmed | int) observed: '  x  '
  structure('  x  ', Trimmed | int) expected: 'x'

The hook runs for Trimmed on its own but not for Trimmed | int. Trimmed | int | None behaves the same way.

Expected behaviour

'x' in both cases, as 26.1.0 returns for the same script. The Type Aliases section of docs/defaulthooks.md says alias hooks can be overridden with predicate hooks, and its versionchanged 26.2.0 note about the passthrough strategy doesn't say those hooks are skipped inside unions.

Versions

  • Python 3.12.3
  • cattrs 26.2.1 (26.1.0 is not affected)
  • attrs 26.1.0
  • OS: Linux

Cause

In configure_union_passthrough (src/cattrs/strategies/_unions.py, main at 73fa273), get_passthrough_base (lines 180-181) replaces an alias with its value, so Trimmed becomes str in args (line 183) and in non_literal_classes (lines 202-207). The alias is then not in spillover, and the native union hook returns the value as is because val.__class__ in classes (lines 251 and 262). Before 26.2.0 the alias was not unwrapped, went to spillover, and converter.structure(val, Trimmed) reached the user hook. cattrs.preconf.json.make_converter() configures the passthrough at src/cattrs/preconf/json.py line 64, and the other preconf converters do the same, so any hook on a str, int, float, bool or bytes alias used in such a union is affected.

Proposed fix

Unwrap an alias only when the converter has no dedicated hook for it, for example when converter.get_structure_hook(alias) is still the product of the default type alias factory; otherwise leave the alias in spillover while the union is still claimed through its other members. A test that registers a hook on an alias and structures Alias | int would cover it.

I'm happy to open a PR with this fix and a test.

Lingua principale
Python
Stelle
1.1k
Fork
171
Merge medio
14h 46m
PR unite (30g)
5

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di python-attrs/cattrs

Tutte le issue di python-attrs/cattrs

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.