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 ( 

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 AbcSet, 

23 get_args, 

24 get_origin, 

25 is_bare, 

26 is_frozenset, 

27 is_mapping, 

28 is_mutable_sequence, 

29 is_sequence, 

30 is_subclass, 

31) 

32from ._compat import is_mutable_set as is_set 

33from .dispatch import StructureHook, UnstructureHook 

34from .errors import IterableValidationError, IterableValidationNote 

35from .fns import identity 

36from .gen import ( 

37 AttributeOverride, 

38 already_generating, 

39 make_dict_structure_fn_from_attrs, 

40 make_dict_unstructure_fn_from_attrs, 

41 make_hetero_tuple_unstructure_fn, 

42 mapping_structure_factory, 

43 mapping_unstructure_factory, 

44) 

45from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory 

46 

47if TYPE_CHECKING: 

48 from .converters import BaseConverter 

49 

50__all__ = [ 

51 "defaultdict_structure_factory", 

52 "homogenous_tuple_structure_factory", 

53 "is_abstract_set", 

54 "is_any_set", 

55 "is_defaultdict", 

56 "is_frozenset", 

57 "is_mapping", 

58 "is_mutable_sequence", 

59 "is_namedtuple", 

60 "is_sequence", 

61 "is_set", 

62 "iterable_unstructure_factory", 

63 "list_structure_factory", 

64 "mapping_structure_factory", 

65 "mapping_unstructure_factory", 

66 "namedtuple_dict_structure_factory", 

67 "namedtuple_dict_unstructure_factory", 

68 "namedtuple_structure_factory", 

69 "namedtuple_unstructure_factory", 

70] 

71 

72 

73def is_any_set(type) -> bool: 

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

75 return is_set(type) or is_frozenset(type) 

76 

77 

78def is_abstract_set(type) -> bool: 

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

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

81 

82 

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

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

85 

86 if is_subclass(type, tuple): 

87 for cl in type.mro(): 

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

89 if NamedTuple in orig_bases: 

90 return True 

91 return False 

92 

93 

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

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

96 either. 

97 """ 

98 return all( 

99 converter.get_unstructure_hook(t) == identity 

100 for t in type.__annotations__.values() 

101 ) 

102 

103 

104T = TypeVar("T") 

105 

106 

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

108 """A hook factory for structuring lists. 

109 

110 Converts any given iterable into a list. 

111 """ 

112 

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

114 

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

116 return list(obj) 

117 

118 return structure_list 

119 

120 elem_type = type.__args__[0] 

121 

122 try: 

123 handler = converter.get_structure_hook(elem_type) 

124 except RecursionError: 

125 # Break the cycle by using late binding. 

126 handler = converter.structure 

127 

128 if converter.detailed_validation: 

129 

130 def structure_list( 

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

132 ) -> list[T]: 

133 errors = [] 

134 res = [] 

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

136 for e in obj: 

137 try: 

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

139 except Exception as e: 

140 msg = IterableValidationNote( 

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

142 ) 

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

144 errors.append(e) 

145 finally: 

146 ix += 1 

147 if errors: 

148 raise IterableValidationError( 

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

150 ) 

151 

152 return res 

153 

154 else: 

155 

156 def structure_list( 

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

158 ) -> list[T]: 

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

160 

161 return structure_list 

162 

163 

164def homogenous_tuple_structure_factory( 

165 type: type, converter: BaseConverter 

166) -> StructureHook: 

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

168 

169 Converts any given iterable into a tuple. 

170 """ 

171 

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

173 

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

175 return tuple(obj) 

176 

177 return structure_tuple 

178 

179 elem_type = type.__args__[0] 

180 

181 try: 

182 handler = converter.get_structure_hook(elem_type) 

183 except RecursionError: 

184 # Break the cycle by using late binding. 

185 handler = converter.structure 

186 

187 if converter.detailed_validation: 

188 

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

190 list_structure = list_structure_factory(type, converter) 

191 

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

