Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

123 statements  

1# SPDX-License-Identifier: MIT 

2 

3 

4import copy 

5 

6from ._compat import PY_3_9_PLUS, get_generic_base 

7from ._make import _OBJ_SETATTR, NOTHING, fields 

8from .exceptions import AttrsAttributeNotFoundError 

9 

10 

11def asdict( 

12 inst, 

13 recurse=True, 

14 filter=None, 

15 dict_factory=dict, 

16 retain_collection_types=False, 

17 value_serializer=None, 

18): 

19 """ 

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

21 

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

23 

24 Args: 

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

26 

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

28 

29 filter (~typing.Callable): 

30 A callable whose return code determines whether an attribute or 

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

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

33 second argument. 

34 

35 dict_factory (~typing.Callable): 

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

37 ordered dictionaries instead of normal Python dictionaries, pass in 

38 ``collections.OrderedDict``. 

39 

40 retain_collection_types (bool): 

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

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

43 

44 value_serializer (typing.Callable | None): 

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

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

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

48 been applied. 

49 

50 Returns: 

51 Return type of *dict_factory*. 

52 

53 Raises: 

54 attrs.exceptions.NotAnAttrsClassError: 

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

56 

57 .. versionadded:: 16.0.0 *dict_factory* 

58 .. versionadded:: 16.1.0 *retain_collection_types* 

59 .. versionadded:: 20.3.0 *value_serializer* 

60 .. versionadded:: 21.3.0 

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

62 """ 

63 attrs = fields(inst.__class__) 

64 rv = dict_factory() 

65 for a in attrs: 

66 v = getattr(inst, a.name) 

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

68 continue 

69 

70 if value_serializer is not None: 

71 v = value_serializer(inst, a, v) 

72 

73 if recurse is True: 

74 if has(v.__class__): 

75 rv[a.name] = asdict( 

76 v, 

77 recurse=True, 

78 filter=filter, 

79 dict_factory=dict_factory, 

80 retain_collection_types=retain_collection_types, 

81 value_serializer=value_serializer, 

82 ) 

83 elif isinstance(v, (tuple, list, set, frozenset)): 

84 cf = v.__class__ if retain_collection_types is True else list 

85 items = [ 

86 _asdict_anything( 

87 i, 

88 is_key=False, 

89 filter=filter, 

90 dict_factory=dict_factory, 

91 retain_collection_types=retain_collection_types, 

92 value_serializer=value_serializer, 

93 ) 

94 for i in v 

95 ] 

96 try: 

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

98 except TypeError: 

99 if not issubclass(cf, tuple): 

100 raise 

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

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

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

104 elif isinstance(v, dict): 

105 df = dict_factory 

106 rv[a.name] = df( 

107 ( 

108 _asdict_anything( 

109 kk, 

110 is_key=True, 

111 filter=filter, 

112 dict_factory=df, 

113 retain_collection_types=retain_collection_types, 

114 value_serializer=value_serializer, 

115 ), 

116 _asdict_anything( 

117 vv, 

118 is_key=False, 

119 filter=filter, 

120 dict_factory=df, 

121 retain_collection_types=retain_collection_types, 

122 value_serializer=value_serializer, 

123 ), 

124 ) 

125 for kk, vv in v.items() 

126 ) 

127 else: 

128 rv[a.name] = v 

129 else: 

130 rv[a.name] = v 

131 return rv 

132 

133 

134def _asdict_anything( 

135 val, 

136 is_key, 

137 filter, 

138 dict_factory, 

139 retain_collection_types, 

140 value_serializer, 

141): 

142 """ 

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

144 """ 

145 if getattr(val.__class__, "__attrs_attrs__", None) is not None: 

146 # Attrs class. 

147 rv = asdict( 

148 val, 

149 recurse=True, 

150 filter=filter, 

151 dict_factory=dict_factory, 

152 retain_collection_types=retain_collection_types, 

153 value_serializer=value_serializer, 

154 ) 

155 elif isinstance(val, (tuple, list, set, frozenset)): 

156 if retain_collection_types is True: 

157 cf = val.__class__ 

158 elif is_key: 

159 cf = tuple 

