Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_funcs.py: 19%

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

118 statements  

1# SPDX-License-Identifier: MIT 

2 

3 

4import copy 

5 

6from ._compat import get_generic_base 

7from ._make import _OBJ_SETATTR, NOTHING, fields 

8from .exceptions import AttrsAttributeNotFoundError 

9 

10 

11_ATOMIC_TYPES = frozenset( 

12 { 

13 type(None), 

14 bool, 

15 int, 

16 float, 

17 str, 

18 complex, 

19 bytes, 

20 type(...), 

21 type, 

22 range, 

23 property, 

24 } 

25) 

26 

27 

28def asdict( 

29 inst, 

30 recurse=True, 

31 filter=None, 

32 dict_factory=dict, 

33 retain_collection_types=False, 

34 value_serializer=None, 

35): 

36 """ 

37 Return the *attrs* attribute values of *inst* as a dict. 

38 

39 Optionally recurse into other *attrs*-decorated classes. 

40 

41 Args: 

42 inst: Instance of an *attrs*-decorated class. 

43 

44 recurse (bool): Recurse into classes that are also *attrs*-decorated. 

45 

46 filter (~typing.Callable): 

47 A callable whose return code determines whether an attribute or 

48 element is included (`True`) or dropped (`False`). Is called with 

49 the `attrs.Attribute` as the first argument and the value as the 

50 second argument. 

51 

52 dict_factory (~typing.Callable): 

53 A callable to produce dictionaries from. For example, to produce 

54 ordered dictionaries instead of normal Python dictionaries, pass in 

55 ``collections.OrderedDict``. 

56 

57 retain_collection_types (bool): 

58 Do not convert to `list` when encountering an attribute whose type 

59 is `tuple` or `set`. Only meaningful if *recurse* is `True`. 

60 

61 value_serializer (typing.Callable | None): 

62 A hook that is called for every attribute or dict key/value. It 

63 receives the current instance, field and value and must return the 

64 (updated) value. The hook is run *after* the optional *filter* has 

65 been applied. 

66 

67 Returns: 

68 Return type of *dict_factory*. 

69 

70 Raises: 

71 attrs.exceptions.NotAnAttrsClassError: 

72 If *cls* is not an *attrs* class. 

73 

74 .. versionadded:: 16.0.0 *dict_factory* 

75 .. versionadded:: 16.1.0 *retain_collection_types* 

76 .. versionadded:: 20.3.0 *value_serializer* 

77 .. versionadded:: 21.3.0 

78 If a dict has a collection for a key, it is serialized as a tuple. 

79 """ 

80 attrs = fields(inst.__class__) 

81 rv = dict_factory() 

82 for a in attrs: 

83 v = getattr(inst, a.name) 

84 if filter is not None and not filter(a, v): 

85 continue 

86 

87 if value_serializer is not None: 

88 v = value_serializer(inst, a, v) 

89 

90 if recurse is True: 

91 value_type = type(v) 

92 if value_type in _ATOMIC_TYPES: 

93 rv[a.name] = v 

94 elif has(value_type): 

95 rv[a.name] = asdict( 

96 v, 

97 recurse=True, 

98 filter=filter, 

99 dict_factory=dict_factory, 

100 retain_collection_types=retain_collection_types, 

101 value_serializer=value_serializer, 

102 ) 

103 elif issubclass(value_type, (tuple, list, set, frozenset)): 

104 cf = value_type if retain_collection_types is True else list 

105 items = [ 

106 _asdict_anything( 

107 i, 

108 is_key=False, 

109 filter=filter, 

110 dict_factory=dict_factory, 

111 retain_collection_types=retain_collection_types, 

112 value_serializer=value_serializer, 

113 ) 

114 for i in v 

115 ] 

116 try: 

117 rv[a.name] = cf(items) 

118 except TypeError: 

119 if not issubclass(cf, tuple): 

120 raise 

121 # Workaround for TypeError: cf.__new__() missing 1 required 

