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

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

121 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 TYPE_CHECKING, Any, DefaultDict, Literal, NamedTuple, TypeVar 

9 

10from attrs import NOTHING, Attribute, NothingType 

11 

12from ._compat import ( 

13 ANIES, 

14 AbcSet, 

15 get_args, 

16 get_full_type_hints, 

17 get_origin, 

18 is_bare, 

19 is_frozenset, 

20 is_mapping, 

21 is_mutable_sequence, 

22 is_sequence, 

23 is_subclass, 

24) 

25from ._compat import is_mutable_set as is_set 

26from .dispatch import StructureHook, UnstructureHook 

27from .errors import IterableValidationError, IterableValidationNote 

28from .fns import identity 

29from .gen import ( 

30 AttributeOverride, 

31 already_generating, 

32 make_dict_structure_fn_from_attrs, 

33 make_dict_unstructure_fn_from_attrs, 

34 make_hetero_tuple_unstructure_fn, 

35 mapping_structure_factory, 

36 mapping_unstructure_factory, 

37) 

38from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory 

39 

40if TYPE_CHECKING: 

41 from .converters import BaseConverter 

42 

43__all__ = [ 

44 "defaultdict_structure_factory", 

45 "homogenous_tuple_structure_factory", 

46 "is_abstract_set", 

47 "is_any_set", 

48 "is_defaultdict", 

49 "is_frozenset", 

50 "is_mapping", 

51 "is_mutable_sequence", 

52 "is_namedtuple", 

53 "is_sequence", 

54 "is_set", 

55 "iterable_unstructure_factory", 

56 "list_structure_factory", 

57 "mapping_structure_factory", 

58 "mapping_unstructure_factory", 

59 "namedtuple_dict_structure_factory", 

60 "namedtuple_dict_unstructure_factory", 

61 "namedtuple_structure_factory", 

62 "namedtuple_unstructure_factory", 

63] 

64 

65 

66def is_any_set(type) -> bool: 

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

68 return is_set(type) or is_frozenset(type) 

69 

70 

71def is_abstract_set(type) -> bool: 

72 """A predicate function for abstract (collection.abc) sets.""" 

73 return type is AbcSet or (getattr(type, "__origin__", None) is AbcSet) 

74 

75 

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

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

78 

79 if is_subclass(type, tuple): 

80 for cl in type.mro(): 

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

82 if NamedTuple in orig_bases: 

83 return True 

84 return False 

85 

86 

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

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

89 either. 

90 """ 

91 return all( 

92 converter.get_unstructure_hook(t) == identity 

93 for t in type.__annotations__.values() 

94 ) 

95 

96 

97T = TypeVar("T") 

98 

99 

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

101 """A hook factory for structuring lists. 

102 

103 Converts any given iterable into a list. 

104 """ 

105 

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

107 

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

109 return list(obj) 

110 

111 return structure_list 

112 

113 elem_type = type.__args__[0] 

114 

115 try: 

116 handler = converter.get_structure_hook(elem_type) 

117 except RecursionError: 

118 # Break the cycle by using late binding. 

119 handler = converter.structure 

120 

121 if converter.detailed_validation: 

122 

123 def structure_list( 

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

125 ) -> list[T]: 

126 errors = [] 

127 res = [] 

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

129 for e in obj: 

130 try: 

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

132 except Exception as e: 

133 msg = IterableValidationNote( 

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

135 ) 

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

137 errors.append(e) 

138 finally: 

139 ix += 1 

140 if errors: 

141 raise IterableValidationError( 

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

143 ) 

144 

145 return res 

146 

147 else: 

148 

149 def structure_list( 

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

151 ) -> list[T]: 

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

153 

154 return structure_list 

155 

156 

157def homogenous_tuple_structure_factory( 

158 type: type, converter: BaseConverter 

159) -> StructureHook: 

160 """A hook factory for homogenous (all elements the same, indeterminate length) tuples. 

161 

162 Converts any given iterable into a tuple. 