160 else: 

161 cf = list 

162 

163 rv = cf( 

164 [ 

165 _asdict_anything( 

166 i, 

167 is_key=False, 

168 filter=filter, 

169 dict_factory=dict_factory, 

170 retain_collection_types=retain_collection_types, 

171 value_serializer=value_serializer, 

172 ) 

173 for i in val 

174 ] 

175 ) 

176 elif isinstance(val, dict): 

177 df = dict_factory 

178 rv = df( 

179 ( 

180 _asdict_anything( 

181 kk, 

182 is_key=True, 

183 filter=filter, 

184 dict_factory=df, 

185 retain_collection_types=retain_collection_types, 

186 value_serializer=value_serializer, 

187 ), 

188 _asdict_anything( 

189 vv, 

190 is_key=False, 

191 filter=filter, 

192 dict_factory=df, 

193 retain_collection_types=retain_collection_types, 

194 value_serializer=value_serializer, 

195 ), 

196 ) 

197 for kk, vv in val.items() 

198 ) 

199 else: 

200 rv = val 

201 if value_serializer is not None: 

202 rv = value_serializer(None, None, rv) 

203 

204 return rv 

205 

206 

207def astuple( 

208 inst, 

209 recurse=True, 

210 filter=None, 

211 tuple_factory=tuple, 

212 retain_collection_types=False, 

213): 

214 """ 

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

216 

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

218 

219 Args: 

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

221 

222 recurse (bool): 

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

224 

225 filter (~typing.Callable): 

226 A callable whose return code determines whether an attribute or 

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

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

229 second argument. 

230 

231 tuple_factory (~typing.Callable): 

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

233 instead of tuples. 

234 

235 retain_collection_types (bool): 

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

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

238 *recurse* is `True`. 

239 

240 Returns: 

241 Return type of *tuple_factory* 

242 

243 Raises: 

244 attrs.exceptions.NotAnAttrsClassError: 

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

246 

247 .. versionadded:: 16.2.0 

248 """ 

249 attrs = fields(inst.__class__) 

250 rv = [] 

251 retain = retain_collection_types # Very long. :/ 

252 for a in attrs: 

253 v = getattr(inst, a.name) 

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

255 continue 

256 if recurse is True: 

257 if has(v.__class__): 

258 rv.append( 

259 astuple( 

260 v, 

261 recurse=True, 

262 filter=filter, 

263 tuple_factory=tuple_factory, 

264 retain_collection_types=retain, 

265 ) 

266 ) 

267 elif isinstance(v, (tuple, list, set, frozenset)): 

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

269 items = [ 

270 ( 

271 astuple( 

272 j, 

273 recurse=True, 

274 filter=filter, 

275 tuple_factory=tuple_factory, 

276 retain_collection_types=retain, 

277 ) 

278 if has(j.__class__) 

279 else j 

280 ) 

281 for j in v 

282 ] 

283 try: 

284 rv.append(cf(items)) 

285 except TypeError: 

286 if not issubclass(cf, tuple): 

287 raise 

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

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

290 rv.append(cf(*items)) 

291 elif isinstance(v, dict): 

292 df = v.__class__ if retain is True else dict 

293 rv.append( 

294 df( 

295 ( 

296 ( 

297 astuple( 

298 kk, 

299 tuple_factory=tuple_factory, 

300 retain_collection_types=retain, 

301 ) 

302 if has(kk.__class__) 

303 else kk 

304 ), 

305 ( 

306 astuple( 

307 vv, 

308 tuple_factory=tuple_factory, 

309 retain_collection_types=retain, 

310 ) 

311 if has(vv.__class__) 

312 else vv 

313 ), 

314 ) 

315 for kk, vv in v.items() 

316 ) 

317 ) 

318 else: 

319 rv.append(v) 

320 else: 

321 rv.append(v) 

322 

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

324 

325 

326def has(cls): 

327 """ 

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

329 

330 Args: 

331 cls (type): Class to introspect. 

332 

333 Raises: 

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

335 

336 Returns: 

337 bool: 

338 """ 

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

340 if attrs is not None: 

341 return True 

342 

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

