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

131 statements  

1# SPDX-License-Identifier: MIT 

2 

3 

4import copy 

5 

6from ._compat import PY_3_9_PLUS, get_generic_base 

7from ._make import NOTHING, _obj_setattr, 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 :param inst: Instance of an *attrs*-decorated class. 

25 :param bool recurse: Recurse into classes that are also 

26 *attrs*-decorated. 

27 :param callable filter: A callable whose return code determines whether an 

28 attribute or element is included (``True``) or dropped (``False``). Is 

29 called with the `attrs.Attribute` as the first argument and the 

30 value as the second argument. 

31 :param callable dict_factory: A callable to produce dictionaries from. For 

32 example, to produce ordered dictionaries instead of normal Python 

33 dictionaries, pass in ``collections.OrderedDict``. 

34 :param bool retain_collection_types: Do not convert to ``list`` when 

35 encountering an attribute whose type is ``tuple`` or ``set``. Only 

36 meaningful if ``recurse`` is ``True``. 

37 :param Optional[callable] value_serializer: A hook that is called for every 

38 attribute or dict key/value. It receives the current instance, field 

39 and value and must return the (updated) value. The hook is run *after* 

40 the optional *filter* has been applied. 

41 

42 :rtype: return type of *dict_factory* 

43 

44 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* 

45 class. 

46 

47 .. versionadded:: 16.0.0 *dict_factory* 

48 .. versionadded:: 16.1.0 *retain_collection_types* 

49 .. versionadded:: 20.3.0 *value_serializer* 

50 .. versionadded:: 21.3.0 If a dict has a collection for a key, it is 

51 serialized as a tuple. 

52 """ 

53 attrs = fields(inst.__class__) 

54 rv = dict_factory() 

55 for a in attrs: 

56 v = getattr(inst, a.name) 

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

58 continue 

59 

60 if value_serializer is not None: 

61 v = value_serializer(inst, a, v) 

62 

63 if recurse is True: 

64 if has(v.__class__): 

65 rv[a.name] = asdict( 

66 v, 

67 recurse=True, 

68 filter=filter, 

69 dict_factory=dict_factory, 

70 retain_collection_types=retain_collection_types, 

71 value_serializer=value_serializer, 

72 ) 

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

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

75 items = [ 

76 _asdict_anything( 

77 i, 

78 is_key=False, 

79 filter=filter, 

80 dict_factory=dict_factory, 

81 retain_collection_types=retain_collection_types, 

82 value_serializer=value_serializer, 

83 ) 

84 for i in v 

85 ] 

86 try: 

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

88 except TypeError: 

89 if not issubclass(cf, tuple): 

90 raise 

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

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

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

94 elif isinstance(v, dict): 

95 df = dict_factory 

96 rv[a.name] = df( 

97 ( 

98 _asdict_anything( 

99 kk, 

100 is_key=True, 

101 filter=filter, 

102 dict_factory=df, 

103 retain_collection_types=retain_collection_types, 

104 value_serializer=value_serializer, 

105 ), 

106 _asdict_anything( 

107 vv, 

108 is_key=False, 

109 filter=filter, 

110 dict_factory=df, 

111 retain_collection_types=retain_collection_types, 

112 value_serializer=value_serializer, 

113 ), 

114 ) 

115 for kk, vv in v.items() 

116 ) 

117 else: 

118 rv[a.name] = v 

119 else: 

120 rv[a.name] = v 

121 return rv 

122 

123 

124def _asdict_anything( 

125 val, 

126 is_key, 

127 filter, 

128 dict_factory, 

129 retain_collection_types, 

130 value_serializer, 

131): 

132 """ 

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

