Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cattrs/cols.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

102 statements  

1"""Utility functions for collections.""" 

2 

3from __future__ import annotations 

4 

5from collections import defaultdict 

6from collections.abc import Callable, Iterable 

7from functools import partial 

8from typing import ( 

9 TYPE_CHECKING, 

10 Any, 

11 DefaultDict, 

12 Literal, 

13 NamedTuple, 

14 TypeVar, 

15 get_type_hints, 

16) 

17 

18from attrs import NOTHING, Attribute, NothingType 

19 

20from ._compat import ( 

21 ANIES, 

22 get_args, 

23 get_origin, 

24 is_bare, 

25 is_frozenset, 

26 is_mapping, 

27 is_sequence, 

28 is_subclass, 

29) 

30from ._compat import is_mutable_set as is_set 

31from .dispatch import StructureHook, UnstructureHook 

32from .errors import IterableValidationError, IterableValidationNote 

33from .fns import identity 

34from .gen import ( 

35 AttributeOverride, 

36 already_generating, 

37 make_dict_structure_fn_from_attrs, 

38 make_dict_unstructure_fn_from_attrs, 

39 make_hetero_tuple_unstructure_fn, 

40 mapping_structure_factory, 

41 mapping_unstructure_factory, 

42) 

43from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory 

44 

45if TYPE_CHECKING: 

46 from .converters import BaseConverter 

47 

48__all__ = [ 

49 "defaultdict_structure_factory", 

50 "is_any_set", 

51 "is_defaultdict", 

52 "is_frozenset", 

53 "is_mapping", 

54 "is_namedtuple", 

55 "is_sequence", 

56 "is_set", 

57 "iterable_unstructure_factory", 

58 "list_structure_factory", 

59 "mapping_structure_factory", 

60 "mapping_unstructure_factory", 

61 "namedtuple_dict_structure_factory", 

62 "namedtuple_dict_unstructure_factory", 

63 "namedtuple_structure_factory", 

64 "namedtuple_unstructure_factory", 

65] 

66 

67 

68def is_any_set(type) -> bool: 

69 """A predicate function for both mutable and frozensets.""" 

70 return is_set(type) or is_frozenset(type) 

71 

72 

73def is_namedtuple(type: Any) -> bool: 

74 """A predicate function for named tuples.""" 

75 

76 if is_subclass(type, tuple): 

77 for cl in type.mro(): 

78 orig_bases = cl.__dict__.get("__orig_bases__", ()) 

79 if NamedTuple in orig_bases: 

80 return True 

81 return False 

82 

83 

84def _is_passthrough(type: type[tuple], converter: BaseConverter) -> bool: 

85 """If all fields would be passed through, this class should not be processed 

86 either. 

87 """ 

88 return all( 

89 converter.get_unstructure_hook(t) == identity 

90 for t in type.__annotations__.values() 

91 ) 

92 

93 

94T = TypeVar("T") 

95 

96 

97def list_structure_factory(type: type, converter: BaseConverter) -> StructureHook: 

98 """A hook factory for structuring lists. 

99 

100 Converts any given iterable into a list. 

101 """ 

102 

103 if is_bare(type) or type.__args__[0] in ANIES: 

104 

105 def structure_list(obj: Iterable[T], _: type = type) -> list[T]: 

106 return list(obj) 

107 

108 return structure_list 

109 

110 elem_type = type.__args__[0] 

111 

112 try: 

113 handler = converter.get_structure_hook(elem_type) 

114 except RecursionError: 

115 # Break the cycle by using late binding. 

116 handler = converter.structure 

117 

118 if converter.detailed_validation: 

119 

120 def structure_list( 

121 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type 

122 ) -> list[T]: 

123 errors = [] 

124 res = [] 

125 ix = 0 # Avoid `enumerate` for performance. 

126 for e in obj: 

127 try: 

128 res.append(handler(e, _elem_type)) 

129 except Exception as e: 

130 msg = IterableValidationNote( 

131 f"Structuring {type} @ index {ix}", ix, elem_type 

132 ) 

133 e.__notes__ = [*getattr(e, "__notes__", []), msg] 

134 errors.append(e) 

135 finally: 

136 ix += 1 

137 if errors: 

138 raise IterableValidationError( 

139 f"While structuring {type!r}", errors, type 

140 ) 

141 

142 return res 

143 

144 else: 

145 

146 def structure_list( 

147 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type 

148 ) -> list[T]: 

149 return [_handler(e, _elem_type) for e in obj] 

150 

151 return structure_list 

152 

153 

154def namedtuple_unstructure_factory( 

155 cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None 

156) -> UnstructureHook: 

157 """A hook factory for unstructuring namedtuples. 

158 

159 :param unstructure_to: Force unstructuring to this type, if provided. 

160 """ 

161 

162 if unstructure_to is None and _is_passthrough(cl, converter): 

163 return identity 

164 