122 # positional argument (which appears, for a namedturle) 

123 rv[a.name] = cf(*items) 

124 elif issubclass(value_type, dict): 

125 df = dict_factory 

126 rv[a.name] = df( 

127 ( 

128 _asdict_anything( 

129 kk, 

130 is_key=True, 

131 filter=filter, 

132 dict_factory=df, 

133 retain_collection_types=retain_collection_types, 

134 value_serializer=value_serializer, 

135 ), 

136 _asdict_anything( 

137 vv, 

138 is_key=False, 

139 filter=filter, 

140 dict_factory=df, 

141 retain_collection_types=retain_collection_types, 

142 value_serializer=value_serializer, 

143 ), 

144 ) 

145 for kk, vv in v.items() 

146 ) 

147 else: 

148 rv[a.name] = v 

149 else: 

150 rv[a.name] = v 

151 return rv 

152 

153 

154def _asdict_anything( 

155 val, 

156 is_key, 

157 filter, 

158 dict_factory, 

159 retain_collection_types, 

160 value_serializer, 

161): 

162 """ 

163 ``asdict`` only works on attrs instances, this works on anything. 

164 """ 

165 val_type = type(val) 

166 if val_type in _ATOMIC_TYPES: 

167 rv = val 

168 if value_serializer is not None: 

169 rv = value_serializer(None, None, rv) 

170 elif getattr(val_type, "__attrs_attrs__", None) is not None: 

171 # Attrs class. 

172 rv = asdict( 

173 val, 

174 recurse=True, 

175 filter=filter, 

176 dict_factory=dict_factory, 

177 retain_collection_types=retain_collection_types, 

178 value_serializer=value_serializer, 

179 ) 

180 elif issubclass(val_type, (tuple, list, set, frozenset)): 

181 if retain_collection_types is True: 

182 cf = val.__class__ 

183 elif is_key: 

184 cf = tuple 

185 else: 

186 cf = list 

187 

188 rv = cf( 

189 [ 

190 _asdict_anything( 

191 i, 

192 is_key=False, 

193 filter=filter, 

194 dict_factory=dict_factory, 

195 retain_collection_types=retain_collection_types, 

196 value_serializer=value_serializer, 

197 ) 

198 for i in val 

199 ] 

200 ) 

201 elif issubclass(val_type, dict): 

202 df = dict_factory 

203 rv = df( 

204 ( 

205 _asdict_anything( 

206 kk, 

207 is_key=True, 

208 filter=filter, 

209 dict_factory=df, 

210 retain_collection_types=retain_collection_types, 

211 value_serializer=value_serializer, 

212 ), 

213 _asdict_anything( 

214 vv, 

215 is_key=False, 

216 filter=filter, 

217 dict_factory=df, 

218 retain_collection_types=retain_collection_types, 

219 value_serializer=value_serializer, 

220 ), 

221 ) 

222 for kk, vv in val.items() 

223 ) 

224 else: 

225 rv = val 

226 if value_serializer is not None: 

227 rv = value_serializer(None, None, rv) 

228 

229 return rv 

230 

231 

232def astuple( 

233 inst, 

234 recurse=True, 

235 filter=None, 

236 tuple_factory=tuple, 

237 retain_collection_types=False, 

238): 

239 """ 

240 Return the *attrs* attribute values of *inst* as a tuple. 

241 

242 Optionally recurse into other *attrs*-decorated classes. 

243 

244 Args: 

245 inst: Instance of an *attrs*-decorated class. 

246 

247 recurse (bool): 

248 Recurse into classes that are also *attrs*-decorated. 

249 

250 filter (~typing.Callable): 

251 A callable whose return code determines whether an attribute or 

252 element is included (`True`) or dropped (`False`). Is called with 

253 the `attrs.Attribute` as the first argument and the value as the 

254 second argument. 

255 

256 tuple_factory (~typing.Callable): 

257 A callable to produce tuples from. For example, to produce lists 

258 instead of tuples. 

259 

260 retain_collection_types (bool): 

261 Do not convert to `list` or `dict` when encountering an attribute 

262 which type is `tuple`, `dict` or `set`. Only meaningful if 

263 *recurse* is `True`. 

264 

265 Returns: 

266 Return type of *tuple_factory* 

267 

268 Raises: 

269 attrs.exceptions.NotAnAttrsClassError: 

270 If *cls* is not an *attrs* class. 

271 

272 .. versionadded:: 16.2.0 

273 """ 