134 """ 

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

136 # Attrs class. 

137 rv = asdict( 

138 val, 

139 recurse=True, 

140 filter=filter, 

141 dict_factory=dict_factory, 

142 retain_collection_types=retain_collection_types, 

143 value_serializer=value_serializer, 

144 ) 

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

146 if retain_collection_types is True: 

147 cf = val.__class__ 

148 elif is_key: 

149 cf = tuple 

150 else: 

151 cf = list 

152 

153 rv = cf( 

154 [ 

155 _asdict_anything( 

156 i, 

157 is_key=False, 

158 filter=filter, 

159 dict_factory=dict_factory, 

160 retain_collection_types=retain_collection_types, 

161 value_serializer=value_serializer, 

162 ) 

163 for i in val 

164 ] 

165 ) 

166 elif isinstance(val, dict): 

167 df = dict_factory 

168 rv = df( 

169 ( 

170 _asdict_anything( 

171 kk, 

172 is_key=True, 

173 filter=filter, 

174 dict_factory=df, 

175 retain_collection_types=retain_collection_types, 

176 value_serializer=value_serializer, 

177 ), 

178 _asdict_anything( 

179 vv, 

180 is_key=False, 

181 filter=filter, 

182 dict_factory=df, 

183 retain_collection_types=retain_collection_types, 

184 value_serializer=value_serializer, 

185 ), 

186 ) 

187 for kk, vv in val.items() 

188 ) 

189 else: 

190 rv = val 

191 if value_serializer is not None: 

192 rv = value_serializer(None, None, rv) 

193 

194 return rv 

195 

196 

197def astuple( 

198 inst, 

199 recurse=True, 

200 filter=None, 

201 tuple_factory=tuple, 

202 retain_collection_types=False, 

203): 

204 """ 

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

206 

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

208 

209 :param inst: Instance of an *attrs*-decorated class. 

210 :param bool recurse: Recurse into classes that are also 

211 *attrs*-decorated. 

212 :param callable filter: A callable whose return code determines whether an 

213 attribute or element is included (``True``) or dropped (``False``). Is 

214 called with the `attrs.Attribute` as the first argument and the 

215 value as the second argument. 

216 :param callable tuple_factory: A callable to produce tuples from. For 

217 example, to produce lists instead of tuples. 

218 :param bool retain_collection_types: Do not convert to ``list`` 

219 or ``dict`` when encountering an attribute which type is 

220 ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is 

221 ``True``. 

222 

223 :rtype: return type of *tuple_factory* 

224 

225 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* 

226 class. 

227 

228 .. versionadded:: 16.2.0 

229 """ 

230 attrs = fields(inst.__class__) 

231 rv = [] 

232 retain = retain_collection_types # Very long. :/ 

233 for a in attrs: 

234 v = getattr(inst, a.name) 

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

236 continue 

237 if recurse is True: 

238 if has(v.__class__): 

239 rv.append( 

240 astuple( 

241 v, 

242 recurse=True, 

243 filter=filter, 

244 tuple_factory=tuple_factory, 

245 retain_collection_types=retain, 

246 ) 

247 ) 

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

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

250 items = [ 

251 astuple( 

252 j, 

253 recurse=True, 

254 filter=filter, 

255 tuple_factory=tuple_factory, 

256 retain_collection_types=retain, 

257 ) 

258 if has(j.__class__) 

259 else j 

260 for j in v 

261 ] 

262 try: 

263 rv.append(cf(items)) 

264 except TypeError: 

265 if not issubclass(cf, tuple): 

266 raise 

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

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

269 rv.append(cf(*items)) 

270 elif isinstance(v, dict): 

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

272 rv.append( 

273 df( 

274 ( 

275 astuple( 

276 kk, 

277 tuple_factory=tuple_factory, 

278 retain_collection_types=retain, 

279 ) 

280 if has(kk.__class__) 

281 else kk, 

282 astuple( 

283 vv, 

284 tuple_factory=tuple_factory, 

285 retain_collection_types=retain, 

286 ) 

287 if has(vv.__class__) 

288 else vv, 

289 ) 

290 for kk, vv in v.items() 

291 ) 

292 ) 

293 else: 

294 rv.append(v) 

295 else: 

296 rv.append(v) 

297 

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

299 

300 

301def has(cls): 

302 """ 

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

304 

305 :param type cls: Class to introspect. 

306 :raise TypeError: If *cls* is not a class. 

307 

308 :rtype: bool 

309 """ 

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

311 if attrs is not None: 

312 return True 

313 

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

315 generic_base = get_generic_base(cls) 

316 if generic_base is not None: 

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

318 if generic_attrs is not None: 

319 # Stick it on here for speed next time. 

320 cls.__attrs_attrs__ = generic_attrs 

321 return generic_attrs is not None 

322 return False 

323 

324 

325def assoc(inst, **changes): 

326 """ 

327 Copy *inst* and apply *changes*. 

328 

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

330 that create the new instance. 

331 

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

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

334 

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

