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

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 

4from ._compat import get_generic_base 

5from ._make import _OBJ_SETATTR, NOTHING, fields 

6from .exceptions import AttrsAttributeNotFoundError 

7 

8 

9_ATOMIC_TYPES = frozenset( 

10 { 

11 type(None), 

12 bool, 

13 int, 

14 float, 

15 str, 

16 complex, 

17 bytes, 

18 type(...), 

19 type, 

20 range, 

21 property, 

22 } 

23) 

24 

25 

26def asdict( 

27 inst, 

28 recurse=True, 

29 filter=None, 

30 dict_factory=dict, 

31 retain_collection_types=False, 

32 value_serializer=None, 

33): 

34 """ 

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

36 

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

38 

39 Args: 

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

41 

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

43 

44 filter (~typing.Callable): 

45 A callable whose return code determines whether an attribute or 

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

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

48 second argument. 

49 

50 dict_factory (~typing.Callable): 

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

52 ordered dictionaries instead of normal Python dictionaries, pass in 

53 ``collections.OrderedDict``. 

54 

55 retain_collection_types (bool): 

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

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

58 

59 value_serializer (typing.Callable | None): 

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

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

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

63 been applied. 

64 

65 Returns: 

66 Return type of *dict_factory*. 

67 

68 Raises: 

69 attrs.exceptions.NotAnAttrsClassError: 

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

71 

72 .. versionadded:: 16.0.0 *dict_factory* 

73 .. versionadded:: 16.1.0 *retain_collection_types* 

74 .. versionadded:: 20.3.0 *value_serializer* 

75 .. versionadded:: 21.3.0 

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

77 """ 

78 attrs = fields(inst.__class__) 

79 rv = dict_factory() 

80 for a in attrs: 

81 v = getattr(inst, a.name) 

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

83 continue 

84 

85 if value_serializer is not None: 

86 v = value_serializer(inst, a, v) 

87 

88 if recurse is True: 

89 value_type = type(v) 

90 if value_type in _ATOMIC_TYPES: 

91 rv[a.name] = v 

92 elif has(value_type): 

93 rv[a.name] = asdict( 

94 v, 

95 recurse=True, 

96 filter=filter, 

97 dict_factory=dict_factory, 

98 retain_collection_types=retain_collection_types, 

99 value_serializer=value_serializer, 

100 ) 

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

102 cf = value_type if retain_collection_types is True else list 

103 items = [ 

104 _asdict_anything( 

105 i, 

106 is_key=False, 

107 filter=filter, 

108 dict_factory=dict_factory, 

109 retain_collection_types=retain_collection_types, 

110 value_serializer=value_serializer, 

111 ) 

112 for i in v 

113 ] 

114 try: 

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

116 except TypeError: 

117 if not issubclass(cf, tuple): 

118 raise 

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

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

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

122 elif issubclass(value_type, dict): 

123 df = dict_factory 

124 rv[a.name] = df( 

125 ( 

126 _asdict_anything( 

127 kk, 

128 is_key=True, 

129 filter=filter, 

130 dict_factory=df, 

131 retain_collection_types=retain_collection_types, 

132 value_serializer=value_serializer, 

133 ), 

134 _asdict_anything( 

135 vv, 

136 is_key=False, 

137 filter=filter, 

138 dict_factory=df, 

139 retain_collection_types=retain_collection_types, 

140 value_serializer=value_serializer, 

141 ), 

142 ) 

143 for kk, vv in v.items() 

144 ) 

145 else: 

146 rv[a.name] = v 

147 else: 

148 rv[a.name] = v 

149 return rv 

150 

151 

152def _asdict_anything( 

153 val, 

154 is_key, 

155 filter, 

156 dict_factory, 

157 retain_collection_types, 

158 value_serializer, 

159): 

160 """ 

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

162 """ 

163 val_type = type(val) 

164 if val_type in _ATOMIC_TYPES: 

165 rv = val 

166 if value_serializer is not None: 

167 rv = value_serializer(None, None, rv) 

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

169 # Attrs class. 

170 rv = asdict( 

171 val, 

172 recurse=True, 

173 filter=filter, 

174 dict_factory=dict_factory, 

175 retain_collection_types=retain_collection_types, 

176 value_serializer=value_serializer, 

177 ) 

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

179 if retain_collection_types is True: 

180 cf = val.__class__ 

181 elif is_key: 

182 cf = tuple 

183 else: 

184 cf = list 

185 

186 rv = cf( 

187 [ 

188 _asdict_anything( 

189 i, 

190 is_key=False, 

191 filter=filter, 

192 dict_factory=dict_factory, 

193 retain_collection_types=retain_collection_types, 

194 value_serializer=value_serializer, 

195 ) 

196 for i in val 

197 ] 

198 ) 

199 elif issubclass(val_type, dict): 

200 df = dict_factory 

201 rv = df( 

202 ( 

203 _asdict_anything( 

204 kk, 

205 is_key=True, 

206 filter=filter, 

207 dict_factory=df, 

208 retain_collection_types=retain_collection_types, 

209 value_serializer=value_serializer, 

210 ), 

211 _asdict_anything( 

212 vv, 

213 is_key=False, 

214 filter=filter, 

215 dict_factory=df, 

216 retain_collection_types=retain_collection_types, 

217 value_serializer=value_serializer, 

218 ), 

219 ) 

220 for kk, vv in val.items() 

221 ) 