193 return tuple(list_structure(obj, _)) 

194 

195 else: 

196 

197 def structure_tuple( 

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

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

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

201 

202 return structure_tuple 

203 

204 

205def namedtuple_unstructure_factory( 

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

207) -> UnstructureHook: 

208 """A hook factory for unstructuring namedtuples. 

209 

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

211 """ 

212 

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

214 return identity 

215 

216 return make_hetero_tuple_unstructure_fn( 

217 cl, 

218 converter, 

219 unstructure_to=tuple if unstructure_to is None else unstructure_to, 

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

221 ) 

222 

223 

224def namedtuple_structure_factory( 

225 cl: type[tuple], converter: BaseConverter 

226) -> StructureHook: 

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

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

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

230 base_hook = converter.get_structure_hook(hetero_tuple_type) 

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

232 

233 

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

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

236 return [ 

237 Attribute( 

238 name, 

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

240 None, 

241 False, 

242 False, 

243 False, 

244 True, 

245 False, 

246 type=a, 

247 alias=name, 

248 ) 

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

250 ] 

251 

252 

253def namedtuple_dict_structure_factory( 

254 cl: type[tuple], 

255 converter: BaseConverter, 

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

257 forbid_extra_keys: bool = False, 

258 use_linecache: bool = True, 

259 /, 

260 **kwargs: AttributeOverride, 

261) -> StructureHook: 

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

263 

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

265 if unknown keys are encountered. 

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

267 

268 .. versionadded:: 24.1.0 

269 """ 

270 try: 

271 working_set = already_generating.working_set 

272 except AttributeError: 

273 working_set = set() 

274 already_generating.working_set = working_set 

275 else: 

276 if cl in working_set: 

277 raise RecursionError() 

278 

279 working_set.add(cl) 

280 

281 try: 

282 return make_dict_structure_fn_from_attrs( 

283 _namedtuple_to_attrs(cl), 

284 cl, 

285 converter, 

286 _cattrs_forbid_extra_keys=forbid_extra_keys, 

287 _cattrs_use_detailed_validation=detailed_validation, 

288 _cattrs_use_linecache=use_linecache, 

289 **kwargs, 

290 ) 

291 finally: 

292 working_set.remove(cl) 

293 if not working_set: 

294 del already_generating.working_set 

295 

296 

297def namedtuple_dict_unstructure_factory( 

298 cl: type[tuple], 

299 converter: BaseConverter, 

300 omit_if_default: bool = False, 

301 use_linecache: bool = True, 

302 /, 

303 **kwargs: AttributeOverride, 

304) -> UnstructureHook: 

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

306 

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

308 will be omitted in the result dictionary. 

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

310 

311 .. versionadded:: 24.1.0 

312 """ 

313 try: 

314 working_set = already_generating.working_set 

315 except AttributeError: 

316 working_set = set() 

317 already_generating.working_set = working_set 

318 if cl in working_set: 

319 raise RecursionError() 

320 

321 working_set.add(cl) 

322 

323 try: 

324 return make_dict_unstructure_fn_from_attrs( 

325 _namedtuple_to_attrs(cl), 

326 cl, 

327 converter, 

328 _cattrs_omit_if_default=omit_if_default, 

329 _cattrs_use_linecache=use_linecache, 

330 **kwargs, 

331 ) 

332 finally: 

333 working_set.remove(cl) 

334 if not working_set: 

335 del already_generating.working_set 

336 

337 

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

339 """Is this type a defaultdict? 

340 

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

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

343 """ 

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

345 

346 

347def defaultdict_structure_factory( 

348 type: type[defaultdict], 

349 converter: BaseConverter, 

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

351) -> StructureHook: 

352 """A structure hook factory for defaultdicts. 

353 

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

355 """ 

356 if default_factory is NOTHING: 

357 default_factory = get_args(type)[1] 

358 return mapping_structure_factory( 

359 type, converter, partial(defaultdict, default_factory) 

360 )