1from __future__ import annotations
2
3from typing import TYPE_CHECKING, Any
4
5from attrs import NOTHING, Attribute, Factory
6
7from .._compat import is_bare_final
8from ..dispatch import StructureHook
9from ..errors import StructureHandlerNotFoundError
10from ..fns import raise_error
11
12if TYPE_CHECKING:
13 from ..converters import BaseConverter
14
15
16def find_structure_handler(
17 a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False
18) -> StructureHook | None:
19 """Find the appropriate structure handler to use.
20
21 Return `None` if no handler should be used.
22 """
23 try:
24 if a.converter is not None and prefer_attrs_converters:
25 # If the user as requested to use attrib converters, use nothing
26 # so it falls back to that.
27 handler = None
28 elif (
29 a.converter is not None and not prefer_attrs_converters and type is not None
30 ):
31 try:
32 handler = c.get_structure_hook(type, cache_result=False)
33 except StructureHandlerNotFoundError:
34 handler = None
35 else:
36 # The legacy way, should still work.
37 if handler == raise_error:
38 handler = None
39 elif type is not None:
40 if (
41 is_bare_final(type)
42 and a.default is not NOTHING
43 and not isinstance(a.default, Factory)
44 ):
45 # This is a special case where we can use the
46 # type of the default to dispatch on.
47 type = a.default.__class__
48 handler = c.get_structure_hook(type, cache_result=False)
49 if handler == c._structure_call:
50 # Finals can't really be used with _structure_call, so
51 # we wrap it so the rest of the toolchain doesn't get
52 # confused.
53
54 def handler(v, _, _h=handler):
55 return _h(v, type)
56
57 else:
58 handler = c.get_structure_hook(type, cache_result=False)
59 else:
60 handler = c.structure
61 return handler
62 except RecursionError:
63 # This means we're dealing with a reference cycle, so use late binding.
64 return c.structure