Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_funcs.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

106 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 

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 resolve_types( 

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

399): 

400 """ 

401 Resolve any strings and forward annotations in type annotations. 

402 

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

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

405 only use them for static type checking. 

406 

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

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

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

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

411 `typing.get_type_hints` for more details. 

412 

413 Args: 

414 cls (type): Class to resolve. 

415 

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

417 

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

419 

420 attribs (list | None): 

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

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

423 class yet. 

424 

425 include_extras (bool): 

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

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

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

429 accurately. 

430 

431 Raises: 

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

433 

434 attrs.exceptions.NotAnAttrsClassError: 

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

436 

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

438 

439 Returns: 

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

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

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

443 

444 .. versionadded:: 20.1.0 

445 .. versionadded:: 21.1.0 *attribs* 

446 .. versionadded:: 23.1.0 *include_extras* 

447 """ 

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

449 # done it already. 

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

451 import typing 

452 

453 kwargs = { 

454 "globalns": globalns, 

455 "localns": localns, 

456 "include_extras": include_extras, 

457 } 

458 

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

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

461 if field.name in hints: 

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

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

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

465 # been resolved. 

466 cls.__attrs_types_resolved__ = cls 

467 

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

469 return cls