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

94 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# SPDX-License-Identifier: MIT 

2 

3 

4import copy 

5 

6from ._make import NOTHING, _obj_setattr, fields 

7from .exceptions import AttrsAttributeNotFoundError 

8 

9 

10def asdict( 

11 inst, 

12 recurse=True, 

13 filter=None, 

14 dict_factory=dict, 

15 retain_collection_types=False, 

16 value_serializer=None, 

17): 

18 """ 

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

20 

21 Optionally recurse into other ``attrs``-decorated classes. 

22 

23 :param inst: Instance of an ``attrs``-decorated class. 

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

25 ``attrs``-decorated. 

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

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

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

29 value as the second argument. 

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

31 example, to produce ordered dictionaries instead of normal Python 

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

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

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

35 meaningful if ``recurse`` is ``True``. 

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

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

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

39 the optional *filter* has been applied. 

40 

41 :rtype: return type of *dict_factory* 

42 

43 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` 

44 class. 

45 

46 .. versionadded:: 16.0.0 *dict_factory* 

47 .. versionadded:: 16.1.0 *retain_collection_types* 

48 .. versionadded:: 20.3.0 *value_serializer* 

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

50 serialized as a tuple. 

51 """ 

52 attrs = fields(inst.__class__) 

53 rv = dict_factory() 

54 for a in attrs: 

55 v = getattr(inst, a.name) 

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

57 continue 

58 

59 if value_serializer is not None: 

60 v = value_serializer(inst, a, v) 

61 

62 if recurse is True: 

63 if has(v.__class__): 

64 rv[a.name] = asdict( 

65 v, 

66 recurse=True, 

67 filter=filter, 

68 dict_factory=dict_factory, 

69 retain_collection_types=retain_collection_types, 

70 value_serializer=value_serializer, 

71 ) 

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

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