163 """ 

164 

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

166 

167 def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]: 

168 return tuple(obj) 

169 

170 return structure_tuple 

171 

172 elem_type = type.__args__[0] 

173 

174 try: 

175 handler = converter.get_structure_hook(elem_type) 

176 except RecursionError: 

177 # Break the cycle by using late binding. 

178 handler = converter.structure 

179 

180 if converter.detailed_validation: 

181 

182 # We have to structure into a list first anyway. 

183 list_structure = list_structure_factory(type, converter) 

184 

185 def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]: 

186 return tuple(list_structure(obj, _)) 

187 

188 else: 

189 

190 def structure_tuple( 

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

192 ) -> tuple[T, ...]: 

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

194 

195 return structure_tuple 

196 

197 

198def namedtuple_unstructure_factory( 

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

200) -> UnstructureHook: 

201 """A hook factory for unstructuring namedtuples. 

202 

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

204 """ 

205 

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

207 return identity 

208 

209 return make_hetero_tuple_unstructure_fn( 

210 cl, 

211 converter, 

212 unstructure_to=tuple if unstructure_to is None else unstructure_to, 

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

214 ) 

215 

216 

217def namedtuple_structure_factory( 

218 cl: type[tuple], converter: BaseConverter 

219) -> StructureHook: 

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

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

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

223 base_hook = converter.get_structure_hook(hetero_tuple_type) 

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

225 

226 

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

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

229 return [ 

230 Attribute( 

231 name, 

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

233 None, 

234 False, 

235 False, 

236 False, 

237 True, 

238 False, 

239 type=a, 

240 alias=name, 

241 ) 

242 for name, a in get_full_type_hints(cl).items() 

243 ] 

244 

245 

246def namedtuple_dict_structure_factory( 

247 cl: type[tuple], 

248 converter: BaseConverter, 

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

250 forbid_extra_keys: bool = False, 

251 use_linecache: bool = True, 

252 /, 

253 **kwargs: AttributeOverride, 

254) -> StructureHook: 

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

256 

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

258 if unknown keys are encountered. 

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

260 

261 .. versionadded:: 24.1.0 

262 """ 

263 try: 

264 working_set = already_generating.working_set 

265 except AttributeError: 

266 working_set = set() 

267 already_generating.working_set = working_set 

268 else: 

269 if cl in working_set: 

270 raise RecursionError() 

271 

272 working_set.add(cl) 

273 

274 try: 

275 return make_dict_structure_fn_from_attrs( 

276 _namedtuple_to_attrs(cl), 

277 cl, 

278 converter, 

279 _cattrs_forbid_extra_keys=forbid_extra_keys, 

280 _cattrs_use_detailed_validation=detailed_validation, 

281 _cattrs_use_linecache=use_linecache, 

282 **kwargs, 

283 ) 

284 finally: 

285 working_set.remove(cl) 

286 if not working_set: 

287 del already_generating.working_set 

288 

289 

290def namedtuple_dict_unstructure_factory( 

291 cl: type[tuple], 

292 converter: BaseConverter, 

293 omit_if_default: bool = False, 

294 use_linecache: bool = True, 

295 /, 

296 **kwargs: AttributeOverride, 

297) -> UnstructureHook: 

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

299 

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

301 will be omitted in the result dictionary. 

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

303 

304 .. versionadded:: 24.1.0 

305 """ 

306 try: 

307 working_set = already_generating.working_set 

308 except AttributeError: 

309 working_set = set() 

310 already_generating.working_set = working_set 

311 if cl in working_set: 

312 raise RecursionError() 

313 

314 working_set.add(cl) 

315 

316 try: 

317 return make_dict_unstructure_fn_from_attrs( 

318 _namedtuple_to_attrs(cl), 

319 cl, 

320 converter, 

321 _cattrs_omit_if_default=omit_if_default, 

322 _cattrs_use_linecache=use_linecache, 

323 **kwargs, 

324 ) 

325 finally: 

326 working_set.remove(cl) 

327 if not working_set: 

328 del already_generating.working_set 

329 

330 

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

332 """Is this type a defaultdict? 

333 

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

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

336 """ 

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

338 

339 

340def defaultdict_structure_factory( 

341 type: type[defaultdict], 

342 converter: BaseConverter, 

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

344) -> StructureHook: 

345 """A structure hook factory for defaultdicts. 

346 

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

348 """ 

349 if default_factory is NOTHING: 

350 default_factory = get_args(type)[1] 

351 return mapping_structure_factory( 

352 type, converter, partial(defaultdict, default_factory) 

353 )