222 else: 

223 rv = val 

224 if value_serializer is not None: 

225 rv = value_serializer(None, None, rv) 

226 

227 return rv 

228 

229 

230def astuple( 

231 inst, 

232 recurse=True, 

233 filter=None, 

234 tuple_factory=tuple, 

235 retain_collection_types=False, 

236): 

237 """ 

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

239 

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

241 

242 Args: 

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

244 

245 recurse (bool): 

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

247 

248 filter (~typing.Callable): 

249 A callable whose return code determines whether an attribute or 

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

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

252 second argument. 

253 

254 tuple_factory (~typing.Callable): 

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

256 instead of tuples. 

257 

258 retain_collection_types (bool): 

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

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

261 *recurse* is `True`. 

262 

263 Returns: 

264 Return type of *tuple_factory* 

265 

266 Raises: 

267 attrs.exceptions.NotAnAttrsClassError: 

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

269 

270 .. versionadded:: 16.2.0 

271 """ 

272 attrs = fields(inst.__class__) 

273 rv = [] 

274 retain = retain_collection_types # Very long. :/ 

275 for a in attrs: 

276 v = getattr(inst, a.name) 

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

278 continue 

279 value_type = type(v) 

280 if recurse is True: 

281 if value_type in _ATOMIC_TYPES: 

282 rv.append(v) 

283 elif has(value_type): 

284 rv.append( 

285 astuple( 

286 v, 

287 recurse=True, 

288 filter=filter, 

289 tuple_factory=tuple_factory, 

290 retain_collection_types=retain, 

291 ) 

292 ) 

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

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

295 items = [ 

296 ( 

297 astuple( 

298 j, 

299 recurse=True, 

300 filter=filter, 

301 tuple_factory=tuple_factory, 

302 retain_collection_types=retain, 

303 ) 

304 if has(j.__class__) 

305 else j 

306 ) 

307 for j in v 

308 ] 

309 try: 

310 rv.append(cf(items)) 

311 except TypeError: 

312 if not issubclass(cf, tuple): 

313 raise 

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

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

316 rv.append(cf(*items)) 

317 elif issubclass(value_type, dict): 

318 df = value_type if retain is True else dict 

319 rv.append( 

320 df( 

321 ( 

322 ( 

323 astuple( 

324 kk, 

325 tuple_factory=tuple_factory, 

326 retain_collection_types=retain, 

327 ) 

328 if has(kk.__class__) 

329 else kk 

330 ), 

331 ( 

332 astuple( 

333 vv, 

334 tuple_factory=tuple_factory, 

335 retain_collection_types=retain, 

336 ) 

337 if has(vv.__class__) 

338 else vv 

339 ), 

340 ) 

341 for kk, vv in v.items() 

342 ) 

343 ) 

344 else: 

345 rv.append(v) 

346 else: 

347 rv.append(v) 

348 

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

350 

351 

352def has(cls): 

353 """ 

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

355 

356 Args: 

357 cls (type): Class to introspect. 

358 

359 Raises: 

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

361 

362 Returns: 

363 bool: 

364 """ 

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

366 if attrs is not None: 

367 return True 

368 

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

370 generic_base = get_generic_base(cls) 

371 if generic_base is not None: 

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

373 if generic_attrs is not None: 

374 # Stick it on here for speed next time. 

375 cls.__attrs_attrs__ = generic_attrs 

376 return generic_attrs is not None 

377 return False 

378 

379 

380def assoc(inst, **changes): 

381 """ 

382 Copy *inst* and apply *changes*. 

383 

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

385 that create the new instance. 

386 

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

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

389 

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

391 

392 Args: 

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

394 

395 changes: Keyword changes in the new copy. 

396 

397 Returns: 

398 A copy of inst with *changes* incorporated. 

399 

400 Raises: 

401 attrs.exceptions.AttrsAttributeNotFoundError: 

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

403 

404 attrs.exceptions.NotAnAttrsClassError: 

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

406 

407 .. deprecated:: 17.1.0 

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

409 removed du to the slightly different approach compared to 

410 `attrs.evolve`, though. 

411 """ 

412 import copy 

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 by passing ``include_extras=True`` to 

455 `typing.get_type_hints`. 

456 

457 Raises: 

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

459 

460 attrs.exceptions.NotAnAttrsClassError: 

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

462 

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

464 

465 Returns: 

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

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

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

469 

470 .. versionadded:: 20.1.0 

471 .. versionadded:: 21.1.0 *attribs* 

472 .. versionadded:: 23.1.0 *include_extras* 

473 """ 

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

475 # done it already. 

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

477 import typing 

478 

479 kwargs = { 

480 "globalns": globalns, 

481 "localns": localns, 

482 "include_extras": include_extras, 

483 } 

484 

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

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

487 if field.name in hints: 

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

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

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

491 # been resolved. 

492 cls.__attrs_types_resolved__ = cls 

493 

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

495 return cls