274 attrs = fields(inst.__class__) 

275 rv = [] 

276 retain = retain_collection_types # Very long. :/ 

277 for a in attrs: 

278 v = getattr(inst, a.name) 

279 if filter is not None and not filter(a, v): 

280 continue 

281 value_type = type(v) 

282 if recurse is True: 

283 if value_type in _ATOMIC_TYPES: 

284 rv.append(v) 

285 elif has(value_type): 

286 rv.append( 

287 astuple( 

288 v, 

289 recurse=True, 

290 filter=filter, 

291 tuple_factory=tuple_factory, 

292 retain_collection_types=retain, 

293 ) 

294 ) 

295 elif issubclass(value_type, (tuple, list, set, frozenset)): 

296 cf = v.__class__ if retain is True else list 

297 items = [ 

298 ( 

299 astuple( 

300 j, 

301 recurse=True, 

302 filter=filter, 

303 tuple_factory=tuple_factory, 

304 retain_collection_types=retain, 

305 ) 

306 if has(j.__class__) 

307 else j 

308 ) 

309 for j in v 

310 ] 

311 try: 

312 rv.append(cf(items)) 

313 except TypeError: 

314 if not issubclass(cf, tuple): 

315 raise 

316 # Workaround for TypeError: cf.__new__() missing 1 required 

317 # positional argument (which appears, for a namedturle) 

318 rv.append(cf(*items)) 

319 elif issubclass(value_type, dict): 

320 df = value_type if retain is True else dict 

321 rv.append( 

322 df( 

323 ( 

324 ( 

325 astuple( 

326 kk, 

327 tuple_factory=tuple_factory, 

328 retain_collection_types=retain, 

329 ) 

330 if has(kk.__class__) 

331 else kk 

332 ), 

333 ( 

334 astuple( 

335 vv, 

336 tuple_factory=tuple_factory, 

337 retain_collection_types=retain, 

338 ) 

339 if has(vv.__class__) 

340 else vv 

341 ), 

342 ) 

343 for kk, vv in v.items() 

344 ) 

345 ) 

346 else: 

347 rv.append(v) 

348 else: 

349 rv.append(v) 

350 

351 return rv if tuple_factory is list else tuple_factory(rv) 

352 

353 

354def has(cls): 

355 """ 

356 Check whether *cls* is a class with *attrs* attributes. 

357 

358 Args: 

359 cls (type): Class to introspect. 

360 

361 Raises: 

362 TypeError: If *cls* is not a class. 

363 

364 Returns: 

365 bool: 

366 """ 

367 attrs = getattr(cls, "__attrs_attrs__", None) 

368 if attrs is not None: 

369 return True 

370 

371 # No attrs, maybe it's a specialized generic (A[str])? 

372 generic_base = get_generic_base(cls) 

373 if generic_base is not None: 

374 generic_attrs = getattr(generic_base, "__attrs_attrs__", None) 

375 if generic_attrs is not None: 

376 # Stick it on here for speed next time. 

377 cls.__attrs_attrs__ = generic_attrs 

378 return generic_attrs is not None 

379 return False 

380 

381 

382def assoc(inst, **changes): 