165 return make_hetero_tuple_unstructure_fn( 

166 cl, 

167 converter, 

168 unstructure_to=tuple if unstructure_to is None else unstructure_to, 

169 type_args=tuple(cl.__annotations__.values()), 

170 ) 

171 

172 

173def namedtuple_structure_factory( 

174 cl: type[tuple], converter: BaseConverter 

175) -> StructureHook: 

176 """A hook factory for structuring namedtuples from iterables.""" 

177 # We delegate to the existing infrastructure for heterogenous tuples. 

178 hetero_tuple_type = tuple[tuple(cl.__annotations__.values())] 

179 base_hook = converter.get_structure_hook(hetero_tuple_type) 

180 return lambda v, _: cl(*base_hook(v, hetero_tuple_type)) 

181 

182 

183def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]: 

184 """Generate pseudo attributes for a namedtuple.""" 

185 return [ 

186 Attribute( 

187 name, 

188 cl._field_defaults.get(name, NOTHING), 

189 None, 

190 False, 

191 False, 

192 False, 

193 True, 

194 False, 

195 type=a, 

196 alias=name, 

197 ) 

198 for name, a in get_type_hints(cl).items() 

199 ] 

200 

201 

202def namedtuple_dict_structure_factory( 

203 cl: type[tuple], 

204 converter: BaseConverter, 

205 detailed_validation: bool | Literal["from_converter"] = "from_converter", 

206 forbid_extra_keys: bool = False, 

207 use_linecache: bool = True, 

208 /, 

209 **kwargs: AttributeOverride, 

210) -> StructureHook: 

211 """A hook factory for hooks structuring namedtuples from dictionaries. 

212 

213 :param forbid_extra_keys: Whether the hook should raise a `ForbiddenExtraKeysError` 

214 if unknown keys are encountered. 

215 :param use_linecache: Whether to store the source code in the Python linecache. 

216 

217 .. versionadded:: 24.1.0 

218 """ 

219 try: 

220 working_set = already_generating.working_set 

221 except AttributeError: 

222 working_set = set() 

223 already_generating.working_set = working_set 

224 else: 

225 if cl in working_set: 

226 raise RecursionError() 

227 

228 working_set.add(cl) 

229 

230 try: 

231 return make_dict_structure_fn_from_attrs( 

232 _namedtuple_to_attrs(cl), 

233 cl, 

234 converter, 

235 _cattrs_forbid_extra_keys=forbid_extra_keys, 

236 _cattrs_use_detailed_validation=detailed_validation, 

237 _cattrs_use_linecache=use_linecache, 

238 **kwargs, 

239 ) 

240 finally: 

241 working_set.remove(cl) 

242 if not working_set: 

243 del already_generating.working_set 

244 

245 

246def namedtuple_dict_unstructure_factory( 

247 cl: type[tuple], 

248 converter: BaseConverter, 

249 omit_if_default: bool = False, 

250 use_linecache: bool = True, 

251 /, 

252 **kwargs: AttributeOverride, 

253) -> UnstructureHook: 

254 """A hook factory for hooks unstructuring namedtuples to dictionaries. 

255 

256 :param omit_if_default: When true, attributes equal to their default values 

257 will be omitted in the result dictionary. 

258 :param use_linecache: Whether to store the source code in the Python linecache. 

259 

260 .. versionadded:: 24.1.0 

261 """ 

262 try: 

263 working_set = already_generating.working_set 

264 except AttributeError: 

265 working_set = set() 

266 already_generating.working_set = working_set 

267 if cl in working_set: 

268 raise RecursionError() 

269 

270 working_set.add(cl) 

271 

272 try: 

273 return make_dict_unstructure_fn_from_attrs( 

274 _namedtuple_to_attrs(cl), 

275 cl, 

276 converter, 

277 _cattrs_omit_if_default=omit_if_default, 

278 _cattrs_use_linecache=use_linecache, 

279 **kwargs, 

280 ) 

281 finally: 

282 working_set.remove(cl) 

283 if not working_set: 

284 del already_generating.working_set 

285 

286 

287def is_defaultdict(type: Any) -> bool: 

288 """Is this type a defaultdict? 

289 

290 Bare defaultdicts (defaultdicts with no type arguments) are not supported 

291 since there's no way to discover their _default_factory_. 

292 """ 

293 return is_subclass(get_origin(type), (defaultdict, DefaultDict)) 

294 

295 

296def defaultdict_structure_factory( 

297 type: type[defaultdict], 

298 converter: BaseConverter, 

299 default_factory: Callable[[], Any] | NothingType = NOTHING, 

300) -> StructureHook: 

301 """A structure hook factory for defaultdicts. 

302 

303 The value type parameter will be used as the _default factory_. 

304 """ 

305 if default_factory is NOTHING: 

306 default_factory = get_args(type)[1] 

307 return mapping_structure_factory( 

308 type, converter, partial(defaultdict, default_factory) 

309 )