74 rv[a.name] = cf( 

75 [ 

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 ) 

87 elif isinstance(v, dict): 

88 df = dict_factory 

89 rv[a.name] = df( 

90 ( 

91 _asdict_anything( 

92 kk, 

93 is_key=True, 

94 filter=filter, 

95 dict_factory=df, 

96 retain_collection_types=retain_collection_types, 

97 value_serializer=value_serializer, 

98 ), 

99 _asdict_anything( 

100 vv, 

101 is_key=False, 

102 filter=filter, 

103 dict_factory=df, 

104 retain_collection_types=retain_collection_types, 

105 value_serializer=value_serializer, 

106 ), 

107 ) 

108 for kk, vv in v.items() 

109 ) 

110 else: 

111 rv[a.name] = v 

112 else: 

113 rv[a.name] = v 

114 return rv 

115 

116 

117def _asdict_anything( 

118 val, 

119 is_key, 

120 filter, 

121 dict_factory, 

122 retain_collection_types, 

123 value_serializer, 

124): 

125 """ 

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

127 """ 

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

129 # Attrs class. 

130 rv = asdict( 

131 val, 

132 recurse=True, 

133 filter=filter, 

134 dict_factory=dict_factory, 

135 retain_collection_types=retain_collection_types, 

136 value_serializer=value_serializer, 

137 ) 

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

139 if retain_collection_types is True: 

140 cf = val.__class__ 

141 elif is_key: 

142 cf = tuple 

143 else: 

144 cf = list 

145 

146 rv = cf( 

147 [ 

148 _asdict_anything( 

149 i, 

150 is_key=False, 

151 filter=filter, 

152 dict_factory=dict_factory, 

153 retain_collection_types=retain_collection_types, 

154 value_serializer=value_serializer, 

155 ) 

156 for i in val 

157 ] 

158 ) 

159 elif isinstance(val, dict): 

160 df = dict_factory 

161 rv = df( 

162 ( 

163 _asdict_anything( 

164 kk, 

165 is_key=True, 

166 filter=filter, 

167 dict_factory=df, 

168 retain_collection_types=retain_collection_types, 

169 value_serializer=value_serializer, 

170 ), 

171 _asdict_anything( 

172 vv, 

173 is_key=False, 

174 filter=filter, 

175 dict_factory=df, 

176 retain_collection_types=retain_collection_types, 

177 value_serializer=value_serializer, 

178 ), 

179 ) 

180 for kk, vv in val.items() 

181 ) 

182 else: 

183 rv = val 

184 if value_serializer is not None: 

185 rv = value_serializer(None, None, rv) 

186 

187 return rv 

188 

189 

190def astuple( 

191 inst, 

192 recurse=True, 

193 filter=None, 

194 tuple_factory=tuple, 

195 retain_collection_types=False, 

196): 

197 """ 

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

199 

200 Optionally recurse into other ``attrs``-decorated classes. 

201 

202 :param inst: Instance of an ``attrs``-decorated class. 

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

204 ``attrs``-decorated. 

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

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

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

208 value as the second argument. 

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

210 example, to produce lists instead of tuples. 

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

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

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

214 ``True``. 

215 

216 :rtype: return type of *tuple_factory* 

217 

218 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` 

219 class. 

220 

221 .. versionadded:: 16.2.0 

222 """ 

223 attrs = fields(inst.__class__) 

224 rv = [] 

225 retain = retain_collection_types # Very long. :/ 

226 for a in attrs: 

227 v = getattr(inst, a.name) 

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

229 continue 

230 if recurse is True: 

231 if has(v.__class__): 

232 rv.append( 

233 astuple( 

234 v, 

235 recurse=True, 

236 filter=filter, 

237 tuple_factory=tuple_factory, 

238 retain_collection_types=retain, 

239 ) 

240 ) 

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

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

243 rv.append( 

244 cf( 

245 [ 

246 astuple( 

247 j, 

248 recurse=True, 

249 filter=filter, 

250 tuple_factory=tuple_factory, 

251 retain_collection_types=retain, 

252 ) 

253 if has(j.__class__) 

254 else j 

255 for j in v 

256 ] 

257 ) 

258 ) 

259 elif isinstance(v, dict): 

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

261 rv.append( 

262 df( 

263 ( 

264 astuple( 

265 kk, 

266 tuple_factory=tuple_factory, 

267 retain_collection_types=retain, 

268 ) 

269 if has(kk.__class__) 

270 else kk, 

271 astuple( 

272 vv, 

273 tuple_factory=tuple_factory, 

274 retain_collection_types=retain, 

275 ) 

276 if has(vv.__class__) 

277 else vv, 

278 ) 

279 for kk, vv in v.items() 

280 ) 

281 ) 

282 else: 

283 rv.append(v) 

284 else: 

285 rv.append(v) 

286 

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

288 

289 

290def has(cls): 

291 """ 

292 Check whether *cls* is a class with ``attrs`` attributes. 

293 

294 :param type cls: Class to introspect. 

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

296 

297 :rtype: bool 

298 """ 

299 return getattr(cls, "__attrs_attrs__", None) is not None 

300 

301 

302def assoc(inst, **changes): 

303 """ 

304 Copy *inst* and apply *changes*. 

305 

306 :param inst: Instance of a class with ``attrs`` attributes. 

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

308 

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

310 

311 :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't 

312 be found on *cls*. 

313 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` 

314 class. 

315 

316 .. deprecated:: 17.1.0 

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

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

319 compared to `attrs.evolve`. 

320 """ 

321 import warnings 

322 

323 warnings.warn( 

324 "assoc is deprecated and will be removed after 2018/01.", 

325 DeprecationWarning, 

326 stacklevel=2, 

327 ) 

328 new = copy.copy(inst) 

329 attrs = fields(inst.__class__) 

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

331 a = getattr(attrs, k, NOTHING) 

332 if a is NOTHING: 

333 raise AttrsAttributeNotFoundError( 

334 f"{k} is not an attrs attribute on {new.__class__}." 

335 ) 

336 _obj_setattr(new, k, v) 

337 return new 

338 

339 

340def evolve(inst, **changes): 

341 """ 

342 Create a new instance, based on *inst* with *changes* applied. 

343 

344 :param inst: Instance of a class with ``attrs`` attributes. 

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

346 

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

348 

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

350 ``__init__``. 

351 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` 

352 class. 

353 

354 .. versionadded:: 17.1.0 

355 """ 

356 cls = inst.__class__ 

357 attrs = fields(cls) 

358 for a in attrs: 

359 if not a.init: 

360 continue 

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

362 init_name = a.alias 

363 if init_name not in changes: 

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

365 

366 return cls(**changes) 

367 

368 

369def resolve_types(cls, globalns=None, localns=None, attribs=None): 

370 """ 

371 Resolve any strings and forward annotations in type annotations. 

372 

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

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

375 use them for static type checking. 

376 

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

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

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

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

381 `typing.get_type_hints` for more details. 

382 

383 :param type cls: Class to resolve. 

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

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

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

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

388 since *cls* is not an ``attrs`` class yet. 

389 

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

391 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` 

392 class and you didn't pass any attribs. 

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

394 

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

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

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

398 

399 .. versionadded:: 20.1.0 

400 .. versionadded:: 21.1.0 *attribs* 

401 

402 """ 

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

404 # done it already. 

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

406 import typing 

407 

408 hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) 

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

410 if field.name in hints: 

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

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

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

414 # been resolved. 

415 cls.__attrs_types_resolved__ = cls 

416 

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

418 return cls