383 """ 

384 Copy *inst* and apply *changes*. 

385 

386 This is different from `evolve` that applies the changes to the arguments 

387 that create the new instance. 

388 

389 `evolve`'s behavior is preferable, but there are `edge cases`_ where it 

390 doesn't work. Therefore `assoc` is deprecated, but will not be removed. 

391 

392 .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251 

393 

394 Args: 

395 inst: Instance of a class with *attrs* attributes. 

396 

397 changes: Keyword changes in the new copy. 

398 

399 Returns: 

400 A copy of inst with *changes* incorporated. 

401 

402 Raises: 

403 attrs.exceptions.AttrsAttributeNotFoundError: 

404 If *attr_name* couldn't be found on *cls*. 

405 

406 attrs.exceptions.NotAnAttrsClassError: 

407 If *cls* is not an *attrs* class. 

408 

409 .. deprecated:: 17.1.0 

410 Use `attrs.evolve` instead if you can. This function will not be 

411 removed du to the slightly different approach compared to 

412 `attrs.evolve`, though. 

413 """ 

414 new = copy.copy(inst) 

415 attrs = fields(inst.__class__) 

416 for k, v in changes.items(): 

417 a = getattr(attrs, k, NOTHING) 

418 if a is NOTHING: 

419 msg = f"{k} is not an attrs attribute on {new.__class__}." 

420 raise AttrsAttributeNotFoundError(msg) 

421 _OBJ_SETATTR(new, k, v) 

422 return new 

423 

424 

425def resolve_types( 

426 cls, globalns=None, localns=None, attribs=None, include_extras=True 

427): 

428 """ 

429 Resolve any strings and forward annotations in type annotations. 

430 

431 This is only required if you need concrete types in :class:`Attribute`'s 

432 *type* field. In other words, you don't need to resolve your types if you 

433 only use them for static type checking. 

434 

435 With no arguments, names will be looked up in the module in which the class 

436 was created. If this is not what you want, for example, if the name only 

437 exists inside a method, you may pass *globalns* or *localns* to specify 

438 other dictionaries in which to look up these names. See the docs of 

439 `typing.get_type_hints` for more details. 

440 

441 Args: 

442 cls (type): Class to resolve. 

443 

444 globalns (dict | None): Dictionary containing global variables. 

445 

446 localns (dict | None): Dictionary containing local variables. 

447 

448 attribs (list | None): 

449 List of attribs for the given class. This is necessary when calling 

450 from inside a ``field_transformer`` since *cls* is not an *attrs* 

451 class yet. 

452 

453 include_extras (bool): 

454 Resolve more accurately, if possible. Pass ``include_extras`` to 

455 ``typing.get_hints``, if supported by the typing module. On 

456 supported Python versions (3.9+), this resolves the types more 

457 accurately. 

458 

459 Raises: 

460 TypeError: If *cls* is not a class. 

461 

462 attrs.exceptions.NotAnAttrsClassError: 

463 If *cls* is not an *attrs* class and you didn't pass any attribs. 

464 

465 NameError: If types cannot be resolved because of missing variables. 

466 

467 Returns: 

468 *cls* so you can use this function also as a class decorator. Please 

469 note that you have to apply it **after** `attrs.define`. That means the 

470 decorator has to come in the line **before** `attrs.define`. 

471 

472 .. versionadded:: 20.1.0 

473 .. versionadded:: 21.1.0 *attribs* 

474 .. versionadded:: 23.1.0 *include_extras* 

475 """ 

476 # Since calling get_type_hints is expensive we cache whether we've 

477 # done it already. 

478 if getattr(cls, "__attrs_types_resolved__", None) != cls: 

479 import typing 

480 

481 kwargs = { 

482 "globalns": globalns, 

483 "localns": localns, 

484 "include_extras": include_extras, 

485 } 

486 

487 hints = typing.get_type_hints(cls, **kwargs) 

488 for field in fields(cls) if attribs is None else attribs: 

489 if field.name in hints: 

490 # Since fields have been frozen we must work around it. 

491 _OBJ_SETATTR(field, "type", hints[field.name]) 

492 # We store the class we resolved so that subclasses know they haven't 

493 # been resolved. 

494 cls.__attrs_types_resolved__ = cls 

495 

496 # Return the class so you can use it as a decorator too. 

497 return cls