Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

オープン
#797 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@Eryx-Z がすでに取り組んでいます。

2026年10月16日 から。

  • #799 @Eryx-Z による — オープン

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
62/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python
領域
data

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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.

主要言語
Python
スター
1.1k
フォーク
171
平均マージ
14時間 46分
マージ済み PR(30日)
5

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

python-attrs/cattrs のほかの issue

python-attrs/cattrs の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。