344 generic_base = get_generic_base(cls) 

345 if generic_base is not None: 

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

347 if generic_attrs is not None: 

348 # Stick it on here for speed next time. 

349 cls.__attrs_attrs__ = generic_attrs 

350 return generic_attrs is not None 

351 return False 

352 

353 

354def assoc(inst, **changes): 

355 """ 

356 Copy *inst* and apply *changes*. 

357 

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

359 that create the new instance. 

360 

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

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

363 

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

365 

366 Args: 

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

368 

369 changes: Keyword changes in the new copy. 

370 

371 Returns: 

372 A copy of inst with *changes* incorporated. 

373 

374 Raises: 

375 attrs.exceptions.AttrsAttributeNotFoundError: 

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

377 

378 attrs.exceptions.NotAnAttrsClassError: 

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

380 

381 .. deprecated:: 17.1.0 

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

383 removed du to the slightly different approach compared to 

384 `attrs.evolve`, though. 

385 """ 

386 new = copy.copy(inst) 

387 attrs = fields(inst.__class__) 

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

389 a = getattr(attrs, k, NOTHING) 

390 if a is NOTHING: 

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

392 raise AttrsAttributeNotFoundError(msg) 

393 _OBJ_SETATTR(new, k, v) 

394 return new 

395 

396 

397def evolve(*args, **changes): 

398 """ 

399 Create a new instance, based on the first positional argument with 

400 *changes* applied. 

401 

402 Args: 

403 

404 inst: 

405 Instance of a class with *attrs* attributes. *inst* must be passed 

406 as a positional argument. 

407 

408 changes: 

409 Keyword changes in the new copy. 

410 

411 Returns: 

412 A copy of inst with *changes* incorporated. 

413 

414 Raises: 

415 TypeError: 

416 If *attr_name* couldn't be found in the class ``__init__``. 

417 

418 attrs.exceptions.NotAnAttrsClassError: 

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

420 

421 .. versionadded:: 17.1.0 

422 .. deprecated:: 23.1.0 

423 It is now deprecated to pass the instance using the keyword argument 

424 *inst*. It will raise a warning until at least April 2024, after which 

425 it will become an error. Always pass the instance as a positional 

426 argument. 

427 .. versionchanged:: 24.1.0 

428 *inst* can't be passed as a keyword argument anymore. 

429 """ 

430 try: 

431 (inst,) = args 

432 except ValueError: 

433 msg = ( 

434 f"evolve() takes 1 positional argument, but {len(args)} were given" 

435 ) 

436 raise TypeError(msg) from None 

437 

438 cls = inst.__class__ 

439 attrs = fields(cls) 

440 for a in attrs: 

441 if not a.init: 

442 continue 

443 attr_name = a.name # To deal with private attributes. 

444 init_name = a.alias 

445 if init_name not in changes: 

446 changes[init_name] = getattr(inst, attr_name) 

447 

448 return cls(**changes) 

449 

450 

451def resolve_types( 

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

453): 

454 """ 

455 Resolve any strings and forward annotations in type annotations. 

456 

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

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

459 only use them for static type checking. 

460 

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

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

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

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

465 `typing.get_type_hints` for more details. 

466 

467 Args: 

468 cls (type): Class to resolve. 

469 

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

471 

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

473 

474 attribs (list | None): 

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

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

477 class yet. 

478 

479 include_extras (bool): 

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

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

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

483 accurately. 

484 

485 Raises: 

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

487 

488 attrs.exceptions.NotAnAttrsClassError: 

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

490 

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

492 

493 Returns: 

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

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

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

497 

498 .. versionadded:: 20.1.0 

499 .. versionadded:: 21.1.0 *attribs* 

500 .. versionadded:: 23.1.0 *include_extras* 

501 """ 

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

503 # done it already. 

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

505 import typing 

506 

507 kwargs = {"globalns": globalns, "localns": localns} 

508 

509 if PY_3_9_PLUS: 

510 kwargs["include_extras"] = include_extras 

511 

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

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

514 if field.name in hints: 

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

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

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

518 # been resolved. 

519 cls.__attrs_types_resolved__ = cls 

520 

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

522 return cls