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 homogeneous (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 # We have to structure into a list first anyway. 

182 list_structure = list_structure_factory(type, converter) 

183 

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

185 return tuple(list_structure(obj, _)) 

186 

187 else: 

188 

189 def structure_tuple( 

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

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

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

193 

194 return structure_tuple 

195 

196 

197def namedtuple_unstructure_factory( 

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

199) -> UnstructureHook: 

200 """A hook factory for unstructuring namedtuples. 

201 

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

203 """ 

204 

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

206 return identity 

207 

208 return make_hetero_tuple_unstructure_fn( 

209 cl, 

210 converter, 

211 unstructure_to=tuple if unstructure_to is None else unstructure_to, 

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

213 ) 

214 

215 

216def namedtuple_structure_factory( 

217 cl: type[tuple], converter: BaseConverter 

218) -> StructureHook: 

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

220 # We delegate to the existing infrastructure for heterogeneous tuples. 

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

222 base_hook = converter.get_structure_hook(hetero_tuple_type) 

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

224 

225 

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

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

228 return [ 

229 Attribute( 

230 name, 

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

232 None, 

233 False, 

234 False, 

235 False, 

236 True, 

237 False, 

238 type=a, 

239 alias=name, 

240 ) 

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

242 ] 

243 

244 

245def namedtuple_dict_structure_factory( 

246 cl: type[tuple], 

247 converter: BaseConverter, 

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

249 forbid_extra_keys: bool = False, 

250 use_linecache: bool = True, 

251 /, 

252 **kwargs: AttributeOverride, 

253) -> StructureHook: 

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

255 

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

257 if unknown keys are encountered. 

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 else: 

268 if cl in working_set: 

269 raise RecursionError() 

270 

271 working_set.add(cl) 

272 

273 try: 

274 return make_dict_structure_fn_from_attrs( 

275 _namedtuple_to_attrs(cl), 

276 cl, 

277 converter, 

278 _cattrs_forbid_extra_keys=forbid_extra_keys, 

279 _cattrs_detailed_validation=detailed_validation, 

280 _cattrs_use_linecache=use_linecache, 

281 **kwargs, 

282 ) 

283 finally: 

284 working_set.remove(cl) 

285 if not working_set: 

286 del already_generating.working_set 

287 

288 

289def namedtuple_dict_unstructure_factory( 

290 cl: type[tuple], 

291 converter: BaseConverter, 

292 omit_if_default: bool = False, 

293 use_linecache: bool = True, 

294 /, 

295 **kwargs: AttributeOverride, 

296) -> UnstructureHook: 

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

298 

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

300 will be omitted in the result dictionary. 

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

302 

303 .. versionadded:: 24.1.0 

304 """ 

305 try: 

306 working_set = already_generating.working_set 

307 except AttributeError: 

308 working_set = set() 

309 already_generating.working_set = working_set 

310 if cl in working_set: 

311 raise RecursionError() 

312 

313 working_set.add(cl) 

314 

315 try: 

316 return make_dict_unstructure_fn_from_attrs( 

317 _namedtuple_to_attrs(cl), 

318 cl, 

319 converter, 

320 _cattrs_omit_if_default=omit_if_default, 

321 _cattrs_use_linecache=use_linecache, 

322 **kwargs, 

323 ) 

324 finally: 

325 working_set.remove(cl) 

326 if not working_set: 

327 del already_generating.working_set 

328 

329 

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

331 """Is this type a defaultdict? 

332 

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

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

335 """ 

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

337 

338 

339def defaultdict_structure_factory( 

340 type: type[defaultdict], 

341 converter: BaseConverter, 

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

343) -> StructureHook: 

344 """A structure hook factory for defaultdicts. 

345 

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

347 """ 

348 if default_factory is NOTHING: 

349 default_factory = get_args(type)[1] 

350 return mapping_structure_factory( 

351 type, converter, partial(defaultdict, default_factory) 

352 )