336 

337 :param inst: Instance of a class with *attrs* attributes. 

338 :param changes: Keyword changes in the new copy. 

339 

340 :return: A copy of inst with *changes* incorporated. 

341 

342 :raise attrs.exceptions.AttrsAttributeNotFoundError: If *attr_name* 

343 couldn't be found on *cls*. 

344 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* 

345 class. 

346 

347 .. deprecated:: 17.1.0 

348 Use `attrs.evolve` instead if you can. 

349 This function will not be removed du to the slightly different approach 

350 compared to `attrs.evolve`. 

351 """ 

352 new = copy.copy(inst) 

353 attrs = fields(inst.__class__) 

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

355 a = getattr(attrs, k, NOTHING) 

356 if a is NOTHING: 

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

358 raise AttrsAttributeNotFoundError(msg) 

359 _obj_setattr(new, k, v) 

360 return new 

361 

362 

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

364 """ 

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

366 *changes* applied. 

367 

368 :param inst: Instance of a class with *attrs* attributes. 

369 :param changes: Keyword changes in the new copy. 

370 

371 :return: A copy of inst with *changes* incorporated. 

372 

373 :raise TypeError: If *attr_name* couldn't be found in the class 

374 ``__init__``. 

375 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* 

376 class. 

377 

378 .. versionadded:: 17.1.0 

379 .. deprecated:: 23.1.0 

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

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

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

383 argument. 

384 """ 

385 # Try to get instance by positional argument first. 

386 # Use changes otherwise and warn it'll break. 

387 if args: 

388 try: 

389 (inst,) = args 

390 except ValueError: 

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

392 raise TypeError(msg) from None 

393 else: 

394 try: 

395 inst = changes.pop("inst") 

396 except KeyError: 

397 msg = "evolve() missing 1 required positional argument: 'inst'" 

398 raise TypeError(msg) from None 

399 

400 import warnings 

401 

402 warnings.warn( 

403 "Passing the instance per keyword argument is deprecated and " 

404 "will stop working in, or after, April 2024.", 

405 DeprecationWarning, 

406 stacklevel=2, 

407 ) 

408 

409 cls = inst.__class__ 

410 attrs = fields(cls) 

411 for a in attrs: 

412 if not a.init: 

413 continue 

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

415 init_name = a.alias 

416 if init_name not in changes: 

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

418 

419 return cls(**changes) 

420 

421 

422def resolve_types( 

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

424): 

425 """ 

426 Resolve any strings and forward annotations in type annotations. 

427 

428 This is only required if you need concrete types in `Attribute`'s *type* 

429 field. In other words, you don't need to resolve your types if you only 

430 use them for static type checking. 

431 

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

433 was created. If this is not what you want, e.g. if the name only exists 

434 inside a method, you may pass *globalns* or *localns* to specify other 

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

436 `typing.get_type_hints` for more details. 

437 

438 :param type cls: Class to resolve. 

439 :param Optional[dict] globalns: Dictionary containing global variables. 

440 :param Optional[dict] localns: Dictionary containing local variables. 

441 :param Optional[list] attribs: List of attribs for the given class. 

442 This is necessary when calling from inside a ``field_transformer`` 

443 since *cls* is not an *attrs* class yet. 

444 :param bool include_extras: Resolve more accurately, if possible. 

445 Pass ``include_extras`` to ``typing.get_hints``, if supported by the 

446 typing module. On supported Python versions (3.9+), this resolves the 

447 types more accurately. 

448 

449 :raise TypeError: If *cls* is not a class. 

450 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* 

451 class and you didn't pass any attribs. 

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

453 

454 :returns: *cls* so you can use this function also as a class decorator. 

455 Please note that you have to apply it **after** `attrs.define`. That 

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

457 

458 .. versionadded:: 20.1.0 

459 .. versionadded:: 21.1.0 *attribs* 

460 .. versionadded:: 23.1.0 *include_extras* 

461 

462 """ 

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

464 # done it already. 

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

466 import typing 

467 

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

469 

470 if PY_3_9_PLUS: 

471 kwargs["include_extras"] = include_extras 

472 

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

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

475 if field.name in hints: 

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

477 _obj_setattr(field, "type", hints[field.name]) 

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

479 # been resolved. 

480 cls.__attrs_types_resolved__ = cls 

481 

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

483 return cls