1# SPDX-License-Identifier: MIT

2

3"""

4 These are keyword-only APIs that call `attr.s` and `attr.ib` with different

5 default values.

6"""

7

8

9 from functools import partial

10

11 from . import setters

12 from ._funcs import asdict as _asdict

13 from ._funcs import astuple as _astuple

14 from ._make import (

15 NOTHING,

16 _frozen_setattrs,

17 _ng_default_on_setattr,

18 attrib,

19 attrs,

20)

21 from .exceptions import UnannotatedAttributeError

22

23

24 def define(

25 maybe_cls=None,

26 *,

27 these=None,

28 repr=None,

29 unsafe_hash=None,

30 hash=None,

31 init=None,

32 slots=True,

33 frozen=False,

34 weakref_slot=True,

35 str=False,

36 auto_attribs=None,

37 kw_only=False,

38 cache_hash=False,

39 auto_exc=True,

40 eq=None,

41 order=False,

42 auto_detect=True,

43 getstate_setstate=None,

44 on_setattr=None,

45 field_transformer=None,

46 match_args=True,

47):

48 r"""

49 Define an ``attrs`` class.

50

51 Differences to the classic `attr.s` that it uses underneath:

52

53 - Automatically detect whether or not *auto_attribs* should be `True` (c.f.

54 *auto_attribs* parameter).

55 - If *frozen* is `False`, run converters and validators when setting an

56 attribute by default.

57 - *slots=True*

58

59 .. caution::

60

61 Usually this has only upsides and few visible effects in everyday

62 programming. But it *can* lead to some suprising behaviors, so please

63 make sure to read :term:`slotted classes`.

64 - *auto_exc=True*

65 - *auto_detect=True*

66 - *order=False*

67 - Some options that were only relevant on Python 2 or were kept around for

68 backwards-compatibility have been removed.

69

70 Please note that these are all defaults and you can change them as you

71 wish.

72

73 :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves

74 exactly like `attr.s`. If left `None`, `attr.s` will try to guess:

75

76 1. If any attributes are annotated and no unannotated `attrs.fields`\ s

77 are found, it assumes *auto_attribs=True*.

78 2. Otherwise it assumes *auto_attribs=False* and tries to collect

79 `attrs.fields`\ s.

80

81 For now, please refer to `attr.s` for the rest of the parameters.

82

83 .. versionadded:: 20.1.0

84 .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``.

85 .. versionadded:: 22.2.0

86 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).

87 """

88

89 def do_it(cls, auto_attribs):

90 return attrs(

91 maybe_cls=cls,

92 these=these,

93 repr=repr,

94 hash=hash,

95 unsafe_hash=unsafe_hash,

96 init=init,

97 slots=slots,

98 frozen=frozen,

99 weakref_slot=weakref_slot,

100 str=str,

101 auto_attribs=auto_attribs,

102 kw_only=kw_only,

103 cache_hash=cache_hash,

104 auto_exc=auto_exc,

105 eq=eq,

106 order=order,

107 auto_detect=auto_detect,

108 collect_by_mro=True,

109 getstate_setstate=getstate_setstate,

110 on_setattr=on_setattr,

111 field_transformer=field_transformer,

112 match_args=match_args,

113 )

114

115 def wrap(cls):

116 """

117 Making this a wrapper ensures this code runs during class creation.

118

119 We also ensure that frozen-ness of classes is inherited.

120 """

121 nonlocal frozen, on_setattr

122

123 had_on_setattr = on_setattr not in (None, setters.NO_OP)

124

125 # By default, mutable classes convert & validate on setattr.

126 if frozen is False and on_setattr is None:

127 on_setattr = _ng_default_on_setattr

128

129 # However, if we subclass a frozen class, we inherit the immutability

130 # and disable on_setattr.

131 for base_cls in cls.__bases__:

132 if base_cls.__setattr__ is _frozen_setattrs:

133 if had_on_setattr:

134 raise ValueError(

135 "Frozen classes can't use on_setattr "

136 "(frozen-ness was inherited)."

137 )

138

139 on_setattr = setters.NO_OP

140 break

141

142 if auto_attribs is not None:

143 return do_it(cls, auto_attribs)

144

145 try:

146 return do_it(cls, True)

147 except UnannotatedAttributeError:

148 return do_it(cls, False)

149

150 # maybe_cls's type depends on the usage of the decorator. It's a class

151 # if it's used as `@attrs` but ``None`` if used as `@attrs()`.

152 if maybe_cls is None:

153 return wrap

154 else:

155 return wrap(maybe_cls)

156

157

158 mutable = define

159 frozen = partial(define, frozen=True, on_setattr=None)

160

161

162 def field(

163 *,

164 default=NOTHING,

165 validator=None,

166 repr=True,

167 hash=None,

168 init=True,

169 metadata=None,

170 converter=None,

171 factory=None,

172 kw_only=False,

173 eq=None,

174 order=None,

175 on_setattr=None,

176 alias=None,

177):

178 """

179 Identical to `attr.ib`, except keyword-only and with some arguments

180 removed.

181

182 .. versionadded:: 20.1.0

183 """

184 return attrib(

185 default=default,

186 validator=validator,

187 repr=repr,

188 hash=hash,

189 init=init,

190 metadata=metadata,

191 converter=converter,

192 factory=factory,

193 kw_only=kw_only,

194 eq=eq,

195 order=order,

196 on_setattr=on_setattr,

197 alias=alias,

198 )

199

200

201 def asdict(inst, *, recurse=True, filter=None, value_serializer=None):

202 """

203 Same as `attr.asdict`, except that collections types are always retained

204 and dict is always used as *dict_factory*.

205

206 .. versionadded:: 21.3.0

207 """

208 return _asdict(

209 inst=inst,

210 recurse=recurse,

211 filter=filter,

212 value_serializer=value_serializer,

213 retain_collection_types=True,

214 )

215

216

217 def astuple(inst, *, recurse=True, filter=None):

218 """

219 Same as `attr.astuple`, except that collections types are always retained

220 and `tuple` is always used as the *tuple_factory*.

221

222 .. versionadded:: 21.3.0

223 """

224 return _astuple(

225 inst=inst, recurse=recurse, filter=filter, retain_collection_types=True

226 )