@@ -403,7 +403,12 @@ def _invoke_from_json(hook: Any, value: Any, converter: DataConverter | None) ->
403403 > ``from_json`` must be a ``@classmethod`` or ``@staticmethod`` -- the hook
404404 > is resolved off the *type* (no instance exists yet during reconstruction).
405405 > A plain instance method would have ``self`` consume the value and is
406- > unsupported regardless of the converter-arity detection below.
406+ > unsupported regardless of the converter detection below.
407+ >
408+ > The converter is passed positionally as the second argument, so a hook
409+ > opts in only by naming that parameter exactly ``converter``. This reserved
410+ > name avoids misreading an unrelated second parameter (e.g.
411+ > ``from_json(cls, value, strict=False)``) as converter-aware.
407412 """
408413 if converter is not None and _hook_accepts_converter (hook ):
409414 return hook (value , converter )
@@ -412,26 +417,27 @@ def _invoke_from_json(hook: Any, value: Any, converter: DataConverter | None) ->
412417
413418@functools .lru_cache (maxsize = 2048 )
414419def _hook_accepts_converter (hook : Any ) -> bool :
415- """Return True if a bound ``from_json`` hook can accept a second argument .
420+ """Return True if a bound ``from_json`` hook opts in to receiving the converter .
416421
417422 The hook is inspected as accessed off the type (``cls``/``self`` already
418423 bound), so a classmethod ``from_json(cls, value, converter)`` presents as
419- ``(value, converter)``. Results are cached because reconstruction runs on
420- hot paths; bound classmethods hash equal across attribute accesses, so the
421- cache stays effective and bounded.
424+ ``(value, converter)``. A hook is treated as converter-aware only when its
425+ second positional parameter is named exactly ``converter`` -- the reserved
426+ name for this argument -- so an unrelated second parameter such as
427+ ``strict=False`` is not mistaken for it. Results are cached because
428+ reconstruction runs on hot paths; bound classmethods hash equal across
429+ attribute accesses, so the cache stays effective and bounded.
422430 """
423431 try :
424432 sig = inspect .signature (hook )
425433 except (TypeError , ValueError ):
426434 return False
427- positional = 0
428- for param in sig .parameters .values ():
435+ positional = [
436+ param for param in sig .parameters .values ()
429437 if param .kind in (inspect .Parameter .POSITIONAL_ONLY ,
430- inspect .Parameter .POSITIONAL_OR_KEYWORD ):
431- positional += 1
432- elif param .kind is inspect .Parameter .VAR_POSITIONAL :
433- return True
434- return positional >= 2
438+ inspect .Parameter .POSITIONAL_OR_KEYWORD )
439+ ]
440+ return len (positional ) >= 2 and positional [1 ].name == "converter"
435441
436442
437443def _coerce_generic (value : Any , expected_type : Any , origin : Any ,
0 commit comments