Show More
@@ -0,0 +1,486 b'' | |||||
|
1 | import sys | |||
|
2 | ||||
|
3 | from typing import ( | |||
|
4 | Any, | |||
|
5 | Callable, | |||
|
6 | ClassVar, | |||
|
7 | Dict, | |||
|
8 | Generic, | |||
|
9 | List, | |||
|
10 | Mapping, | |||
|
11 | Optional, | |||
|
12 | Protocol, | |||
|
13 | Sequence, | |||
|
14 | Tuple, | |||
|
15 | Type, | |||
|
16 | TypeVar, | |||
|
17 | Union, | |||
|
18 | overload, | |||
|
19 | ) | |||
|
20 | ||||
|
21 | # `import X as X` is required to make these public | |||
|
22 | from . import converters as converters | |||
|
23 | from . import exceptions as exceptions | |||
|
24 | from . import filters as filters | |||
|
25 | from . import setters as setters | |||
|
26 | from . import validators as validators | |||
|
27 | from ._cmp import cmp_using as cmp_using | |||
|
28 | from ._version_info import VersionInfo | |||
|
29 | ||||
|
30 | __version__: str | |||
|
31 | __version_info__: VersionInfo | |||
|
32 | __title__: str | |||
|
33 | __description__: str | |||
|
34 | __url__: str | |||
|
35 | __uri__: str | |||
|
36 | __author__: str | |||
|
37 | __email__: str | |||
|
38 | __license__: str | |||
|
39 | __copyright__: str | |||
|
40 | ||||
|
41 | _T = TypeVar("_T") | |||
|
42 | _C = TypeVar("_C", bound=type) | |||
|
43 | ||||
|
44 | _EqOrderType = Union[bool, Callable[[Any], Any]] | |||
|
45 | _ValidatorType = Callable[[Any, Attribute[_T], _T], Any] | |||
|
46 | _ConverterType = Callable[[Any], Any] | |||
|
47 | _FilterType = Callable[[Attribute[_T], _T], bool] | |||
|
48 | _ReprType = Callable[[Any], str] | |||
|
49 | _ReprArgType = Union[bool, _ReprType] | |||
|
50 | _OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any] | |||
|
51 | _OnSetAttrArgType = Union[ | |||
|
52 | _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType | |||
|
53 | ] | |||
|
54 | _FieldTransformer = Callable[ | |||
|
55 | [type, List[Attribute[Any]]], List[Attribute[Any]] | |||
|
56 | ] | |||
|
57 | # FIXME: in reality, if multiple validators are passed they must be in a list | |||
|
58 | # or tuple, but those are invariant and so would prevent subtypes of | |||
|
59 | # _ValidatorType from working when passed in a list or tuple. | |||
|
60 | _ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] | |||
|
61 | ||||
|
62 | # A protocol to be able to statically accept an attrs class. | |||
|
63 | class AttrsInstance(Protocol): | |||
|
64 | __attrs_attrs__: ClassVar[Any] | |||
|
65 | ||||
|
66 | # _make -- | |||
|
67 | ||||
|
68 | NOTHING: object | |||
|
69 | ||||
|
70 | # NOTE: Factory lies about its return type to make this possible: | |||
|
71 | # `x: List[int] # = Factory(list)` | |||
|
72 | # Work around mypy issue #4554 in the common case by using an overload. | |||
|
73 | if sys.version_info >= (3, 8): | |||
|
74 | from typing import Literal | |||
|
75 | @overload | |||
|
76 | def Factory(factory: Callable[[], _T]) -> _T: ... | |||
|
77 | @overload | |||
|
78 | def Factory( | |||
|
79 | factory: Callable[[Any], _T], | |||
|
80 | takes_self: Literal[True], | |||
|
81 | ) -> _T: ... | |||
|
82 | @overload | |||
|
83 | def Factory( | |||
|
84 | factory: Callable[[], _T], | |||
|
85 | takes_self: Literal[False], | |||
|
86 | ) -> _T: ... | |||
|
87 | ||||
|
88 | else: | |||
|
89 | @overload | |||
|
90 | def Factory(factory: Callable[[], _T]) -> _T: ... | |||
|
91 | @overload | |||
|
92 | def Factory( | |||
|
93 | factory: Union[Callable[[Any], _T], Callable[[], _T]], | |||
|
94 | takes_self: bool = ..., | |||
|
95 | ) -> _T: ... | |||
|
96 | ||||
|
97 | # Static type inference support via __dataclass_transform__ implemented as per: | |||
|
98 | # https://github.com/microsoft/pyright/blob/1.1.135/specs/dataclass_transforms.md | |||
|
99 | # This annotation must be applied to all overloads of "define" and "attrs" | |||
|
100 | # | |||
|
101 | # NOTE: This is a typing construct and does not exist at runtime. Extensions | |||
|
102 | # wrapping attrs decorators should declare a separate __dataclass_transform__ | |||
|
103 | # signature in the extension module using the specification linked above to | |||
|
104 | # provide pyright support. | |||
|
105 | def __dataclass_transform__( | |||
|
106 | *, | |||
|
107 | eq_default: bool = True, | |||
|
108 | order_default: bool = False, | |||
|
109 | kw_only_default: bool = False, | |||
|
110 | field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()), | |||
|
111 | ) -> Callable[[_T], _T]: ... | |||
|
112 | ||||
|
113 | class Attribute(Generic[_T]): | |||
|
114 | name: str | |||
|
115 | default: Optional[_T] | |||
|
116 | validator: Optional[_ValidatorType[_T]] | |||
|
117 | repr: _ReprArgType | |||
|
118 | cmp: _EqOrderType | |||
|
119 | eq: _EqOrderType | |||
|
120 | order: _EqOrderType | |||
|
121 | hash: Optional[bool] | |||
|
122 | init: bool | |||
|
123 | converter: Optional[_ConverterType] | |||
|
124 | metadata: Dict[Any, Any] | |||
|
125 | type: Optional[Type[_T]] | |||
|
126 | kw_only: bool | |||
|
127 | on_setattr: _OnSetAttrType | |||
|
128 | def evolve(self, **changes: Any) -> "Attribute[Any]": ... | |||
|
129 | ||||
|
130 | # NOTE: We had several choices for the annotation to use for type arg: | |||
|
131 | # 1) Type[_T] | |||
|
132 | # - Pros: Handles simple cases correctly | |||
|
133 | # - Cons: Might produce less informative errors in the case of conflicting | |||
|
134 | # TypeVars e.g. `attr.ib(default='bad', type=int)` | |||
|
135 | # 2) Callable[..., _T] | |||
|
136 | # - Pros: Better error messages than #1 for conflicting TypeVars | |||
|
137 | # - Cons: Terrible error messages for validator checks. | |||
|
138 | # e.g. attr.ib(type=int, validator=validate_str) | |||
|
139 | # -> error: Cannot infer function type argument | |||
|
140 | # 3) type (and do all of the work in the mypy plugin) | |||
|
141 | # - Pros: Simple here, and we could customize the plugin with our own errors. | |||
|
142 | # - Cons: Would need to write mypy plugin code to handle all the cases. | |||
|
143 | # We chose option #1. | |||
|
144 | ||||
|
145 | # `attr` lies about its return type to make the following possible: | |||
|
146 | # attr() -> Any | |||
|
147 | # attr(8) -> int | |||
|
148 | # attr(validator=<some callable>) -> Whatever the callable expects. | |||
|
149 | # This makes this type of assignments possible: | |||
|
150 | # x: int = attr(8) | |||
|
151 | # | |||
|
152 | # This form catches explicit None or no default but with no other arguments | |||
|
153 | # returns Any. | |||
|
154 | @overload | |||
|
155 | def attrib( | |||
|
156 | default: None = ..., | |||
|
157 | validator: None = ..., | |||
|
158 | repr: _ReprArgType = ..., | |||
|
159 | cmp: Optional[_EqOrderType] = ..., | |||
|
160 | hash: Optional[bool] = ..., | |||
|
161 | init: bool = ..., | |||
|
162 | metadata: Optional[Mapping[Any, Any]] = ..., | |||
|
163 | type: None = ..., | |||
|
164 | converter: None = ..., | |||
|
165 | factory: None = ..., | |||
|
166 | kw_only: bool = ..., | |||
|
167 | eq: Optional[_EqOrderType] = ..., | |||
|
168 | order: Optional[_EqOrderType] = ..., | |||
|
169 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
170 | ) -> Any: ... | |||
|
171 | ||||
|
172 | # This form catches an explicit None or no default and infers the type from the | |||
|
173 | # other arguments. | |||
|
174 | @overload | |||
|
175 | def attrib( | |||
|
176 | default: None = ..., | |||
|
177 | validator: Optional[_ValidatorArgType[_T]] = ..., | |||
|
178 | repr: _ReprArgType = ..., | |||
|
179 | cmp: Optional[_EqOrderType] = ..., | |||
|
180 | hash: Optional[bool] = ..., | |||
|
181 | init: bool = ..., | |||
|
182 | metadata: Optional[Mapping[Any, Any]] = ..., | |||
|
183 | type: Optional[Type[_T]] = ..., | |||
|
184 | converter: Optional[_ConverterType] = ..., | |||
|
185 | factory: Optional[Callable[[], _T]] = ..., | |||
|
186 | kw_only: bool = ..., | |||
|
187 | eq: Optional[_EqOrderType] = ..., | |||
|
188 | order: Optional[_EqOrderType] = ..., | |||
|
189 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
190 | ) -> _T: ... | |||
|
191 | ||||
|
192 | # This form catches an explicit default argument. | |||
|
193 | @overload | |||
|
194 | def attrib( | |||
|
195 | default: _T, | |||
|
196 | validator: Optional[_ValidatorArgType[_T]] = ..., | |||
|
197 | repr: _ReprArgType = ..., | |||
|
198 | cmp: Optional[_EqOrderType] = ..., | |||
|
199 | hash: Optional[bool] = ..., | |||
|
200 | init: bool = ..., | |||
|
201 | metadata: Optional[Mapping[Any, Any]] = ..., | |||
|
202 | type: Optional[Type[_T]] = ..., | |||
|
203 | converter: Optional[_ConverterType] = ..., | |||
|
204 | factory: Optional[Callable[[], _T]] = ..., | |||
|
205 | kw_only: bool = ..., | |||
|
206 | eq: Optional[_EqOrderType] = ..., | |||
|
207 | order: Optional[_EqOrderType] = ..., | |||
|
208 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
209 | ) -> _T: ... | |||
|
210 | ||||
|
211 | # This form covers type=non-Type: e.g. forward references (str), Any | |||
|
212 | @overload | |||
|
213 | def attrib( | |||
|
214 | default: Optional[_T] = ..., | |||
|
215 | validator: Optional[_ValidatorArgType[_T]] = ..., | |||
|
216 | repr: _ReprArgType = ..., | |||
|
217 | cmp: Optional[_EqOrderType] = ..., | |||
|
218 | hash: Optional[bool] = ..., | |||
|
219 | init: bool = ..., | |||
|
220 | metadata: Optional[Mapping[Any, Any]] = ..., | |||
|
221 | type: object = ..., | |||
|
222 | converter: Optional[_ConverterType] = ..., | |||
|
223 | factory: Optional[Callable[[], _T]] = ..., | |||
|
224 | kw_only: bool = ..., | |||
|
225 | eq: Optional[_EqOrderType] = ..., | |||
|
226 | order: Optional[_EqOrderType] = ..., | |||
|
227 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
228 | ) -> Any: ... | |||
|
229 | @overload | |||
|
230 | def field( | |||
|
231 | *, | |||
|
232 | default: None = ..., | |||
|
233 | validator: None = ..., | |||
|
234 | repr: _ReprArgType = ..., | |||
|
235 | hash: Optional[bool] = ..., | |||
|
236 | init: bool = ..., | |||
|
237 | metadata: Optional[Mapping[Any, Any]] = ..., | |||
|
238 | converter: None = ..., | |||
|
239 | factory: None = ..., | |||
|
240 | kw_only: bool = ..., | |||
|
241 | eq: Optional[bool] = ..., | |||
|
242 | order: Optional[bool] = ..., | |||
|
243 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
244 | ) -> Any: ... | |||
|
245 | ||||
|
246 | # This form catches an explicit None or no default and infers the type from the | |||
|
247 | # other arguments. | |||
|
248 | @overload | |||
|
249 | def field( | |||
|
250 | *, | |||
|
251 | default: None = ..., | |||
|
252 | validator: Optional[_ValidatorArgType[_T]] = ..., | |||
|
253 | repr: _ReprArgType = ..., | |||
|
254 | hash: Optional[bool] = ..., | |||
|
255 | init: bool = ..., | |||
|
256 | metadata: Optional[Mapping[Any, Any]] = ..., | |||
|
257 | converter: Optional[_ConverterType] = ..., | |||
|
258 | factory: Optional[Callable[[], _T]] = ..., | |||
|
259 | kw_only: bool = ..., | |||
|
260 | eq: Optional[_EqOrderType] = ..., | |||
|
261 | order: Optional[_EqOrderType] = ..., | |||
|
262 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
263 | ) -> _T: ... | |||
|
264 | ||||
|
265 | # This form catches an explicit default argument. | |||
|
266 | @overload | |||
|
267 | def field( | |||
|
268 | *, | |||
|
269 | default: _T, | |||
|
270 | validator: Optional[_ValidatorArgType[_T]] = ..., | |||
|
271 | repr: _ReprArgType = ..., | |||
|
272 | hash: Optional[bool] = ..., | |||
|
273 | init: bool = ..., | |||
|
274 | metadata: Optional[Mapping[Any, Any]] = ..., | |||
|
275 | converter: Optional[_ConverterType] = ..., | |||
|
276 | factory: Optional[Callable[[], _T]] = ..., | |||
|
277 | kw_only: bool = ..., | |||
|
278 | eq: Optional[_EqOrderType] = ..., | |||
|
279 | order: Optional[_EqOrderType] = ..., | |||
|
280 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
281 | ) -> _T: ... | |||
|
282 | ||||
|
283 | # This form covers type=non-Type: e.g. forward references (str), Any | |||
|
284 | @overload | |||
|
285 | def field( | |||
|
286 | *, | |||
|
287 | default: Optional[_T] = ..., | |||
|
288 | validator: Optional[_ValidatorArgType[_T]] = ..., | |||
|
289 | repr: _ReprArgType = ..., | |||
|
290 | hash: Optional[bool] = ..., | |||
|
291 | init: bool = ..., | |||
|
292 | metadata: Optional[Mapping[Any, Any]] = ..., | |||
|
293 | converter: Optional[_ConverterType] = ..., | |||
|
294 | factory: Optional[Callable[[], _T]] = ..., | |||
|
295 | kw_only: bool = ..., | |||
|
296 | eq: Optional[_EqOrderType] = ..., | |||
|
297 | order: Optional[_EqOrderType] = ..., | |||
|
298 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
299 | ) -> Any: ... | |||
|
300 | @overload | |||
|
301 | @__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) | |||
|
302 | def attrs( | |||
|
303 | maybe_cls: _C, | |||
|
304 | these: Optional[Dict[str, Any]] = ..., | |||
|
305 | repr_ns: Optional[str] = ..., | |||
|
306 | repr: bool = ..., | |||
|
307 | cmp: Optional[_EqOrderType] = ..., | |||
|
308 | hash: Optional[bool] = ..., | |||
|
309 | init: bool = ..., | |||
|
310 | slots: bool = ..., | |||
|
311 | frozen: bool = ..., | |||
|
312 | weakref_slot: bool = ..., | |||
|
313 | str: bool = ..., | |||
|
314 | auto_attribs: bool = ..., | |||
|
315 | kw_only: bool = ..., | |||
|
316 | cache_hash: bool = ..., | |||
|
317 | auto_exc: bool = ..., | |||
|
318 | eq: Optional[_EqOrderType] = ..., | |||
|
319 | order: Optional[_EqOrderType] = ..., | |||
|
320 | auto_detect: bool = ..., | |||
|
321 | collect_by_mro: bool = ..., | |||
|
322 | getstate_setstate: Optional[bool] = ..., | |||
|
323 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
324 | field_transformer: Optional[_FieldTransformer] = ..., | |||
|
325 | match_args: bool = ..., | |||
|
326 | ) -> _C: ... | |||
|
327 | @overload | |||
|
328 | @__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) | |||
|
329 | def attrs( | |||
|
330 | maybe_cls: None = ..., | |||
|
331 | these: Optional[Dict[str, Any]] = ..., | |||
|
332 | repr_ns: Optional[str] = ..., | |||
|
333 | repr: bool = ..., | |||
|
334 | cmp: Optional[_EqOrderType] = ..., | |||
|
335 | hash: Optional[bool] = ..., | |||
|
336 | init: bool = ..., | |||
|
337 | slots: bool = ..., | |||
|
338 | frozen: bool = ..., | |||
|
339 | weakref_slot: bool = ..., | |||
|
340 | str: bool = ..., | |||
|
341 | auto_attribs: bool = ..., | |||
|
342 | kw_only: bool = ..., | |||
|
343 | cache_hash: bool = ..., | |||
|
344 | auto_exc: bool = ..., | |||
|
345 | eq: Optional[_EqOrderType] = ..., | |||
|
346 | order: Optional[_EqOrderType] = ..., | |||
|
347 | auto_detect: bool = ..., | |||
|
348 | collect_by_mro: bool = ..., | |||
|
349 | getstate_setstate: Optional[bool] = ..., | |||
|
350 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
351 | field_transformer: Optional[_FieldTransformer] = ..., | |||
|
352 | match_args: bool = ..., | |||
|
353 | ) -> Callable[[_C], _C]: ... | |||
|
354 | @overload | |||
|
355 | @__dataclass_transform__(field_descriptors=(attrib, field)) | |||
|
356 | def define( | |||
|
357 | maybe_cls: _C, | |||
|
358 | *, | |||
|
359 | these: Optional[Dict[str, Any]] = ..., | |||
|
360 | repr: bool = ..., | |||
|
361 | hash: Optional[bool] = ..., | |||
|
362 | init: bool = ..., | |||
|
363 | slots: bool = ..., | |||
|
364 | frozen: bool = ..., | |||
|
365 | weakref_slot: bool = ..., | |||
|
366 | str: bool = ..., | |||
|
367 | auto_attribs: bool = ..., | |||
|
368 | kw_only: bool = ..., | |||
|
369 | cache_hash: bool = ..., | |||
|
370 | auto_exc: bool = ..., | |||
|
371 | eq: Optional[bool] = ..., | |||
|
372 | order: Optional[bool] = ..., | |||
|
373 | auto_detect: bool = ..., | |||
|
374 | getstate_setstate: Optional[bool] = ..., | |||
|
375 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
376 | field_transformer: Optional[_FieldTransformer] = ..., | |||
|
377 | match_args: bool = ..., | |||
|
378 | ) -> _C: ... | |||
|
379 | @overload | |||
|
380 | @__dataclass_transform__(field_descriptors=(attrib, field)) | |||
|
381 | def define( | |||
|
382 | maybe_cls: None = ..., | |||
|
383 | *, | |||
|
384 | these: Optional[Dict[str, Any]] = ..., | |||
|
385 | repr: bool = ..., | |||
|
386 | hash: Optional[bool] = ..., | |||
|
387 | init: bool = ..., | |||
|
388 | slots: bool = ..., | |||
|
389 | frozen: bool = ..., | |||
|
390 | weakref_slot: bool = ..., | |||
|
391 | str: bool = ..., | |||
|
392 | auto_attribs: bool = ..., | |||
|
393 | kw_only: bool = ..., | |||
|
394 | cache_hash: bool = ..., | |||
|
395 | auto_exc: bool = ..., | |||
|
396 | eq: Optional[bool] = ..., | |||
|
397 | order: Optional[bool] = ..., | |||
|
398 | auto_detect: bool = ..., | |||
|
399 | getstate_setstate: Optional[bool] = ..., | |||
|
400 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
401 | field_transformer: Optional[_FieldTransformer] = ..., | |||
|
402 | match_args: bool = ..., | |||
|
403 | ) -> Callable[[_C], _C]: ... | |||
|
404 | ||||
|
405 | mutable = define | |||
|
406 | frozen = define # they differ only in their defaults | |||
|
407 | ||||
|
408 | def fields(cls: Type[AttrsInstance]) -> Any: ... | |||
|
409 | def fields_dict(cls: Type[AttrsInstance]) -> Dict[str, Attribute[Any]]: ... | |||
|
410 | def validate(inst: AttrsInstance) -> None: ... | |||
|
411 | def resolve_types( | |||
|
412 | cls: _C, | |||
|
413 | globalns: Optional[Dict[str, Any]] = ..., | |||
|
414 | localns: Optional[Dict[str, Any]] = ..., | |||
|
415 | attribs: Optional[List[Attribute[Any]]] = ..., | |||
|
416 | ) -> _C: ... | |||
|
417 | ||||
|
418 | # TODO: add support for returning a proper attrs class from the mypy plugin | |||
|
419 | # we use Any instead of _CountingAttr so that e.g. `make_class('Foo', | |||
|
420 | # [attr.ib()])` is valid | |||
|
421 | def make_class( | |||
|
422 | name: str, | |||
|
423 | attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], | |||
|
424 | bases: Tuple[type, ...] = ..., | |||
|
425 | repr_ns: Optional[str] = ..., | |||
|
426 | repr: bool = ..., | |||
|
427 | cmp: Optional[_EqOrderType] = ..., | |||
|
428 | hash: Optional[bool] = ..., | |||
|
429 | init: bool = ..., | |||
|
430 | slots: bool = ..., | |||
|
431 | frozen: bool = ..., | |||
|
432 | weakref_slot: bool = ..., | |||
|
433 | str: bool = ..., | |||
|
434 | auto_attribs: bool = ..., | |||
|
435 | kw_only: bool = ..., | |||
|
436 | cache_hash: bool = ..., | |||
|
437 | auto_exc: bool = ..., | |||
|
438 | eq: Optional[_EqOrderType] = ..., | |||
|
439 | order: Optional[_EqOrderType] = ..., | |||
|
440 | collect_by_mro: bool = ..., | |||
|
441 | on_setattr: Optional[_OnSetAttrArgType] = ..., | |||
|
442 | field_transformer: Optional[_FieldTransformer] = ..., | |||
|
443 | ) -> type: ... | |||
|
444 | ||||
|
445 | # _funcs -- | |||
|
446 | ||||
|
447 | # TODO: add support for returning TypedDict from the mypy plugin | |||
|
448 | # FIXME: asdict/astuple do not honor their factory args. Waiting on one of | |||
|
449 | # these: | |||
|
450 | # https://github.com/python/mypy/issues/4236 | |||
|
451 | # https://github.com/python/typing/issues/253 | |||
|
452 | # XXX: remember to fix attrs.asdict/astuple too! | |||
|
453 | def asdict( | |||
|
454 | inst: AttrsInstance, | |||
|
455 | recurse: bool = ..., | |||
|
456 | filter: Optional[_FilterType[Any]] = ..., | |||
|
457 | dict_factory: Type[Mapping[Any, Any]] = ..., | |||
|
458 | retain_collection_types: bool = ..., | |||
|
459 | value_serializer: Optional[ | |||
|
460 | Callable[[type, Attribute[Any], Any], Any] | |||
|
461 | ] = ..., | |||
|
462 | tuple_keys: Optional[bool] = ..., | |||
|
463 | ) -> Dict[str, Any]: ... | |||
|
464 | ||||
|
465 | # TODO: add support for returning NamedTuple from the mypy plugin | |||
|
466 | def astuple( | |||
|
467 | inst: AttrsInstance, | |||
|
468 | recurse: bool = ..., | |||
|
469 | filter: Optional[_FilterType[Any]] = ..., | |||
|
470 | tuple_factory: Type[Sequence[Any]] = ..., | |||
|
471 | retain_collection_types: bool = ..., | |||
|
472 | ) -> Tuple[Any, ...]: ... | |||
|
473 | def has(cls: type) -> bool: ... | |||
|
474 | def assoc(inst: _T, **changes: Any) -> _T: ... | |||
|
475 | def evolve(inst: _T, **changes: Any) -> _T: ... | |||
|
476 | ||||
|
477 | # _config -- | |||
|
478 | ||||
|
479 | def set_run_validators(run: bool) -> None: ... | |||
|
480 | def get_run_validators() -> bool: ... | |||
|
481 | ||||
|
482 | # aliases -- | |||
|
483 | ||||
|
484 | s = attributes = attrs | |||
|
485 | ib = attr = attrib | |||
|
486 | dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;) |
@@ -0,0 +1,155 b'' | |||||
|
1 | # SPDX-License-Identifier: MIT | |||
|
2 | ||||
|
3 | ||||
|
4 | import functools | |||
|
5 | import types | |||
|
6 | ||||
|
7 | from ._make import _make_ne | |||
|
8 | ||||
|
9 | ||||
|
10 | _operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} | |||
|
11 | ||||
|
12 | ||||
|
13 | def cmp_using( | |||
|
14 | eq=None, | |||
|
15 | lt=None, | |||
|
16 | le=None, | |||
|
17 | gt=None, | |||
|
18 | ge=None, | |||
|
19 | require_same_type=True, | |||
|
20 | class_name="Comparable", | |||
|
21 | ): | |||
|
22 | """ | |||
|
23 | Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and | |||
|
24 | ``cmp`` arguments to customize field comparison. | |||
|
25 | ||||
|
26 | The resulting class will have a full set of ordering methods if | |||
|
27 | at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided. | |||
|
28 | ||||
|
29 | :param Optional[callable] eq: `callable` used to evaluate equality | |||
|
30 | of two objects. | |||
|
31 | :param Optional[callable] lt: `callable` used to evaluate whether | |||
|
32 | one object is less than another object. | |||
|
33 | :param Optional[callable] le: `callable` used to evaluate whether | |||
|
34 | one object is less than or equal to another object. | |||
|
35 | :param Optional[callable] gt: `callable` used to evaluate whether | |||
|
36 | one object is greater than another object. | |||
|
37 | :param Optional[callable] ge: `callable` used to evaluate whether | |||
|
38 | one object is greater than or equal to another object. | |||
|
39 | ||||
|
40 | :param bool require_same_type: When `True`, equality and ordering methods | |||
|
41 | will return `NotImplemented` if objects are not of the same type. | |||
|
42 | ||||
|
43 | :param Optional[str] class_name: Name of class. Defaults to 'Comparable'. | |||
|
44 | ||||
|
45 | See `comparison` for more details. | |||
|
46 | ||||
|
47 | .. versionadded:: 21.1.0 | |||
|
48 | """ | |||
|
49 | ||||
|
50 | body = { | |||
|
51 | "__slots__": ["value"], | |||
|
52 | "__init__": _make_init(), | |||
|
53 | "_requirements": [], | |||
|
54 | "_is_comparable_to": _is_comparable_to, | |||
|
55 | } | |||
|
56 | ||||
|
57 | # Add operations. | |||
|
58 | num_order_functions = 0 | |||
|
59 | has_eq_function = False | |||
|
60 | ||||
|
61 | if eq is not None: | |||
|
62 | has_eq_function = True | |||
|
63 | body["__eq__"] = _make_operator("eq", eq) | |||
|
64 | body["__ne__"] = _make_ne() | |||
|
65 | ||||
|
66 | if lt is not None: | |||
|
67 | num_order_functions += 1 | |||
|
68 | body["__lt__"] = _make_operator("lt", lt) | |||
|
69 | ||||
|
70 | if le is not None: | |||
|
71 | num_order_functions += 1 | |||
|
72 | body["__le__"] = _make_operator("le", le) | |||
|
73 | ||||
|
74 | if gt is not None: | |||
|
75 | num_order_functions += 1 | |||
|
76 | body["__gt__"] = _make_operator("gt", gt) | |||
|
77 | ||||
|
78 | if ge is not None: | |||
|
79 | num_order_functions += 1 | |||
|
80 | body["__ge__"] = _make_operator("ge", ge) | |||
|
81 | ||||
|
82 | type_ = types.new_class( | |||
|
83 | class_name, (object,), {}, lambda ns: ns.update(body) | |||
|
84 | ) | |||
|
85 | ||||
|
86 | # Add same type requirement. | |||
|
87 | if require_same_type: | |||
|
88 | type_._requirements.append(_check_same_type) | |||
|
89 | ||||
|
90 | # Add total ordering if at least one operation was defined. | |||
|
91 | if 0 < num_order_functions < 4: | |||
|
92 | if not has_eq_function: | |||
|
93 | # functools.total_ordering requires __eq__ to be defined, | |||
|
94 | # so raise early error here to keep a nice stack. | |||
|
95 | raise ValueError( | |||
|
96 | "eq must be define is order to complete ordering from " | |||
|
97 | "lt, le, gt, ge." | |||
|
98 | ) | |||
|
99 | type_ = functools.total_ordering(type_) | |||
|
100 | ||||
|
101 | return type_ | |||
|
102 | ||||
|
103 | ||||
|
104 | def _make_init(): | |||
|
105 | """ | |||
|
106 | Create __init__ method. | |||
|
107 | """ | |||
|
108 | ||||
|
109 | def __init__(self, value): | |||
|
110 | """ | |||
|
111 | Initialize object with *value*. | |||
|
112 | """ | |||
|
113 | self.value = value | |||
|
114 | ||||
|
115 | return __init__ | |||
|
116 | ||||
|
117 | ||||
|
118 | def _make_operator(name, func): | |||
|
119 | """ | |||
|
120 | Create operator method. | |||
|
121 | """ | |||
|
122 | ||||
|
123 | def method(self, other): | |||
|
124 | if not self._is_comparable_to(other): | |||
|
125 | return NotImplemented | |||
|
126 | ||||
|
127 | result = func(self.value, other.value) | |||
|
128 | if result is NotImplemented: | |||
|
129 | return NotImplemented | |||
|
130 | ||||
|
131 | return result | |||
|
132 | ||||
|
133 | method.__name__ = "__%s__" % (name,) | |||
|
134 | method.__doc__ = "Return a %s b. Computed by attrs." % ( | |||
|
135 | _operation_names[name], | |||
|
136 | ) | |||
|
137 | ||||
|
138 | return method | |||
|
139 | ||||
|
140 | ||||
|
141 | def _is_comparable_to(self, other): | |||
|
142 | """ | |||
|
143 | Check whether `other` is comparable to `self`. | |||
|
144 | """ | |||
|
145 | for func in self._requirements: | |||
|
146 | if not func(self, other): | |||
|
147 | return False | |||
|
148 | return True | |||
|
149 | ||||
|
150 | ||||
|
151 | def _check_same_type(self, other): | |||
|
152 | """ | |||
|
153 | Return True if *self* and *other* are of the same type, False otherwise. | |||
|
154 | """ | |||
|
155 | return other.value.__class__ is self.value.__class__ |
@@ -0,0 +1,13 b'' | |||||
|
1 | from typing import Any, Callable, Optional, Type | |||
|
2 | ||||
|
3 | _CompareWithType = Callable[[Any, Any], bool] | |||
|
4 | ||||
|
5 | def cmp_using( | |||
|
6 | eq: Optional[_CompareWithType], | |||
|
7 | lt: Optional[_CompareWithType], | |||
|
8 | le: Optional[_CompareWithType], | |||
|
9 | gt: Optional[_CompareWithType], | |||
|
10 | ge: Optional[_CompareWithType], | |||
|
11 | require_same_type: bool, | |||
|
12 | class_name: str, | |||
|
13 | ) -> Type: ... |
@@ -0,0 +1,220 b'' | |||||
|
1 | # SPDX-License-Identifier: MIT | |||
|
2 | ||||
|
3 | """ | |||
|
4 | These are Python 3.6+-only and keyword-only APIs that call `attr.s` and | |||
|
5 | `attr.ib` with different 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 | hash=None, | |||
|
30 | init=None, | |||
|
31 | slots=True, | |||
|
32 | frozen=False, | |||
|
33 | weakref_slot=True, | |||
|
34 | str=False, | |||
|
35 | auto_attribs=None, | |||
|
36 | kw_only=False, | |||
|
37 | cache_hash=False, | |||
|
38 | auto_exc=True, | |||
|
39 | eq=None, | |||
|
40 | order=False, | |||
|
41 | auto_detect=True, | |||
|
42 | getstate_setstate=None, | |||
|
43 | on_setattr=None, | |||
|
44 | field_transformer=None, | |||
|
45 | match_args=True, | |||
|
46 | ): | |||
|
47 | r""" | |||
|
48 | Define an ``attrs`` class. | |||
|
49 | ||||
|
50 | Differences to the classic `attr.s` that it uses underneath: | |||
|
51 | ||||
|
52 | - Automatically detect whether or not *auto_attribs* should be `True` (c.f. | |||
|
53 | *auto_attribs* parameter). | |||
|
54 | - If *frozen* is `False`, run converters and validators when setting an | |||
|
55 | attribute by default. | |||
|
56 | - *slots=True* | |||
|
57 | ||||
|
58 | .. caution:: | |||
|
59 | ||||
|
60 | Usually this has only upsides and few visible effects in everyday | |||
|
61 | programming. But it *can* lead to some suprising behaviors, so please | |||
|
62 | make sure to read :term:`slotted classes`. | |||
|
63 | - *auto_exc=True* | |||
|
64 | - *auto_detect=True* | |||
|
65 | - *order=False* | |||
|
66 | - Some options that were only relevant on Python 2 or were kept around for | |||
|
67 | backwards-compatibility have been removed. | |||
|
68 | ||||
|
69 | Please note that these are all defaults and you can change them as you | |||
|
70 | wish. | |||
|
71 | ||||
|
72 | :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves | |||
|
73 | exactly like `attr.s`. If left `None`, `attr.s` will try to guess: | |||
|
74 | ||||
|
75 | 1. If any attributes are annotated and no unannotated `attrs.fields`\ s | |||
|
76 | are found, it assumes *auto_attribs=True*. | |||
|
77 | 2. Otherwise it assumes *auto_attribs=False* and tries to collect | |||
|
78 | `attrs.fields`\ s. | |||
|
79 | ||||
|
80 | For now, please refer to `attr.s` for the rest of the parameters. | |||
|
81 | ||||
|
82 | .. versionadded:: 20.1.0 | |||
|
83 | .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. | |||
|
84 | """ | |||
|
85 | ||||
|
86 | def do_it(cls, auto_attribs): | |||
|
87 | return attrs( | |||
|
88 | maybe_cls=cls, | |||
|
89 | these=these, | |||
|
90 | repr=repr, | |||
|
91 | hash=hash, | |||
|
92 | init=init, | |||
|
93 | slots=slots, | |||
|
94 | frozen=frozen, | |||
|
95 | weakref_slot=weakref_slot, | |||
|
96 | str=str, | |||
|
97 | auto_attribs=auto_attribs, | |||
|
98 | kw_only=kw_only, | |||
|
99 | cache_hash=cache_hash, | |||
|
100 | auto_exc=auto_exc, | |||
|
101 | eq=eq, | |||
|
102 | order=order, | |||
|
103 | auto_detect=auto_detect, | |||
|
104 | collect_by_mro=True, | |||
|
105 | getstate_setstate=getstate_setstate, | |||
|
106 | on_setattr=on_setattr, | |||
|
107 | field_transformer=field_transformer, | |||
|
108 | match_args=match_args, | |||
|
109 | ) | |||
|
110 | ||||
|
111 | def wrap(cls): | |||
|
112 | """ | |||
|
113 | Making this a wrapper ensures this code runs during class creation. | |||
|
114 | ||||
|
115 | We also ensure that frozen-ness of classes is inherited. | |||
|
116 | """ | |||
|
117 | nonlocal frozen, on_setattr | |||
|
118 | ||||
|
119 | had_on_setattr = on_setattr not in (None, setters.NO_OP) | |||
|
120 | ||||
|
121 | # By default, mutable classes convert & validate on setattr. | |||
|
122 | if frozen is False and on_setattr is None: | |||
|
123 | on_setattr = _ng_default_on_setattr | |||
|
124 | ||||
|
125 | # However, if we subclass a frozen class, we inherit the immutability | |||
|
126 | # and disable on_setattr. | |||
|
127 | for base_cls in cls.__bases__: | |||
|
128 | if base_cls.__setattr__ is _frozen_setattrs: | |||
|
129 | if had_on_setattr: | |||
|
130 | raise ValueError( | |||
|
131 | "Frozen classes can't use on_setattr " | |||
|
132 | "(frozen-ness was inherited)." | |||
|
133 | ) | |||
|
134 | ||||
|
135 | on_setattr = setters.NO_OP | |||
|
136 | break | |||
|
137 | ||||
|
138 | if auto_attribs is not None: | |||
|
139 | return do_it(cls, auto_attribs) | |||
|
140 | ||||
|
141 | try: | |||
|
142 | return do_it(cls, True) | |||
|
143 | except UnannotatedAttributeError: | |||
|
144 | return do_it(cls, False) | |||
|
145 | ||||
|
146 | # maybe_cls's type depends on the usage of the decorator. It's a class | |||
|
147 | # if it's used as `@attrs` but ``None`` if used as `@attrs()`. | |||
|
148 | if maybe_cls is None: | |||
|
149 | return wrap | |||
|
150 | else: | |||
|
151 | return wrap(maybe_cls) | |||
|
152 | ||||
|
153 | ||||
|
154 | mutable = define | |||
|
155 | frozen = partial(define, frozen=True, on_setattr=None) | |||
|
156 | ||||
|
157 | ||||
|
158 | def field( | |||
|
159 | *, | |||
|
160 | default=NOTHING, | |||
|
161 | validator=None, | |||
|
162 | repr=True, | |||
|
163 | hash=None, | |||
|
164 | init=True, | |||
|
165 | metadata=None, | |||
|
166 | converter=None, | |||
|
167 | factory=None, | |||
|
168 | kw_only=False, | |||
|
169 | eq=None, | |||
|
170 | order=None, | |||
|
171 | on_setattr=None, | |||
|
172 | ): | |||
|
173 | """ | |||
|
174 | Identical to `attr.ib`, except keyword-only and with some arguments | |||
|
175 | removed. | |||
|
176 | ||||
|
177 | .. versionadded:: 20.1.0 | |||
|
178 | """ | |||
|
179 | return attrib( | |||
|
180 | default=default, | |||
|
181 | validator=validator, | |||
|
182 | repr=repr, | |||
|
183 | hash=hash, | |||
|
184 | init=init, | |||
|
185 | metadata=metadata, | |||
|
186 | converter=converter, | |||
|
187 | factory=factory, | |||
|
188 | kw_only=kw_only, | |||
|
189 | eq=eq, | |||
|
190 | order=order, | |||
|
191 | on_setattr=on_setattr, | |||
|
192 | ) | |||
|
193 | ||||
|
194 | ||||
|
195 | def asdict(inst, *, recurse=True, filter=None, value_serializer=None): | |||
|
196 | """ | |||
|
197 | Same as `attr.asdict`, except that collections types are always retained | |||
|
198 | and dict is always used as *dict_factory*. | |||
|
199 | ||||
|
200 | .. versionadded:: 21.3.0 | |||
|
201 | """ | |||
|
202 | return _asdict( | |||
|
203 | inst=inst, | |||
|
204 | recurse=recurse, | |||
|
205 | filter=filter, | |||
|
206 | value_serializer=value_serializer, | |||
|
207 | retain_collection_types=True, | |||
|
208 | ) | |||
|
209 | ||||
|
210 | ||||
|
211 | def astuple(inst, *, recurse=True, filter=None): | |||
|
212 | """ | |||
|
213 | Same as `attr.astuple`, except that collections types are always retained | |||
|
214 | and `tuple` is always used as the *tuple_factory*. | |||
|
215 | ||||
|
216 | .. versionadded:: 21.3.0 | |||
|
217 | """ | |||
|
218 | return _astuple( | |||
|
219 | inst=inst, recurse=recurse, filter=filter, retain_collection_types=True | |||
|
220 | ) |
@@ -0,0 +1,86 b'' | |||||
|
1 | # SPDX-License-Identifier: MIT | |||
|
2 | ||||
|
3 | ||||
|
4 | from functools import total_ordering | |||
|
5 | ||||
|
6 | from ._funcs import astuple | |||
|
7 | from ._make import attrib, attrs | |||
|
8 | ||||
|
9 | ||||
|
10 | @total_ordering | |||
|
11 | @attrs(eq=False, order=False, slots=True, frozen=True) | |||
|
12 | class VersionInfo: | |||
|
13 | """ | |||
|
14 | A version object that can be compared to tuple of length 1--4: | |||
|
15 | ||||
|
16 | >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) | |||
|
17 | True | |||
|
18 | >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) | |||
|
19 | True | |||
|
20 | >>> vi = attr.VersionInfo(19, 2, 0, "final") | |||
|
21 | >>> vi < (19, 1, 1) | |||
|
22 | False | |||
|
23 | >>> vi < (19,) | |||
|
24 | False | |||
|
25 | >>> vi == (19, 2,) | |||
|
26 | True | |||
|
27 | >>> vi == (19, 2, 1) | |||
|
28 | False | |||
|
29 | ||||
|
30 | .. versionadded:: 19.2 | |||
|
31 | """ | |||
|
32 | ||||
|
33 | year = attrib(type=int) | |||
|
34 | minor = attrib(type=int) | |||
|
35 | micro = attrib(type=int) | |||
|
36 | releaselevel = attrib(type=str) | |||
|
37 | ||||
|
38 | @classmethod | |||
|
39 | def _from_version_string(cls, s): | |||
|
40 | """ | |||
|
41 | Parse *s* and return a _VersionInfo. | |||
|
42 | """ | |||
|
43 | v = s.split(".") | |||
|
44 | if len(v) == 3: | |||
|
45 | v.append("final") | |||
|
46 | ||||
|
47 | return cls( | |||
|
48 | year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] | |||
|
49 | ) | |||
|
50 | ||||
|
51 | def _ensure_tuple(self, other): | |||
|
52 | """ | |||
|
53 | Ensure *other* is a tuple of a valid length. | |||
|
54 | ||||
|
55 | Returns a possibly transformed *other* and ourselves as a tuple of | |||
|
56 | the same length as *other*. | |||
|
57 | """ | |||
|
58 | ||||
|
59 | if self.__class__ is other.__class__: | |||
|
60 | other = astuple(other) | |||
|
61 | ||||
|
62 | if not isinstance(other, tuple): | |||
|
63 | raise NotImplementedError | |||
|
64 | ||||
|
65 | if not (1 <= len(other) <= 4): | |||
|
66 | raise NotImplementedError | |||
|
67 | ||||
|
68 | return astuple(self)[: len(other)], other | |||
|
69 | ||||
|
70 | def __eq__(self, other): | |||
|
71 | try: | |||
|
72 | us, them = self._ensure_tuple(other) | |||
|
73 | except NotImplementedError: | |||
|
74 | return NotImplemented | |||
|
75 | ||||
|
76 | return us == them | |||
|
77 | ||||
|
78 | def __lt__(self, other): | |||
|
79 | try: | |||
|
80 | us, them = self._ensure_tuple(other) | |||
|
81 | except NotImplementedError: | |||
|
82 | return NotImplemented | |||
|
83 | ||||
|
84 | # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't | |||
|
85 | # have to do anything special with releaselevel for now. | |||
|
86 | return us < them |
@@ -0,0 +1,9 b'' | |||||
|
1 | class VersionInfo: | |||
|
2 | @property | |||
|
3 | def year(self) -> int: ... | |||
|
4 | @property | |||
|
5 | def minor(self) -> int: ... | |||
|
6 | @property | |||
|
7 | def micro(self) -> int: ... | |||
|
8 | @property | |||
|
9 | def releaselevel(self) -> str: ... |
@@ -0,0 +1,13 b'' | |||||
|
1 | from typing import Callable, Optional, TypeVar, overload | |||
|
2 | ||||
|
3 | from . import _ConverterType | |||
|
4 | ||||
|
5 | _T = TypeVar("_T") | |||
|
6 | ||||
|
7 | def pipe(*validators: _ConverterType) -> _ConverterType: ... | |||
|
8 | def optional(converter: _ConverterType) -> _ConverterType: ... | |||
|
9 | @overload | |||
|
10 | def default_if_none(default: _T) -> _ConverterType: ... | |||
|
11 | @overload | |||
|
12 | def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ... | |||
|
13 | def to_bool(val: str) -> bool: ... |
@@ -0,0 +1,17 b'' | |||||
|
1 | from typing import Any | |||
|
2 | ||||
|
3 | class FrozenError(AttributeError): | |||
|
4 | msg: str = ... | |||
|
5 | ||||
|
6 | class FrozenInstanceError(FrozenError): ... | |||
|
7 | class FrozenAttributeError(FrozenError): ... | |||
|
8 | class AttrsAttributeNotFoundError(ValueError): ... | |||
|
9 | class NotAnAttrsClassError(ValueError): ... | |||
|
10 | class DefaultAlreadySetError(RuntimeError): ... | |||
|
11 | class UnannotatedAttributeError(RuntimeError): ... | |||
|
12 | class PythonTooOldError(RuntimeError): ... | |||
|
13 | ||||
|
14 | class NotCallableError(TypeError): | |||
|
15 | msg: str = ... | |||
|
16 | value: Any = ... | |||
|
17 | def __init__(self, msg: str, value: Any) -> None: ... |
@@ -0,0 +1,6 b'' | |||||
|
1 | from typing import Any, Union | |||
|
2 | ||||
|
3 | from . import Attribute, _FilterType | |||
|
4 | ||||
|
5 | def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... | |||
|
6 | def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,73 b'' | |||||
|
1 | # SPDX-License-Identifier: MIT | |||
|
2 | ||||
|
3 | """ | |||
|
4 | Commonly used hooks for on_setattr. | |||
|
5 | """ | |||
|
6 | ||||
|
7 | ||||
|
8 | from . import _config | |||
|
9 | from .exceptions import FrozenAttributeError | |||
|
10 | ||||
|
11 | ||||
|
12 | def pipe(*setters): | |||
|
13 | """ | |||
|
14 | Run all *setters* and return the return value of the last one. | |||
|
15 | ||||
|
16 | .. versionadded:: 20.1.0 | |||
|
17 | """ | |||
|
18 | ||||
|
19 | def wrapped_pipe(instance, attrib, new_value): | |||
|
20 | rv = new_value | |||
|
21 | ||||
|
22 | for setter in setters: | |||
|
23 | rv = setter(instance, attrib, rv) | |||
|
24 | ||||
|
25 | return rv | |||
|
26 | ||||
|
27 | return wrapped_pipe | |||
|
28 | ||||
|
29 | ||||
|
30 | def frozen(_, __, ___): | |||
|
31 | """ | |||
|
32 | Prevent an attribute to be modified. | |||
|
33 | ||||
|
34 | .. versionadded:: 20.1.0 | |||
|
35 | """ | |||
|
36 | raise FrozenAttributeError() | |||
|
37 | ||||
|
38 | ||||
|
39 | def validate(instance, attrib, new_value): | |||
|
40 | """ | |||
|
41 | Run *attrib*'s validator on *new_value* if it has one. | |||
|
42 | ||||
|
43 | .. versionadded:: 20.1.0 | |||
|
44 | """ | |||
|
45 | if _config._run_validators is False: | |||
|
46 | return new_value | |||
|
47 | ||||
|
48 | v = attrib.validator | |||
|
49 | if not v: | |||
|
50 | return new_value | |||
|
51 | ||||
|
52 | v(instance, attrib, new_value) | |||
|
53 | ||||
|
54 | return new_value | |||
|
55 | ||||
|
56 | ||||
|
57 | def convert(instance, attrib, new_value): | |||
|
58 | """ | |||
|
59 | Run *attrib*'s converter -- if it has one -- on *new_value* and return the | |||
|
60 | result. | |||
|
61 | ||||
|
62 | .. versionadded:: 20.1.0 | |||
|
63 | """ | |||
|
64 | c = attrib.converter | |||
|
65 | if c: | |||
|
66 | return c(new_value) | |||
|
67 | ||||
|
68 | return new_value | |||
|
69 | ||||
|
70 | ||||
|
71 | # Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. | |||
|
72 | # autodata stopped working, so the docstring is inlined in the API docs. | |||
|
73 | NO_OP = object() |
@@ -0,0 +1,19 b'' | |||||
|
1 | from typing import Any, NewType, NoReturn, TypeVar, cast | |||
|
2 | ||||
|
3 | from . import Attribute, _OnSetAttrType | |||
|
4 | ||||
|
5 | _T = TypeVar("_T") | |||
|
6 | ||||
|
7 | def frozen( | |||
|
8 | instance: Any, attribute: Attribute[Any], new_value: Any | |||
|
9 | ) -> NoReturn: ... | |||
|
10 | def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... | |||
|
11 | def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... | |||
|
12 | ||||
|
13 | # convert is allowed to return Any, because they can be chained using pipe. | |||
|
14 | def convert( | |||
|
15 | instance: Any, attribute: Attribute[Any], new_value: Any | |||
|
16 | ) -> Any: ... | |||
|
17 | ||||
|
18 | _NoOpType = NewType("_NoOpType", object) | |||
|
19 | NO_OP: _NoOpType |
@@ -0,0 +1,80 b'' | |||||
|
1 | from typing import ( | |||
|
2 | Any, | |||
|
3 | AnyStr, | |||
|
4 | Callable, | |||
|
5 | Container, | |||
|
6 | ContextManager, | |||
|
7 | Iterable, | |||
|
8 | List, | |||
|
9 | Mapping, | |||
|
10 | Match, | |||
|
11 | Optional, | |||
|
12 | Pattern, | |||
|
13 | Tuple, | |||
|
14 | Type, | |||
|
15 | TypeVar, | |||
|
16 | Union, | |||
|
17 | overload, | |||
|
18 | ) | |||
|
19 | ||||
|
20 | from . import _ValidatorType | |||
|
21 | from . import _ValidatorArgType | |||
|
22 | ||||
|
23 | _T = TypeVar("_T") | |||
|
24 | _T1 = TypeVar("_T1") | |||
|
25 | _T2 = TypeVar("_T2") | |||
|
26 | _T3 = TypeVar("_T3") | |||
|
27 | _I = TypeVar("_I", bound=Iterable) | |||
|
28 | _K = TypeVar("_K") | |||
|
29 | _V = TypeVar("_V") | |||
|
30 | _M = TypeVar("_M", bound=Mapping) | |||
|
31 | ||||
|
32 | def set_disabled(run: bool) -> None: ... | |||
|
33 | def get_disabled() -> bool: ... | |||
|
34 | def disabled() -> ContextManager[None]: ... | |||
|
35 | ||||
|
36 | # To be more precise on instance_of use some overloads. | |||
|
37 | # If there are more than 3 items in the tuple then we fall back to Any | |||
|
38 | @overload | |||
|
39 | def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ... | |||
|
40 | @overload | |||
|
41 | def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ... | |||
|
42 | @overload | |||
|
43 | def instance_of( | |||
|
44 | type: Tuple[Type[_T1], Type[_T2]] | |||
|
45 | ) -> _ValidatorType[Union[_T1, _T2]]: ... | |||
|
46 | @overload | |||
|
47 | def instance_of( | |||
|
48 | type: Tuple[Type[_T1], Type[_T2], Type[_T3]] | |||
|
49 | ) -> _ValidatorType[Union[_T1, _T2, _T3]]: ... | |||
|
50 | @overload | |||
|
51 | def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... | |||
|
52 | def provides(interface: Any) -> _ValidatorType[Any]: ... | |||
|
53 | def optional( | |||
|
54 | validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] | |||
|
55 | ) -> _ValidatorType[Optional[_T]]: ... | |||
|
56 | def in_(options: Container[_T]) -> _ValidatorType[_T]: ... | |||
|
57 | def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... | |||
|
58 | def matches_re( | |||
|
59 | regex: Union[Pattern[AnyStr], AnyStr], | |||
|
60 | flags: int = ..., | |||
|
61 | func: Optional[ | |||
|
62 | Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]] | |||
|
63 | ] = ..., | |||
|
64 | ) -> _ValidatorType[AnyStr]: ... | |||
|
65 | def deep_iterable( | |||
|
66 | member_validator: _ValidatorArgType[_T], | |||
|
67 | iterable_validator: Optional[_ValidatorType[_I]] = ..., | |||
|
68 | ) -> _ValidatorType[_I]: ... | |||
|
69 | def deep_mapping( | |||
|
70 | key_validator: _ValidatorType[_K], | |||
|
71 | value_validator: _ValidatorType[_V], | |||
|
72 | mapping_validator: Optional[_ValidatorType[_M]] = ..., | |||
|
73 | ) -> _ValidatorType[_M]: ... | |||
|
74 | def is_callable() -> _ValidatorType[_T]: ... | |||
|
75 | def lt(val: _T) -> _ValidatorType[_T]: ... | |||
|
76 | def le(val: _T) -> _ValidatorType[_T]: ... | |||
|
77 | def ge(val: _T) -> _ValidatorType[_T]: ... | |||
|
78 | def gt(val: _T) -> _ValidatorType[_T]: ... | |||
|
79 | def max_len(length: int) -> _ValidatorType[_T]: ... | |||
|
80 | def min_len(length: int) -> _ValidatorType[_T]: ... |
@@ -1,6 +1,6 b'' | |||||
1 | The MIT License (MIT) |
|
1 | The MIT License (MIT) | |
2 |
|
2 | |||
3 | Copyright (c) 2015 Hynek Schlawack |
|
3 | Copyright (c) 2015 Hynek Schlawack and the attrs contributors | |
4 |
|
4 | |||
5 | Permission is hereby granted, free of charge, to any person obtaining a copy |
|
5 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
6 | of this software and associated documentation files (the "Software"), to deal |
|
6 | of this software and associated documentation files (the "Software"), to deal |
@@ -1,37 +1,35 b'' | |||||
1 | from __future__ import absolute_import, division, print_function |
|
1 | # SPDX-License-Identifier: MIT | |
|
2 | ||||
|
3 | ||||
|
4 | import sys | |||
|
5 | ||||
|
6 | from functools import partial | |||
2 |
|
7 | |||
3 | from ._funcs import ( |
|
8 | from . import converters, exceptions, filters, setters, validators | |
4 | asdict, |
|
9 | from ._cmp import cmp_using | |
5 | assoc, |
|
10 | from ._config import get_run_validators, set_run_validators | |
6 | astuple, |
|
11 | from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types | |
7 | evolve, |
|
|||
8 | has, |
|
|||
9 | ) |
|
|||
10 | from ._make import ( |
|
12 | from ._make import ( | |
|
13 | NOTHING, | |||
11 | Attribute, |
|
14 | Attribute, | |
12 | Factory, |
|
15 | Factory, | |
13 | NOTHING, |
|
16 | attrib, | |
14 | attr, |
|
17 | attrs, | |
15 | attributes, |
|
|||
16 | fields, |
|
18 | fields, | |
|
19 | fields_dict, | |||
17 | make_class, |
|
20 | make_class, | |
18 | validate, |
|
21 | validate, | |
19 | ) |
|
22 | ) | |
20 |
from ._ |
|
23 | from ._version_info import VersionInfo | |
21 | get_run_validators, |
|
|||
22 | set_run_validators, |
|
|||
23 | ) |
|
|||
24 | from . import exceptions |
|
|||
25 | from . import filters |
|
|||
26 | from . import converters |
|
|||
27 | from . import validators |
|
|||
28 |
|
24 | |||
29 |
|
25 | |||
30 |
__version__ = " |
|
26 | __version__ = "22.1.0" | |
|
27 | __version_info__ = VersionInfo._from_version_string(__version__) | |||
31 |
|
28 | |||
32 | __title__ = "attrs" |
|
29 | __title__ = "attrs" | |
33 | __description__ = "Classes Without Boilerplate" |
|
30 | __description__ = "Classes Without Boilerplate" | |
34 |
__ur |
|
31 | __url__ = "https://www.attrs.org/" | |
|
32 | __uri__ = __url__ | |||
35 | __doc__ = __description__ + " <" + __uri__ + ">" |
|
33 | __doc__ = __description__ + " <" + __uri__ + ">" | |
36 |
|
34 | |||
37 | __author__ = "Hynek Schlawack" |
|
35 | __author__ = "Hynek Schlawack" | |
@@ -41,8 +39,9 b' from . import validators' | |||||
41 | __copyright__ = "Copyright (c) 2015 Hynek Schlawack" |
|
39 | __copyright__ = "Copyright (c) 2015 Hynek Schlawack" | |
42 |
|
40 | |||
43 |
|
41 | |||
44 |
s = attrs = attr |
|
42 | s = attributes = attrs | |
45 |
ib = attr |
|
43 | ib = attr = attrib | |
|
44 | dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) | |||
46 |
|
45 | |||
47 | __all__ = [ |
|
46 | __all__ = [ | |
48 | "Attribute", |
|
47 | "Attribute", | |
@@ -55,17 +54,26 b' ib = attrib = attr' | |||||
55 | "attrib", |
|
54 | "attrib", | |
56 | "attributes", |
|
55 | "attributes", | |
57 | "attrs", |
|
56 | "attrs", | |
|
57 | "cmp_using", | |||
58 | "converters", |
|
58 | "converters", | |
59 | "evolve", |
|
59 | "evolve", | |
60 | "exceptions", |
|
60 | "exceptions", | |
61 | "fields", |
|
61 | "fields", | |
|
62 | "fields_dict", | |||
62 | "filters", |
|
63 | "filters", | |
63 | "get_run_validators", |
|
64 | "get_run_validators", | |
64 | "has", |
|
65 | "has", | |
65 | "ib", |
|
66 | "ib", | |
66 | "make_class", |
|
67 | "make_class", | |
|
68 | "resolve_types", | |||
67 | "s", |
|
69 | "s", | |
68 | "set_run_validators", |
|
70 | "set_run_validators", | |
|
71 | "setters", | |||
69 | "validate", |
|
72 | "validate", | |
70 | "validators", |
|
73 | "validators", | |
71 | ] |
|
74 | ] | |
|
75 | ||||
|
76 | if sys.version_info[:2] >= (3, 6): | |||
|
77 | from ._next_gen import define, field, frozen, mutable # noqa: F401 | |||
|
78 | ||||
|
79 | __all__.extend(("define", "field", "frozen", "mutable")) |
@@ -1,90 +1,185 b'' | |||||
1 | from __future__ import absolute_import, division, print_function |
|
1 | # SPDX-License-Identifier: MIT | |
|
2 | ||||
|
3 | ||||
|
4 | import inspect | |||
|
5 | import platform | |||
|
6 | import sys | |||
|
7 | import threading | |||
|
8 | import types | |||
|
9 | import warnings | |||
|
10 | ||||
|
11 | from collections.abc import Mapping, Sequence # noqa | |||
|
12 | ||||
|
13 | ||||
|
14 | PYPY = platform.python_implementation() == "PyPy" | |||
|
15 | PY36 = sys.version_info[:2] >= (3, 6) | |||
|
16 | HAS_F_STRINGS = PY36 | |||
|
17 | PY310 = sys.version_info[:2] >= (3, 10) | |||
2 |
|
18 | |||
3 | import sys |
|
19 | ||
4 | import types |
|
20 | if PYPY or PY36: | |
|
21 | ordered_dict = dict | |||
|
22 | else: | |||
|
23 | from collections import OrderedDict | |||
|
24 | ||||
|
25 | ordered_dict = OrderedDict | |||
|
26 | ||||
|
27 | ||||
|
28 | def just_warn(*args, **kw): | |||
|
29 | warnings.warn( | |||
|
30 | "Running interpreter doesn't sufficiently support code object " | |||
|
31 | "introspection. Some features like bare super() or accessing " | |||
|
32 | "__class__ will not work with slotted classes.", | |||
|
33 | RuntimeWarning, | |||
|
34 | stacklevel=2, | |||
|
35 | ) | |||
5 |
|
36 | |||
6 |
|
37 | |||
7 | PY2 = sys.version_info[0] == 2 |
|
38 | class _AnnotationExtractor: | |
|
39 | """ | |||
|
40 | Extract type annotations from a callable, returning None whenever there | |||
|
41 | is none. | |||
|
42 | """ | |||
|
43 | ||||
|
44 | __slots__ = ["sig"] | |||
|
45 | ||||
|
46 | def __init__(self, callable): | |||
|
47 | try: | |||
|
48 | self.sig = inspect.signature(callable) | |||
|
49 | except (ValueError, TypeError): # inspect failed | |||
|
50 | self.sig = None | |||
|
51 | ||||
|
52 | def get_first_param_type(self): | |||
|
53 | """ | |||
|
54 | Return the type annotation of the first argument if it's not empty. | |||
|
55 | """ | |||
|
56 | if not self.sig: | |||
|
57 | return None | |||
|
58 | ||||
|
59 | params = list(self.sig.parameters.values()) | |||
|
60 | if params and params[0].annotation is not inspect.Parameter.empty: | |||
|
61 | return params[0].annotation | |||
|
62 | ||||
|
63 | return None | |||
|
64 | ||||
|
65 | def get_return_type(self): | |||
|
66 | """ | |||
|
67 | Return the return type if it's not empty. | |||
|
68 | """ | |||
|
69 | if ( | |||
|
70 | self.sig | |||
|
71 | and self.sig.return_annotation is not inspect.Signature.empty | |||
|
72 | ): | |||
|
73 | return self.sig.return_annotation | |||
|
74 | ||||
|
75 | return None | |||
8 |
|
76 | |||
9 |
|
77 | |||
10 | if PY2: |
|
78 | def make_set_closure_cell(): | |
11 | from UserDict import IterableUserDict |
|
79 | """Return a function of two arguments (cell, value) which sets | |
12 |
|
80 | the value stored in the closure cell `cell` to `value`. | ||
13 | # We 'bundle' isclass instead of using inspect as importing inspect is |
|
81 | """ | |
14 | # fairly expensive (order of 10-15 ms for a modern machine in 2016) |
|
82 | # pypy makes this easy. (It also supports the logic below, but | |
15 | def isclass(klass): |
|
83 | # why not do the easy/fast thing?) | |
16 | return isinstance(klass, (type, types.ClassType)) |
|
84 | if PYPY: | |
17 |
|
85 | |||
18 | # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. |
|
86 | def set_closure_cell(cell, value): | |
19 | TYPE = "type" |
|
87 | cell.__setstate__((value,)) | |
|
88 | ||||
|
89 | return set_closure_cell | |||
20 |
|
90 | |||
21 | def iteritems(d): |
|
91 | # Otherwise gotta do it the hard way. | |
22 | return d.iteritems() |
|
|||
23 |
|
92 | |||
24 | def iterkeys(d): |
|
93 | # Create a function that will set its first cellvar to `value`. | |
25 | return d.iterkeys() |
|
94 | def set_first_cellvar_to(value): | |
|
95 | x = value | |||
|
96 | return | |||
26 |
|
97 | |||
27 | # Python 2 is bereft of a read-only dict proxy, so we make one! |
|
98 | # This function will be eliminated as dead code, but | |
28 | class ReadOnlyDict(IterableUserDict): |
|
99 | # not before its reference to `x` forces `x` to be | |
29 | """ |
|
100 | # represented as a closure cell rather than a local. | |
30 | Best-effort read-only dict wrapper. |
|
101 | def force_x_to_be_a_cell(): # pragma: no cover | |
31 | """ |
|
102 | return x | |
32 |
|
103 | |||
33 | def __setitem__(self, key, val): |
|
104 | try: | |
34 | # We gently pretend we're a Python 3 mappingproxy. |
|
105 | # Extract the code object and make sure our assumptions about | |
35 | raise TypeError("'mappingproxy' object does not support item " |
|
106 | # the closure behavior are correct. | |
36 | "assignment") |
|
107 | co = set_first_cellvar_to.__code__ | |
|
108 | if co.co_cellvars != ("x",) or co.co_freevars != (): | |||
|
109 | raise AssertionError # pragma: no cover | |||
37 |
|
110 | |||
38 | def update(self, _): |
|
111 | # Convert this code object to a code object that sets the | |
39 | # We gently pretend we're a Python 3 mappingproxy. |
|
112 | # function's first _freevar_ (not cellvar) to the argument. | |
40 | raise AttributeError("'mappingproxy' object has no attribute " |
|
113 | if sys.version_info >= (3, 8): | |
41 | "'update'") |
|
|||
42 |
|
114 | |||
43 | def __delitem__(self, _): |
|
115 | def set_closure_cell(cell, value): | |
44 | # We gently pretend we're a Python 3 mappingproxy. |
|
116 | cell.cell_contents = value | |
45 | raise TypeError("'mappingproxy' object does not support item " |
|
|||
46 | "deletion") |
|
|||
47 |
|
117 | |||
48 |
|
|
118 | else: | |
49 | # We gently pretend we're a Python 3 mappingproxy. |
|
119 | args = [co.co_argcount] | |
50 | raise AttributeError("'mappingproxy' object has no attribute " |
|
120 | args.append(co.co_kwonlyargcount) | |
51 | "'clear'") |
|
121 | args.extend( | |
52 |
|
122 | [ | ||
53 | def pop(self, key, default=None): |
|
123 | co.co_nlocals, | |
54 | # We gently pretend we're a Python 3 mappingproxy. |
|
124 | co.co_stacksize, | |
55 | raise AttributeError("'mappingproxy' object has no attribute " |
|
125 | co.co_flags, | |
56 | "'pop'") |
|
126 | co.co_code, | |
|
127 | co.co_consts, | |||
|
128 | co.co_names, | |||
|
129 | co.co_varnames, | |||
|
130 | co.co_filename, | |||
|
131 | co.co_name, | |||
|
132 | co.co_firstlineno, | |||
|
133 | co.co_lnotab, | |||
|
134 | # These two arguments are reversed: | |||
|
135 | co.co_cellvars, | |||
|
136 | co.co_freevars, | |||
|
137 | ] | |||
|
138 | ) | |||
|
139 | set_first_freevar_code = types.CodeType(*args) | |||
57 |
|
140 | |||
58 | def popitem(self): |
|
141 | def set_closure_cell(cell, value): | |
59 | # We gently pretend we're a Python 3 mappingproxy. |
|
142 | # Create a function using the set_first_freevar_code, | |
60 | raise AttributeError("'mappingproxy' object has no attribute " |
|
143 | # whose first closure cell is `cell`. Calling it will | |
61 | "'popitem'") |
|
144 | # change the value of that cell. | |
62 |
|
145 | setter = types.FunctionType( | ||
63 | def setdefault(self, key, default=None): |
|
146 | set_first_freevar_code, {}, "setter", (), (cell,) | |
64 | # We gently pretend we're a Python 3 mappingproxy. |
|
147 | ) | |
65 | raise AttributeError("'mappingproxy' object has no attribute " |
|
148 | # And call it to set the cell. | |
66 | "'setdefault'") |
|
149 | setter(value) | |
67 |
|
150 | |||
68 | def __repr__(self): |
|
151 | # Make sure it works on this interpreter: | |
69 | # Override to be identical to the Python 3 version. |
|
152 | def make_func_with_cell(): | |
70 | return "mappingproxy(" + repr(self.data) + ")" |
|
153 | x = None | |
|
154 | ||||
|
155 | def func(): | |||
|
156 | return x # pragma: no cover | |||
71 |
|
157 | |||
72 | def metadata_proxy(d): |
|
158 | return func | |
73 | res = ReadOnlyDict() |
|
159 | ||
74 | res.data.update(d) # We blocked update, so we have to do it like this. |
|
160 | cell = make_func_with_cell().__closure__[0] | |
75 | return res |
|
161 | set_closure_cell(cell, 100) | |
|
162 | if cell.cell_contents != 100: | |||
|
163 | raise AssertionError # pragma: no cover | |||
76 |
|
164 | |||
77 | else: |
|
165 | except Exception: | |
78 | def isclass(klass): |
|
166 | return just_warn | |
79 | return isinstance(klass, type) |
|
167 | else: | |
|
168 | return set_closure_cell | |||
80 |
|
169 | |||
81 | TYPE = "class" |
|
170 | ||
|
171 | set_closure_cell = make_set_closure_cell() | |||
82 |
|
172 | |||
83 | def iteritems(d): |
|
173 | # Thread-local global to track attrs instances which are already being repr'd. | |
84 | return d.items() |
|
174 | # This is needed because there is no other (thread-safe) way to pass info | |
85 |
|
175 | # about the instances that are already being repr'd through the call stack | ||
86 | def iterkeys(d): |
|
176 | # in order to ensure we don't perform infinite recursion. | |
87 | return d.keys() |
|
177 | # | |
88 |
|
178 | # For instance, if an instance contains a dict which contains that instance, | ||
89 | def metadata_proxy(d): |
|
179 | # we need to know that we're already repr'ing the outside instance from within | |
90 | return types.MappingProxyType(dict(d)) |
|
180 | # the dict's repr() call. | |
|
181 | # | |||
|
182 | # This lives here rather than in _make.py so that the functions in _make.py | |||
|
183 | # don't have a direct reference to the thread-local in their globals dict. | |||
|
184 | # If they have such a reference, it breaks cloudpickle. | |||
|
185 | repr_context = threading.local() |
@@ -1,4 +1,4 b'' | |||||
1 | from __future__ import absolute_import, division, print_function |
|
1 | # SPDX-License-Identifier: MIT | |
2 |
|
2 | |||
3 |
|
3 | |||
4 | __all__ = ["set_run_validators", "get_run_validators"] |
|
4 | __all__ = ["set_run_validators", "get_run_validators"] | |
@@ -9,6 +9,10 b' from __future__ import absolute_import, ' | |||||
9 | def set_run_validators(run): |
|
9 | def set_run_validators(run): | |
10 | """ |
|
10 | """ | |
11 | Set whether or not validators are run. By default, they are run. |
|
11 | Set whether or not validators are run. By default, they are run. | |
|
12 | ||||
|
13 | .. deprecated:: 21.3.0 It will not be removed, but it also will not be | |||
|
14 | moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()` | |||
|
15 | instead. | |||
12 | """ |
|
16 | """ | |
13 | if not isinstance(run, bool): |
|
17 | if not isinstance(run, bool): | |
14 | raise TypeError("'run' must be bool.") |
|
18 | raise TypeError("'run' must be bool.") | |
@@ -19,5 +23,9 b' def set_run_validators(run):' | |||||
19 | def get_run_validators(): |
|
23 | def get_run_validators(): | |
20 | """ |
|
24 | """ | |
21 | Return whether or not validators are run. |
|
25 | Return whether or not validators are run. | |
|
26 | ||||
|
27 | .. deprecated:: 21.3.0 It will not be removed, but it also will not be | |||
|
28 | moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()` | |||
|
29 | instead. | |||
22 | """ |
|
30 | """ | |
23 | return _run_validators |
|
31 | return _run_validators |
@@ -1,14 +1,20 b'' | |||||
1 | from __future__ import absolute_import, division, print_function |
|
1 | # SPDX-License-Identifier: MIT | |
|
2 | ||||
2 |
|
3 | |||
3 | import copy |
|
4 | import copy | |
4 |
|
5 | |||
5 | from ._compat import iteritems |
|
6 | from ._make import NOTHING, _obj_setattr, fields | |
6 | from ._make import NOTHING, fields, _obj_setattr |
|
|||
7 | from .exceptions import AttrsAttributeNotFoundError |
|
7 | from .exceptions import AttrsAttributeNotFoundError | |
8 |
|
8 | |||
9 |
|
9 | |||
10 | def asdict(inst, recurse=True, filter=None, dict_factory=dict, |
|
10 | def asdict( | |
11 | retain_collection_types=False): |
|
11 | inst, | |
|
12 | recurse=True, | |||
|
13 | filter=None, | |||
|
14 | dict_factory=dict, | |||
|
15 | retain_collection_types=False, | |||
|
16 | value_serializer=None, | |||
|
17 | ): | |||
12 | """ |
|
18 | """ | |
13 | Return the ``attrs`` attribute values of *inst* as a dict. |
|
19 | Return the ``attrs`` attribute values of *inst* as a dict. | |
14 |
|
20 | |||
@@ -17,9 +23,9 b' def asdict(inst, recurse=True, filter=No' | |||||
17 | :param inst: Instance of an ``attrs``-decorated class. |
|
23 | :param inst: Instance of an ``attrs``-decorated class. | |
18 | :param bool recurse: Recurse into classes that are also |
|
24 | :param bool recurse: Recurse into classes that are also | |
19 | ``attrs``-decorated. |
|
25 | ``attrs``-decorated. | |
20 |
:param callable filter: A callable whose return code deter |
|
26 | :param callable filter: A callable whose return code determines whether an | |
21 | attribute or element is included (``True``) or dropped (``False``). Is |
|
27 | attribute or element is included (``True``) or dropped (``False``). Is | |
22 |
called with the |
|
28 | called with the `attrs.Attribute` as the first argument and the | |
23 | value as the second argument. |
|
29 | value as the second argument. | |
24 | :param callable dict_factory: A callable to produce dictionaries from. For |
|
30 | :param callable dict_factory: A callable to produce dictionaries from. For | |
25 | example, to produce ordered dictionaries instead of normal Python |
|
31 | example, to produce ordered dictionaries instead of normal Python | |
@@ -27,6 +33,10 b' def asdict(inst, recurse=True, filter=No' | |||||
27 | :param bool retain_collection_types: Do not convert to ``list`` when |
|
33 | :param bool retain_collection_types: Do not convert to ``list`` when | |
28 | encountering an attribute whose type is ``tuple`` or ``set``. Only |
|
34 | encountering an attribute whose type is ``tuple`` or ``set``. Only | |
29 | meaningful if ``recurse`` is ``True``. |
|
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. | |||
30 |
|
40 | |||
31 | :rtype: return type of *dict_factory* |
|
41 | :rtype: return type of *dict_factory* | |
32 |
|
42 | |||
@@ -35,6 +45,9 b' def asdict(inst, recurse=True, filter=No' | |||||
35 |
|
45 | |||
36 | .. versionadded:: 16.0.0 *dict_factory* |
|
46 | .. versionadded:: 16.0.0 *dict_factory* | |
37 | .. versionadded:: 16.1.0 *retain_collection_types* |
|
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. | |||
38 | """ |
|
51 | """ | |
39 | attrs = fields(inst.__class__) |
|
52 | attrs = fields(inst.__class__) | |
40 | rv = dict_factory() |
|
53 | rv = dict_factory() | |
@@ -42,24 +55,58 b' def asdict(inst, recurse=True, filter=No' | |||||
42 | v = getattr(inst, a.name) |
|
55 | v = getattr(inst, a.name) | |
43 | if filter is not None and not filter(a, v): |
|
56 | if filter is not None and not filter(a, v): | |
44 | continue |
|
57 | continue | |
|
58 | ||||
|
59 | if value_serializer is not None: | |||
|
60 | v = value_serializer(inst, a, v) | |||
|
61 | ||||
45 | if recurse is True: |
|
62 | if recurse is True: | |
46 | if has(v.__class__): |
|
63 | if has(v.__class__): | |
47 |
rv[a.name] = asdict( |
|
64 | rv[a.name] = asdict( | |
48 | dict_factory=dict_factory) |
|
65 | v, | |
49 | elif isinstance(v, (tuple, list, set)): |
|
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)): | |||
50 | cf = v.__class__ if retain_collection_types is True else list |
|
73 | cf = v.__class__ if retain_collection_types is True else list | |
51 |
rv[a.name] = cf( |
|
74 | rv[a.name] = cf( | |
52 | asdict(i, recurse=True, filter=filter, |
|
75 | [ | |
53 |
|
|
76 | _asdict_anything( | |
54 | if has(i.__class__) else i |
|
77 | i, | |
55 |
|
|
78 | is_key=False, | |
56 | ]) |
|
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 | ) | |||
57 | elif isinstance(v, dict): |
|
87 | elif isinstance(v, dict): | |
58 | df = dict_factory |
|
88 | df = dict_factory | |
59 |
rv[a.name] = df( |
|
89 | rv[a.name] = df( | |
60 | asdict(kk, dict_factory=df) if has(kk.__class__) else kk, |
|
90 | ( | |
61 | asdict(vv, dict_factory=df) if has(vv.__class__) else vv) |
|
91 | _asdict_anything( | |
62 |
|
|
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 | ) | |||
63 | else: |
|
110 | else: | |
64 | rv[a.name] = v |
|
111 | rv[a.name] = v | |
65 | else: |
|
112 | else: | |
@@ -67,8 +114,86 b' def asdict(inst, recurse=True, filter=No' | |||||
67 | return rv |
|
114 | return rv | |
68 |
|
115 | |||
69 |
|
116 | |||
70 | def astuple(inst, recurse=True, filter=None, tuple_factory=tuple, |
|
117 | def _asdict_anything( | |
71 | retain_collection_types=False): |
|
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 | ||||
|
190 | def astuple( | |||
|
191 | inst, | |||
|
192 | recurse=True, | |||
|
193 | filter=None, | |||
|
194 | tuple_factory=tuple, | |||
|
195 | retain_collection_types=False, | |||
|
196 | ): | |||
72 | """ |
|
197 | """ | |
73 | Return the ``attrs`` attribute values of *inst* as a tuple. |
|
198 | Return the ``attrs`` attribute values of *inst* as a tuple. | |
74 |
|
199 | |||
@@ -79,7 +204,7 b' def astuple(inst, recurse=True, filter=N' | |||||
79 | ``attrs``-decorated. |
|
204 | ``attrs``-decorated. | |
80 | :param callable filter: A callable whose return code determines whether an |
|
205 | :param callable filter: A callable whose return code determines whether an | |
81 | attribute or element is included (``True``) or dropped (``False``). Is |
|
206 | attribute or element is included (``True``) or dropped (``False``). Is | |
82 |
called with the |
|
207 | called with the `attrs.Attribute` as the first argument and the | |
83 | value as the second argument. |
|
208 | value as the second argument. | |
84 | :param callable tuple_factory: A callable to produce tuples from. For |
|
209 | :param callable tuple_factory: A callable to produce tuples from. For | |
85 | example, to produce lists instead of tuples. |
|
210 | example, to produce lists instead of tuples. | |
@@ -104,38 +229,61 b' def astuple(inst, recurse=True, filter=N' | |||||
104 | continue |
|
229 | continue | |
105 | if recurse is True: |
|
230 | if recurse is True: | |
106 | if has(v.__class__): |
|
231 | if has(v.__class__): | |
107 | rv.append(astuple(v, recurse=True, filter=filter, |
|
232 | rv.append( | |
108 | tuple_factory=tuple_factory, |
|
233 | astuple( | |
109 | retain_collection_types=retain)) |
|
234 | v, | |
110 | elif isinstance(v, (tuple, list, set)): |
|
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)): | |||
111 | cf = v.__class__ if retain is True else list |
|
242 | cf = v.__class__ if retain is True else list | |
112 |
rv.append( |
|
243 | rv.append( | |
113 | astuple(j, recurse=True, filter=filter, |
|
244 | cf( | |
114 | tuple_factory=tuple_factory, |
|
245 | [ | |
115 |
|
|
246 | astuple( | |
116 |
|
|
247 | j, | |
117 | for j in v |
|
248 | recurse=True, | |
118 | ])) |
|
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 | ) | |||
119 | elif isinstance(v, dict): |
|
259 | elif isinstance(v, dict): | |
120 | df = v.__class__ if retain is True else dict |
|
260 | df = v.__class__ if retain is True else dict | |
121 |
rv.append( |
|
261 | rv.append( | |
|
262 | df( | |||
122 | ( |
|
263 | ( | |
123 | astuple( |
|
264 | astuple( | |
124 | kk, |
|
265 | kk, | |
125 | tuple_factory=tuple_factory, |
|
266 | tuple_factory=tuple_factory, | |
126 | retain_collection_types=retain |
|
267 | retain_collection_types=retain, | |
127 |
) |
|
268 | ) | |
|
269 | if has(kk.__class__) | |||
|
270 | else kk, | |||
128 | astuple( |
|
271 | astuple( | |
129 | vv, |
|
272 | vv, | |
130 | tuple_factory=tuple_factory, |
|
273 | tuple_factory=tuple_factory, | |
131 | retain_collection_types=retain |
|
274 | retain_collection_types=retain, | |
132 |
) |
|
275 | ) | |
|
276 | if has(vv.__class__) | |||
|
277 | else vv, | |||
133 | ) |
|
278 | ) | |
134 |
for kk, vv in |
|
279 | for kk, vv in v.items() | |
|
280 | ) | |||
|
281 | ) | |||
135 | else: |
|
282 | else: | |
136 | rv.append(v) |
|
283 | rv.append(v) | |
137 | else: |
|
284 | else: | |
138 | rv.append(v) |
|
285 | rv.append(v) | |
|
286 | ||||
139 | return rv if tuple_factory is list else tuple_factory(rv) |
|
287 | return rv if tuple_factory is list else tuple_factory(rv) | |
140 |
|
288 | |||
141 |
|
289 | |||
@@ -146,7 +294,7 b' def has(cls):' | |||||
146 | :param type cls: Class to introspect. |
|
294 | :param type cls: Class to introspect. | |
147 | :raise TypeError: If *cls* is not a class. |
|
295 | :raise TypeError: If *cls* is not a class. | |
148 |
|
296 | |||
149 |
:rtype: |
|
297 | :rtype: bool | |
150 | """ |
|
298 | """ | |
151 | return getattr(cls, "__attrs_attrs__", None) is not None |
|
299 | return getattr(cls, "__attrs_attrs__", None) is not None | |
152 |
|
300 | |||
@@ -166,19 +314,26 b' def assoc(inst, **changes):' | |||||
166 | class. |
|
314 | class. | |
167 |
|
315 | |||
168 | .. deprecated:: 17.1.0 |
|
316 | .. deprecated:: 17.1.0 | |
169 |
Use |
|
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`. | |||
170 | """ |
|
320 | """ | |
171 | import warnings |
|
321 | import warnings | |
172 | warnings.warn("assoc is deprecated and will be removed after 2018/01.", |
|
322 | ||
173 | DeprecationWarning) |
|
323 | warnings.warn( | |
|
324 | "assoc is deprecated and will be removed after 2018/01.", | |||
|
325 | DeprecationWarning, | |||
|
326 | stacklevel=2, | |||
|
327 | ) | |||
174 | new = copy.copy(inst) |
|
328 | new = copy.copy(inst) | |
175 | attrs = fields(inst.__class__) |
|
329 | attrs = fields(inst.__class__) | |
176 |
for k, v in |
|
330 | for k, v in changes.items(): | |
177 | a = getattr(attrs, k, NOTHING) |
|
331 | a = getattr(attrs, k, NOTHING) | |
178 | if a is NOTHING: |
|
332 | if a is NOTHING: | |
179 | raise AttrsAttributeNotFoundError( |
|
333 | raise AttrsAttributeNotFoundError( | |
180 | "{k} is not an attrs attribute on {cl}." |
|
334 | "{k} is not an attrs attribute on {cl}.".format( | |
181 |
|
|
335 | k=k, cl=new.__class__ | |
|
336 | ) | |||
182 | ) |
|
337 | ) | |
183 | _obj_setattr(new, k, v) |
|
338 | _obj_setattr(new, k, v) | |
184 | return new |
|
339 | return new | |
@@ -209,4 +364,57 b' def evolve(inst, **changes):' | |||||
209 | init_name = attr_name if attr_name[0] != "_" else attr_name[1:] |
|
364 | init_name = attr_name if attr_name[0] != "_" else attr_name[1:] | |
210 | if init_name not in changes: |
|
365 | if init_name not in changes: | |
211 | changes[init_name] = getattr(inst, attr_name) |
|
366 | changes[init_name] = getattr(inst, attr_name) | |
|
367 | ||||
212 | return cls(**changes) |
|
368 | return cls(**changes) | |
|
369 | ||||
|
370 | ||||
|
371 | def resolve_types(cls, globalns=None, localns=None, attribs=None): | |||
|
372 | """ | |||
|
373 | Resolve any strings and forward annotations in type annotations. | |||
|
374 | ||||
|
375 | This is only required if you need concrete types in `Attribute`'s *type* | |||
|
376 | field. In other words, you don't need to resolve your types if you only | |||
|
377 | use them for static type checking. | |||
|
378 | ||||
|
379 | With no arguments, names will be looked up in the module in which the class | |||
|
380 | was created. If this is not what you want, e.g. if the name only exists | |||
|
381 | inside a method, you may pass *globalns* or *localns* to specify other | |||
|
382 | dictionaries in which to look up these names. See the docs of | |||
|
383 | `typing.get_type_hints` for more details. | |||
|
384 | ||||
|
385 | :param type cls: Class to resolve. | |||
|
386 | :param Optional[dict] globalns: Dictionary containing global variables. | |||
|
387 | :param Optional[dict] localns: Dictionary containing local variables. | |||
|
388 | :param Optional[list] attribs: List of attribs for the given class. | |||
|
389 | This is necessary when calling from inside a ``field_transformer`` | |||
|
390 | since *cls* is not an ``attrs`` class yet. | |||
|
391 | ||||
|
392 | :raise TypeError: If *cls* is not a class. | |||
|
393 | :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` | |||
|
394 | class and you didn't pass any attribs. | |||
|
395 | :raise NameError: If types cannot be resolved because of missing variables. | |||
|
396 | ||||
|
397 | :returns: *cls* so you can use this function also as a class decorator. | |||
|
398 | Please note that you have to apply it **after** `attrs.define`. That | |||
|
399 | means the decorator has to come in the line **before** `attrs.define`. | |||
|
400 | ||||
|
401 | .. versionadded:: 20.1.0 | |||
|
402 | .. versionadded:: 21.1.0 *attribs* | |||
|
403 | ||||
|
404 | """ | |||
|
405 | # Since calling get_type_hints is expensive we cache whether we've | |||
|
406 | # done it already. | |||
|
407 | if getattr(cls, "__attrs_types_resolved__", None) != cls: | |||
|
408 | import typing | |||
|
409 | ||||
|
410 | hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) | |||
|
411 | for field in fields(cls) if attribs is None else attribs: | |||
|
412 | if field.name in hints: | |||
|
413 | # Since fields have been frozen we must work around it. | |||
|
414 | _obj_setattr(field, "type", hints[field.name]) | |||
|
415 | # We store the class we resolved so that subclasses know they haven't | |||
|
416 | # been resolved. | |||
|
417 | cls.__attrs_types_resolved__ = cls | |||
|
418 | ||||
|
419 | # Return the class so you can use it as a decorator too. | |||
|
420 | return cls |
This diff has been collapsed as it changes many lines, (3118 lines changed) Show them Hide them | |||||
@@ -1,50 +1,79 b'' | |||||
1 | from __future__ import absolute_import, division, print_function |
|
1 | # SPDX-License-Identifier: MIT | |
2 |
|
2 | |||
3 | import hashlib |
|
3 | import copy | |
4 | import linecache |
|
4 | import linecache | |
|
5 | import sys | |||
|
6 | import types | |||
|
7 | import typing | |||
5 |
|
8 | |||
6 | from operator import itemgetter |
|
9 | from operator import itemgetter | |
7 |
|
10 | |||
8 | from . import _config |
|
11 | # We need to import _compat itself in addition to the _compat members to avoid | |
9 | from ._compat import PY2, iteritems, isclass, iterkeys, metadata_proxy |
|
12 | # having the thread-local in the globals here. | |
|
13 | from . import _compat, _config, setters | |||
|
14 | from ._compat import ( | |||
|
15 | HAS_F_STRINGS, | |||
|
16 | PY310, | |||
|
17 | PYPY, | |||
|
18 | _AnnotationExtractor, | |||
|
19 | ordered_dict, | |||
|
20 | set_closure_cell, | |||
|
21 | ) | |||
10 | from .exceptions import ( |
|
22 | from .exceptions import ( | |
11 | DefaultAlreadySetError, |
|
23 | DefaultAlreadySetError, | |
12 | FrozenInstanceError, |
|
24 | FrozenInstanceError, | |
13 | NotAnAttrsClassError, |
|
25 | NotAnAttrsClassError, | |
|
26 | UnannotatedAttributeError, | |||
14 | ) |
|
27 | ) | |
15 |
|
28 | |||
16 |
|
29 | |||
17 | # This is used at least twice, so cache it here. |
|
30 | # This is used at least twice, so cache it here. | |
18 | _obj_setattr = object.__setattr__ |
|
31 | _obj_setattr = object.__setattr__ | |
19 |
_init_convert_pat = "__attr_convert |
|
32 | _init_converter_pat = "__attr_converter_%s" | |
20 | _init_factory_pat = "__attr_factory_{}" |
|
33 | _init_factory_pat = "__attr_factory_{}" | |
21 | _tuple_property_pat = " {attr_name} = property(itemgetter({index}))" |
|
34 | _tuple_property_pat = ( | |
22 | _empty_metadata_singleton = metadata_proxy({}) |
|
35 | " {attr_name} = _attrs_property(_attrs_itemgetter({index}))" | |
23 |
|
36 | ) | ||
24 |
|
37 | _classvar_prefixes = ( | ||
25 | class _Nothing(object): |
|
38 | "typing.ClassVar", | |
|
39 | "t.ClassVar", | |||
|
40 | "ClassVar", | |||
|
41 | "typing_extensions.ClassVar", | |||
|
42 | ) | |||
|
43 | # we don't use a double-underscore prefix because that triggers | |||
|
44 | # name mangling when trying to create a slot for the field | |||
|
45 | # (when slots=True) | |||
|
46 | _hash_cache_field = "_attrs_cached_hash" | |||
|
47 | ||||
|
48 | _empty_metadata_singleton = types.MappingProxyType({}) | |||
|
49 | ||||
|
50 | # Unique object for unequivocal getattr() defaults. | |||
|
51 | _sentinel = object() | |||
|
52 | ||||
|
53 | _ng_default_on_setattr = setters.pipe(setters.convert, setters.validate) | |||
|
54 | ||||
|
55 | ||||
|
56 | class _Nothing: | |||
26 | """ |
|
57 | """ | |
27 | Sentinel class to indicate the lack of a value when ``None`` is ambiguous. |
|
58 | Sentinel class to indicate the lack of a value when ``None`` is ambiguous. | |
28 |
|
59 | |||
29 | All instances of `_Nothing` are equal. |
|
60 | ``_Nothing`` is a singleton. There is only ever one of it. | |
|
61 | ||||
|
62 | .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. | |||
30 | """ |
|
63 | """ | |
31 | def __copy__(self): |
|
64 | ||
32 | return self |
|
65 | _singleton = None | |
33 |
|
66 | |||
34 |
def __ |
|
67 | def __new__(cls): | |
35 | return self |
|
68 | if _Nothing._singleton is None: | |
36 |
|
69 | _Nothing._singleton = super().__new__(cls) | ||
37 | def __eq__(self, other): |
|
70 | return _Nothing._singleton | |
38 | return other.__class__ == _Nothing |
|
|||
39 |
|
||||
40 | def __ne__(self, other): |
|
|||
41 | return not self == other |
|
|||
42 |
|
71 | |||
43 | def __repr__(self): |
|
72 | def __repr__(self): | |
44 | return "NOTHING" |
|
73 | return "NOTHING" | |
45 |
|
74 | |||
46 |
def __ |
|
75 | def __bool__(self): | |
47 |
return |
|
76 | return False | |
48 |
|
77 | |||
49 |
|
78 | |||
50 | NOTHING = _Nothing() |
|
79 | NOTHING = _Nothing() | |
@@ -53,92 +82,255 b' Sentinel to indicate the lack of a value' | |||||
53 | """ |
|
82 | """ | |
54 |
|
83 | |||
55 |
|
84 | |||
56 | def attr(default=NOTHING, validator=None, |
|
85 | class _CacheHashWrapper(int): | |
57 | repr=True, cmp=True, hash=None, init=True, |
|
86 | """ | |
58 | convert=None, metadata={}): |
|
87 | An integer subclass that pickles / copies as None | |
59 | r""" |
|
88 | ||
|
89 | This is used for non-slots classes with ``cache_hash=True``, to avoid | |||
|
90 | serializing a potentially (even likely) invalid hash value. Since ``None`` | |||
|
91 | is the default value for uncalculated hashes, whenever this is copied, | |||
|
92 | the copy's value for the hash should automatically reset. | |||
|
93 | ||||
|
94 | See GH #613 for more details. | |||
|
95 | """ | |||
|
96 | ||||
|
97 | def __reduce__(self, _none_constructor=type(None), _args=()): | |||
|
98 | return _none_constructor, _args | |||
|
99 | ||||
|
100 | ||||
|
101 | def attrib( | |||
|
102 | default=NOTHING, | |||
|
103 | validator=None, | |||
|
104 | repr=True, | |||
|
105 | cmp=None, | |||
|
106 | hash=None, | |||
|
107 | init=True, | |||
|
108 | metadata=None, | |||
|
109 | type=None, | |||
|
110 | converter=None, | |||
|
111 | factory=None, | |||
|
112 | kw_only=False, | |||
|
113 | eq=None, | |||
|
114 | order=None, | |||
|
115 | on_setattr=None, | |||
|
116 | ): | |||
|
117 | """ | |||
60 | Create a new attribute on a class. |
|
118 | Create a new attribute on a class. | |
61 |
|
119 | |||
62 | .. warning:: |
|
120 | .. warning:: | |
63 |
|
121 | |||
64 | Does *not* do anything unless the class is also decorated with |
|
122 | Does *not* do anything unless the class is also decorated with | |
65 |
|
|
123 | `attr.s`! | |
66 |
|
124 | |||
67 | :param default: A value that is used if an ``attrs``-generated ``__init__`` |
|
125 | :param default: A value that is used if an ``attrs``-generated ``__init__`` | |
68 | is used and no value is passed while instantiating or the attribute is |
|
126 | is used and no value is passed while instantiating or the attribute is | |
69 | excluded using ``init=False``. |
|
127 | excluded using ``init=False``. | |
70 |
|
128 | |||
71 |
If the value is an instance of |
|
129 | If the value is an instance of `attrs.Factory`, its callable will be | |
72 | used to construct a new value (useful for mutable datatypes like lists |
|
130 | used to construct a new value (useful for mutable data types like lists | |
73 | or dicts). |
|
131 | or dicts). | |
74 |
|
132 | |||
75 |
If a default is not set (or set manually to ` |
|
133 | If a default is not set (or set manually to `attrs.NOTHING`), a value | |
76 |
*must* be supplied when instantiating; otherwise a |
|
134 | *must* be supplied when instantiating; otherwise a `TypeError` | |
77 | will be raised. |
|
135 | will be raised. | |
78 |
|
136 | |||
79 | The default can also be set using decorator notation as shown below. |
|
137 | The default can also be set using decorator notation as shown below. | |
80 |
|
138 | |||
81 |
:type default: Any value |
|
139 | :type default: Any value | |
82 |
|
140 | |||
83 | :param validator: :func:`callable` that is called by ``attrs``-generated |
|
141 | :param callable factory: Syntactic sugar for | |
|
142 | ``default=attr.Factory(factory)``. | |||
|
143 | ||||
|
144 | :param validator: `callable` that is called by ``attrs``-generated | |||
84 | ``__init__`` methods after the instance has been initialized. They |
|
145 | ``__init__`` methods after the instance has been initialized. They | |
85 |
receive the initialized instance, the :c |
|
146 | receive the initialized instance, the :func:`~attrs.Attribute`, and the | |
86 | passed value. |
|
147 | passed value. | |
87 |
|
148 | |||
88 | The return value is *not* inspected so the validator has to throw an |
|
149 | The return value is *not* inspected so the validator has to throw an | |
89 | exception itself. |
|
150 | exception itself. | |
90 |
|
151 | |||
91 |
If a ` |
|
152 | If a `list` is passed, its items are treated as validators and must | |
92 | all pass. |
|
153 | all pass. | |
93 |
|
154 | |||
94 | Validators can be globally disabled and re-enabled using |
|
155 | Validators can be globally disabled and re-enabled using | |
95 |
|
|
156 | `get_run_validators`. | |
96 |
|
157 | |||
97 | The validator can also be set using decorator notation as shown below. |
|
158 | The validator can also be set using decorator notation as shown below. | |
98 |
|
159 | |||
99 | :type validator: ``callable`` or a ``list`` of ``callable``\ s. |
|
160 | :type validator: `callable` or a `list` of `callable`\\ s. | |
100 |
|
161 | |||
101 |
:param |
|
162 | :param repr: Include this attribute in the generated ``__repr__`` | |
102 | method. |
|
163 | method. If ``True``, include the attribute; if ``False``, omit it. By | |
103 | :param bool cmp: Include this attribute in the generated comparison methods |
|
164 | default, the built-in ``repr()`` function is used. To override how the | |
104 | (``__eq__`` et al). |
|
165 | attribute value is formatted, pass a ``callable`` that takes a single | |
105 | :param hash: Include this attribute in the generated ``__hash__`` |
|
166 | value and returns a string. Note that the resulting string is used | |
106 | method. If ``None`` (default), mirror *cmp*'s value. This is the |
|
167 | as-is, i.e. it will be used directly *instead* of calling ``repr()`` | |
107 | correct behavior according the Python spec. Setting this value to |
|
168 | (the default). | |
108 | anything else than ``None`` is *discouraged*. |
|
169 | :type repr: a `bool` or a `callable` to use a custom function. | |
109 | :type hash: ``bool`` or ``None`` |
|
170 | ||
|
171 | :param eq: If ``True`` (default), include this attribute in the | |||
|
172 | generated ``__eq__`` and ``__ne__`` methods that check two instances | |||
|
173 | for equality. To override how the attribute value is compared, | |||
|
174 | pass a ``callable`` that takes a single value and returns the value | |||
|
175 | to be compared. | |||
|
176 | :type eq: a `bool` or a `callable`. | |||
|
177 | ||||
|
178 | :param order: If ``True`` (default), include this attributes in the | |||
|
179 | generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. | |||
|
180 | To override how the attribute value is ordered, | |||
|
181 | pass a ``callable`` that takes a single value and returns the value | |||
|
182 | to be ordered. | |||
|
183 | :type order: a `bool` or a `callable`. | |||
|
184 | ||||
|
185 | :param cmp: Setting *cmp* is equivalent to setting *eq* and *order* to the | |||
|
186 | same value. Must not be mixed with *eq* or *order*. | |||
|
187 | :type cmp: a `bool` or a `callable`. | |||
|
188 | ||||
|
189 | :param Optional[bool] hash: Include this attribute in the generated | |||
|
190 | ``__hash__`` method. If ``None`` (default), mirror *eq*'s value. This | |||
|
191 | is the correct behavior according the Python spec. Setting this value | |||
|
192 | to anything else than ``None`` is *discouraged*. | |||
110 | :param bool init: Include this attribute in the generated ``__init__`` |
|
193 | :param bool init: Include this attribute in the generated ``__init__`` | |
111 | method. It is possible to set this to ``False`` and set a default |
|
194 | method. It is possible to set this to ``False`` and set a default | |
112 | value. In that case this attributed is unconditionally initialized |
|
195 | value. In that case this attributed is unconditionally initialized | |
113 | with the specified default value or factory. |
|
196 | with the specified default value or factory. | |
114 |
:param callable convert: |
|
197 | :param callable converter: `callable` that is called by | |
115 | ``attrs``-generated ``__init__`` methods to convert attribute's value |
|
198 | ``attrs``-generated ``__init__`` methods to convert attribute's value | |
116 | to the desired format. It is given the passed-in value, and the |
|
199 | to the desired format. It is given the passed-in value, and the | |
117 | returned value will be used as the new value of the attribute. The |
|
200 | returned value will be used as the new value of the attribute. The | |
118 | value is converted before being passed to the validator, if any. |
|
201 | value is converted before being passed to the validator, if any. | |
119 | :param metadata: An arbitrary mapping, to be used by third-party |
|
202 | :param metadata: An arbitrary mapping, to be used by third-party | |
120 |
components. See |
|
203 | components. See `extending_metadata`. | |
121 |
|
204 | :param type: The type of the attribute. In Python 3.6 or greater, the | ||
122 | .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. |
|
205 | preferred method to specify the type is using a variable annotation | |
123 | .. versionchanged:: 17.1.0 |
|
206 | (see :pep:`526`). | |
124 | *hash* is ``None`` and therefore mirrors *cmp* by default . |
|
207 | This argument is provided for backward compatibility. | |
|
208 | Regardless of the approach used, the type will be stored on | |||
|
209 | ``Attribute.type``. | |||
|
210 | ||||
|
211 | Please note that ``attrs`` doesn't do anything with this metadata by | |||
|
212 | itself. You can use it as part of your own code or for | |||
|
213 | `static type checking <types>`. | |||
|
214 | :param kw_only: Make this attribute keyword-only (Python 3+) | |||
|
215 | in the generated ``__init__`` (if ``init`` is ``False``, this | |||
|
216 | parameter is ignored). | |||
|
217 | :param on_setattr: Allows to overwrite the *on_setattr* setting from | |||
|
218 | `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used. | |||
|
219 | Set to `attrs.setters.NO_OP` to run **no** `setattr` hooks for this | |||
|
220 | attribute -- regardless of the setting in `attr.s`. | |||
|
221 | :type on_setattr: `callable`, or a list of callables, or `None`, or | |||
|
222 | `attrs.setters.NO_OP` | |||
|
223 | ||||
|
224 | .. versionadded:: 15.2.0 *convert* | |||
|
225 | .. versionadded:: 16.3.0 *metadata* | |||
|
226 | .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. | |||
|
227 | .. versionchanged:: 17.1.0 | |||
|
228 | *hash* is ``None`` and therefore mirrors *eq* by default. | |||
|
229 | .. versionadded:: 17.3.0 *type* | |||
|
230 | .. deprecated:: 17.4.0 *convert* | |||
|
231 | .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated | |||
|
232 | *convert* to achieve consistency with other noun-based arguments. | |||
|
233 | .. versionadded:: 18.1.0 | |||
|
234 | ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. | |||
|
235 | .. versionadded:: 18.2.0 *kw_only* | |||
|
236 | .. versionchanged:: 19.2.0 *convert* keyword argument removed. | |||
|
237 | .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. | |||
|
238 | .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. | |||
|
239 | .. versionadded:: 19.2.0 *eq* and *order* | |||
|
240 | .. versionadded:: 20.1.0 *on_setattr* | |||
|
241 | .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 | |||
|
242 | .. versionchanged:: 21.1.0 | |||
|
243 | *eq*, *order*, and *cmp* also accept a custom callable | |||
|
244 | .. versionchanged:: 21.1.0 *cmp* undeprecated | |||
125 | """ |
|
245 | """ | |
|
246 | eq, eq_key, order, order_key = _determine_attrib_eq_order( | |||
|
247 | cmp, eq, order, True | |||
|
248 | ) | |||
|
249 | ||||
126 | if hash is not None and hash is not True and hash is not False: |
|
250 | if hash is not None and hash is not True and hash is not False: | |
127 | raise TypeError( |
|
251 | raise TypeError( | |
128 | "Invalid value for hash. Must be True, False, or None." |
|
252 | "Invalid value for hash. Must be True, False, or None." | |
129 | ) |
|
253 | ) | |
|
254 | ||||
|
255 | if factory is not None: | |||
|
256 | if default is not NOTHING: | |||
|
257 | raise ValueError( | |||
|
258 | "The `default` and `factory` arguments are mutually " | |||
|
259 | "exclusive." | |||
|
260 | ) | |||
|
261 | if not callable(factory): | |||
|
262 | raise ValueError("The `factory` argument must be a callable.") | |||
|
263 | default = Factory(factory) | |||
|
264 | ||||
|
265 | if metadata is None: | |||
|
266 | metadata = {} | |||
|
267 | ||||
|
268 | # Apply syntactic sugar by auto-wrapping. | |||
|
269 | if isinstance(on_setattr, (list, tuple)): | |||
|
270 | on_setattr = setters.pipe(*on_setattr) | |||
|
271 | ||||
|
272 | if validator and isinstance(validator, (list, tuple)): | |||
|
273 | validator = and_(*validator) | |||
|
274 | ||||
|
275 | if converter and isinstance(converter, (list, tuple)): | |||
|
276 | converter = pipe(*converter) | |||
|
277 | ||||
130 | return _CountingAttr( |
|
278 | return _CountingAttr( | |
131 | default=default, |
|
279 | default=default, | |
132 | validator=validator, |
|
280 | validator=validator, | |
133 | repr=repr, |
|
281 | repr=repr, | |
134 |
cmp= |
|
282 | cmp=None, | |
135 | hash=hash, |
|
283 | hash=hash, | |
136 | init=init, |
|
284 | init=init, | |
137 | convert=convert, |
|
285 | converter=converter, | |
138 | metadata=metadata, |
|
286 | metadata=metadata, | |
|
287 | type=type, | |||
|
288 | kw_only=kw_only, | |||
|
289 | eq=eq, | |||
|
290 | eq_key=eq_key, | |||
|
291 | order=order, | |||
|
292 | order_key=order_key, | |||
|
293 | on_setattr=on_setattr, | |||
139 | ) |
|
294 | ) | |
140 |
|
295 | |||
141 |
|
296 | |||
|
297 | def _compile_and_eval(script, globs, locs=None, filename=""): | |||
|
298 | """ | |||
|
299 | "Exec" the script with the given global (globs) and local (locs) variables. | |||
|
300 | """ | |||
|
301 | bytecode = compile(script, filename, "exec") | |||
|
302 | eval(bytecode, globs, locs) | |||
|
303 | ||||
|
304 | ||||
|
305 | def _make_method(name, script, filename, globs): | |||
|
306 | """ | |||
|
307 | Create the method with the script given and return the method object. | |||
|
308 | """ | |||
|
309 | locs = {} | |||
|
310 | ||||
|
311 | # In order of debuggers like PDB being able to step through the code, | |||
|
312 | # we add a fake linecache entry. | |||
|
313 | count = 1 | |||
|
314 | base_filename = filename | |||
|
315 | while True: | |||
|
316 | linecache_tuple = ( | |||
|
317 | len(script), | |||
|
318 | None, | |||
|
319 | script.splitlines(True), | |||
|
320 | filename, | |||
|
321 | ) | |||
|
322 | old_val = linecache.cache.setdefault(filename, linecache_tuple) | |||
|
323 | if old_val == linecache_tuple: | |||
|
324 | break | |||
|
325 | else: | |||
|
326 | filename = "{}-{}>".format(base_filename[:-1], count) | |||
|
327 | count += 1 | |||
|
328 | ||||
|
329 | _compile_and_eval(script, globs, locs, filename) | |||
|
330 | ||||
|
331 | return locs[name] | |||
|
332 | ||||
|
333 | ||||
142 | def _make_attr_tuple_class(cls_name, attr_names): |
|
334 | def _make_attr_tuple_class(cls_name, attr_names): | |
143 | """ |
|
335 | """ | |
144 | Create a tuple subclass to hold `Attribute`s for an `attrs` class. |
|
336 | Create a tuple subclass to hold `Attribute`s for an `attrs` class. | |
@@ -156,75 +348,273 b' def _make_attr_tuple_class(cls_name, att' | |||||
156 | ] |
|
348 | ] | |
157 | if attr_names: |
|
349 | if attr_names: | |
158 | for i, attr_name in enumerate(attr_names): |
|
350 | for i, attr_name in enumerate(attr_names): | |
159 |
attr_class_template.append( |
|
351 | attr_class_template.append( | |
160 | index=i, |
|
352 | _tuple_property_pat.format(index=i, attr_name=attr_name) | |
161 | attr_name=attr_name, |
|
353 | ) | |
162 | )) |
|
|||
163 | else: |
|
354 | else: | |
164 | attr_class_template.append(" pass") |
|
355 | attr_class_template.append(" pass") | |
165 | globs = {"itemgetter": itemgetter} |
|
356 | globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property} | |
166 |
|
|
357 | _compile_and_eval("\n".join(attr_class_template), globs) | |
167 | return globs[attr_class_name] |
|
358 | return globs[attr_class_name] | |
168 |
|
359 | |||
169 |
|
360 | |||
170 | def _transform_attrs(cls, these): |
|
361 | # Tuple class for extracted attributes from a class definition. | |
|
362 | # `base_attrs` is a subset of `attrs`. | |||
|
363 | _Attributes = _make_attr_tuple_class( | |||
|
364 | "_Attributes", | |||
|
365 | [ | |||
|
366 | # all attributes to build dunder methods for | |||
|
367 | "attrs", | |||
|
368 | # attributes that have been inherited | |||
|
369 | "base_attrs", | |||
|
370 | # map inherited attributes to their originating classes | |||
|
371 | "base_attrs_map", | |||
|
372 | ], | |||
|
373 | ) | |||
|
374 | ||||
|
375 | ||||
|
376 | def _is_class_var(annot): | |||
|
377 | """ | |||
|
378 | Check whether *annot* is a typing.ClassVar. | |||
|
379 | ||||
|
380 | The string comparison hack is used to avoid evaluating all string | |||
|
381 | annotations which would put attrs-based classes at a performance | |||
|
382 | disadvantage compared to plain old classes. | |||
|
383 | """ | |||
|
384 | annot = str(annot) | |||
|
385 | ||||
|
386 | # Annotation can be quoted. | |||
|
387 | if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): | |||
|
388 | annot = annot[1:-1] | |||
|
389 | ||||
|
390 | return annot.startswith(_classvar_prefixes) | |||
|
391 | ||||
|
392 | ||||
|
393 | def _has_own_attribute(cls, attrib_name): | |||
|
394 | """ | |||
|
395 | Check whether *cls* defines *attrib_name* (and doesn't just inherit it). | |||
|
396 | ||||
|
397 | Requires Python 3. | |||
|
398 | """ | |||
|
399 | attr = getattr(cls, attrib_name, _sentinel) | |||
|
400 | if attr is _sentinel: | |||
|
401 | return False | |||
|
402 | ||||
|
403 | for base_cls in cls.__mro__[1:]: | |||
|
404 | a = getattr(base_cls, attrib_name, None) | |||
|
405 | if attr is a: | |||
|
406 | return False | |||
|
407 | ||||
|
408 | return True | |||
|
409 | ||||
|
410 | ||||
|
411 | def _get_annotations(cls): | |||
|
412 | """ | |||
|
413 | Get annotations for *cls*. | |||
|
414 | """ | |||
|
415 | if _has_own_attribute(cls, "__annotations__"): | |||
|
416 | return cls.__annotations__ | |||
|
417 | ||||
|
418 | return {} | |||
|
419 | ||||
|
420 | ||||
|
421 | def _counter_getter(e): | |||
|
422 | """ | |||
|
423 | Key function for sorting to avoid re-creating a lambda for every class. | |||
171 |
|
|
424 | """ | |
172 | Transforms all `_CountingAttr`s on a class into `Attribute`s and saves the |
|
425 | return e[1].counter | |
173 | list in `__attrs_attrs__`. |
|
426 | ||
|
427 | ||||
|
428 | def _collect_base_attrs(cls, taken_attr_names): | |||
|
429 | """ | |||
|
430 | Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. | |||
|
431 | """ | |||
|
432 | base_attrs = [] | |||
|
433 | base_attr_map = {} # A dictionary of base attrs to their classes. | |||
|
434 | ||||
|
435 | # Traverse the MRO and collect attributes. | |||
|
436 | for base_cls in reversed(cls.__mro__[1:-1]): | |||
|
437 | for a in getattr(base_cls, "__attrs_attrs__", []): | |||
|
438 | if a.inherited or a.name in taken_attr_names: | |||
|
439 | continue | |||
|
440 | ||||
|
441 | a = a.evolve(inherited=True) | |||
|
442 | base_attrs.append(a) | |||
|
443 | base_attr_map[a.name] = base_cls | |||
|
444 | ||||
|
445 | # For each name, only keep the freshest definition i.e. the furthest at the | |||
|
446 | # back. base_attr_map is fine because it gets overwritten with every new | |||
|
447 | # instance. | |||
|
448 | filtered = [] | |||
|
449 | seen = set() | |||
|
450 | for a in reversed(base_attrs): | |||
|
451 | if a.name in seen: | |||
|
452 | continue | |||
|
453 | filtered.insert(0, a) | |||
|
454 | seen.add(a.name) | |||
|
455 | ||||
|
456 | return filtered, base_attr_map | |||
|
457 | ||||
|
458 | ||||
|
459 | def _collect_base_attrs_broken(cls, taken_attr_names): | |||
|
460 | """ | |||
|
461 | Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. | |||
|
462 | ||||
|
463 | N.B. *taken_attr_names* will be mutated. | |||
|
464 | ||||
|
465 | Adhere to the old incorrect behavior. | |||
|
466 | ||||
|
467 | Notably it collects from the front and considers inherited attributes which | |||
|
468 | leads to the buggy behavior reported in #428. | |||
|
469 | """ | |||
|
470 | base_attrs = [] | |||
|
471 | base_attr_map = {} # A dictionary of base attrs to their classes. | |||
|
472 | ||||
|
473 | # Traverse the MRO and collect attributes. | |||
|
474 | for base_cls in cls.__mro__[1:-1]: | |||
|
475 | for a in getattr(base_cls, "__attrs_attrs__", []): | |||
|
476 | if a.name in taken_attr_names: | |||
|
477 | continue | |||
|
478 | ||||
|
479 | a = a.evolve(inherited=True) | |||
|
480 | taken_attr_names.add(a.name) | |||
|
481 | base_attrs.append(a) | |||
|
482 | base_attr_map[a.name] = base_cls | |||
|
483 | ||||
|
484 | return base_attrs, base_attr_map | |||
|
485 | ||||
|
486 | ||||
|
487 | def _transform_attrs( | |||
|
488 | cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer | |||
|
489 | ): | |||
|
490 | """ | |||
|
491 | Transform all `_CountingAttr`s on a class into `Attribute`s. | |||
174 |
|
492 | |||
175 | If *these* is passed, use that and don't look for them on the class. |
|
493 | If *these* is passed, use that and don't look for them on the class. | |
|
494 | ||||
|
495 | *collect_by_mro* is True, collect them in the correct MRO order, otherwise | |||
|
496 | use the old -- incorrect -- order. See #428. | |||
|
497 | ||||
|
498 | Return an `_Attributes`. | |||
176 | """ |
|
499 | """ | |
177 | super_cls = [] |
|
500 | cd = cls.__dict__ | |
178 | for c in reversed(cls.__mro__[1:-1]): |
|
501 | anns = _get_annotations(cls) | |
179 | sub_attrs = getattr(c, "__attrs_attrs__", None) |
|
502 | ||
180 |
|
|
503 | if these is not None: | |
181 | super_cls.extend(a for a in sub_attrs if a not in super_cls) |
|
504 | ca_list = [(name, ca) for name, ca in these.items()] | |
182 | if these is None: |
|
505 | ||
183 | ca_list = [(name, attr) |
|
506 | if not isinstance(these, ordered_dict): | |
184 | for name, attr |
|
507 | ca_list.sort(key=_counter_getter) | |
185 | in cls.__dict__.items() |
|
508 | elif auto_attribs is True: | |
186 | if isinstance(attr, _CountingAttr)] |
|
509 | ca_names = { | |
|
510 | name | |||
|
511 | for name, attr in cd.items() | |||
|
512 | if isinstance(attr, _CountingAttr) | |||
|
513 | } | |||
|
514 | ca_list = [] | |||
|
515 | annot_names = set() | |||
|
516 | for attr_name, type in anns.items(): | |||
|
517 | if _is_class_var(type): | |||
|
518 | continue | |||
|
519 | annot_names.add(attr_name) | |||
|
520 | a = cd.get(attr_name, NOTHING) | |||
|
521 | ||||
|
522 | if not isinstance(a, _CountingAttr): | |||
|
523 | if a is NOTHING: | |||
|
524 | a = attrib() | |||
|
525 | else: | |||
|
526 | a = attrib(default=a) | |||
|
527 | ca_list.append((attr_name, a)) | |||
|
528 | ||||
|
529 | unannotated = ca_names - annot_names | |||
|
530 | if len(unannotated) > 0: | |||
|
531 | raise UnannotatedAttributeError( | |||
|
532 | "The following `attr.ib`s lack a type annotation: " | |||
|
533 | + ", ".join( | |||
|
534 | sorted(unannotated, key=lambda n: cd.get(n).counter) | |||
|
535 | ) | |||
|
536 | + "." | |||
|
537 | ) | |||
187 | else: |
|
538 | else: | |
188 |
ca_list = |
|
539 | ca_list = sorted( | |
189 | for name, ca |
|
540 | ( | |
190 | in iteritems(these)] |
|
541 | (name, attr) | |
191 |
|
542 | for name, attr in cd.items() | ||
192 | non_super_attrs = [ |
|
543 | if isinstance(attr, _CountingAttr) | |
193 | Attribute.from_counting_attr(name=attr_name, ca=ca) |
|
544 | ), | |
194 | for attr_name, ca |
|
545 | key=lambda e: e[1].counter, | |
195 | in sorted(ca_list, key=lambda e: e[1].counter) |
|
546 | ) | |
|
547 | ||||
|
548 | own_attrs = [ | |||
|
549 | Attribute.from_counting_attr( | |||
|
550 | name=attr_name, ca=ca, type=anns.get(attr_name) | |||
|
551 | ) | |||
|
552 | for attr_name, ca in ca_list | |||
196 | ] |
|
553 | ] | |
197 | attr_names = [a.name for a in super_cls + non_super_attrs] |
|
554 | ||
198 |
|
555 | if collect_by_mro: | ||
199 | AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) |
|
556 | base_attrs, base_attr_map = _collect_base_attrs( | |
200 |
|
557 | cls, {a.name for a in own_attrs} | ||
201 | cls.__attrs_attrs__ = AttrsClass(super_cls + [ |
|
558 | ) | |
202 | Attribute.from_counting_attr(name=attr_name, ca=ca) |
|
559 | else: | |
203 | for attr_name, ca |
|
560 | base_attrs, base_attr_map = _collect_base_attrs_broken( | |
204 | in sorted(ca_list, key=lambda e: e[1].counter) |
|
561 | cls, {a.name for a in own_attrs} | |
205 |
|
|
562 | ) | |
206 |
|
563 | |||
|
564 | if kw_only: | |||
|
565 | own_attrs = [a.evolve(kw_only=True) for a in own_attrs] | |||
|
566 | base_attrs = [a.evolve(kw_only=True) for a in base_attrs] | |||
|
567 | ||||
|
568 | attrs = base_attrs + own_attrs | |||
|
569 | ||||
|
570 | # Mandatory vs non-mandatory attr order only matters when they are part of | |||
|
571 | # the __init__ signature and when they aren't kw_only (which are moved to | |||
|
572 | # the end and can be mandatory or non-mandatory in any order, as they will | |||
|
573 | # be specified as keyword args anyway). Check the order of those attrs: | |||
207 | had_default = False |
|
574 | had_default = False | |
208 | for a in cls.__attrs_attrs__: |
|
575 | for a in (a for a in attrs if a.init is not False and a.kw_only is False): | |
209 | if these is None and a not in super_cls: |
|
576 | if had_default is True and a.default is NOTHING: | |
210 | setattr(cls, a.name, a) |
|
|||
211 | if had_default is True and a.default is NOTHING and a.init is True: |
|
|||
212 | raise ValueError( |
|
577 | raise ValueError( | |
213 | "No mandatory attributes allowed after an attribute with a " |
|
578 | "No mandatory attributes allowed after an attribute with a " | |
214 |
"default value or factory. Attribute in question: |
|
579 | "default value or factory. Attribute in question: %r" % (a,) | |
215 | .format(a=a) |
|
|||
216 | ) |
|
580 | ) | |
217 | elif had_default is False and \ |
|
581 | ||
218 |
|
|
582 | if had_default is False and a.default is not NOTHING: | |
219 | a.init is not False: |
|
|||
220 | had_default = True |
|
583 | had_default = True | |
221 |
|
584 | |||
222 |
|
585 | if field_transformer is not None: | ||
223 | def _frozen_setattrs(self, name, value): |
|
586 | attrs = field_transformer(cls, attrs) | |
224 | """ |
|
587 | ||
225 | Attached to frozen classes as __setattr__. |
|
588 | # Create AttrsClass *after* applying the field_transformer since it may | |
226 | """ |
|
589 | # add or remove attributes! | |
227 | raise FrozenInstanceError() |
|
590 | attr_names = [a.name for a in attrs] | |
|
591 | AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) | |||
|
592 | ||||
|
593 | return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map)) | |||
|
594 | ||||
|
595 | ||||
|
596 | if PYPY: | |||
|
597 | ||||
|
598 | def _frozen_setattrs(self, name, value): | |||
|
599 | """ | |||
|
600 | Attached to frozen classes as __setattr__. | |||
|
601 | """ | |||
|
602 | if isinstance(self, BaseException) and name in ( | |||
|
603 | "__cause__", | |||
|
604 | "__context__", | |||
|
605 | ): | |||
|
606 | BaseException.__setattr__(self, name, value) | |||
|
607 | return | |||
|
608 | ||||
|
609 | raise FrozenInstanceError() | |||
|
610 | ||||
|
611 | else: | |||
|
612 | ||||
|
613 | def _frozen_setattrs(self, name, value): | |||
|
614 | """ | |||
|
615 | Attached to frozen classes as __setattr__. | |||
|
616 | """ | |||
|
617 | raise FrozenInstanceError() | |||
228 |
|
618 | |||
229 |
|
619 | |||
230 | def _frozen_delattrs(self, name): |
|
620 | def _frozen_delattrs(self, name): | |
@@ -234,44 +624,661 b' def _frozen_delattrs(self, name):' | |||||
234 | raise FrozenInstanceError() |
|
624 | raise FrozenInstanceError() | |
235 |
|
625 | |||
236 |
|
626 | |||
237 | def attributes(maybe_cls=None, these=None, repr_ns=None, |
|
627 | class _ClassBuilder: | |
238 | repr=True, cmp=True, hash=None, init=True, |
|
628 | """ | |
239 | slots=False, frozen=False, str=False): |
|
629 | Iteratively build *one* class. | |
|
630 | """ | |||
|
631 | ||||
|
632 | __slots__ = ( | |||
|
633 | "_attr_names", | |||
|
634 | "_attrs", | |||
|
635 | "_base_attr_map", | |||
|
636 | "_base_names", | |||
|
637 | "_cache_hash", | |||
|
638 | "_cls", | |||
|
639 | "_cls_dict", | |||
|
640 | "_delete_attribs", | |||
|
641 | "_frozen", | |||
|
642 | "_has_pre_init", | |||
|
643 | "_has_post_init", | |||
|
644 | "_is_exc", | |||
|
645 | "_on_setattr", | |||
|
646 | "_slots", | |||
|
647 | "_weakref_slot", | |||
|
648 | "_wrote_own_setattr", | |||
|
649 | "_has_custom_setattr", | |||
|
650 | ) | |||
|
651 | ||||
|
652 | def __init__( | |||
|
653 | self, | |||
|
654 | cls, | |||
|
655 | these, | |||
|
656 | slots, | |||
|
657 | frozen, | |||
|
658 | weakref_slot, | |||
|
659 | getstate_setstate, | |||
|
660 | auto_attribs, | |||
|
661 | kw_only, | |||
|
662 | cache_hash, | |||
|
663 | is_exc, | |||
|
664 | collect_by_mro, | |||
|
665 | on_setattr, | |||
|
666 | has_custom_setattr, | |||
|
667 | field_transformer, | |||
|
668 | ): | |||
|
669 | attrs, base_attrs, base_map = _transform_attrs( | |||
|
670 | cls, | |||
|
671 | these, | |||
|
672 | auto_attribs, | |||
|
673 | kw_only, | |||
|
674 | collect_by_mro, | |||
|
675 | field_transformer, | |||
|
676 | ) | |||
|
677 | ||||
|
678 | self._cls = cls | |||
|
679 | self._cls_dict = dict(cls.__dict__) if slots else {} | |||
|
680 | self._attrs = attrs | |||
|
681 | self._base_names = {a.name for a in base_attrs} | |||
|
682 | self._base_attr_map = base_map | |||
|
683 | self._attr_names = tuple(a.name for a in attrs) | |||
|
684 | self._slots = slots | |||
|
685 | self._frozen = frozen | |||
|
686 | self._weakref_slot = weakref_slot | |||
|
687 | self._cache_hash = cache_hash | |||
|
688 | self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) | |||
|
689 | self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) | |||
|
690 | self._delete_attribs = not bool(these) | |||
|
691 | self._is_exc = is_exc | |||
|
692 | self._on_setattr = on_setattr | |||
|
693 | ||||
|
694 | self._has_custom_setattr = has_custom_setattr | |||
|
695 | self._wrote_own_setattr = False | |||
|
696 | ||||
|
697 | self._cls_dict["__attrs_attrs__"] = self._attrs | |||
|
698 | ||||
|
699 | if frozen: | |||
|
700 | self._cls_dict["__setattr__"] = _frozen_setattrs | |||
|
701 | self._cls_dict["__delattr__"] = _frozen_delattrs | |||
|
702 | ||||
|
703 | self._wrote_own_setattr = True | |||
|
704 | elif on_setattr in ( | |||
|
705 | _ng_default_on_setattr, | |||
|
706 | setters.validate, | |||
|
707 | setters.convert, | |||
|
708 | ): | |||
|
709 | has_validator = has_converter = False | |||
|
710 | for a in attrs: | |||
|
711 | if a.validator is not None: | |||
|
712 | has_validator = True | |||
|
713 | if a.converter is not None: | |||
|
714 | has_converter = True | |||
|
715 | ||||
|
716 | if has_validator and has_converter: | |||
|
717 | break | |||
|
718 | if ( | |||
|
719 | ( | |||
|
720 | on_setattr == _ng_default_on_setattr | |||
|
721 | and not (has_validator or has_converter) | |||
|
722 | ) | |||
|
723 | or (on_setattr == setters.validate and not has_validator) | |||
|
724 | or (on_setattr == setters.convert and not has_converter) | |||
|
725 | ): | |||
|
726 | # If class-level on_setattr is set to convert + validate, but | |||
|
727 | # there's no field to convert or validate, pretend like there's | |||
|
728 | # no on_setattr. | |||
|
729 | self._on_setattr = None | |||
|
730 | ||||
|
731 | if getstate_setstate: | |||
|
732 | ( | |||
|
733 | self._cls_dict["__getstate__"], | |||
|
734 | self._cls_dict["__setstate__"], | |||
|
735 | ) = self._make_getstate_setstate() | |||
|
736 | ||||
|
737 | def __repr__(self): | |||
|
738 | return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__) | |||
|
739 | ||||
|
740 | def build_class(self): | |||
|
741 | """ | |||
|
742 | Finalize class based on the accumulated configuration. | |||
|
743 | ||||
|
744 | Builder cannot be used after calling this method. | |||
|
745 | """ | |||
|
746 | if self._slots is True: | |||
|
747 | return self._create_slots_class() | |||
|
748 | else: | |||
|
749 | return self._patch_original_class() | |||
|
750 | ||||
|
751 | def _patch_original_class(self): | |||
|
752 | """ | |||
|
753 | Apply accumulated methods and return the class. | |||
|
754 | """ | |||
|
755 | cls = self._cls | |||
|
756 | base_names = self._base_names | |||
|
757 | ||||
|
758 | # Clean class of attribute definitions (`attr.ib()`s). | |||
|
759 | if self._delete_attribs: | |||
|
760 | for name in self._attr_names: | |||
|
761 | if ( | |||
|
762 | name not in base_names | |||
|
763 | and getattr(cls, name, _sentinel) is not _sentinel | |||
|
764 | ): | |||
|
765 | try: | |||
|
766 | delattr(cls, name) | |||
|
767 | except AttributeError: | |||
|
768 | # This can happen if a base class defines a class | |||
|
769 | # variable and we want to set an attribute with the | |||
|
770 | # same name by using only a type annotation. | |||
|
771 | pass | |||
|
772 | ||||
|
773 | # Attach our dunder methods. | |||
|
774 | for name, value in self._cls_dict.items(): | |||
|
775 | setattr(cls, name, value) | |||
|
776 | ||||
|
777 | # If we've inherited an attrs __setattr__ and don't write our own, | |||
|
778 | # reset it to object's. | |||
|
779 | if not self._wrote_own_setattr and getattr( | |||
|
780 | cls, "__attrs_own_setattr__", False | |||
|
781 | ): | |||
|
782 | cls.__attrs_own_setattr__ = False | |||
|
783 | ||||
|
784 | if not self._has_custom_setattr: | |||
|
785 | cls.__setattr__ = _obj_setattr | |||
|
786 | ||||
|
787 | return cls | |||
|
788 | ||||
|
789 | def _create_slots_class(self): | |||
|
790 | """ | |||
|
791 | Build and return a new class with a `__slots__` attribute. | |||
|
792 | """ | |||
|
793 | cd = { | |||
|
794 | k: v | |||
|
795 | for k, v in self._cls_dict.items() | |||
|
796 | if k not in tuple(self._attr_names) + ("__dict__", "__weakref__") | |||
|
797 | } | |||
|
798 | ||||
|
799 | # If our class doesn't have its own implementation of __setattr__ | |||
|
800 | # (either from the user or by us), check the bases, if one of them has | |||
|
801 | # an attrs-made __setattr__, that needs to be reset. We don't walk the | |||
|
802 | # MRO because we only care about our immediate base classes. | |||
|
803 | # XXX: This can be confused by subclassing a slotted attrs class with | |||
|
804 | # XXX: a non-attrs class and subclass the resulting class with an attrs | |||
|
805 | # XXX: class. See `test_slotted_confused` for details. For now that's | |||
|
806 | # XXX: OK with us. | |||
|
807 | if not self._wrote_own_setattr: | |||
|
808 | cd["__attrs_own_setattr__"] = False | |||
|
809 | ||||
|
810 | if not self._has_custom_setattr: | |||
|
811 | for base_cls in self._cls.__bases__: | |||
|
812 | if base_cls.__dict__.get("__attrs_own_setattr__", False): | |||
|
813 | cd["__setattr__"] = _obj_setattr | |||
|
814 | break | |||
|
815 | ||||
|
816 | # Traverse the MRO to collect existing slots | |||
|
817 | # and check for an existing __weakref__. | |||
|
818 | existing_slots = dict() | |||
|
819 | weakref_inherited = False | |||
|
820 | for base_cls in self._cls.__mro__[1:-1]: | |||
|
821 | if base_cls.__dict__.get("__weakref__", None) is not None: | |||
|
822 | weakref_inherited = True | |||
|
823 | existing_slots.update( | |||
|
824 | { | |||
|
825 | name: getattr(base_cls, name) | |||
|
826 | for name in getattr(base_cls, "__slots__", []) | |||
|
827 | } | |||
|
828 | ) | |||
|
829 | ||||
|
830 | base_names = set(self._base_names) | |||
|
831 | ||||
|
832 | names = self._attr_names | |||
|
833 | if ( | |||
|
834 | self._weakref_slot | |||
|
835 | and "__weakref__" not in getattr(self._cls, "__slots__", ()) | |||
|
836 | and "__weakref__" not in names | |||
|
837 | and not weakref_inherited | |||
|
838 | ): | |||
|
839 | names += ("__weakref__",) | |||
|
840 | ||||
|
841 | # We only add the names of attributes that aren't inherited. | |||
|
842 | # Setting __slots__ to inherited attributes wastes memory. | |||
|
843 | slot_names = [name for name in names if name not in base_names] | |||
|
844 | # There are slots for attributes from current class | |||
|
845 | # that are defined in parent classes. | |||
|
846 | # As their descriptors may be overridden by a child class, | |||
|
847 | # we collect them here and update the class dict | |||
|
848 | reused_slots = { | |||
|
849 | slot: slot_descriptor | |||
|
850 | for slot, slot_descriptor in existing_slots.items() | |||
|
851 | if slot in slot_names | |||
|
852 | } | |||
|
853 | slot_names = [name for name in slot_names if name not in reused_slots] | |||
|
854 | cd.update(reused_slots) | |||
|
855 | if self._cache_hash: | |||
|
856 | slot_names.append(_hash_cache_field) | |||
|
857 | cd["__slots__"] = tuple(slot_names) | |||
|
858 | ||||
|
859 | cd["__qualname__"] = self._cls.__qualname__ | |||
|
860 | ||||
|
861 | # Create new class based on old class and our methods. | |||
|
862 | cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) | |||
|
863 | ||||
|
864 | # The following is a fix for | |||
|
865 | # <https://github.com/python-attrs/attrs/issues/102>. On Python 3, | |||
|
866 | # if a method mentions `__class__` or uses the no-arg super(), the | |||
|
867 | # compiler will bake a reference to the class in the method itself | |||
|
868 | # as `method.__closure__`. Since we replace the class with a | |||
|
869 | # clone, we rewrite these references so it keeps working. | |||
|
870 | for item in cls.__dict__.values(): | |||
|
871 | if isinstance(item, (classmethod, staticmethod)): | |||
|
872 | # Class- and staticmethods hide their functions inside. | |||
|
873 | # These might need to be rewritten as well. | |||
|
874 | closure_cells = getattr(item.__func__, "__closure__", None) | |||
|
875 | elif isinstance(item, property): | |||
|
876 | # Workaround for property `super()` shortcut (PY3-only). | |||
|
877 | # There is no universal way for other descriptors. | |||
|
878 | closure_cells = getattr(item.fget, "__closure__", None) | |||
|
879 | else: | |||
|
880 | closure_cells = getattr(item, "__closure__", None) | |||
|
881 | ||||
|
882 | if not closure_cells: # Catch None or the empty list. | |||
|
883 | continue | |||
|
884 | for cell in closure_cells: | |||
|
885 | try: | |||
|
886 | match = cell.cell_contents is self._cls | |||
|
887 | except ValueError: # ValueError: Cell is empty | |||
|
888 | pass | |||
|
889 | else: | |||
|
890 | if match: | |||
|
891 | set_closure_cell(cell, cls) | |||
|
892 | ||||
|
893 | return cls | |||
|
894 | ||||
|
895 | def add_repr(self, ns): | |||
|
896 | self._cls_dict["__repr__"] = self._add_method_dunders( | |||
|
897 | _make_repr(self._attrs, ns, self._cls) | |||
|
898 | ) | |||
|
899 | return self | |||
|
900 | ||||
|
901 | def add_str(self): | |||
|
902 | repr = self._cls_dict.get("__repr__") | |||
|
903 | if repr is None: | |||
|
904 | raise ValueError( | |||
|
905 | "__str__ can only be generated if a __repr__ exists." | |||
|
906 | ) | |||
|
907 | ||||
|
908 | def __str__(self): | |||
|
909 | return self.__repr__() | |||
|
910 | ||||
|
911 | self._cls_dict["__str__"] = self._add_method_dunders(__str__) | |||
|
912 | return self | |||
|
913 | ||||
|
914 | def _make_getstate_setstate(self): | |||
|
915 | """ | |||
|
916 | Create custom __setstate__ and __getstate__ methods. | |||
|
917 | """ | |||
|
918 | # __weakref__ is not writable. | |||
|
919 | state_attr_names = tuple( | |||
|
920 | an for an in self._attr_names if an != "__weakref__" | |||
|
921 | ) | |||
|
922 | ||||
|
923 | def slots_getstate(self): | |||
|
924 | """ | |||
|
925 | Automatically created by attrs. | |||
|
926 | """ | |||
|
927 | return tuple(getattr(self, name) for name in state_attr_names) | |||
|
928 | ||||
|
929 | hash_caching_enabled = self._cache_hash | |||
|
930 | ||||
|
931 | def slots_setstate(self, state): | |||
|
932 | """ | |||
|
933 | Automatically created by attrs. | |||
|
934 | """ | |||
|
935 | __bound_setattr = _obj_setattr.__get__(self, Attribute) | |||
|
936 | for name, value in zip(state_attr_names, state): | |||
|
937 | __bound_setattr(name, value) | |||
|
938 | ||||
|
939 | # The hash code cache is not included when the object is | |||
|
940 | # serialized, but it still needs to be initialized to None to | |||
|
941 | # indicate that the first call to __hash__ should be a cache | |||
|
942 | # miss. | |||
|
943 | if hash_caching_enabled: | |||
|
944 | __bound_setattr(_hash_cache_field, None) | |||
|
945 | ||||
|
946 | return slots_getstate, slots_setstate | |||
|
947 | ||||
|
948 | def make_unhashable(self): | |||
|
949 | self._cls_dict["__hash__"] = None | |||
|
950 | return self | |||
|
951 | ||||
|
952 | def add_hash(self): | |||
|
953 | self._cls_dict["__hash__"] = self._add_method_dunders( | |||
|
954 | _make_hash( | |||
|
955 | self._cls, | |||
|
956 | self._attrs, | |||
|
957 | frozen=self._frozen, | |||
|
958 | cache_hash=self._cache_hash, | |||
|
959 | ) | |||
|
960 | ) | |||
|
961 | ||||
|
962 | return self | |||
|
963 | ||||
|
964 | def add_init(self): | |||
|
965 | self._cls_dict["__init__"] = self._add_method_dunders( | |||
|
966 | _make_init( | |||
|
967 | self._cls, | |||
|
968 | self._attrs, | |||
|
969 | self._has_pre_init, | |||
|
970 | self._has_post_init, | |||
|
971 | self._frozen, | |||
|
972 | self._slots, | |||
|
973 | self._cache_hash, | |||
|
974 | self._base_attr_map, | |||
|
975 | self._is_exc, | |||
|
976 | self._on_setattr, | |||
|
977 | attrs_init=False, | |||
|
978 | ) | |||
|
979 | ) | |||
|
980 | ||||
|
981 | return self | |||
|
982 | ||||
|
983 | def add_match_args(self): | |||
|
984 | self._cls_dict["__match_args__"] = tuple( | |||
|
985 | field.name | |||
|
986 | for field in self._attrs | |||
|
987 | if field.init and not field.kw_only | |||
|
988 | ) | |||
|
989 | ||||
|
990 | def add_attrs_init(self): | |||
|
991 | self._cls_dict["__attrs_init__"] = self._add_method_dunders( | |||
|
992 | _make_init( | |||
|
993 | self._cls, | |||
|
994 | self._attrs, | |||
|
995 | self._has_pre_init, | |||
|
996 | self._has_post_init, | |||
|
997 | self._frozen, | |||
|
998 | self._slots, | |||
|
999 | self._cache_hash, | |||
|
1000 | self._base_attr_map, | |||
|
1001 | self._is_exc, | |||
|
1002 | self._on_setattr, | |||
|
1003 | attrs_init=True, | |||
|
1004 | ) | |||
|
1005 | ) | |||
|
1006 | ||||
|
1007 | return self | |||
|
1008 | ||||
|
1009 | def add_eq(self): | |||
|
1010 | cd = self._cls_dict | |||
|
1011 | ||||
|
1012 | cd["__eq__"] = self._add_method_dunders( | |||
|
1013 | _make_eq(self._cls, self._attrs) | |||
|
1014 | ) | |||
|
1015 | cd["__ne__"] = self._add_method_dunders(_make_ne()) | |||
|
1016 | ||||
|
1017 | return self | |||
|
1018 | ||||
|
1019 | def add_order(self): | |||
|
1020 | cd = self._cls_dict | |||
|
1021 | ||||
|
1022 | cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( | |||
|
1023 | self._add_method_dunders(meth) | |||
|
1024 | for meth in _make_order(self._cls, self._attrs) | |||
|
1025 | ) | |||
|
1026 | ||||
|
1027 | return self | |||
|
1028 | ||||
|
1029 | def add_setattr(self): | |||
|
1030 | if self._frozen: | |||
|
1031 | return self | |||
|
1032 | ||||
|
1033 | sa_attrs = {} | |||
|
1034 | for a in self._attrs: | |||
|
1035 | on_setattr = a.on_setattr or self._on_setattr | |||
|
1036 | if on_setattr and on_setattr is not setters.NO_OP: | |||
|
1037 | sa_attrs[a.name] = a, on_setattr | |||
|
1038 | ||||
|
1039 | if not sa_attrs: | |||
|
1040 | return self | |||
|
1041 | ||||
|
1042 | if self._has_custom_setattr: | |||
|
1043 | # We need to write a __setattr__ but there already is one! | |||
|
1044 | raise ValueError( | |||
|
1045 | "Can't combine custom __setattr__ with on_setattr hooks." | |||
|
1046 | ) | |||
|
1047 | ||||
|
1048 | # docstring comes from _add_method_dunders | |||
|
1049 | def __setattr__(self, name, val): | |||
|
1050 | try: | |||
|
1051 | a, hook = sa_attrs[name] | |||
|
1052 | except KeyError: | |||
|
1053 | nval = val | |||
|
1054 | else: | |||
|
1055 | nval = hook(self, a, val) | |||
|
1056 | ||||
|
1057 | _obj_setattr(self, name, nval) | |||
|
1058 | ||||
|
1059 | self._cls_dict["__attrs_own_setattr__"] = True | |||
|
1060 | self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) | |||
|
1061 | self._wrote_own_setattr = True | |||
|
1062 | ||||
|
1063 | return self | |||
|
1064 | ||||
|
1065 | def _add_method_dunders(self, method): | |||
|
1066 | """ | |||
|
1067 | Add __module__ and __qualname__ to a *method* if possible. | |||
|
1068 | """ | |||
|
1069 | try: | |||
|
1070 | method.__module__ = self._cls.__module__ | |||
|
1071 | except AttributeError: | |||
|
1072 | pass | |||
|
1073 | ||||
|
1074 | try: | |||
|
1075 | method.__qualname__ = ".".join( | |||
|
1076 | (self._cls.__qualname__, method.__name__) | |||
|
1077 | ) | |||
|
1078 | except AttributeError: | |||
|
1079 | pass | |||
|
1080 | ||||
|
1081 | try: | |||
|
1082 | method.__doc__ = "Method generated by attrs for class %s." % ( | |||
|
1083 | self._cls.__qualname__, | |||
|
1084 | ) | |||
|
1085 | except AttributeError: | |||
|
1086 | pass | |||
|
1087 | ||||
|
1088 | return method | |||
|
1089 | ||||
|
1090 | ||||
|
1091 | def _determine_attrs_eq_order(cmp, eq, order, default_eq): | |||
|
1092 | """ | |||
|
1093 | Validate the combination of *cmp*, *eq*, and *order*. Derive the effective | |||
|
1094 | values of eq and order. If *eq* is None, set it to *default_eq*. | |||
|
1095 | """ | |||
|
1096 | if cmp is not None and any((eq is not None, order is not None)): | |||
|
1097 | raise ValueError("Don't mix `cmp` with `eq' and `order`.") | |||
|
1098 | ||||
|
1099 | # cmp takes precedence due to bw-compatibility. | |||
|
1100 | if cmp is not None: | |||
|
1101 | return cmp, cmp | |||
|
1102 | ||||
|
1103 | # If left None, equality is set to the specified default and ordering | |||
|
1104 | # mirrors equality. | |||
|
1105 | if eq is None: | |||
|
1106 | eq = default_eq | |||
|
1107 | ||||
|
1108 | if order is None: | |||
|
1109 | order = eq | |||
|
1110 | ||||
|
1111 | if eq is False and order is True: | |||
|
1112 | raise ValueError("`order` can only be True if `eq` is True too.") | |||
|
1113 | ||||
|
1114 | return eq, order | |||
|
1115 | ||||
|
1116 | ||||
|
1117 | def _determine_attrib_eq_order(cmp, eq, order, default_eq): | |||
|
1118 | """ | |||
|
1119 | Validate the combination of *cmp*, *eq*, and *order*. Derive the effective | |||
|
1120 | values of eq and order. If *eq* is None, set it to *default_eq*. | |||
|
1121 | """ | |||
|
1122 | if cmp is not None and any((eq is not None, order is not None)): | |||
|
1123 | raise ValueError("Don't mix `cmp` with `eq' and `order`.") | |||
|
1124 | ||||
|
1125 | def decide_callable_or_boolean(value): | |||
|
1126 | """ | |||
|
1127 | Decide whether a key function is used. | |||
|
1128 | """ | |||
|
1129 | if callable(value): | |||
|
1130 | value, key = True, value | |||
|
1131 | else: | |||
|
1132 | key = None | |||
|
1133 | return value, key | |||
|
1134 | ||||
|
1135 | # cmp takes precedence due to bw-compatibility. | |||
|
1136 | if cmp is not None: | |||
|
1137 | cmp, cmp_key = decide_callable_or_boolean(cmp) | |||
|
1138 | return cmp, cmp_key, cmp, cmp_key | |||
|
1139 | ||||
|
1140 | # If left None, equality is set to the specified default and ordering | |||
|
1141 | # mirrors equality. | |||
|
1142 | if eq is None: | |||
|
1143 | eq, eq_key = default_eq, None | |||
|
1144 | else: | |||
|
1145 | eq, eq_key = decide_callable_or_boolean(eq) | |||
|
1146 | ||||
|
1147 | if order is None: | |||
|
1148 | order, order_key = eq, eq_key | |||
|
1149 | else: | |||
|
1150 | order, order_key = decide_callable_or_boolean(order) | |||
|
1151 | ||||
|
1152 | if eq is False and order is True: | |||
|
1153 | raise ValueError("`order` can only be True if `eq` is True too.") | |||
|
1154 | ||||
|
1155 | return eq, eq_key, order, order_key | |||
|
1156 | ||||
|
1157 | ||||
|
1158 | def _determine_whether_to_implement( | |||
|
1159 | cls, flag, auto_detect, dunders, default=True | |||
|
1160 | ): | |||
|
1161 | """ | |||
|
1162 | Check whether we should implement a set of methods for *cls*. | |||
|
1163 | ||||
|
1164 | *flag* is the argument passed into @attr.s like 'init', *auto_detect* the | |||
|
1165 | same as passed into @attr.s and *dunders* is a tuple of attribute names | |||
|
1166 | whose presence signal that the user has implemented it themselves. | |||
|
1167 | ||||
|
1168 | Return *default* if no reason for either for or against is found. | |||
|
1169 | """ | |||
|
1170 | if flag is True or flag is False: | |||
|
1171 | return flag | |||
|
1172 | ||||
|
1173 | if flag is None and auto_detect is False: | |||
|
1174 | return default | |||
|
1175 | ||||
|
1176 | # Logically, flag is None and auto_detect is True here. | |||
|
1177 | for dunder in dunders: | |||
|
1178 | if _has_own_attribute(cls, dunder): | |||
|
1179 | return False | |||
|
1180 | ||||
|
1181 | return default | |||
|
1182 | ||||
|
1183 | ||||
|
1184 | def attrs( | |||
|
1185 | maybe_cls=None, | |||
|
1186 | these=None, | |||
|
1187 | repr_ns=None, | |||
|
1188 | repr=None, | |||
|
1189 | cmp=None, | |||
|
1190 | hash=None, | |||
|
1191 | init=None, | |||
|
1192 | slots=False, | |||
|
1193 | frozen=False, | |||
|
1194 | weakref_slot=True, | |||
|
1195 | str=False, | |||
|
1196 | auto_attribs=False, | |||
|
1197 | kw_only=False, | |||
|
1198 | cache_hash=False, | |||
|
1199 | auto_exc=False, | |||
|
1200 | eq=None, | |||
|
1201 | order=None, | |||
|
1202 | auto_detect=False, | |||
|
1203 | collect_by_mro=False, | |||
|
1204 | getstate_setstate=None, | |||
|
1205 | on_setattr=None, | |||
|
1206 | field_transformer=None, | |||
|
1207 | match_args=True, | |||
|
1208 | ): | |||
240 | r""" |
|
1209 | r""" | |
241 | A class decorator that adds `dunder |
|
1210 | A class decorator that adds `dunder | |
242 | <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the |
|
1211 | <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the | |
243 |
specified attributes using |
|
1212 | specified attributes using `attr.ib` or the *these* argument. | |
244 |
|
1213 | |||
245 |
:param these: A dictionary of name to |
|
1214 | :param these: A dictionary of name to `attr.ib` mappings. This is | |
246 | useful to avoid the definition of your attributes within the class body |
|
1215 | useful to avoid the definition of your attributes within the class body | |
247 | because you can't (e.g. if you want to add ``__repr__`` methods to |
|
1216 | because you can't (e.g. if you want to add ``__repr__`` methods to | |
248 | Django models) or don't want to. |
|
1217 | Django models) or don't want to. | |
249 |
|
1218 | |||
250 | If *these* is not ``None``, ``attrs`` will *not* search the class body |
|
1219 | If *these* is not ``None``, ``attrs`` will *not* search the class body | |
251 | for attributes. |
|
1220 | for attributes and will *not* remove any attributes from it. | |
252 |
|
1221 | |||
253 | :type these: :class:`dict` of :class:`str` to :func:`attr.ib` |
|
1222 | If *these* is an ordered dict (`dict` on Python 3.6+, | |
|
1223 | `collections.OrderedDict` otherwise), the order is deduced from | |||
|
1224 | the order of the attributes inside *these*. Otherwise the order | |||
|
1225 | of the definition of the attributes is used. | |||
|
1226 | ||||
|
1227 | :type these: `dict` of `str` to `attr.ib` | |||
254 |
|
1228 | |||
255 | :param str repr_ns: When using nested classes, there's no way in Python 2 |
|
1229 | :param str repr_ns: When using nested classes, there's no way in Python 2 | |
256 | to automatically detect that. Therefore it's possible to set the |
|
1230 | to automatically detect that. Therefore it's possible to set the | |
257 | namespace explicitly for a more meaningful ``repr`` output. |
|
1231 | namespace explicitly for a more meaningful ``repr`` output. | |
|
1232 | :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*, | |||
|
1233 | *order*, and *hash* arguments explicitly, assume they are set to | |||
|
1234 | ``True`` **unless any** of the involved methods for one of the | |||
|
1235 | arguments is implemented in the *current* class (i.e. it is *not* | |||
|
1236 | inherited from some base class). | |||
|
1237 | ||||
|
1238 | So for example by implementing ``__eq__`` on a class yourself, | |||
|
1239 | ``attrs`` will deduce ``eq=False`` and will create *neither* | |||
|
1240 | ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible | |||
|
1241 | ``__ne__`` by default, so it *should* be enough to only implement | |||
|
1242 | ``__eq__`` in most cases). | |||
|
1243 | ||||
|
1244 | .. warning:: | |||
|
1245 | ||||
|
1246 | If you prevent ``attrs`` from creating the ordering methods for you | |||
|
1247 | (``order=False``, e.g. by implementing ``__le__``), it becomes | |||
|
1248 | *your* responsibility to make sure its ordering is sound. The best | |||
|
1249 | way is to use the `functools.total_ordering` decorator. | |||
|
1250 | ||||
|
1251 | ||||
|
1252 | Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*, | |||
|
1253 | *cmp*, or *hash* overrides whatever *auto_detect* would determine. | |||
|
1254 | ||||
|
1255 | *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises | |||
|
1256 | an `attrs.exceptions.PythonTooOldError`. | |||
|
1257 | ||||
258 | :param bool repr: Create a ``__repr__`` method with a human readable |
|
1258 | :param bool repr: Create a ``__repr__`` method with a human readable | |
259 |
repres |
|
1259 | representation of ``attrs`` attributes.. | |
260 | :param bool str: Create a ``__str__`` method that is identical to |
|
1260 | :param bool str: Create a ``__str__`` method that is identical to | |
261 | ``__repr__``. This is usually not necessary except for |
|
1261 | ``__repr__``. This is usually not necessary except for | |
262 |
|
|
1262 | `Exception`\ s. | |
263 | :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``, |
|
1263 | :param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__`` | |
264 |
|
|
1264 | and ``__ne__`` methods that check two instances for equality. | |
265 | a tuple of its ``attrs`` attributes. But the attributes are *only* |
|
1265 | ||
266 | compared, if the type of both classes is *identical*! |
|
1266 | They compare the instances as if they were tuples of their ``attrs`` | |
267 | :param hash: If ``None`` (default), the ``__hash__`` method is generated |
|
1267 | attributes if and only if the types of both classes are *identical*! | |
268 | according how *cmp* and *frozen* are set. |
|
1268 | :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``, | |
|
1269 | ``__gt__``, and ``__ge__`` methods that behave like *eq* above and | |||
|
1270 | allow instances to be ordered. If ``None`` (default) mirror value of | |||
|
1271 | *eq*. | |||
|
1272 | :param Optional[bool] cmp: Setting *cmp* is equivalent to setting *eq* | |||
|
1273 | and *order* to the same value. Must not be mixed with *eq* or *order*. | |||
|
1274 | :param Optional[bool] hash: If ``None`` (default), the ``__hash__`` method | |||
|
1275 | is generated according how *eq* and *frozen* are set. | |||
269 |
|
1276 | |||
270 | 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. |
|
1277 | 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. | |
271 |
2. If * |
|
1278 | 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to | |
272 | None, marking it unhashable (which it is). |
|
1279 | None, marking it unhashable (which it is). | |
273 |
3. If * |
|
1280 | 3. If *eq* is False, ``__hash__`` will be left untouched meaning the | |
274 |
``__hash__`` method of the |
|
1281 | ``__hash__`` method of the base class will be used (if base class is | |
275 | ``object``, this means it will fall back to id-based hashing.). |
|
1282 | ``object``, this means it will fall back to id-based hashing.). | |
276 |
|
1283 | |||
277 | Although not recommended, you can decide for yourself and force |
|
1284 | Although not recommended, you can decide for yourself and force | |
@@ -279,29 +1286,37 b' def attributes(maybe_cls=None, these=Non' | |||||
279 | didn't freeze it programmatically) by passing ``True`` or not. Both of |
|
1286 | didn't freeze it programmatically) by passing ``True`` or not. Both of | |
280 | these cases are rather special and should be used carefully. |
|
1287 | these cases are rather special and should be used carefully. | |
281 |
|
1288 | |||
282 | See the `Python documentation \ |
|
1289 | See our documentation on `hashing`, Python's documentation on | |
283 | <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_ |
|
1290 | `object.__hash__`, and the `GitHub issue that led to the default \ | |
284 | and the `GitHub issue that led to the default behavior \ |
|
1291 | behavior <https://github.com/python-attrs/attrs/issues/136>`_ for more | |
285 | <https://github.com/python-attrs/attrs/issues/136>`_ for more details. |
|
1292 | details. | |
286 | :type hash: ``bool`` or ``None`` |
|
1293 | :param bool init: Create a ``__init__`` method that initializes the | |
287 | :param bool init: Create a ``__init__`` method that initialiazes the |
|
1294 | ``attrs`` attributes. Leading underscores are stripped for the argument | |
288 | ``attrs`` attributes. Leading underscores are stripped for the |
|
1295 | name. If a ``__attrs_pre_init__`` method exists on the class, it will | |
289 | argument name. If a ``__attrs_post_init__`` method exists on the |
|
1296 | be called before the class is initialized. If a ``__attrs_post_init__`` | |
290 |
class, it will be called after the class is fully |
|
1297 | method exists on the class, it will be called after the class is fully | |
291 | :param bool slots: Create a slots_-style class that's more |
|
1298 | initialized. | |
292 | memory-efficient. See :ref:`slots` for further ramifications. |
|
1299 | ||
|
1300 | If ``init`` is ``False``, an ``__attrs_init__`` method will be | |||
|
1301 | injected instead. This allows you to define a custom ``__init__`` | |||
|
1302 | method that can do pre-init work such as ``super().__init__()``, | |||
|
1303 | and then call ``__attrs_init__()`` and ``__attrs_post_init__()``. | |||
|
1304 | :param bool slots: Create a `slotted class <slotted classes>` that's more | |||
|
1305 | memory-efficient. Slotted classes are generally superior to the default | |||
|
1306 | dict classes, but have some gotchas you should know about, so we | |||
|
1307 | encourage you to read the `glossary entry <slotted classes>`. | |||
293 | :param bool frozen: Make instances immutable after initialization. If |
|
1308 | :param bool frozen: Make instances immutable after initialization. If | |
294 | someone attempts to modify a frozen instance, |
|
1309 | someone attempts to modify a frozen instance, | |
295 |
|
|
1310 | `attr.exceptions.FrozenInstanceError` is raised. | |
296 |
|
1311 | |||
297 |
|
|
1312 | .. note:: | |
298 |
|
1313 | |||
299 | 1. This is achieved by installing a custom ``__setattr__`` method |
|
1314 | 1. This is achieved by installing a custom ``__setattr__`` method | |
300 |
on your class so you can't implement |
|
1315 | on your class, so you can't implement your own. | |
301 |
|
1316 | |||
302 | 2. True immutability is impossible in Python. |
|
1317 | 2. True immutability is impossible in Python. | |
303 |
|
1318 | |||
304 |
3. This *does* have a minor a runtime performance |
|
1319 | 3. This *does* have a minor a runtime performance `impact | |
305 | <how-frozen>` when initializing new instances. In other words: |
|
1320 | <how-frozen>` when initializing new instances. In other words: | |
306 | ``__init__`` is slightly slower with ``frozen=True``. |
|
1321 | ``__init__`` is slightly slower with ``frozen=True``. | |
307 |
|
1322 | |||
@@ -310,316 +1325,651 b' def attributes(maybe_cls=None, these=Non' | |||||
310 | circumvent that limitation by using |
|
1325 | circumvent that limitation by using | |
311 | ``object.__setattr__(self, "attribute_name", value)``. |
|
1326 | ``object.__setattr__(self, "attribute_name", value)``. | |
312 |
|
1327 | |||
313 | .. _slots: https://docs.python.org/3.5/reference/datamodel.html#slots |
|
1328 | 5. Subclasses of a frozen class are frozen too. | |
314 |
|
1329 | |||
315 | .. versionadded:: 16.0.0 *slots* |
|
1330 | :param bool weakref_slot: Make instances weak-referenceable. This has no | |
316 | .. versionadded:: 16.1.0 *frozen* |
|
1331 | effect unless ``slots`` is also enabled. | |
317 | .. versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``. |
|
1332 | :param bool auto_attribs: If ``True``, collect :pep:`526`-annotated | |
318 | .. versionchanged:: |
|
1333 | attributes (Python 3.6 and later only) from the class body. | |
319 | 17.1.0 *hash* supports ``None`` as value which is also the default |
|
1334 | ||
320 | now. |
|
1335 | In this case, you **must** annotate every field. If ``attrs`` | |
|
1336 | encounters a field that is set to an `attr.ib` but lacks a type | |||
|
1337 | annotation, an `attr.exceptions.UnannotatedAttributeError` is | |||
|
1338 | raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't | |||
|
1339 | want to set a type. | |||
|
1340 | ||||
|
1341 | If you assign a value to those attributes (e.g. ``x: int = 42``), that | |||
|
1342 | value becomes the default value like if it were passed using | |||
|
1343 | ``attr.ib(default=42)``. Passing an instance of `attrs.Factory` also | |||
|
1344 | works as expected in most cases (see warning below). | |||
|
1345 | ||||
|
1346 | Attributes annotated as `typing.ClassVar`, and attributes that are | |||
|
1347 | neither annotated nor set to an `attr.ib` are **ignored**. | |||
|
1348 | ||||
|
1349 | .. warning:: | |||
|
1350 | For features that use the attribute name to create decorators (e.g. | |||
|
1351 | `validators <validators>`), you still *must* assign `attr.ib` to | |||
|
1352 | them. Otherwise Python will either not find the name or try to use | |||
|
1353 | the default value to call e.g. ``validator`` on it. | |||
|
1354 | ||||
|
1355 | These errors can be quite confusing and probably the most common bug | |||
|
1356 | report on our bug tracker. | |||
|
1357 | ||||
|
1358 | :param bool kw_only: Make all attributes keyword-only (Python 3+) | |||
|
1359 | in the generated ``__init__`` (if ``init`` is ``False``, this | |||
|
1360 | parameter is ignored). | |||
|
1361 | :param bool cache_hash: Ensure that the object's hash code is computed | |||
|
1362 | only once and stored on the object. If this is set to ``True``, | |||
|
1363 | hashing must be either explicitly or implicitly enabled for this | |||
|
1364 | class. If the hash code is cached, avoid any reassignments of | |||
|
1365 | fields involved in hash code computation or mutations of the objects | |||
|
1366 | those fields point to after object creation. If such changes occur, | |||
|
1367 | the behavior of the object's hash code is undefined. | |||
|
1368 | :param bool auto_exc: If the class subclasses `BaseException` | |||
|
1369 | (which implicitly includes any subclass of any exception), the | |||
|
1370 | following happens to behave like a well-behaved Python exceptions | |||
|
1371 | class: | |||
|
1372 | ||||
|
1373 | - the values for *eq*, *order*, and *hash* are ignored and the | |||
|
1374 | instances compare and hash by the instance's ids (N.B. ``attrs`` will | |||
|
1375 | *not* remove existing implementations of ``__hash__`` or the equality | |||
|
1376 | methods. It just won't add own ones.), | |||
|
1377 | - all attributes that are either passed into ``__init__`` or have a | |||
|
1378 | default value are additionally available as a tuple in the ``args`` | |||
|
1379 | attribute, | |||
|
1380 | - the value of *str* is ignored leaving ``__str__`` to base classes. | |||
|
1381 | :param bool collect_by_mro: Setting this to `True` fixes the way ``attrs`` | |||
|
1382 | collects attributes from base classes. The default behavior is | |||
|
1383 | incorrect in certain cases of multiple inheritance. It should be on by | |||
|
1384 | default but is kept off for backward-compatibility. | |||
|
1385 | ||||
|
1386 | See issue `#428 <https://github.com/python-attrs/attrs/issues/428>`_ for | |||
|
1387 | more details. | |||
|
1388 | ||||
|
1389 | :param Optional[bool] getstate_setstate: | |||
|
1390 | .. note:: | |||
|
1391 | This is usually only interesting for slotted classes and you should | |||
|
1392 | probably just set *auto_detect* to `True`. | |||
|
1393 | ||||
|
1394 | If `True`, ``__getstate__`` and | |||
|
1395 | ``__setstate__`` are generated and attached to the class. This is | |||
|
1396 | necessary for slotted classes to be pickleable. If left `None`, it's | |||
|
1397 | `True` by default for slotted classes and ``False`` for dict classes. | |||
|
1398 | ||||
|
1399 | If *auto_detect* is `True`, and *getstate_setstate* is left `None`, | |||
|
1400 | and **either** ``__getstate__`` or ``__setstate__`` is detected directly | |||
|
1401 | on the class (i.e. not inherited), it is set to `False` (this is usually | |||
|
1402 | what you want). | |||
|
1403 | ||||
|
1404 | :param on_setattr: A callable that is run whenever the user attempts to set | |||
|
1405 | an attribute (either by assignment like ``i.x = 42`` or by using | |||
|
1406 | `setattr` like ``setattr(i, "x", 42)``). It receives the same arguments | |||
|
1407 | as validators: the instance, the attribute that is being modified, and | |||
|
1408 | the new value. | |||
|
1409 | ||||
|
1410 | If no exception is raised, the attribute is set to the return value of | |||
|
1411 | the callable. | |||
|
1412 | ||||
|
1413 | If a list of callables is passed, they're automatically wrapped in an | |||
|
1414 | `attrs.setters.pipe`. | |||
|
1415 | :type on_setattr: `callable`, or a list of callables, or `None`, or | |||
|
1416 | `attrs.setters.NO_OP` | |||
|
1417 | ||||
|
1418 | :param Optional[callable] field_transformer: | |||
|
1419 | A function that is called with the original class object and all | |||
|
1420 | fields right before ``attrs`` finalizes the class. You can use | |||
|
1421 | this, e.g., to automatically add converters or validators to | |||
|
1422 | fields based on their types. See `transform-fields` for more details. | |||
|
1423 | ||||
|
1424 | :param bool match_args: | |||
|
1425 | If `True` (default), set ``__match_args__`` on the class to support | |||
|
1426 | :pep:`634` (Structural Pattern Matching). It is a tuple of all | |||
|
1427 | non-keyword-only ``__init__`` parameter names on Python 3.10 and later. | |||
|
1428 | Ignored on older Python versions. | |||
|
1429 | ||||
|
1430 | .. versionadded:: 16.0.0 *slots* | |||
|
1431 | .. versionadded:: 16.1.0 *frozen* | |||
|
1432 | .. versionadded:: 16.3.0 *str* | |||
|
1433 | .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. | |||
|
1434 | .. versionchanged:: 17.1.0 | |||
|
1435 | *hash* supports ``None`` as value which is also the default now. | |||
|
1436 | .. versionadded:: 17.3.0 *auto_attribs* | |||
|
1437 | .. versionchanged:: 18.1.0 | |||
|
1438 | If *these* is passed, no attributes are deleted from the class body. | |||
|
1439 | .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. | |||
|
1440 | .. versionadded:: 18.2.0 *weakref_slot* | |||
|
1441 | .. deprecated:: 18.2.0 | |||
|
1442 | ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a | |||
|
1443 | `DeprecationWarning` if the classes compared are subclasses of | |||
|
1444 | each other. ``__eq`` and ``__ne__`` never tried to compared subclasses | |||
|
1445 | to each other. | |||
|
1446 | .. versionchanged:: 19.2.0 | |||
|
1447 | ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider | |||
|
1448 | subclasses comparable anymore. | |||
|
1449 | .. versionadded:: 18.2.0 *kw_only* | |||
|
1450 | .. versionadded:: 18.2.0 *cache_hash* | |||
|
1451 | .. versionadded:: 19.1.0 *auto_exc* | |||
|
1452 | .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. | |||
|
1453 | .. versionadded:: 19.2.0 *eq* and *order* | |||
|
1454 | .. versionadded:: 20.1.0 *auto_detect* | |||
|
1455 | .. versionadded:: 20.1.0 *collect_by_mro* | |||
|
1456 | .. versionadded:: 20.1.0 *getstate_setstate* | |||
|
1457 | .. versionadded:: 20.1.0 *on_setattr* | |||
|
1458 | .. versionadded:: 20.3.0 *field_transformer* | |||
|
1459 | .. versionchanged:: 21.1.0 | |||
|
1460 | ``init=False`` injects ``__attrs_init__`` | |||
|
1461 | .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` | |||
|
1462 | .. versionchanged:: 21.1.0 *cmp* undeprecated | |||
|
1463 | .. versionadded:: 21.3.0 *match_args* | |||
321 | """ |
|
1464 | """ | |
|
1465 | eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) | |||
|
1466 | hash_ = hash # work around the lack of nonlocal | |||
|
1467 | ||||
|
1468 | if isinstance(on_setattr, (list, tuple)): | |||
|
1469 | on_setattr = setters.pipe(*on_setattr) | |||
|
1470 | ||||
322 | def wrap(cls): |
|
1471 | def wrap(cls): | |
323 | if getattr(cls, "__class__", None) is None: |
|
1472 | is_frozen = frozen or _has_frozen_base_class(cls) | |
324 | raise TypeError("attrs only works with new-style classes.") |
|
1473 | is_exc = auto_exc is True and issubclass(cls, BaseException) | |
325 |
|
1474 | has_own_setattr = auto_detect and _has_own_attribute( | ||
326 | if repr is False and str is True: |
|
1475 | cls, "__setattr__" | |
327 | raise ValueError( |
|
1476 | ) | |
328 | "__str__ can only be generated if a __repr__ exists." |
|
1477 | ||
329 | ) |
|
1478 | if has_own_setattr and is_frozen: | |
330 |
|
1479 | raise ValueError("Can't freeze a class with a custom __setattr__.") | ||
331 | if slots: |
|
1480 | ||
332 | # Only need this later if we're using slots. |
|
1481 | builder = _ClassBuilder( | |
333 | if these is None: |
|
1482 | cls, | |
334 | ca_list = [name |
|
1483 | these, | |
335 | for name, attr |
|
1484 | slots, | |
336 | in cls.__dict__.items() |
|
1485 | is_frozen, | |
337 | if isinstance(attr, _CountingAttr)] |
|
1486 | weakref_slot, | |
338 | else: |
|
1487 | _determine_whether_to_implement( | |
339 | ca_list = list(iterkeys(these)) |
|
1488 | cls, | |
340 | _transform_attrs(cls, these) |
|
1489 | getstate_setstate, | |
341 |
|
1490 | auto_detect, | ||
342 | # Can't just re-use frozen name because Python's scoping. :( |
|
1491 | ("__getstate__", "__setstate__"), | |
343 | # Can't compare function objects because Python 2 is terrible. :( |
|
1492 | default=slots, | |
344 | effectively_frozen = _has_frozen_superclass(cls) or frozen |
|
1493 | ), | |
345 | if repr is True: |
|
1494 | auto_attribs, | |
346 | cls = _add_repr(cls, ns=repr_ns) |
|
1495 | kw_only, | |
|
1496 | cache_hash, | |||
|
1497 | is_exc, | |||
|
1498 | collect_by_mro, | |||
|
1499 | on_setattr, | |||
|
1500 | has_own_setattr, | |||
|
1501 | field_transformer, | |||
|
1502 | ) | |||
|
1503 | if _determine_whether_to_implement( | |||
|
1504 | cls, repr, auto_detect, ("__repr__",) | |||
|
1505 | ): | |||
|
1506 | builder.add_repr(repr_ns) | |||
347 | if str is True: |
|
1507 | if str is True: | |
348 | cls.__str__ = cls.__repr__ |
|
1508 | builder.add_str() | |
349 | if cmp is True: |
|
1509 | ||
350 | cls = _add_cmp(cls) |
|
1510 | eq = _determine_whether_to_implement( | |
351 |
|
1511 | cls, eq_, auto_detect, ("__eq__", "__ne__") | ||
|
1512 | ) | |||
|
1513 | if not is_exc and eq is True: | |||
|
1514 | builder.add_eq() | |||
|
1515 | if not is_exc and _determine_whether_to_implement( | |||
|
1516 | cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") | |||
|
1517 | ): | |||
|
1518 | builder.add_order() | |||
|
1519 | ||||
|
1520 | builder.add_setattr() | |||
|
1521 | ||||
|
1522 | if ( | |||
|
1523 | hash_ is None | |||
|
1524 | and auto_detect is True | |||
|
1525 | and _has_own_attribute(cls, "__hash__") | |||
|
1526 | ): | |||
|
1527 | hash = False | |||
|
1528 | else: | |||
|
1529 | hash = hash_ | |||
352 | if hash is not True and hash is not False and hash is not None: |
|
1530 | if hash is not True and hash is not False and hash is not None: | |
|
1531 | # Can't use `hash in` because 1 == True for example. | |||
353 | raise TypeError( |
|
1532 | raise TypeError( | |
354 | "Invalid value for hash. Must be True, False, or None." |
|
1533 | "Invalid value for hash. Must be True, False, or None." | |
355 | ) |
|
1534 | ) | |
356 |
elif hash is False or (hash is None and |
|
1535 | elif hash is False or (hash is None and eq is False) or is_exc: | |
357 | pass |
|
1536 | # Don't do anything. Should fall back to __object__'s __hash__ | |
358 | elif hash is True or (hash is None and cmp is True and frozen is True): |
|
1537 | # which is by id. | |
359 |
|
|
1538 | if cache_hash: | |
|
1539 | raise TypeError( | |||
|
1540 | "Invalid value for cache_hash. To use hash caching," | |||
|
1541 | " hashing must be either explicitly or implicitly " | |||
|
1542 | "enabled." | |||
|
1543 | ) | |||
|
1544 | elif hash is True or ( | |||
|
1545 | hash is None and eq is True and is_frozen is True | |||
|
1546 | ): | |||
|
1547 | # Build a __hash__ if told so, or if it's safe. | |||
|
1548 | builder.add_hash() | |||
360 | else: |
|
1549 | else: | |
361 | cls.__hash__ = None |
|
1550 | # Raise TypeError on attempts to hash. | |
362 |
|
1551 | if cache_hash: | ||
363 | if init is True: |
|
1552 | raise TypeError( | |
364 | cls = _add_init(cls, effectively_frozen) |
|
1553 | "Invalid value for cache_hash. To use hash caching," | |
365 | if effectively_frozen is True: |
|
1554 | " hashing must be either explicitly or implicitly " | |
366 | cls.__setattr__ = _frozen_setattrs |
|
1555 | "enabled." | |
367 | cls.__delattr__ = _frozen_delattrs |
|
1556 | ) | |
368 | if slots is True: |
|
1557 | builder.make_unhashable() | |
369 | # slots and frozen require __getstate__/__setstate__ to work |
|
1558 | ||
370 | cls = _add_pickle(cls) |
|
1559 | if _determine_whether_to_implement( | |
371 | if slots is True: |
|
1560 | cls, init, auto_detect, ("__init__",) | |
372 | cls_dict = dict(cls.__dict__) |
|
1561 | ): | |
373 | cls_dict["__slots__"] = tuple(ca_list) |
|
1562 | builder.add_init() | |
374 | for ca_name in ca_list: |
|
1563 | else: | |
375 | # It might not actually be in there, e.g. if using 'these'. |
|
1564 | builder.add_attrs_init() | |
376 | cls_dict.pop(ca_name, None) |
|
1565 | if cache_hash: | |
377 | cls_dict.pop("__dict__", None) |
|
1566 | raise TypeError( | |
378 |
|
1567 | "Invalid value for cache_hash. To use hash caching," | ||
379 | qualname = getattr(cls, "__qualname__", None) |
|
1568 | " init must be True." | |
380 | cls = type(cls)(cls.__name__, cls.__bases__, cls_dict) |
|
1569 | ) | |
381 | if qualname is not None: |
|
1570 | ||
382 | cls.__qualname__ = qualname |
|
1571 | if ( | |
383 |
|
1572 | PY310 | ||
384 | return cls |
|
1573 | and match_args | |
385 |
|
1574 | and not _has_own_attribute(cls, "__match_args__") | ||
386 | # attrs_or class type depends on the usage of the decorator. It's a class |
|
1575 | ): | |
387 | # if it's used as `@attributes` but ``None`` if used # as `@attributes()`. |
|
1576 | builder.add_match_args() | |
|
1577 | ||||
|
1578 | return builder.build_class() | |||
|
1579 | ||||
|
1580 | # maybe_cls's type depends on the usage of the decorator. It's a class | |||
|
1581 | # if it's used as `@attrs` but ``None`` if used as `@attrs()`. | |||
388 | if maybe_cls is None: |
|
1582 | if maybe_cls is None: | |
389 | return wrap |
|
1583 | return wrap | |
390 | else: |
|
1584 | else: | |
391 | return wrap(maybe_cls) |
|
1585 | return wrap(maybe_cls) | |
392 |
|
1586 | |||
393 |
|
1587 | |||
394 | if PY2: |
|
1588 | _attrs = attrs | |
395 | def _has_frozen_superclass(cls): |
|
1589 | """ | |
396 | """ |
|
1590 | Internal alias so we can use it in functions that take an argument called | |
397 | Check whether *cls* has a frozen ancestor by looking at its |
|
1591 | *attrs*. | |
398 | __setattr__. |
|
1592 | """ | |
399 | """ |
|
1593 | ||
400 | return ( |
|
1594 | ||
401 | getattr( |
|
1595 | def _has_frozen_base_class(cls): | |
402 | cls.__setattr__, "__module__", None |
|
1596 | """ | |
403 | ) == _frozen_setattrs.__module__ and |
|
1597 | Check whether *cls* has a frozen ancestor by looking at its | |
404 | cls.__setattr__.__name__ == _frozen_setattrs.__name__ |
|
1598 | __setattr__. | |
|
1599 | """ | |||
|
1600 | return cls.__setattr__ is _frozen_setattrs | |||
|
1601 | ||||
|
1602 | ||||
|
1603 | def _generate_unique_filename(cls, func_name): | |||
|
1604 | """ | |||
|
1605 | Create a "filename" suitable for a function being generated. | |||
|
1606 | """ | |||
|
1607 | unique_filename = "<attrs generated {} {}.{}>".format( | |||
|
1608 | func_name, | |||
|
1609 | cls.__module__, | |||
|
1610 | getattr(cls, "__qualname__", cls.__name__), | |||
|
1611 | ) | |||
|
1612 | return unique_filename | |||
|
1613 | ||||
|
1614 | ||||
|
1615 | def _make_hash(cls, attrs, frozen, cache_hash): | |||
|
1616 | attrs = tuple( | |||
|
1617 | a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) | |||
|
1618 | ) | |||
|
1619 | ||||
|
1620 | tab = " " | |||
|
1621 | ||||
|
1622 | unique_filename = _generate_unique_filename(cls, "hash") | |||
|
1623 | type_hash = hash(unique_filename) | |||
|
1624 | # If eq is custom generated, we need to include the functions in globs | |||
|
1625 | globs = {} | |||
|
1626 | ||||
|
1627 | hash_def = "def __hash__(self" | |||
|
1628 | hash_func = "hash((" | |||
|
1629 | closing_braces = "))" | |||
|
1630 | if not cache_hash: | |||
|
1631 | hash_def += "):" | |||
|
1632 | else: | |||
|
1633 | hash_def += ", *" | |||
|
1634 | ||||
|
1635 | hash_def += ( | |||
|
1636 | ", _cache_wrapper=" | |||
|
1637 | + "__import__('attr._make')._make._CacheHashWrapper):" | |||
405 | ) |
|
1638 | ) | |
406 | else: |
|
1639 | hash_func = "_cache_wrapper(" + hash_func | |
407 | def _has_frozen_superclass(cls): |
|
1640 | closing_braces += ")" | |
|
1641 | ||||
|
1642 | method_lines = [hash_def] | |||
|
1643 | ||||
|
1644 | def append_hash_computation_lines(prefix, indent): | |||
408 | """ |
|
1645 | """ | |
409 | Check whether *cls* has a frozen ancestor by looking at its |
|
1646 | Generate the code for actually computing the hash code. | |
410 | __setattr__. |
|
1647 | Below this will either be returned directly or used to compute | |
|
1648 | a value which is then cached, depending on the value of cache_hash | |||
411 | """ |
|
1649 | """ | |
412 | return cls.__setattr__ == _frozen_setattrs |
|
1650 | ||
413 |
|
1651 | method_lines.extend( | ||
414 |
|
1652 | [ | ||
415 | def _attrs_to_tuple(obj, attrs): |
|
1653 | indent + prefix + hash_func, | |
416 | """ |
|
1654 | indent + " %d," % (type_hash,), | |
417 | Create a tuple of all values of *obj*'s *attrs*. |
|
1655 | ] | |
418 | """ |
|
1656 | ) | |
419 | return tuple(getattr(obj, a.name) for a in attrs) |
|
1657 | ||
420 |
|
1658 | for a in attrs: | ||
421 |
|
1659 | if a.eq_key: | ||
422 | def _add_hash(cls, attrs=None): |
|
1660 | cmp_name = "_%s_key" % (a.name,) | |
|
1661 | globs[cmp_name] = a.eq_key | |||
|
1662 | method_lines.append( | |||
|
1663 | indent + " %s(self.%s)," % (cmp_name, a.name) | |||
|
1664 | ) | |||
|
1665 | else: | |||
|
1666 | method_lines.append(indent + " self.%s," % a.name) | |||
|
1667 | ||||
|
1668 | method_lines.append(indent + " " + closing_braces) | |||
|
1669 | ||||
|
1670 | if cache_hash: | |||
|
1671 | method_lines.append(tab + "if self.%s is None:" % _hash_cache_field) | |||
|
1672 | if frozen: | |||
|
1673 | append_hash_computation_lines( | |||
|
1674 | "object.__setattr__(self, '%s', " % _hash_cache_field, tab * 2 | |||
|
1675 | ) | |||
|
1676 | method_lines.append(tab * 2 + ")") # close __setattr__ | |||
|
1677 | else: | |||
|
1678 | append_hash_computation_lines( | |||
|
1679 | "self.%s = " % _hash_cache_field, tab * 2 | |||
|
1680 | ) | |||
|
1681 | method_lines.append(tab + "return self.%s" % _hash_cache_field) | |||
|
1682 | else: | |||
|
1683 | append_hash_computation_lines("return ", tab) | |||
|
1684 | ||||
|
1685 | script = "\n".join(method_lines) | |||
|
1686 | return _make_method("__hash__", script, unique_filename, globs) | |||
|
1687 | ||||
|
1688 | ||||
|
1689 | def _add_hash(cls, attrs): | |||
423 | """ |
|
1690 | """ | |
424 | Add a hash method to *cls*. |
|
1691 | Add a hash method to *cls*. | |
425 | """ |
|
1692 | """ | |
426 | if attrs is None: |
|
1693 | cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False) | |
427 | attrs = [a |
|
1694 | return cls | |
428 | for a in cls.__attrs_attrs__ |
|
1695 | ||
429 | if a.hash is True or (a.hash is None and a.cmp is True)] |
|
1696 | ||
430 |
|
1697 | def _make_ne(): | ||
431 | def hash_(self): |
|
1698 | """ | |
|
1699 | Create __ne__ method. | |||
|
1700 | """ | |||
|
1701 | ||||
|
1702 | def __ne__(self, other): | |||
432 | """ |
|
1703 | """ | |
433 | Automatically created by attrs. |
|
1704 | Check equality and either forward a NotImplemented or | |
|
1705 | return the result negated. | |||
434 | """ |
|
1706 | """ | |
435 | return hash(_attrs_to_tuple(self, attrs)) |
|
1707 | result = self.__eq__(other) | |
436 |
|
1708 | if result is NotImplemented: | ||
437 | cls.__hash__ = hash_ |
|
1709 | return NotImplemented | |
438 | return cls |
|
1710 | ||
439 |
|
1711 | return not result | ||
440 |
|
1712 | |||
441 | def _add_cmp(cls, attrs=None): |
|
1713 | return __ne__ | |
|
1714 | ||||
|
1715 | ||||
|
1716 | def _make_eq(cls, attrs): | |||
|
1717 | """ | |||
|
1718 | Create __eq__ method for *cls* with *attrs*. | |||
442 |
|
|
1719 | """ | |
443 | Add comparison methods to *cls*. |
|
1720 | attrs = [a for a in attrs if a.eq] | |
|
1721 | ||||
|
1722 | unique_filename = _generate_unique_filename(cls, "eq") | |||
|
1723 | lines = [ | |||
|
1724 | "def __eq__(self, other):", | |||
|
1725 | " if other.__class__ is not self.__class__:", | |||
|
1726 | " return NotImplemented", | |||
|
1727 | ] | |||
|
1728 | ||||
|
1729 | # We can't just do a big self.x = other.x and... clause due to | |||
|
1730 | # irregularities like nan == nan is false but (nan,) == (nan,) is true. | |||
|
1731 | globs = {} | |||
|
1732 | if attrs: | |||
|
1733 | lines.append(" return (") | |||
|
1734 | others = [" ) == ("] | |||
|
1735 | for a in attrs: | |||
|
1736 | if a.eq_key: | |||
|
1737 | cmp_name = "_%s_key" % (a.name,) | |||
|
1738 | # Add the key function to the global namespace | |||
|
1739 | # of the evaluated function. | |||
|
1740 | globs[cmp_name] = a.eq_key | |||
|
1741 | lines.append( | |||
|
1742 | " %s(self.%s)," | |||
|
1743 | % ( | |||
|
1744 | cmp_name, | |||
|
1745 | a.name, | |||
|
1746 | ) | |||
|
1747 | ) | |||
|
1748 | others.append( | |||
|
1749 | " %s(other.%s)," | |||
|
1750 | % ( | |||
|
1751 | cmp_name, | |||
|
1752 | a.name, | |||
|
1753 | ) | |||
|
1754 | ) | |||
|
1755 | else: | |||
|
1756 | lines.append(" self.%s," % (a.name,)) | |||
|
1757 | others.append(" other.%s," % (a.name,)) | |||
|
1758 | ||||
|
1759 | lines += others + [" )"] | |||
|
1760 | else: | |||
|
1761 | lines.append(" return True") | |||
|
1762 | ||||
|
1763 | script = "\n".join(lines) | |||
|
1764 | ||||
|
1765 | return _make_method("__eq__", script, unique_filename, globs) | |||
|
1766 | ||||
|
1767 | ||||
|
1768 | def _make_order(cls, attrs): | |||
444 |
|
|
1769 | """ | |
445 | if attrs is None: |
|
1770 | Create ordering methods for *cls* with *attrs*. | |
446 | attrs = [a for a in cls.__attrs_attrs__ if a.cmp] |
|
1771 | """ | |
|
1772 | attrs = [a for a in attrs if a.order] | |||
447 |
|
1773 | |||
448 | def attrs_to_tuple(obj): |
|
1774 | def attrs_to_tuple(obj): | |
449 | """ |
|
1775 | """ | |
450 | Save us some typing. |
|
1776 | Save us some typing. | |
451 | """ |
|
1777 | """ | |
452 |
return |
|
1778 | return tuple( | |
453 |
|
1779 | key(value) if key else value | ||
454 | def eq(self, other): |
|
1780 | for value, key in ( | |
|
1781 | (getattr(obj, a.name), a.order_key) for a in attrs | |||
|
1782 | ) | |||
|
1783 | ) | |||
|
1784 | ||||
|
1785 | def __lt__(self, other): | |||
|
1786 | """ | |||
|
1787 | Automatically created by attrs. | |||
|
1788 | """ | |||
|
1789 | if other.__class__ is self.__class__: | |||
|
1790 | return attrs_to_tuple(self) < attrs_to_tuple(other) | |||
|
1791 | ||||
|
1792 | return NotImplemented | |||
|
1793 | ||||
|
1794 | def __le__(self, other): | |||
|
1795 | """ | |||
|
1796 | Automatically created by attrs. | |||
|
1797 | """ | |||
|
1798 | if other.__class__ is self.__class__: | |||
|
1799 | return attrs_to_tuple(self) <= attrs_to_tuple(other) | |||
|
1800 | ||||
|
1801 | return NotImplemented | |||
|
1802 | ||||
|
1803 | def __gt__(self, other): | |||
|
1804 | """ | |||
|
1805 | Automatically created by attrs. | |||
|
1806 | """ | |||
|
1807 | if other.__class__ is self.__class__: | |||
|
1808 | return attrs_to_tuple(self) > attrs_to_tuple(other) | |||
|
1809 | ||||
|
1810 | return NotImplemented | |||
|
1811 | ||||
|
1812 | def __ge__(self, other): | |||
455 | """ |
|
1813 | """ | |
456 | Automatically created by attrs. |
|
1814 | Automatically created by attrs. | |
457 | """ |
|
1815 | """ | |
458 | if other.__class__ is self.__class__: |
|
1816 | if other.__class__ is self.__class__: | |
459 | return attrs_to_tuple(self) == attrs_to_tuple(other) |
|
|||
460 | else: |
|
|||
461 | return NotImplemented |
|
|||
462 |
|
||||
463 | def ne(self, other): |
|
|||
464 | """ |
|
|||
465 | Automatically created by attrs. |
|
|||
466 | """ |
|
|||
467 | result = eq(self, other) |
|
|||
468 | if result is NotImplemented: |
|
|||
469 | return NotImplemented |
|
|||
470 | else: |
|
|||
471 | return not result |
|
|||
472 |
|
||||
473 | def lt(self, other): |
|
|||
474 | """ |
|
|||
475 | Automatically created by attrs. |
|
|||
476 | """ |
|
|||
477 | if isinstance(other, self.__class__): |
|
|||
478 | return attrs_to_tuple(self) < attrs_to_tuple(other) |
|
|||
479 | else: |
|
|||
480 | return NotImplemented |
|
|||
481 |
|
||||
482 | def le(self, other): |
|
|||
483 | """ |
|
|||
484 | Automatically created by attrs. |
|
|||
485 | """ |
|
|||
486 | if isinstance(other, self.__class__): |
|
|||
487 | return attrs_to_tuple(self) <= attrs_to_tuple(other) |
|
|||
488 | else: |
|
|||
489 | return NotImplemented |
|
|||
490 |
|
||||
491 | def gt(self, other): |
|
|||
492 | """ |
|
|||
493 | Automatically created by attrs. |
|
|||
494 | """ |
|
|||
495 | if isinstance(other, self.__class__): |
|
|||
496 | return attrs_to_tuple(self) > attrs_to_tuple(other) |
|
|||
497 | else: |
|
|||
498 | return NotImplemented |
|
|||
499 |
|
||||
500 | def ge(self, other): |
|
|||
501 | """ |
|
|||
502 | Automatically created by attrs. |
|
|||
503 | """ |
|
|||
504 | if isinstance(other, self.__class__): |
|
|||
505 | return attrs_to_tuple(self) >= attrs_to_tuple(other) |
|
1817 | return attrs_to_tuple(self) >= attrs_to_tuple(other) | |
506 | else: |
|
1818 | ||
507 |
|
|
1819 | return NotImplemented | |
508 |
|
1820 | |||
509 | cls.__eq__ = eq |
|
1821 | return __lt__, __le__, __gt__, __ge__ | |
510 | cls.__ne__ = ne |
|
1822 | ||
511 | cls.__lt__ = lt |
|
1823 | ||
512 | cls.__le__ = le |
|
1824 | def _add_eq(cls, attrs=None): | |
513 | cls.__gt__ = gt |
|
1825 | """ | |
514 | cls.__ge__ = ge |
|
1826 | Add equality methods to *cls* with *attrs*. | |
|
1827 | """ | |||
|
1828 | if attrs is None: | |||
|
1829 | attrs = cls.__attrs_attrs__ | |||
|
1830 | ||||
|
1831 | cls.__eq__ = _make_eq(cls, attrs) | |||
|
1832 | cls.__ne__ = _make_ne() | |||
515 |
|
1833 | |||
516 | return cls |
|
1834 | return cls | |
517 |
|
1835 | |||
518 |
|
1836 | |||
|
1837 | if HAS_F_STRINGS: | |||
|
1838 | ||||
|
1839 | def _make_repr(attrs, ns, cls): | |||
|
1840 | unique_filename = _generate_unique_filename(cls, "repr") | |||
|
1841 | # Figure out which attributes to include, and which function to use to | |||
|
1842 | # format them. The a.repr value can be either bool or a custom | |||
|
1843 | # callable. | |||
|
1844 | attr_names_with_reprs = tuple( | |||
|
1845 | (a.name, (repr if a.repr is True else a.repr), a.init) | |||
|
1846 | for a in attrs | |||
|
1847 | if a.repr is not False | |||
|
1848 | ) | |||
|
1849 | globs = { | |||
|
1850 | name + "_repr": r | |||
|
1851 | for name, r, _ in attr_names_with_reprs | |||
|
1852 | if r != repr | |||
|
1853 | } | |||
|
1854 | globs["_compat"] = _compat | |||
|
1855 | globs["AttributeError"] = AttributeError | |||
|
1856 | globs["NOTHING"] = NOTHING | |||
|
1857 | attribute_fragments = [] | |||
|
1858 | for name, r, i in attr_names_with_reprs: | |||
|
1859 | accessor = ( | |||
|
1860 | "self." + name | |||
|
1861 | if i | |||
|
1862 | else 'getattr(self, "' + name + '", NOTHING)' | |||
|
1863 | ) | |||
|
1864 | fragment = ( | |||
|
1865 | "%s={%s!r}" % (name, accessor) | |||
|
1866 | if r == repr | |||
|
1867 | else "%s={%s_repr(%s)}" % (name, name, accessor) | |||
|
1868 | ) | |||
|
1869 | attribute_fragments.append(fragment) | |||
|
1870 | repr_fragment = ", ".join(attribute_fragments) | |||
|
1871 | ||||
|
1872 | if ns is None: | |||
|
1873 | cls_name_fragment = ( | |||
|
1874 | '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' | |||
|
1875 | ) | |||
|
1876 | else: | |||
|
1877 | cls_name_fragment = ns + ".{self.__class__.__name__}" | |||
|
1878 | ||||
|
1879 | lines = [ | |||
|
1880 | "def __repr__(self):", | |||
|
1881 | " try:", | |||
|
1882 | " already_repring = _compat.repr_context.already_repring", | |||
|
1883 | " except AttributeError:", | |||
|
1884 | " already_repring = {id(self),}", | |||
|
1885 | " _compat.repr_context.already_repring = already_repring", | |||
|
1886 | " else:", | |||
|
1887 | " if id(self) in already_repring:", | |||
|
1888 | " return '...'", | |||
|
1889 | " else:", | |||
|
1890 | " already_repring.add(id(self))", | |||
|
1891 | " try:", | |||
|
1892 | " return f'%s(%s)'" % (cls_name_fragment, repr_fragment), | |||
|
1893 | " finally:", | |||
|
1894 | " already_repring.remove(id(self))", | |||
|
1895 | ] | |||
|
1896 | ||||
|
1897 | return _make_method( | |||
|
1898 | "__repr__", "\n".join(lines), unique_filename, globs=globs | |||
|
1899 | ) | |||
|
1900 | ||||
|
1901 | else: | |||
|
1902 | ||||
|
1903 | def _make_repr(attrs, ns, _): | |||
|
1904 | """ | |||
|
1905 | Make a repr method that includes relevant *attrs*, adding *ns* to the | |||
|
1906 | full name. | |||
|
1907 | """ | |||
|
1908 | ||||
|
1909 | # Figure out which attributes to include, and which function to use to | |||
|
1910 | # format them. The a.repr value can be either bool or a custom | |||
|
1911 | # callable. | |||
|
1912 | attr_names_with_reprs = tuple( | |||
|
1913 | (a.name, repr if a.repr is True else a.repr) | |||
|
1914 | for a in attrs | |||
|
1915 | if a.repr is not False | |||
|
1916 | ) | |||
|
1917 | ||||
|
1918 | def __repr__(self): | |||
|
1919 | """ | |||
|
1920 | Automatically created by attrs. | |||
|
1921 | """ | |||
|
1922 | try: | |||
|
1923 | already_repring = _compat.repr_context.already_repring | |||
|
1924 | except AttributeError: | |||
|
1925 | already_repring = set() | |||
|
1926 | _compat.repr_context.already_repring = already_repring | |||
|
1927 | ||||
|
1928 | if id(self) in already_repring: | |||
|
1929 | return "..." | |||
|
1930 | real_cls = self.__class__ | |||
|
1931 | if ns is None: | |||
|
1932 | class_name = real_cls.__qualname__.rsplit(">.", 1)[-1] | |||
|
1933 | else: | |||
|
1934 | class_name = ns + "." + real_cls.__name__ | |||
|
1935 | ||||
|
1936 | # Since 'self' remains on the stack (i.e.: strongly referenced) | |||
|
1937 | # for the duration of this call, it's safe to depend on id(...) | |||
|
1938 | # stability, and not need to track the instance and therefore | |||
|
1939 | # worry about properties like weakref- or hash-ability. | |||
|
1940 | already_repring.add(id(self)) | |||
|
1941 | try: | |||
|
1942 | result = [class_name, "("] | |||
|
1943 | first = True | |||
|
1944 | for name, attr_repr in attr_names_with_reprs: | |||
|
1945 | if first: | |||
|
1946 | first = False | |||
|
1947 | else: | |||
|
1948 | result.append(", ") | |||
|
1949 | result.extend( | |||
|
1950 | (name, "=", attr_repr(getattr(self, name, NOTHING))) | |||
|
1951 | ) | |||
|
1952 | return "".join(result) + ")" | |||
|
1953 | finally: | |||
|
1954 | already_repring.remove(id(self)) | |||
|
1955 | ||||
|
1956 | return __repr__ | |||
|
1957 | ||||
|
1958 | ||||
519 | def _add_repr(cls, ns=None, attrs=None): |
|
1959 | def _add_repr(cls, ns=None, attrs=None): | |
520 | """ |
|
1960 | """ | |
521 | Add a repr method to *cls*. |
|
1961 | Add a repr method to *cls*. | |
522 | """ |
|
1962 | """ | |
523 | if attrs is None: |
|
1963 | if attrs is None: | |
524 |
attrs = |
|
1964 | attrs = cls.__attrs_attrs__ | |
525 |
|
1965 | |||
526 | def repr_(self): |
|
1966 | cls.__repr__ = _make_repr(attrs, ns, cls) | |
527 | """ |
|
|||
528 | Automatically created by attrs. |
|
|||
529 | """ |
|
|||
530 | real_cls = self.__class__ |
|
|||
531 | if ns is None: |
|
|||
532 | qualname = getattr(real_cls, "__qualname__", None) |
|
|||
533 | if qualname is not None: |
|
|||
534 | class_name = qualname.rsplit(">.", 1)[-1] |
|
|||
535 | else: |
|
|||
536 | class_name = real_cls.__name__ |
|
|||
537 | else: |
|
|||
538 | class_name = ns + "." + real_cls.__name__ |
|
|||
539 |
|
||||
540 | return "{0}({1})".format( |
|
|||
541 | class_name, |
|
|||
542 | ", ".join(a.name + "=" + repr(getattr(self, a.name)) |
|
|||
543 | for a in attrs) |
|
|||
544 | ) |
|
|||
545 | cls.__repr__ = repr_ |
|
|||
546 | return cls |
|
|||
547 |
|
||||
548 |
|
||||
549 | def _add_init(cls, frozen): |
|
|||
550 | """ |
|
|||
551 | Add a __init__ method to *cls*. If *frozen* is True, make it immutable. |
|
|||
552 | """ |
|
|||
553 | attrs = [a for a in cls.__attrs_attrs__ |
|
|||
554 | if a.init or a.default is not NOTHING] |
|
|||
555 |
|
||||
556 | # We cache the generated init methods for the same kinds of attributes. |
|
|||
557 | sha1 = hashlib.sha1() |
|
|||
558 | r = repr(attrs) |
|
|||
559 | if not isinstance(r, bytes): |
|
|||
560 | r = r.encode('utf-8') |
|
|||
561 | sha1.update(r) |
|
|||
562 | unique_filename = "<attrs generated init {0}>".format( |
|
|||
563 | sha1.hexdigest() |
|
|||
564 | ) |
|
|||
565 |
|
||||
566 | script, globs = _attrs_to_script( |
|
|||
567 | attrs, |
|
|||
568 | frozen, |
|
|||
569 | getattr(cls, "__attrs_post_init__", False), |
|
|||
570 | ) |
|
|||
571 | locs = {} |
|
|||
572 | bytecode = compile(script, unique_filename, "exec") |
|
|||
573 | attr_dict = dict((a.name, a) for a in attrs) |
|
|||
574 | globs.update({ |
|
|||
575 | "NOTHING": NOTHING, |
|
|||
576 | "attr_dict": attr_dict, |
|
|||
577 | }) |
|
|||
578 | if frozen is True: |
|
|||
579 | # Save the lookup overhead in __init__ if we need to circumvent |
|
|||
580 | # immutability. |
|
|||
581 | globs["_cached_setattr"] = _obj_setattr |
|
|||
582 | eval(bytecode, globs, locs) |
|
|||
583 | init = locs["__init__"] |
|
|||
584 |
|
||||
585 | # In order of debuggers like PDB being able to step through the code, |
|
|||
586 | # we add a fake linecache entry. |
|
|||
587 | linecache.cache[unique_filename] = ( |
|
|||
588 | len(script), |
|
|||
589 | None, |
|
|||
590 | script.splitlines(True), |
|
|||
591 | unique_filename |
|
|||
592 | ) |
|
|||
593 | cls.__init__ = init |
|
|||
594 | return cls |
|
|||
595 |
|
||||
596 |
|
||||
597 | def _add_pickle(cls): |
|
|||
598 | """ |
|
|||
599 | Add pickle helpers, needed for frozen and slotted classes |
|
|||
600 | """ |
|
|||
601 | def _slots_getstate__(obj): |
|
|||
602 | """ |
|
|||
603 | Play nice with pickle. |
|
|||
604 | """ |
|
|||
605 | return tuple(getattr(obj, a.name) for a in fields(obj.__class__)) |
|
|||
606 |
|
||||
607 | def _slots_setstate__(obj, state): |
|
|||
608 | """ |
|
|||
609 | Play nice with pickle. |
|
|||
610 | """ |
|
|||
611 | __bound_setattr = _obj_setattr.__get__(obj, Attribute) |
|
|||
612 | for a, value in zip(fields(obj.__class__), state): |
|
|||
613 | __bound_setattr(a.name, value) |
|
|||
614 |
|
||||
615 | cls.__getstate__ = _slots_getstate__ |
|
|||
616 | cls.__setstate__ = _slots_setstate__ |
|
|||
617 | return cls |
|
1967 | return cls | |
618 |
|
1968 | |||
619 |
|
1969 | |||
620 | def fields(cls): |
|
1970 | def fields(cls): | |
621 | """ |
|
1971 | """ | |
622 |
Return |
|
1972 | Return the tuple of ``attrs`` attributes for a class. | |
623 |
|
1973 | |||
624 | The tuple also allows accessing the fields by their names (see below for |
|
1974 | The tuple also allows accessing the fields by their names (see below for | |
625 | examples). |
|
1975 | examples). | |
@@ -630,12 +1980,12 b' def fields(cls):' | |||||
630 | :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` |
|
1980 | :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` | |
631 | class. |
|
1981 | class. | |
632 |
|
1982 | |||
633 |
:rtype: tuple (with name accesors) of |
|
1983 | :rtype: tuple (with name accessors) of `attrs.Attribute` | |
634 |
|
1984 | |||
635 | .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields |
|
1985 | .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields | |
636 | by name. |
|
1986 | by name. | |
637 | """ |
|
1987 | """ | |
638 |
if not is |
|
1988 | if not isinstance(cls, type): | |
639 | raise TypeError("Passed object must be a class.") |
|
1989 | raise TypeError("Passed object must be a class.") | |
640 | attrs = getattr(cls, "__attrs_attrs__", None) |
|
1990 | attrs = getattr(cls, "__attrs_attrs__", None) | |
641 | if attrs is None: |
|
1991 | if attrs is None: | |
@@ -645,6 +1995,34 b' def fields(cls):' | |||||
645 | return attrs |
|
1995 | return attrs | |
646 |
|
1996 | |||
647 |
|
1997 | |||
|
1998 | def fields_dict(cls): | |||
|
1999 | """ | |||
|
2000 | Return an ordered dictionary of ``attrs`` attributes for a class, whose | |||
|
2001 | keys are the attribute names. | |||
|
2002 | ||||
|
2003 | :param type cls: Class to introspect. | |||
|
2004 | ||||
|
2005 | :raise TypeError: If *cls* is not a class. | |||
|
2006 | :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` | |||
|
2007 | class. | |||
|
2008 | ||||
|
2009 | :rtype: an ordered dict where keys are attribute names and values are | |||
|
2010 | `attrs.Attribute`\\ s. This will be a `dict` if it's | |||
|
2011 | naturally ordered like on Python 3.6+ or an | |||
|
2012 | :class:`~collections.OrderedDict` otherwise. | |||
|
2013 | ||||
|
2014 | .. versionadded:: 18.1.0 | |||
|
2015 | """ | |||
|
2016 | if not isinstance(cls, type): | |||
|
2017 | raise TypeError("Passed object must be a class.") | |||
|
2018 | attrs = getattr(cls, "__attrs_attrs__", None) | |||
|
2019 | if attrs is None: | |||
|
2020 | raise NotAnAttrsClassError( | |||
|
2021 | "{cls!r} is not an attrs-decorated class.".format(cls=cls) | |||
|
2022 | ) | |||
|
2023 | return ordered_dict((a.name, a) for a in attrs) | |||
|
2024 | ||||
|
2025 | ||||
648 | def validate(inst): |
|
2026 | def validate(inst): | |
649 | """ |
|
2027 | """ | |
650 | Validate all attributes on *inst* that have a validator. |
|
2028 | Validate all attributes on *inst* that have a validator. | |
@@ -662,240 +2040,623 b' def validate(inst):' | |||||
662 | v(inst, a, getattr(inst, a.name)) |
|
2040 | v(inst, a, getattr(inst, a.name)) | |
663 |
|
2041 | |||
664 |
|
2042 | |||
665 | def _attrs_to_script(attrs, frozen, post_init): |
|
2043 | def _is_slot_cls(cls): | |
|
2044 | return "__slots__" in cls.__dict__ | |||
|
2045 | ||||
|
2046 | ||||
|
2047 | def _is_slot_attr(a_name, base_attr_map): | |||
|
2048 | """ | |||
|
2049 | Check if the attribute name comes from a slot class. | |||
|
2050 | """ | |||
|
2051 | return a_name in base_attr_map and _is_slot_cls(base_attr_map[a_name]) | |||
|
2052 | ||||
|
2053 | ||||
|
2054 | def _make_init( | |||
|
2055 | cls, | |||
|
2056 | attrs, | |||
|
2057 | pre_init, | |||
|
2058 | post_init, | |||
|
2059 | frozen, | |||
|
2060 | slots, | |||
|
2061 | cache_hash, | |||
|
2062 | base_attr_map, | |||
|
2063 | is_exc, | |||
|
2064 | cls_on_setattr, | |||
|
2065 | attrs_init, | |||
|
2066 | ): | |||
|
2067 | has_cls_on_setattr = ( | |||
|
2068 | cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP | |||
|
2069 | ) | |||
|
2070 | ||||
|
2071 | if frozen and has_cls_on_setattr: | |||
|
2072 | raise ValueError("Frozen classes can't use on_setattr.") | |||
|
2073 | ||||
|
2074 | needs_cached_setattr = cache_hash or frozen | |||
|
2075 | filtered_attrs = [] | |||
|
2076 | attr_dict = {} | |||
|
2077 | for a in attrs: | |||
|
2078 | if not a.init and a.default is NOTHING: | |||
|
2079 | continue | |||
|
2080 | ||||
|
2081 | filtered_attrs.append(a) | |||
|
2082 | attr_dict[a.name] = a | |||
|
2083 | ||||
|
2084 | if a.on_setattr is not None: | |||
|
2085 | if frozen is True: | |||
|
2086 | raise ValueError("Frozen classes can't use on_setattr.") | |||
|
2087 | ||||
|
2088 | needs_cached_setattr = True | |||
|
2089 | elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: | |||
|
2090 | needs_cached_setattr = True | |||
|
2091 | ||||
|
2092 | unique_filename = _generate_unique_filename(cls, "init") | |||
|
2093 | ||||
|
2094 | script, globs, annotations = _attrs_to_init_script( | |||
|
2095 | filtered_attrs, | |||
|
2096 | frozen, | |||
|
2097 | slots, | |||
|
2098 | pre_init, | |||
|
2099 | post_init, | |||
|
2100 | cache_hash, | |||
|
2101 | base_attr_map, | |||
|
2102 | is_exc, | |||
|
2103 | has_cls_on_setattr, | |||
|
2104 | attrs_init, | |||
|
2105 | ) | |||
|
2106 | if cls.__module__ in sys.modules: | |||
|
2107 | # This makes typing.get_type_hints(CLS.__init__) resolve string types. | |||
|
2108 | globs.update(sys.modules[cls.__module__].__dict__) | |||
|
2109 | ||||
|
2110 | globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) | |||
|
2111 | ||||
|
2112 | if needs_cached_setattr: | |||
|
2113 | # Save the lookup overhead in __init__ if we need to circumvent | |||
|
2114 | # setattr hooks. | |||
|
2115 | globs["_setattr"] = _obj_setattr | |||
|
2116 | ||||
|
2117 | init = _make_method( | |||
|
2118 | "__attrs_init__" if attrs_init else "__init__", | |||
|
2119 | script, | |||
|
2120 | unique_filename, | |||
|
2121 | globs, | |||
|
2122 | ) | |||
|
2123 | init.__annotations__ = annotations | |||
|
2124 | ||||
|
2125 | return init | |||
|
2126 | ||||
|
2127 | ||||
|
2128 | def _setattr(attr_name, value_var, has_on_setattr): | |||
|
2129 | """ | |||
|
2130 | Use the cached object.setattr to set *attr_name* to *value_var*. | |||
|
2131 | """ | |||
|
2132 | return "_setattr(self, '%s', %s)" % (attr_name, value_var) | |||
|
2133 | ||||
|
2134 | ||||
|
2135 | def _setattr_with_converter(attr_name, value_var, has_on_setattr): | |||
|
2136 | """ | |||
|
2137 | Use the cached object.setattr to set *attr_name* to *value_var*, but run | |||
|
2138 | its converter first. | |||
|
2139 | """ | |||
|
2140 | return "_setattr(self, '%s', %s(%s))" % ( | |||
|
2141 | attr_name, | |||
|
2142 | _init_converter_pat % (attr_name,), | |||
|
2143 | value_var, | |||
|
2144 | ) | |||
|
2145 | ||||
|
2146 | ||||
|
2147 | def _assign(attr_name, value, has_on_setattr): | |||
|
2148 | """ | |||
|
2149 | Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise | |||
|
2150 | relegate to _setattr. | |||
|
2151 | """ | |||
|
2152 | if has_on_setattr: | |||
|
2153 | return _setattr(attr_name, value, True) | |||
|
2154 | ||||
|
2155 | return "self.%s = %s" % (attr_name, value) | |||
|
2156 | ||||
|
2157 | ||||
|
2158 | def _assign_with_converter(attr_name, value_var, has_on_setattr): | |||
|
2159 | """ | |||
|
2160 | Unless *attr_name* has an on_setattr hook, use normal assignment after | |||
|
2161 | conversion. Otherwise relegate to _setattr_with_converter. | |||
|
2162 | """ | |||
|
2163 | if has_on_setattr: | |||
|
2164 | return _setattr_with_converter(attr_name, value_var, True) | |||
|
2165 | ||||
|
2166 | return "self.%s = %s(%s)" % ( | |||
|
2167 | attr_name, | |||
|
2168 | _init_converter_pat % (attr_name,), | |||
|
2169 | value_var, | |||
|
2170 | ) | |||
|
2171 | ||||
|
2172 | ||||
|
2173 | def _attrs_to_init_script( | |||
|
2174 | attrs, | |||
|
2175 | frozen, | |||
|
2176 | slots, | |||
|
2177 | pre_init, | |||
|
2178 | post_init, | |||
|
2179 | cache_hash, | |||
|
2180 | base_attr_map, | |||
|
2181 | is_exc, | |||
|
2182 | has_cls_on_setattr, | |||
|
2183 | attrs_init, | |||
|
2184 | ): | |||
666 | """ |
|
2185 | """ | |
667 | Return a script of an initializer for *attrs* and a dict of globals. |
|
2186 | Return a script of an initializer for *attrs* and a dict of globals. | |
668 |
|
2187 | |||
669 | The globals are expected by the generated script. |
|
2188 | The globals are expected by the generated script. | |
670 |
|
2189 | |||
671 |
|
|
2190 | If *frozen* is True, we cannot set the attributes directly so we use | |
672 | a cached ``object.__setattr__``. |
|
2191 | a cached ``object.__setattr__``. | |
673 | """ |
|
2192 | """ | |
674 | lines = [] |
|
2193 | lines = [] | |
|
2194 | if pre_init: | |||
|
2195 | lines.append("self.__attrs_pre_init__()") | |||
|
2196 | ||||
675 | if frozen is True: |
|
2197 | if frozen is True: | |
676 | lines.append( |
|
2198 | if slots is True: | |
677 | # Circumvent the __setattr__ descriptor to save one lookup per |
|
2199 | fmt_setter = _setattr | |
678 | # assignment. |
|
2200 | fmt_setter_with_converter = _setattr_with_converter | |
679 | "_setattr = _cached_setattr.__get__(self, self.__class__)" |
|
2201 | else: | |
680 | ) |
|
2202 | # Dict frozen classes assign directly to __dict__. | |
681 |
|
2203 | # But only if the attribute doesn't come from an ancestor slot | ||
682 | def fmt_setter(attr_name, value_var): |
|
2204 | # class. | |
683 | return "_setattr('%(attr_name)s', %(value_var)s)" % { |
|
2205 | # Note _inst_dict will be used again below if cache_hash is True | |
684 | "attr_name": attr_name, |
|
2206 | lines.append("_inst_dict = self.__dict__") | |
685 | "value_var": value_var, |
|
2207 | ||
686 | } |
|
2208 | def fmt_setter(attr_name, value_var, has_on_setattr): | |
687 |
|
2209 | if _is_slot_attr(attr_name, base_attr_map): | ||
688 | def fmt_setter_with_converter(attr_name, value_var): |
|
2210 | return _setattr(attr_name, value_var, has_on_setattr) | |
689 | conv_name = _init_convert_pat.format(attr_name) |
|
2211 | ||
690 |
return "_ |
|
2212 | return "_inst_dict['%s'] = %s" % (attr_name, value_var) | |
691 | "attr_name": attr_name, |
|
2213 | ||
692 | "value_var": value_var, |
|
2214 | def fmt_setter_with_converter( | |
693 | "conv": conv_name, |
|
2215 | attr_name, value_var, has_on_setattr | |
694 |
|
|
2216 | ): | |
|
2217 | if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): | |||
|
2218 | return _setattr_with_converter( | |||
|
2219 | attr_name, value_var, has_on_setattr | |||
|
2220 | ) | |||
|
2221 | ||||
|
2222 | return "_inst_dict['%s'] = %s(%s)" % ( | |||
|
2223 | attr_name, | |||
|
2224 | _init_converter_pat % (attr_name,), | |||
|
2225 | value_var, | |||
|
2226 | ) | |||
|
2227 | ||||
695 | else: |
|
2228 | else: | |
696 | def fmt_setter(attr_name, value): |
|
2229 | # Not frozen. | |
697 | return "self.%(attr_name)s = %(value)s" % { |
|
2230 | fmt_setter = _assign | |
698 | "attr_name": attr_name, |
|
2231 | fmt_setter_with_converter = _assign_with_converter | |
699 | "value": value, |
|
|||
700 | } |
|
|||
701 |
|
||||
702 | def fmt_setter_with_converter(attr_name, value_var): |
|
|||
703 | conv_name = _init_convert_pat.format(attr_name) |
|
|||
704 | return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % { |
|
|||
705 | "attr_name": attr_name, |
|
|||
706 | "value_var": value_var, |
|
|||
707 | "conv": conv_name, |
|
|||
708 | } |
|
|||
709 |
|
2232 | |||
710 | args = [] |
|
2233 | args = [] | |
|
2234 | kw_only_args = [] | |||
711 | attrs_to_validate = [] |
|
2235 | attrs_to_validate = [] | |
712 |
|
2236 | |||
713 | # This is a dictionary of names to validator and converter callables. |
|
2237 | # This is a dictionary of names to validator and converter callables. | |
714 | # Injecting this into __init__ globals lets us avoid lookups. |
|
2238 | # Injecting this into __init__ globals lets us avoid lookups. | |
715 | names_for_globals = {} |
|
2239 | names_for_globals = {} | |
|
2240 | annotations = {"return": None} | |||
716 |
|
2241 | |||
717 | for a in attrs: |
|
2242 | for a in attrs: | |
718 | if a.validator: |
|
2243 | if a.validator: | |
719 | attrs_to_validate.append(a) |
|
2244 | attrs_to_validate.append(a) | |
|
2245 | ||||
720 | attr_name = a.name |
|
2246 | attr_name = a.name | |
|
2247 | has_on_setattr = a.on_setattr is not None or ( | |||
|
2248 | a.on_setattr is not setters.NO_OP and has_cls_on_setattr | |||
|
2249 | ) | |||
721 | arg_name = a.name.lstrip("_") |
|
2250 | arg_name = a.name.lstrip("_") | |
|
2251 | ||||
722 | has_factory = isinstance(a.default, Factory) |
|
2252 | has_factory = isinstance(a.default, Factory) | |
723 | if has_factory and a.default.takes_self: |
|
2253 | if has_factory and a.default.takes_self: | |
724 | maybe_self = "self" |
|
2254 | maybe_self = "self" | |
725 | else: |
|
2255 | else: | |
726 | maybe_self = "" |
|
2256 | maybe_self = "" | |
|
2257 | ||||
727 | if a.init is False: |
|
2258 | if a.init is False: | |
728 | if has_factory: |
|
2259 | if has_factory: | |
729 | init_factory_name = _init_factory_pat.format(a.name) |
|
2260 | init_factory_name = _init_factory_pat.format(a.name) | |
730 | if a.convert is not None: |
|
2261 | if a.converter is not None: | |
731 |
lines.append( |
|
2262 | lines.append( | |
732 |
|
|
2263 | fmt_setter_with_converter( | |
733 | init_factory_name + "({0})".format(maybe_self))) |
|
2264 | attr_name, | |
734 | conv_name = _init_convert_pat.format(a.name) |
|
2265 | init_factory_name + "(%s)" % (maybe_self,), | |
735 | names_for_globals[conv_name] = a.convert |
|
2266 | has_on_setattr, | |
|
2267 | ) | |||
|
2268 | ) | |||
|
2269 | conv_name = _init_converter_pat % (a.name,) | |||
|
2270 | names_for_globals[conv_name] = a.converter | |||
736 | else: |
|
2271 | else: | |
737 |
lines.append( |
|
2272 | lines.append( | |
738 |
|
|
2273 | fmt_setter( | |
739 | init_factory_name + "({0})".format(maybe_self) |
|
2274 | attr_name, | |
740 | )) |
|
2275 | init_factory_name + "(%s)" % (maybe_self,), | |
|
2276 | has_on_setattr, | |||
|
2277 | ) | |||
|
2278 | ) | |||
741 | names_for_globals[init_factory_name] = a.default.factory |
|
2279 | names_for_globals[init_factory_name] = a.default.factory | |
742 | else: |
|
2280 | else: | |
743 | if a.convert is not None: |
|
2281 | if a.converter is not None: | |
744 |
lines.append( |
|
2282 | lines.append( | |
745 |
|
|
2283 | fmt_setter_with_converter( | |
746 |
|
|
2284 | attr_name, | |
747 |
.f |
|
2285 | "attr_dict['%s'].default" % (attr_name,), | |
748 |
|
|
2286 | has_on_setattr, | |
749 | conv_name = _init_convert_pat.format(a.name) |
|
2287 | ) | |
750 | names_for_globals[conv_name] = a.convert |
|
2288 | ) | |
|
2289 | conv_name = _init_converter_pat % (a.name,) | |||
|
2290 | names_for_globals[conv_name] = a.converter | |||
751 | else: |
|
2291 | else: | |
752 |
lines.append( |
|
2292 | lines.append( | |
753 |
|
|
2293 | fmt_setter( | |
754 |
|
|
2294 | attr_name, | |
755 |
.f |
|
2295 | "attr_dict['%s'].default" % (attr_name,), | |
756 |
|
|
2296 | has_on_setattr, | |
|
2297 | ) | |||
|
2298 | ) | |||
757 | elif a.default is not NOTHING and not has_factory: |
|
2299 | elif a.default is not NOTHING and not has_factory: | |
758 | args.append( |
|
2300 | arg = "%s=attr_dict['%s'].default" % (arg_name, attr_name) | |
759 | "{arg_name}=attr_dict['{attr_name}'].default".format( |
|
2301 | if a.kw_only: | |
760 | arg_name=arg_name, |
|
2302 | kw_only_args.append(arg) | |
761 | attr_name=attr_name, |
|
2303 | else: | |
|
2304 | args.append(arg) | |||
|
2305 | ||||
|
2306 | if a.converter is not None: | |||
|
2307 | lines.append( | |||
|
2308 | fmt_setter_with_converter( | |||
|
2309 | attr_name, arg_name, has_on_setattr | |||
|
2310 | ) | |||
762 | ) |
|
2311 | ) | |
763 | ) |
|
2312 | names_for_globals[ | |
764 | if a.convert is not None: |
|
2313 | _init_converter_pat % (a.name,) | |
765 | lines.append(fmt_setter_with_converter(attr_name, arg_name)) |
|
2314 | ] = a.converter | |
766 | names_for_globals[_init_convert_pat.format(a.name)] = a.convert |
|
|||
767 | else: |
|
2315 | else: | |
768 | lines.append(fmt_setter(attr_name, arg_name)) |
|
2316 | lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) | |
|
2317 | ||||
769 | elif has_factory: |
|
2318 | elif has_factory: | |
770 |
arg |
|
2319 | arg = "%s=NOTHING" % (arg_name,) | |
771 | lines.append("if {arg_name} is not NOTHING:" |
|
2320 | if a.kw_only: | |
772 | .format(arg_name=arg_name)) |
|
2321 | kw_only_args.append(arg) | |
|
2322 | else: | |||
|
2323 | args.append(arg) | |||
|
2324 | lines.append("if %s is not NOTHING:" % (arg_name,)) | |||
|
2325 | ||||
773 | init_factory_name = _init_factory_pat.format(a.name) |
|
2326 | init_factory_name = _init_factory_pat.format(a.name) | |
774 | if a.convert is not None: |
|
2327 | if a.converter is not None: | |
775 | lines.append(" " + fmt_setter_with_converter(attr_name, |
|
2328 | lines.append( | |
776 | arg_name)) |
|
2329 | " " | |
|
2330 | + fmt_setter_with_converter( | |||
|
2331 | attr_name, arg_name, has_on_setattr | |||
|
2332 | ) | |||
|
2333 | ) | |||
777 | lines.append("else:") |
|
2334 | lines.append("else:") | |
778 |
lines.append( |
|
2335 | lines.append( | |
779 |
|
|
2336 | " " | |
780 | init_factory_name + "({0})".format(maybe_self) |
|
2337 | + fmt_setter_with_converter( | |
781 |
|
|
2338 | attr_name, | |
782 | names_for_globals[_init_convert_pat.format(a.name)] = a.convert |
|
2339 | init_factory_name + "(" + maybe_self + ")", | |
|
2340 | has_on_setattr, | |||
|
2341 | ) | |||
|
2342 | ) | |||
|
2343 | names_for_globals[ | |||
|
2344 | _init_converter_pat % (a.name,) | |||
|
2345 | ] = a.converter | |||
783 | else: |
|
2346 | else: | |
784 | lines.append(" " + fmt_setter(attr_name, arg_name)) |
|
2347 | lines.append( | |
|
2348 | " " + fmt_setter(attr_name, arg_name, has_on_setattr) | |||
|
2349 | ) | |||
785 | lines.append("else:") |
|
2350 | lines.append("else:") | |
786 |
lines.append( |
|
2351 | lines.append( | |
787 |
|
|
2352 | " " | |
788 | init_factory_name + "({0})".format(maybe_self) |
|
2353 | + fmt_setter( | |
789 |
|
|
2354 | attr_name, | |
|
2355 | init_factory_name + "(" + maybe_self + ")", | |||
|
2356 | has_on_setattr, | |||
|
2357 | ) | |||
|
2358 | ) | |||
790 | names_for_globals[init_factory_name] = a.default.factory |
|
2359 | names_for_globals[init_factory_name] = a.default.factory | |
791 | else: |
|
2360 | else: | |
792 | args.append(arg_name) |
|
2361 | if a.kw_only: | |
793 | if a.convert is not None: |
|
2362 | kw_only_args.append(arg_name) | |
794 | lines.append(fmt_setter_with_converter(attr_name, arg_name)) |
|
|||
795 | names_for_globals[_init_convert_pat.format(a.name)] = a.convert |
|
|||
796 | else: |
|
2363 | else: | |
797 |
|
|
2364 | args.append(arg_name) | |
|
2365 | ||||
|
2366 | if a.converter is not None: | |||
|
2367 | lines.append( | |||
|
2368 | fmt_setter_with_converter( | |||
|
2369 | attr_name, arg_name, has_on_setattr | |||
|
2370 | ) | |||
|
2371 | ) | |||
|
2372 | names_for_globals[ | |||
|
2373 | _init_converter_pat % (a.name,) | |||
|
2374 | ] = a.converter | |||
|
2375 | else: | |||
|
2376 | lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) | |||
|
2377 | ||||
|
2378 | if a.init is True: | |||
|
2379 | if a.type is not None and a.converter is None: | |||
|
2380 | annotations[arg_name] = a.type | |||
|
2381 | elif a.converter is not None: | |||
|
2382 | # Try to get the type from the converter. | |||
|
2383 | t = _AnnotationExtractor(a.converter).get_first_param_type() | |||
|
2384 | if t: | |||
|
2385 | annotations[arg_name] = t | |||
798 |
|
2386 | |||
799 | if attrs_to_validate: # we can skip this if there are no validators. |
|
2387 | if attrs_to_validate: # we can skip this if there are no validators. | |
800 | names_for_globals["_config"] = _config |
|
2388 | names_for_globals["_config"] = _config | |
801 | lines.append("if _config._run_validators is True:") |
|
2389 | lines.append("if _config._run_validators is True:") | |
802 | for a in attrs_to_validate: |
|
2390 | for a in attrs_to_validate: | |
803 |
val_name = "__attr_validator_ |
|
2391 | val_name = "__attr_validator_" + a.name | |
804 |
attr_name = "__attr_ |
|
2392 | attr_name = "__attr_" + a.name | |
805 | lines.append(" {}(self, {}, self.{})".format( |
|
2393 | lines.append( | |
806 |
val_name, attr_name, a.name) |
|
2394 | " %s(self, %s, self.%s)" % (val_name, attr_name, a.name) | |
|
2395 | ) | |||
807 | names_for_globals[val_name] = a.validator |
|
2396 | names_for_globals[val_name] = a.validator | |
808 | names_for_globals[attr_name] = a |
|
2397 | names_for_globals[attr_name] = a | |
|
2398 | ||||
809 | if post_init: |
|
2399 | if post_init: | |
810 | lines.append("self.__attrs_post_init__()") |
|
2400 | lines.append("self.__attrs_post_init__()") | |
811 |
|
2401 | |||
812 | return """\ |
|
2402 | # because this is set only after __attrs_post_init__ is called, a crash | |
813 | def __init__(self, {args}): |
|
2403 | # will result if post-init tries to access the hash code. This seemed | |
|
2404 | # preferable to setting this beforehand, in which case alteration to | |||
|
2405 | # field values during post-init combined with post-init accessing the | |||
|
2406 | # hash code would result in silent bugs. | |||
|
2407 | if cache_hash: | |||
|
2408 | if frozen: | |||
|
2409 | if slots: | |||
|
2410 | # if frozen and slots, then _setattr defined above | |||
|
2411 | init_hash_cache = "_setattr(self, '%s', %s)" | |||
|
2412 | else: | |||
|
2413 | # if frozen and not slots, then _inst_dict defined above | |||
|
2414 | init_hash_cache = "_inst_dict['%s'] = %s" | |||
|
2415 | else: | |||
|
2416 | init_hash_cache = "self.%s = %s" | |||
|
2417 | lines.append(init_hash_cache % (_hash_cache_field, "None")) | |||
|
2418 | ||||
|
2419 | # For exceptions we rely on BaseException.__init__ for proper | |||
|
2420 | # initialization. | |||
|
2421 | if is_exc: | |||
|
2422 | vals = ",".join("self." + a.name for a in attrs if a.init) | |||
|
2423 | ||||
|
2424 | lines.append("BaseException.__init__(self, %s)" % (vals,)) | |||
|
2425 | ||||
|
2426 | args = ", ".join(args) | |||
|
2427 | if kw_only_args: | |||
|
2428 | args += "%s*, %s" % ( | |||
|
2429 | ", " if args else "", # leading comma | |||
|
2430 | ", ".join(kw_only_args), # kw_only args | |||
|
2431 | ) | |||
|
2432 | return ( | |||
|
2433 | """\ | |||
|
2434 | def {init_name}(self, {args}): | |||
814 | {lines} |
|
2435 | {lines} | |
815 |
|
|
2436 | """.format( | |
816 | args=", ".join(args), |
|
2437 | init_name=("__attrs_init__" if attrs_init else "__init__"), | |
817 | lines="\n ".join(lines) if lines else "pass", |
|
2438 | args=args, | |
818 | ), names_for_globals |
|
2439 | lines="\n ".join(lines) if lines else "pass", | |
819 |
|
2440 | ), | ||
820 |
|
2441 | names_for_globals, | ||
821 | class Attribute(object): |
|
2442 | annotations, | |
|
2443 | ) | |||
|
2444 | ||||
|
2445 | ||||
|
2446 | class Attribute: | |||
822 | """ |
|
2447 | """ | |
823 | *Read-only* representation of an attribute. |
|
2448 | *Read-only* representation of an attribute. | |
824 |
|
2449 | |||
825 | :attribute name: The name of the attribute. |
|
2450 | The class has *all* arguments of `attr.ib` (except for ``factory`` | |
826 |
|
2451 | which is only syntactic sugar for ``default=Factory(...)`` plus the | ||
827 | Plus *all* arguments of :func:`attr.ib`. |
|
2452 | following: | |
|
2453 | ||||
|
2454 | - ``name`` (`str`): The name of the attribute. | |||
|
2455 | - ``inherited`` (`bool`): Whether or not that attribute has been inherited | |||
|
2456 | from a base class. | |||
|
2457 | - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The callables | |||
|
2458 | that are used for comparing and ordering objects by this attribute, | |||
|
2459 | respectively. These are set by passing a callable to `attr.ib`'s ``eq``, | |||
|
2460 | ``order``, or ``cmp`` arguments. See also :ref:`comparison customization | |||
|
2461 | <custom-comparison>`. | |||
|
2462 | ||||
|
2463 | Instances of this class are frequently used for introspection purposes | |||
|
2464 | like: | |||
|
2465 | ||||
|
2466 | - `fields` returns a tuple of them. | |||
|
2467 | - Validators get them passed as the first argument. | |||
|
2468 | - The :ref:`field transformer <transform-fields>` hook receives a list of | |||
|
2469 | them. | |||
|
2470 | ||||
|
2471 | .. versionadded:: 20.1.0 *inherited* | |||
|
2472 | .. versionadded:: 20.1.0 *on_setattr* | |||
|
2473 | .. versionchanged:: 20.2.0 *inherited* is not taken into account for | |||
|
2474 | equality checks and hashing anymore. | |||
|
2475 | .. versionadded:: 21.1.0 *eq_key* and *order_key* | |||
|
2476 | ||||
|
2477 | For the full version history of the fields, see `attr.ib`. | |||
828 | """ |
|
2478 | """ | |
|
2479 | ||||
829 | __slots__ = ( |
|
2480 | __slots__ = ( | |
830 | "name", "default", "validator", "repr", "cmp", "hash", "init", |
|
2481 | "name", | |
831 | "convert", "metadata", |
|
2482 | "default", | |
|
2483 | "validator", | |||
|
2484 | "repr", | |||
|
2485 | "eq", | |||
|
2486 | "eq_key", | |||
|
2487 | "order", | |||
|
2488 | "order_key", | |||
|
2489 | "hash", | |||
|
2490 | "init", | |||
|
2491 | "metadata", | |||
|
2492 | "type", | |||
|
2493 | "converter", | |||
|
2494 | "kw_only", | |||
|
2495 | "inherited", | |||
|
2496 | "on_setattr", | |||
832 | ) |
|
2497 | ) | |
833 |
|
2498 | |||
834 | def __init__(self, name, default, validator, repr, cmp, hash, init, |
|
2499 | def __init__( | |
835 | convert=None, metadata=None): |
|
2500 | self, | |
|
2501 | name, | |||
|
2502 | default, | |||
|
2503 | validator, | |||
|
2504 | repr, | |||
|
2505 | cmp, # XXX: unused, remove along with other cmp code. | |||
|
2506 | hash, | |||
|
2507 | init, | |||
|
2508 | inherited, | |||
|
2509 | metadata=None, | |||
|
2510 | type=None, | |||
|
2511 | converter=None, | |||
|
2512 | kw_only=False, | |||
|
2513 | eq=None, | |||
|
2514 | eq_key=None, | |||
|
2515 | order=None, | |||
|
2516 | order_key=None, | |||
|
2517 | on_setattr=None, | |||
|
2518 | ): | |||
|
2519 | eq, eq_key, order, order_key = _determine_attrib_eq_order( | |||
|
2520 | cmp, eq_key or eq, order_key or order, True | |||
|
2521 | ) | |||
|
2522 | ||||
836 | # Cache this descriptor here to speed things up later. |
|
2523 | # Cache this descriptor here to speed things up later. | |
837 | bound_setattr = _obj_setattr.__get__(self, Attribute) |
|
2524 | bound_setattr = _obj_setattr.__get__(self, Attribute) | |
838 |
|
2525 | |||
|
2526 | # Despite the big red warning, people *do* instantiate `Attribute` | |||
|
2527 | # themselves. | |||
839 | bound_setattr("name", name) |
|
2528 | bound_setattr("name", name) | |
840 | bound_setattr("default", default) |
|
2529 | bound_setattr("default", default) | |
841 | bound_setattr("validator", validator) |
|
2530 | bound_setattr("validator", validator) | |
842 | bound_setattr("repr", repr) |
|
2531 | bound_setattr("repr", repr) | |
843 |
bound_setattr(" |
|
2532 | bound_setattr("eq", eq) | |
|
2533 | bound_setattr("eq_key", eq_key) | |||
|
2534 | bound_setattr("order", order) | |||
|
2535 | bound_setattr("order_key", order_key) | |||
844 | bound_setattr("hash", hash) |
|
2536 | bound_setattr("hash", hash) | |
845 | bound_setattr("init", init) |
|
2537 | bound_setattr("init", init) | |
846 | bound_setattr("convert", convert) |
|
2538 | bound_setattr("converter", converter) | |
847 | bound_setattr("metadata", (metadata_proxy(metadata) if metadata |
|
2539 | bound_setattr( | |
848 | else _empty_metadata_singleton)) |
|
2540 | "metadata", | |
|
2541 | ( | |||
|
2542 | types.MappingProxyType(dict(metadata)) # Shallow copy | |||
|
2543 | if metadata | |||
|
2544 | else _empty_metadata_singleton | |||
|
2545 | ), | |||
|
2546 | ) | |||
|
2547 | bound_setattr("type", type) | |||
|
2548 | bound_setattr("kw_only", kw_only) | |||
|
2549 | bound_setattr("inherited", inherited) | |||
|
2550 | bound_setattr("on_setattr", on_setattr) | |||
849 |
|
2551 | |||
850 | def __setattr__(self, name, value): |
|
2552 | def __setattr__(self, name, value): | |
851 | raise FrozenInstanceError() |
|
2553 | raise FrozenInstanceError() | |
852 |
|
2554 | |||
853 | @classmethod |
|
2555 | @classmethod | |
854 | def from_counting_attr(cls, name, ca): |
|
2556 | def from_counting_attr(cls, name, ca, type=None): | |
|
2557 | # type holds the annotated value. deal with conflicts: | |||
|
2558 | if type is None: | |||
|
2559 | type = ca.type | |||
|
2560 | elif ca.type is not None: | |||
|
2561 | raise ValueError( | |||
|
2562 | "Type annotation and type argument cannot both be present" | |||
|
2563 | ) | |||
855 | inst_dict = { |
|
2564 | inst_dict = { | |
856 | k: getattr(ca, k) |
|
2565 | k: getattr(ca, k) | |
857 | for k |
|
2566 | for k in Attribute.__slots__ | |
858 | in Attribute.__slots__ |
|
2567 | if k | |
859 |
|
|
2568 | not in ( | |
860 |
"name", |
|
2569 | "name", | |
861 |
|
|
2570 | "validator", | |
|
2571 | "default", | |||
|
2572 | "type", | |||
|
2573 | "inherited", | |||
|
2574 | ) # exclude methods and deprecated alias | |||
862 | } |
|
2575 | } | |
863 | return cls(name=name, validator=ca._validator, default=ca._default, |
|
2576 | return cls( | |
864 | **inst_dict) |
|
2577 | name=name, | |
|
2578 | validator=ca._validator, | |||
|
2579 | default=ca._default, | |||
|
2580 | type=type, | |||
|
2581 | cmp=None, | |||
|
2582 | inherited=False, | |||
|
2583 | **inst_dict | |||
|
2584 | ) | |||
|
2585 | ||||
|
2586 | # Don't use attr.evolve since fields(Attribute) doesn't work | |||
|
2587 | def evolve(self, **changes): | |||
|
2588 | """ | |||
|
2589 | Copy *self* and apply *changes*. | |||
|
2590 | ||||
|
2591 | This works similarly to `attr.evolve` but that function does not work | |||
|
2592 | with ``Attribute``. | |||
|
2593 | ||||
|
2594 | It is mainly meant to be used for `transform-fields`. | |||
|
2595 | ||||
|
2596 | .. versionadded:: 20.3.0 | |||
|
2597 | """ | |||
|
2598 | new = copy.copy(self) | |||
|
2599 | ||||
|
2600 | new._setattrs(changes.items()) | |||
|
2601 | ||||
|
2602 | return new | |||
865 |
|
2603 | |||
866 | # Don't use _add_pickle since fields(Attribute) doesn't work |
|
2604 | # Don't use _add_pickle since fields(Attribute) doesn't work | |
867 | def __getstate__(self): |
|
2605 | def __getstate__(self): | |
868 | """ |
|
2606 | """ | |
869 | Play nice with pickle. |
|
2607 | Play nice with pickle. | |
870 | """ |
|
2608 | """ | |
871 | return tuple(getattr(self, name) if name != "metadata" |
|
2609 | return tuple( | |
872 |
|
|
2610 | getattr(self, name) if name != "metadata" else dict(self.metadata) | |
873 |
|
|
2611 | for name in self.__slots__ | |
|
2612 | ) | |||
874 |
|
2613 | |||
875 | def __setstate__(self, state): |
|
2614 | def __setstate__(self, state): | |
876 | """ |
|
2615 | """ | |
877 | Play nice with pickle. |
|
2616 | Play nice with pickle. | |
878 | """ |
|
2617 | """ | |
|
2618 | self._setattrs(zip(self.__slots__, state)) | |||
|
2619 | ||||
|
2620 | def _setattrs(self, name_values_pairs): | |||
879 | bound_setattr = _obj_setattr.__get__(self, Attribute) |
|
2621 | bound_setattr = _obj_setattr.__get__(self, Attribute) | |
880 |
for name, value in |
|
2622 | for name, value in name_values_pairs: | |
881 | if name != "metadata": |
|
2623 | if name != "metadata": | |
882 | bound_setattr(name, value) |
|
2624 | bound_setattr(name, value) | |
883 | else: |
|
2625 | else: | |
884 | bound_setattr(name, metadata_proxy(value) if value else |
|
2626 | bound_setattr( | |
885 | _empty_metadata_singleton) |
|
2627 | name, | |
886 |
|
2628 | types.MappingProxyType(dict(value)) | ||
887 |
|
2629 | if value | ||
888 | _a = [Attribute(name=name, default=NOTHING, validator=None, |
|
2630 | else _empty_metadata_singleton, | |
889 | repr=True, cmp=True, hash=(name != "metadata"), init=True) |
|
2631 | ) | |
890 | for name in Attribute.__slots__] |
|
2632 | ||
|
2633 | ||||
|
2634 | _a = [ | |||
|
2635 | Attribute( | |||
|
2636 | name=name, | |||
|
2637 | default=NOTHING, | |||
|
2638 | validator=None, | |||
|
2639 | repr=True, | |||
|
2640 | cmp=None, | |||
|
2641 | eq=True, | |||
|
2642 | order=False, | |||
|
2643 | hash=(name != "metadata"), | |||
|
2644 | init=True, | |||
|
2645 | inherited=False, | |||
|
2646 | ) | |||
|
2647 | for name in Attribute.__slots__ | |||
|
2648 | ] | |||
891 |
|
2649 | |||
892 | Attribute = _add_hash( |
|
2650 | Attribute = _add_hash( | |
893 | _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), |
|
2651 | _add_eq( | |
894 | attrs=[a for a in _a if a.hash] |
|
2652 | _add_repr(Attribute, attrs=_a), | |
|
2653 | attrs=[a for a in _a if a.name != "inherited"], | |||
|
2654 | ), | |||
|
2655 | attrs=[a for a in _a if a.hash and a.name != "inherited"], | |||
895 | ) |
|
2656 | ) | |
896 |
|
2657 | |||
897 |
|
2658 | |||
898 |
class _CountingAttr |
|
2659 | class _CountingAttr: | |
899 | """ |
|
2660 | """ | |
900 | Intermediate representation of attributes that uses a counter to preserve |
|
2661 | Intermediate representation of attributes that uses a counter to preserve | |
901 | the order in which the attributes have been defined. |
|
2662 | the order in which the attributes have been defined. | |
@@ -903,35 +2664,105 b' class _CountingAttr(object):' | |||||
903 | *Internal* data structure of the attrs library. Running into is most |
|
2664 | *Internal* data structure of the attrs library. Running into is most | |
904 | likely the result of a bug like a forgotten `@attr.s` decorator. |
|
2665 | likely the result of a bug like a forgotten `@attr.s` decorator. | |
905 | """ |
|
2666 | """ | |
906 | __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init", |
|
2667 | ||
907 | "metadata", "_validator", "convert") |
|
2668 | __slots__ = ( | |
|
2669 | "counter", | |||
|
2670 | "_default", | |||
|
2671 | "repr", | |||
|
2672 | "eq", | |||
|
2673 | "eq_key", | |||
|
2674 | "order", | |||
|
2675 | "order_key", | |||
|
2676 | "hash", | |||
|
2677 | "init", | |||
|
2678 | "metadata", | |||
|
2679 | "_validator", | |||
|
2680 | "converter", | |||
|
2681 | "type", | |||
|
2682 | "kw_only", | |||
|
2683 | "on_setattr", | |||
|
2684 | ) | |||
908 | __attrs_attrs__ = tuple( |
|
2685 | __attrs_attrs__ = tuple( | |
909 | Attribute(name=name, default=NOTHING, validator=None, |
|
2686 | Attribute( | |
910 | repr=True, cmp=True, hash=True, init=True) |
|
2687 | name=name, | |
911 | for name |
|
2688 | default=NOTHING, | |
912 | in ("counter", "_default", "repr", "cmp", "hash", "init",) |
|
2689 | validator=None, | |
|
2690 | repr=True, | |||
|
2691 | cmp=None, | |||
|
2692 | hash=True, | |||
|
2693 | init=True, | |||
|
2694 | kw_only=False, | |||
|
2695 | eq=True, | |||
|
2696 | eq_key=None, | |||
|
2697 | order=False, | |||
|
2698 | order_key=None, | |||
|
2699 | inherited=False, | |||
|
2700 | on_setattr=None, | |||
|
2701 | ) | |||
|
2702 | for name in ( | |||
|
2703 | "counter", | |||
|
2704 | "_default", | |||
|
2705 | "repr", | |||
|
2706 | "eq", | |||
|
2707 | "order", | |||
|
2708 | "hash", | |||
|
2709 | "init", | |||
|
2710 | "on_setattr", | |||
|
2711 | ) | |||
913 | ) + ( |
|
2712 | ) + ( | |
914 | Attribute(name="metadata", default=None, validator=None, |
|
2713 | Attribute( | |
915 | repr=True, cmp=True, hash=False, init=True), |
|
2714 | name="metadata", | |
|
2715 | default=None, | |||
|
2716 | validator=None, | |||
|
2717 | repr=True, | |||
|
2718 | cmp=None, | |||
|
2719 | hash=False, | |||
|
2720 | init=True, | |||
|
2721 | kw_only=False, | |||
|
2722 | eq=True, | |||
|
2723 | eq_key=None, | |||
|
2724 | order=False, | |||
|
2725 | order_key=None, | |||
|
2726 | inherited=False, | |||
|
2727 | on_setattr=None, | |||
|
2728 | ), | |||
916 | ) |
|
2729 | ) | |
917 | cls_counter = 0 |
|
2730 | cls_counter = 0 | |
918 |
|
2731 | |||
919 | def __init__(self, default, validator, repr, cmp, hash, init, convert, |
|
2732 | def __init__( | |
920 | metadata): |
|
2733 | self, | |
|
2734 | default, | |||
|
2735 | validator, | |||
|
2736 | repr, | |||
|
2737 | cmp, | |||
|
2738 | hash, | |||
|
2739 | init, | |||
|
2740 | converter, | |||
|
2741 | metadata, | |||
|
2742 | type, | |||
|
2743 | kw_only, | |||
|
2744 | eq, | |||
|
2745 | eq_key, | |||
|
2746 | order, | |||
|
2747 | order_key, | |||
|
2748 | on_setattr, | |||
|
2749 | ): | |||
921 | _CountingAttr.cls_counter += 1 |
|
2750 | _CountingAttr.cls_counter += 1 | |
922 | self.counter = _CountingAttr.cls_counter |
|
2751 | self.counter = _CountingAttr.cls_counter | |
923 | self._default = default |
|
2752 | self._default = default | |
924 | # If validator is a list/tuple, wrap it using helper validator. |
|
2753 | self._validator = validator | |
925 | if validator and isinstance(validator, (list, tuple)): |
|
2754 | self.converter = converter | |
926 | self._validator = and_(*validator) |
|
|||
927 | else: |
|
|||
928 | self._validator = validator |
|
|||
929 | self.repr = repr |
|
2755 | self.repr = repr | |
930 |
self. |
|
2756 | self.eq = eq | |
|
2757 | self.eq_key = eq_key | |||
|
2758 | self.order = order | |||
|
2759 | self.order_key = order_key | |||
931 | self.hash = hash |
|
2760 | self.hash = hash | |
932 | self.init = init |
|
2761 | self.init = init | |
933 | self.convert = convert |
|
|||
934 | self.metadata = metadata |
|
2762 | self.metadata = metadata | |
|
2763 | self.type = type | |||
|
2764 | self.kw_only = kw_only | |||
|
2765 | self.on_setattr = on_setattr | |||
935 |
|
2766 | |||
936 | def validator(self, meth): |
|
2767 | def validator(self, meth): | |
937 | """ |
|
2768 | """ | |
@@ -965,15 +2796,14 b' class _CountingAttr(object):' | |||||
965 | return meth |
|
2796 | return meth | |
966 |
|
2797 | |||
967 |
|
2798 | |||
968 |
_CountingAttr = _add_ |
|
2799 | _CountingAttr = _add_eq(_add_repr(_CountingAttr)) | |
969 |
|
2800 | |||
970 |
|
2801 | |||
971 | @attributes(slots=True, init=False) |
|
2802 | class Factory: | |
972 | class Factory(object): |
|
|||
973 | """ |
|
2803 | """ | |
974 | Stores a factory callable. |
|
2804 | Stores a factory callable. | |
975 |
|
2805 | |||
976 |
If passed as the default value to |
|
2806 | If passed as the default value to `attrs.field`, the factory is used to | |
977 | generate a new value. |
|
2807 | generate a new value. | |
978 |
|
2808 | |||
979 | :param callable factory: A callable that takes either none or exactly one |
|
2809 | :param callable factory: A callable that takes either none or exactly one | |
@@ -983,8 +2813,8 b' class Factory(object):' | |||||
983 |
|
2813 | |||
984 | .. versionadded:: 17.1.0 *takes_self* |
|
2814 | .. versionadded:: 17.1.0 *takes_self* | |
985 | """ |
|
2815 | """ | |
986 | factory = attr() |
|
2816 | ||
987 | takes_self = attr() |
|
2817 | __slots__ = ("factory", "takes_self") | |
988 |
|
2818 | |||
989 | def __init__(self, factory, takes_self=False): |
|
2819 | def __init__(self, factory, takes_self=False): | |
990 | """ |
|
2820 | """ | |
@@ -994,47 +2824,122 b' class Factory(object):' | |||||
994 | self.factory = factory |
|
2824 | self.factory = factory | |
995 | self.takes_self = takes_self |
|
2825 | self.takes_self = takes_self | |
996 |
|
2826 | |||
|
2827 | def __getstate__(self): | |||
|
2828 | """ | |||
|
2829 | Play nice with pickle. | |||
|
2830 | """ | |||
|
2831 | return tuple(getattr(self, name) for name in self.__slots__) | |||
|
2832 | ||||
|
2833 | def __setstate__(self, state): | |||
|
2834 | """ | |||
|
2835 | Play nice with pickle. | |||
|
2836 | """ | |||
|
2837 | for name, value in zip(self.__slots__, state): | |||
|
2838 | setattr(self, name, value) | |||
|
2839 | ||||
|
2840 | ||||
|
2841 | _f = [ | |||
|
2842 | Attribute( | |||
|
2843 | name=name, | |||
|
2844 | default=NOTHING, | |||
|
2845 | validator=None, | |||
|
2846 | repr=True, | |||
|
2847 | cmp=None, | |||
|
2848 | eq=True, | |||
|
2849 | order=False, | |||
|
2850 | hash=True, | |||
|
2851 | init=True, | |||
|
2852 | inherited=False, | |||
|
2853 | ) | |||
|
2854 | for name in Factory.__slots__ | |||
|
2855 | ] | |||
|
2856 | ||||
|
2857 | Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) | |||
|
2858 | ||||
997 |
|
2859 | |||
998 | def make_class(name, attrs, bases=(object,), **attributes_arguments): |
|
2860 | def make_class(name, attrs, bases=(object,), **attributes_arguments): | |
999 | """ |
|
2861 | """ | |
1000 | A quick way to create a new class called *name* with *attrs*. |
|
2862 | A quick way to create a new class called *name* with *attrs*. | |
1001 |
|
2863 | |||
1002 | :param name: The name for the new class. |
|
2864 | :param str name: The name for the new class. | |
1003 | :type name: str |
|
|||
1004 |
|
2865 | |||
1005 | :param attrs: A list of names or a dictionary of mappings of names to |
|
2866 | :param attrs: A list of names or a dictionary of mappings of names to | |
1006 | attributes. |
|
2867 | attributes. | |
1007 | :type attrs: :class:`list` or :class:`dict` |
|
2868 | ||
|
2869 | If *attrs* is a list or an ordered dict (`dict` on Python 3.6+, | |||
|
2870 | `collections.OrderedDict` otherwise), the order is deduced from | |||
|
2871 | the order of the names or attributes inside *attrs*. Otherwise the | |||
|
2872 | order of the definition of the attributes is used. | |||
|
2873 | :type attrs: `list` or `dict` | |||
1008 |
|
2874 | |||
1009 | :param tuple bases: Classes that the new class will subclass. |
|
2875 | :param tuple bases: Classes that the new class will subclass. | |
1010 |
|
2876 | |||
1011 |
:param attributes_arguments: Passed unmodified to |
|
2877 | :param attributes_arguments: Passed unmodified to `attr.s`. | |
1012 |
|
2878 | |||
1013 | :return: A new class with *attrs*. |
|
2879 | :return: A new class with *attrs*. | |
1014 | :rtype: type |
|
2880 | :rtype: type | |
1015 |
|
2881 | |||
1016 |
.. |
|
2882 | .. versionadded:: 17.1.0 *bases* | |
|
2883 | .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. | |||
1017 | """ |
|
2884 | """ | |
1018 | if isinstance(attrs, dict): |
|
2885 | if isinstance(attrs, dict): | |
1019 | cls_dict = attrs |
|
2886 | cls_dict = attrs | |
1020 | elif isinstance(attrs, (list, tuple)): |
|
2887 | elif isinstance(attrs, (list, tuple)): | |
1021 |
cls_dict = |
|
2888 | cls_dict = {a: attrib() for a in attrs} | |
1022 | else: |
|
2889 | else: | |
1023 | raise TypeError("attrs argument must be a dict or a list.") |
|
2890 | raise TypeError("attrs argument must be a dict or a list.") | |
1024 |
|
2891 | |||
1025 | return attributes(**attributes_arguments)(type(name, bases, cls_dict)) |
|
2892 | pre_init = cls_dict.pop("__attrs_pre_init__", None) | |
1026 |
|
2893 | post_init = cls_dict.pop("__attrs_post_init__", None) | ||
1027 |
|
2894 | user_init = cls_dict.pop("__init__", None) | ||
1028 | # These are required by whithin this module so we define them here and merely |
|
2895 | ||
1029 | # import into .validators. |
|
2896 | body = {} | |
1030 |
|
2897 | if pre_init is not None: | ||
1031 |
|
2898 | body["__attrs_pre_init__"] = pre_init | ||
1032 | @attributes(slots=True, hash=True) |
|
2899 | if post_init is not None: | |
1033 | class _AndValidator(object): |
|
2900 | body["__attrs_post_init__"] = post_init | |
|
2901 | if user_init is not None: | |||
|
2902 | body["__init__"] = user_init | |||
|
2903 | ||||
|
2904 | type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) | |||
|
2905 | ||||
|
2906 | # For pickling to work, the __module__ variable needs to be set to the | |||
|
2907 | # frame where the class is created. Bypass this step in environments where | |||
|
2908 | # sys._getframe is not defined (Jython for example) or sys._getframe is not | |||
|
2909 | # defined for arguments greater than 0 (IronPython). | |||
|
2910 | try: | |||
|
2911 | type_.__module__ = sys._getframe(1).f_globals.get( | |||
|
2912 | "__name__", "__main__" | |||
|
2913 | ) | |||
|
2914 | except (AttributeError, ValueError): | |||
|
2915 | pass | |||
|
2916 | ||||
|
2917 | # We do it here for proper warnings with meaningful stacklevel. | |||
|
2918 | cmp = attributes_arguments.pop("cmp", None) | |||
|
2919 | ( | |||
|
2920 | attributes_arguments["eq"], | |||
|
2921 | attributes_arguments["order"], | |||
|
2922 | ) = _determine_attrs_eq_order( | |||
|
2923 | cmp, | |||
|
2924 | attributes_arguments.get("eq"), | |||
|
2925 | attributes_arguments.get("order"), | |||
|
2926 | True, | |||
|
2927 | ) | |||
|
2928 | ||||
|
2929 | return _attrs(these=cls_dict, **attributes_arguments)(type_) | |||
|
2930 | ||||
|
2931 | ||||
|
2932 | # These are required by within this module so we define them here and merely | |||
|
2933 | # import into .validators / .converters. | |||
|
2934 | ||||
|
2935 | ||||
|
2936 | @attrs(slots=True, hash=True) | |||
|
2937 | class _AndValidator: | |||
1034 | """ |
|
2938 | """ | |
1035 | Compose many validators to a single one. |
|
2939 | Compose many validators to a single one. | |
1036 | """ |
|
2940 | """ | |
1037 | _validators = attr() |
|
2941 | ||
|
2942 | _validators = attrib() | |||
1038 |
|
2943 | |||
1039 | def __call__(self, inst, attr, value): |
|
2944 | def __call__(self, inst, attr, value): | |
1040 | for v in self._validators: |
|
2945 | for v in self._validators: | |
@@ -1047,16 +2952,55 b' def and_(*validators):' | |||||
1047 |
|
2952 | |||
1048 | When called on a value, it runs all wrapped validators. |
|
2953 | When called on a value, it runs all wrapped validators. | |
1049 |
|
2954 | |||
1050 | :param validators: Arbitrary number of validators. |
|
2955 | :param callables validators: Arbitrary number of validators. | |
1051 | :type validators: callables |
|
|||
1052 |
|
2956 | |||
1053 | .. versionadded:: 17.1.0 |
|
2957 | .. versionadded:: 17.1.0 | |
1054 | """ |
|
2958 | """ | |
1055 | vals = [] |
|
2959 | vals = [] | |
1056 | for validator in validators: |
|
2960 | for validator in validators: | |
1057 | vals.extend( |
|
2961 | vals.extend( | |
1058 |
validator._validators |
|
2962 | validator._validators | |
|
2963 | if isinstance(validator, _AndValidator) | |||
1059 | else [validator] |
|
2964 | else [validator] | |
1060 | ) |
|
2965 | ) | |
1061 |
|
2966 | |||
1062 | return _AndValidator(tuple(vals)) |
|
2967 | return _AndValidator(tuple(vals)) | |
|
2968 | ||||
|
2969 | ||||
|
2970 | def pipe(*converters): | |||
|
2971 | """ | |||
|
2972 | A converter that composes multiple converters into one. | |||
|
2973 | ||||
|
2974 | When called on a value, it runs all wrapped converters, returning the | |||
|
2975 | *last* value. | |||
|
2976 | ||||
|
2977 | Type annotations will be inferred from the wrapped converters', if | |||
|
2978 | they have any. | |||
|
2979 | ||||
|
2980 | :param callables converters: Arbitrary number of converters. | |||
|
2981 | ||||
|
2982 | .. versionadded:: 20.1.0 | |||
|
2983 | """ | |||
|
2984 | ||||
|
2985 | def pipe_converter(val): | |||
|
2986 | for converter in converters: | |||
|
2987 | val = converter(val) | |||
|
2988 | ||||
|
2989 | return val | |||
|
2990 | ||||
|
2991 | if not converters: | |||
|
2992 | # If the converter list is empty, pipe_converter is the identity. | |||
|
2993 | A = typing.TypeVar("A") | |||
|
2994 | pipe_converter.__annotations__ = {"val": A, "return": A} | |||
|
2995 | else: | |||
|
2996 | # Get parameter type from first converter. | |||
|
2997 | t = _AnnotationExtractor(converters[0]).get_first_param_type() | |||
|
2998 | if t: | |||
|
2999 | pipe_converter.__annotations__["val"] = t | |||
|
3000 | ||||
|
3001 | # Get return type from last converter. | |||
|
3002 | rt = _AnnotationExtractor(converters[-1]).get_return_type() | |||
|
3003 | if rt: | |||
|
3004 | pipe_converter.__annotations__["return"] = rt | |||
|
3005 | ||||
|
3006 | return pipe_converter |
@@ -1,8 +1,22 b'' | |||||
|
1 | # SPDX-License-Identifier: MIT | |||
|
2 | ||||
1 | """ |
|
3 | """ | |
2 | Commonly useful converters. |
|
4 | Commonly useful converters. | |
3 | """ |
|
5 | """ | |
4 |
|
6 | |||
5 | from __future__ import absolute_import, division, print_function |
|
7 | ||
|
8 | import typing | |||
|
9 | ||||
|
10 | from ._compat import _AnnotationExtractor | |||
|
11 | from ._make import NOTHING, Factory, pipe | |||
|
12 | ||||
|
13 | ||||
|
14 | __all__ = [ | |||
|
15 | "default_if_none", | |||
|
16 | "optional", | |||
|
17 | "pipe", | |||
|
18 | "to_bool", | |||
|
19 | ] | |||
6 |
|
20 | |||
7 |
|
21 | |||
8 | def optional(converter): |
|
22 | def optional(converter): | |
@@ -10,10 +24,13 b' def optional(converter):' | |||||
10 | A converter that allows an attribute to be optional. An optional attribute |
|
24 | A converter that allows an attribute to be optional. An optional attribute | |
11 | is one which can be set to ``None``. |
|
25 | is one which can be set to ``None``. | |
12 |
|
26 | |||
|
27 | Type annotations will be inferred from the wrapped converter's, if it | |||
|
28 | has any. | |||
|
29 | ||||
13 | :param callable converter: the converter that is used for non-``None`` |
|
30 | :param callable converter: the converter that is used for non-``None`` | |
14 | values. |
|
31 | values. | |
15 |
|
32 | |||
16 |
.. |
|
33 | .. versionadded:: 17.1.0 | |
17 | """ |
|
34 | """ | |
18 |
|
35 | |||
19 | def optional_converter(val): |
|
36 | def optional_converter(val): | |
@@ -21,4 +38,107 b' def optional(converter):' | |||||
21 | return None |
|
38 | return None | |
22 | return converter(val) |
|
39 | return converter(val) | |
23 |
|
40 | |||
|
41 | xtr = _AnnotationExtractor(converter) | |||
|
42 | ||||
|
43 | t = xtr.get_first_param_type() | |||
|
44 | if t: | |||
|
45 | optional_converter.__annotations__["val"] = typing.Optional[t] | |||
|
46 | ||||
|
47 | rt = xtr.get_return_type() | |||
|
48 | if rt: | |||
|
49 | optional_converter.__annotations__["return"] = typing.Optional[rt] | |||
|
50 | ||||
24 | return optional_converter |
|
51 | return optional_converter | |
|
52 | ||||
|
53 | ||||
|
54 | def default_if_none(default=NOTHING, factory=None): | |||
|
55 | """ | |||
|
56 | A converter that allows to replace ``None`` values by *default* or the | |||
|
57 | result of *factory*. | |||
|
58 | ||||
|
59 | :param default: Value to be used if ``None`` is passed. Passing an instance | |||
|
60 | of `attrs.Factory` is supported, however the ``takes_self`` option | |||
|
61 | is *not*. | |||
|
62 | :param callable factory: A callable that takes no parameters whose result | |||
|
63 | is used if ``None`` is passed. | |||
|
64 | ||||
|
65 | :raises TypeError: If **neither** *default* or *factory* is passed. | |||
|
66 | :raises TypeError: If **both** *default* and *factory* are passed. | |||
|
67 | :raises ValueError: If an instance of `attrs.Factory` is passed with | |||
|
68 | ``takes_self=True``. | |||
|
69 | ||||
|
70 | .. versionadded:: 18.2.0 | |||
|
71 | """ | |||
|
72 | if default is NOTHING and factory is None: | |||
|
73 | raise TypeError("Must pass either `default` or `factory`.") | |||
|
74 | ||||
|
75 | if default is not NOTHING and factory is not None: | |||
|
76 | raise TypeError( | |||
|
77 | "Must pass either `default` or `factory` but not both." | |||
|
78 | ) | |||
|
79 | ||||
|
80 | if factory is not None: | |||
|
81 | default = Factory(factory) | |||
|
82 | ||||
|
83 | if isinstance(default, Factory): | |||
|
84 | if default.takes_self: | |||
|
85 | raise ValueError( | |||
|
86 | "`takes_self` is not supported by default_if_none." | |||
|
87 | ) | |||
|
88 | ||||
|
89 | def default_if_none_converter(val): | |||
|
90 | if val is not None: | |||
|
91 | return val | |||
|
92 | ||||
|
93 | return default.factory() | |||
|
94 | ||||
|
95 | else: | |||
|
96 | ||||
|
97 | def default_if_none_converter(val): | |||
|
98 | if val is not None: | |||
|
99 | return val | |||
|
100 | ||||
|
101 | return default | |||
|
102 | ||||
|
103 | return default_if_none_converter | |||
|
104 | ||||
|
105 | ||||
|
106 | def to_bool(val): | |||
|
107 | """ | |||
|
108 | Convert "boolean" strings (e.g., from env. vars.) to real booleans. | |||
|
109 | ||||
|
110 | Values mapping to :code:`True`: | |||
|
111 | ||||
|
112 | - :code:`True` | |||
|
113 | - :code:`"true"` / :code:`"t"` | |||
|
114 | - :code:`"yes"` / :code:`"y"` | |||
|
115 | - :code:`"on"` | |||
|
116 | - :code:`"1"` | |||
|
117 | - :code:`1` | |||
|
118 | ||||
|
119 | Values mapping to :code:`False`: | |||
|
120 | ||||
|
121 | - :code:`False` | |||
|
122 | - :code:`"false"` / :code:`"f"` | |||
|
123 | - :code:`"no"` / :code:`"n"` | |||
|
124 | - :code:`"off"` | |||
|
125 | - :code:`"0"` | |||
|
126 | - :code:`0` | |||
|
127 | ||||
|
128 | :raises ValueError: for any other value. | |||
|
129 | ||||
|
130 | .. versionadded:: 21.3.0 | |||
|
131 | """ | |||
|
132 | if isinstance(val, str): | |||
|
133 | val = val.lower() | |||
|
134 | truthy = {True, "true", "t", "yes", "y", "on", "1", 1} | |||
|
135 | falsy = {False, "false", "f", "no", "n", "off", "0", 0} | |||
|
136 | try: | |||
|
137 | if val in truthy: | |||
|
138 | return True | |||
|
139 | if val in falsy: | |||
|
140 | return False | |||
|
141 | except TypeError: | |||
|
142 | # Raised when "val" is not hashable (e.g., lists) | |||
|
143 | pass | |||
|
144 | raise ValueError("Cannot convert value to bool: {}".format(val)) |
@@ -1,17 +1,35 b'' | |||||
1 | from __future__ import absolute_import, division, print_function |
|
1 | # SPDX-License-Identifier: MIT | |
2 |
|
2 | |||
3 |
|
3 | |||
4 |
class Frozen |
|
4 | class FrozenError(AttributeError): | |
5 | """ |
|
5 | """ | |
6 |
A frozen/immutable instance |
|
6 | A frozen/immutable instance or attribute have been attempted to be | |
|
7 | modified. | |||
7 |
|
8 | |||
8 | It mirrors the behavior of ``namedtuples`` by using the same error message |
|
9 | It mirrors the behavior of ``namedtuples`` by using the same error message | |
9 |
and subclassing |
|
10 | and subclassing `AttributeError`. | |
|
11 | ||||
|
12 | .. versionadded:: 20.1.0 | |||
|
13 | """ | |||
|
14 | ||||
|
15 | msg = "can't set attribute" | |||
|
16 | args = [msg] | |||
|
17 | ||||
|
18 | ||||
|
19 | class FrozenInstanceError(FrozenError): | |||
|
20 | """ | |||
|
21 | A frozen instance has been attempted to be modified. | |||
10 |
|
22 | |||
11 | .. versionadded:: 16.1.0 |
|
23 | .. versionadded:: 16.1.0 | |
12 | """ |
|
24 | """ | |
13 | msg = "can't set attribute" |
|
25 | ||
14 | args = [msg] |
|
26 | ||
|
27 | class FrozenAttributeError(FrozenError): | |||
|
28 | """ | |||
|
29 | A frozen attribute has been attempted to be modified. | |||
|
30 | ||||
|
31 | .. versionadded:: 20.1.0 | |||
|
32 | """ | |||
15 |
|
33 | |||
16 |
|
34 | |||
17 | class AttrsAttributeNotFoundError(ValueError): |
|
35 | class AttrsAttributeNotFoundError(ValueError): | |
@@ -37,3 +55,38 b' class DefaultAlreadySetError(RuntimeErro' | |||||
37 |
|
55 | |||
38 | .. versionadded:: 17.1.0 |
|
56 | .. versionadded:: 17.1.0 | |
39 | """ |
|
57 | """ | |
|
58 | ||||
|
59 | ||||
|
60 | class UnannotatedAttributeError(RuntimeError): | |||
|
61 | """ | |||
|
62 | A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type | |||
|
63 | annotation. | |||
|
64 | ||||
|
65 | .. versionadded:: 17.3.0 | |||
|
66 | """ | |||
|
67 | ||||
|
68 | ||||
|
69 | class PythonTooOldError(RuntimeError): | |||
|
70 | """ | |||
|
71 | It was attempted to use an ``attrs`` feature that requires a newer Python | |||
|
72 | version. | |||
|
73 | ||||
|
74 | .. versionadded:: 18.2.0 | |||
|
75 | """ | |||
|
76 | ||||
|
77 | ||||
|
78 | class NotCallableError(TypeError): | |||
|
79 | """ | |||
|
80 | A ``attr.ib()`` requiring a callable has been set with a value | |||
|
81 | that is not callable. | |||
|
82 | ||||
|
83 | .. versionadded:: 19.2.0 | |||
|
84 | """ | |||
|
85 | ||||
|
86 | def __init__(self, msg, value): | |||
|
87 | super(TypeError, self).__init__(msg, value) | |||
|
88 | self.msg = msg | |||
|
89 | self.value = value | |||
|
90 | ||||
|
91 | def __str__(self): | |||
|
92 | return str(self.msg) |
@@ -1,10 +1,9 b'' | |||||
|
1 | # SPDX-License-Identifier: MIT | |||
|
2 | ||||
1 | """ |
|
3 | """ | |
2 |
Commonly useful filters for |
|
4 | Commonly useful filters for `attr.asdict`. | |
3 | """ |
|
5 | """ | |
4 |
|
6 | |||
5 | from __future__ import absolute_import, division, print_function |
|
|||
6 |
|
||||
7 | from ._compat import isclass |
|
|||
8 | from ._make import Attribute |
|
7 | from ._make import Attribute | |
9 |
|
8 | |||
10 |
|
9 | |||
@@ -13,19 +12,19 b' def _split_what(what):' | |||||
13 | Returns a tuple of `frozenset`s of classes and attributes. |
|
12 | Returns a tuple of `frozenset`s of classes and attributes. | |
14 | """ |
|
13 | """ | |
15 | return ( |
|
14 | return ( | |
16 |
frozenset(cls for cls in what if is |
|
15 | frozenset(cls for cls in what if isinstance(cls, type)), | |
17 | frozenset(cls for cls in what if isinstance(cls, Attribute)), |
|
16 | frozenset(cls for cls in what if isinstance(cls, Attribute)), | |
18 | ) |
|
17 | ) | |
19 |
|
18 | |||
20 |
|
19 | |||
21 | def include(*what): |
|
20 | def include(*what): | |
22 |
|
|
21 | """ | |
23 |
|
|
22 | Include *what*. | |
24 |
|
23 | |||
25 |
:param what: What to |
|
24 | :param what: What to include. | |
26 |
:type what: |
|
25 | :type what: `list` of `type` or `attrs.Attribute`\\ s | |
27 |
|
26 | |||
28 |
:rtype: |
|
27 | :rtype: `callable` | |
29 | """ |
|
28 | """ | |
30 | cls, attrs = _split_what(what) |
|
29 | cls, attrs = _split_what(what) | |
31 |
|
30 | |||
@@ -36,13 +35,13 b' def include(*what):' | |||||
36 |
|
35 | |||
37 |
|
36 | |||
38 | def exclude(*what): |
|
37 | def exclude(*what): | |
39 |
|
|
38 | """ | |
40 |
|
|
39 | Exclude *what*. | |
41 |
|
40 | |||
42 |
:param what: What to |
|
41 | :param what: What to exclude. | |
43 |
:type what: |
|
42 | :type what: `list` of classes or `attrs.Attribute`\\ s. | |
44 |
|
43 | |||
45 |
:rtype: |
|
44 | :rtype: `callable` | |
46 | """ |
|
45 | """ | |
47 | cls, attrs = _split_what(what) |
|
46 | cls, attrs = _split_what(what) | |
48 |
|
47 |
This diff has been collapsed as it changes many lines, (522 lines changed) Show them Hide them | |||||
@@ -1,24 +1,99 b'' | |||||
|
1 | # SPDX-License-Identifier: MIT | |||
|
2 | ||||
1 | """ |
|
3 | """ | |
2 | Commonly useful validators. |
|
4 | Commonly useful validators. | |
3 | """ |
|
5 | """ | |
4 |
|
6 | |||
5 | from __future__ import absolute_import, division, print_function |
|
7 | ||
|
8 | import operator | |||
|
9 | import re | |||
|
10 | ||||
|
11 | from contextlib import contextmanager | |||
6 |
|
12 | |||
7 | from ._make import attr, attributes, and_, _AndValidator |
|
13 | from ._config import get_run_validators, set_run_validators | |
|
14 | from ._make import _AndValidator, and_, attrib, attrs | |||
|
15 | from .exceptions import NotCallableError | |||
|
16 | ||||
|
17 | ||||
|
18 | try: | |||
|
19 | Pattern = re.Pattern | |||
|
20 | except AttributeError: # Python <3.7 lacks a Pattern type. | |||
|
21 | Pattern = type(re.compile("")) | |||
8 |
|
22 | |||
9 |
|
23 | |||
10 | __all__ = [ |
|
24 | __all__ = [ | |
11 | "and_", |
|
25 | "and_", | |
|
26 | "deep_iterable", | |||
|
27 | "deep_mapping", | |||
|
28 | "disabled", | |||
|
29 | "ge", | |||
|
30 | "get_disabled", | |||
|
31 | "gt", | |||
12 | "in_", |
|
32 | "in_", | |
13 | "instance_of", |
|
33 | "instance_of", | |
|
34 | "is_callable", | |||
|
35 | "le", | |||
|
36 | "lt", | |||
|
37 | "matches_re", | |||
|
38 | "max_len", | |||
|
39 | "min_len", | |||
14 | "optional", |
|
40 | "optional", | |
15 | "provides", |
|
41 | "provides", | |
|
42 | "set_disabled", | |||
16 | ] |
|
43 | ] | |
17 |
|
44 | |||
18 |
|
45 | |||
19 | @attributes(repr=False, slots=True, hash=True) |
|
46 | def set_disabled(disabled): | |
20 | class _InstanceOfValidator(object): |
|
47 | """ | |
21 | type = attr() |
|
48 | Globally disable or enable running validators. | |
|
49 | ||||
|
50 | By default, they are run. | |||
|
51 | ||||
|
52 | :param disabled: If ``True``, disable running all validators. | |||
|
53 | :type disabled: bool | |||
|
54 | ||||
|
55 | .. warning:: | |||
|
56 | ||||
|
57 | This function is not thread-safe! | |||
|
58 | ||||
|
59 | .. versionadded:: 21.3.0 | |||
|
60 | """ | |||
|
61 | set_run_validators(not disabled) | |||
|
62 | ||||
|
63 | ||||
|
64 | def get_disabled(): | |||
|
65 | """ | |||
|
66 | Return a bool indicating whether validators are currently disabled or not. | |||
|
67 | ||||
|
68 | :return: ``True`` if validators are currently disabled. | |||
|
69 | :rtype: bool | |||
|
70 | ||||
|
71 | .. versionadded:: 21.3.0 | |||
|
72 | """ | |||
|
73 | return not get_run_validators() | |||
|
74 | ||||
|
75 | ||||
|
76 | @contextmanager | |||
|
77 | def disabled(): | |||
|
78 | """ | |||
|
79 | Context manager that disables running validators within its context. | |||
|
80 | ||||
|
81 | .. warning:: | |||
|
82 | ||||
|
83 | This context manager is not thread-safe! | |||
|
84 | ||||
|
85 | .. versionadded:: 21.3.0 | |||
|
86 | """ | |||
|
87 | set_run_validators(False) | |||
|
88 | try: | |||
|
89 | yield | |||
|
90 | finally: | |||
|
91 | set_run_validators(True) | |||
|
92 | ||||
|
93 | ||||
|
94 | @attrs(repr=False, slots=True, hash=True) | |||
|
95 | class _InstanceOfValidator: | |||
|
96 | type = attrib() | |||
22 |
|
97 | |||
23 | def __call__(self, inst, attr, value): |
|
98 | def __call__(self, inst, attr, value): | |
24 | """ |
|
99 | """ | |
@@ -27,38 +102,116 b' class _InstanceOfValidator(object):' | |||||
27 | if not isinstance(value, self.type): |
|
102 | if not isinstance(value, self.type): | |
28 | raise TypeError( |
|
103 | raise TypeError( | |
29 | "'{name}' must be {type!r} (got {value!r} that is a " |
|
104 | "'{name}' must be {type!r} (got {value!r} that is a " | |
30 | "{actual!r})." |
|
105 | "{actual!r}).".format( | |
31 |
|
|
106 | name=attr.name, | |
32 | actual=value.__class__, value=value), |
|
107 | type=self.type, | |
33 |
|
|
108 | actual=value.__class__, | |
|
109 | value=value, | |||
|
110 | ), | |||
|
111 | attr, | |||
|
112 | self.type, | |||
|
113 | value, | |||
34 | ) |
|
114 | ) | |
35 |
|
115 | |||
36 | def __repr__(self): |
|
116 | def __repr__(self): | |
37 | return ( |
|
117 | return "<instance_of validator for type {type!r}>".format( | |
38 | "<instance_of validator for type {type!r}>" |
|
118 | type=self.type | |
39 | .format(type=self.type) |
|
|||
40 | ) |
|
119 | ) | |
41 |
|
120 | |||
42 |
|
121 | |||
43 | def instance_of(type): |
|
122 | def instance_of(type): | |
44 | """ |
|
123 | """ | |
45 |
A validator that raises a |
|
124 | A validator that raises a `TypeError` if the initializer is called | |
46 | with a wrong type for this particular attribute (checks are perfomed using |
|
125 | with a wrong type for this particular attribute (checks are performed using | |
47 |
|
|
126 | `isinstance` therefore it's also valid to pass a tuple of types). | |
48 |
|
127 | |||
49 | :param type: The type to check for. |
|
128 | :param type: The type to check for. | |
50 | :type type: type or tuple of types |
|
129 | :type type: type or tuple of types | |
51 |
|
130 | |||
52 | :raises TypeError: With a human readable error message, the attribute |
|
131 | :raises TypeError: With a human readable error message, the attribute | |
53 |
(of type |
|
132 | (of type `attrs.Attribute`), the expected type, and the value it | |
54 | got. |
|
133 | got. | |
55 | """ |
|
134 | """ | |
56 | return _InstanceOfValidator(type) |
|
135 | return _InstanceOfValidator(type) | |
57 |
|
136 | |||
58 |
|
137 | |||
59 |
@attr |
|
138 | @attrs(repr=False, frozen=True, slots=True) | |
60 |
class _ |
|
139 | class _MatchesReValidator: | |
61 |
|
|
140 | pattern = attrib() | |
|
141 | match_func = attrib() | |||
|
142 | ||||
|
143 | def __call__(self, inst, attr, value): | |||
|
144 | """ | |||
|
145 | We use a callable class to be able to change the ``__repr__``. | |||
|
146 | """ | |||
|
147 | if not self.match_func(value): | |||
|
148 | raise ValueError( | |||
|
149 | "'{name}' must match regex {pattern!r}" | |||
|
150 | " ({value!r} doesn't)".format( | |||
|
151 | name=attr.name, pattern=self.pattern.pattern, value=value | |||
|
152 | ), | |||
|
153 | attr, | |||
|
154 | self.pattern, | |||
|
155 | value, | |||
|
156 | ) | |||
|
157 | ||||
|
158 | def __repr__(self): | |||
|
159 | return "<matches_re validator for pattern {pattern!r}>".format( | |||
|
160 | pattern=self.pattern | |||
|
161 | ) | |||
|
162 | ||||
|
163 | ||||
|
164 | def matches_re(regex, flags=0, func=None): | |||
|
165 | r""" | |||
|
166 | A validator that raises `ValueError` if the initializer is called | |||
|
167 | with a string that doesn't match *regex*. | |||
|
168 | ||||
|
169 | :param regex: a regex string or precompiled pattern to match against | |||
|
170 | :param int flags: flags that will be passed to the underlying re function | |||
|
171 | (default 0) | |||
|
172 | :param callable func: which underlying `re` function to call. Valid options | |||
|
173 | are `re.fullmatch`, `re.search`, and `re.match`; the default ``None`` | |||
|
174 | means `re.fullmatch`. For performance reasons, the pattern is always | |||
|
175 | precompiled using `re.compile`. | |||
|
176 | ||||
|
177 | .. versionadded:: 19.2.0 | |||
|
178 | .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. | |||
|
179 | """ | |||
|
180 | valid_funcs = (re.fullmatch, None, re.search, re.match) | |||
|
181 | if func not in valid_funcs: | |||
|
182 | raise ValueError( | |||
|
183 | "'func' must be one of {}.".format( | |||
|
184 | ", ".join( | |||
|
185 | sorted( | |||
|
186 | e and e.__name__ or "None" for e in set(valid_funcs) | |||
|
187 | ) | |||
|
188 | ) | |||
|
189 | ) | |||
|
190 | ) | |||
|
191 | ||||
|
192 | if isinstance(regex, Pattern): | |||
|
193 | if flags: | |||
|
194 | raise TypeError( | |||
|
195 | "'flags' can only be used with a string pattern; " | |||
|
196 | "pass flags to re.compile() instead" | |||
|
197 | ) | |||
|
198 | pattern = regex | |||
|
199 | else: | |||
|
200 | pattern = re.compile(regex, flags) | |||
|
201 | ||||
|
202 | if func is re.match: | |||
|
203 | match_func = pattern.match | |||
|
204 | elif func is re.search: | |||
|
205 | match_func = pattern.search | |||
|
206 | else: | |||
|
207 | match_func = pattern.fullmatch | |||
|
208 | ||||
|
209 | return _MatchesReValidator(pattern, match_func) | |||
|
210 | ||||
|
211 | ||||
|
212 | @attrs(repr=False, slots=True, hash=True) | |||
|
213 | class _ProvidesValidator: | |||
|
214 | interface = attrib() | |||
62 |
|
215 | |||
63 | def __call__(self, inst, attr, value): |
|
216 | def __call__(self, inst, attr, value): | |
64 | """ |
|
217 | """ | |
@@ -67,37 +220,40 b' class _ProvidesValidator(object):' | |||||
67 | if not self.interface.providedBy(value): |
|
220 | if not self.interface.providedBy(value): | |
68 | raise TypeError( |
|
221 | raise TypeError( | |
69 | "'{name}' must provide {interface!r} which {value!r} " |
|
222 | "'{name}' must provide {interface!r} which {value!r} " | |
70 | "doesn't." |
|
223 | "doesn't.".format( | |
71 |
|
|
224 | name=attr.name, interface=self.interface, value=value | |
72 | attr, self.interface, value, |
|
225 | ), | |
|
226 | attr, | |||
|
227 | self.interface, | |||
|
228 | value, | |||
73 | ) |
|
229 | ) | |
74 |
|
230 | |||
75 | def __repr__(self): |
|
231 | def __repr__(self): | |
76 | return ( |
|
232 | return "<provides validator for interface {interface!r}>".format( | |
77 |
|
|
233 | interface=self.interface | |
78 | .format(interface=self.interface) |
|
|||
79 | ) |
|
234 | ) | |
80 |
|
235 | |||
81 |
|
236 | |||
82 | def provides(interface): |
|
237 | def provides(interface): | |
83 | """ |
|
238 | """ | |
84 |
A validator that raises a |
|
239 | A validator that raises a `TypeError` if the initializer is called | |
85 | with an object that does not provide the requested *interface* (checks are |
|
240 | with an object that does not provide the requested *interface* (checks are | |
86 | performed using ``interface.providedBy(value)`` (see `zope.interface |
|
241 | performed using ``interface.providedBy(value)`` (see `zope.interface | |
87 | <https://zopeinterface.readthedocs.io/en/latest/>`_). |
|
242 | <https://zopeinterface.readthedocs.io/en/latest/>`_). | |
88 |
|
243 | |||
89 |
:param |
|
244 | :param interface: The interface to check for. | |
|
245 | :type interface: ``zope.interface.Interface`` | |||
90 |
|
246 | |||
91 | :raises TypeError: With a human readable error message, the attribute |
|
247 | :raises TypeError: With a human readable error message, the attribute | |
92 |
(of type |
|
248 | (of type `attrs.Attribute`), the expected interface, and the | |
93 | value it got. |
|
249 | value it got. | |
94 | """ |
|
250 | """ | |
95 | return _ProvidesValidator(interface) |
|
251 | return _ProvidesValidator(interface) | |
96 |
|
252 | |||
97 |
|
253 | |||
98 |
@attr |
|
254 | @attrs(repr=False, slots=True, hash=True) | |
99 |
class _OptionalValidator |
|
255 | class _OptionalValidator: | |
100 | validator = attr() |
|
256 | validator = attrib() | |
101 |
|
257 | |||
102 | def __call__(self, inst, attr, value): |
|
258 | def __call__(self, inst, attr, value): | |
103 | if value is None: |
|
259 | if value is None: | |
@@ -106,9 +262,8 b' class _OptionalValidator(object):' | |||||
106 | self.validator(inst, attr, value) |
|
262 | self.validator(inst, attr, value) | |
107 |
|
263 | |||
108 | def __repr__(self): |
|
264 | def __repr__(self): | |
109 | return ( |
|
265 | return "<optional validator for {what} or None>".format( | |
110 | "<optional validator for {what} or None>" |
|
266 | what=repr(self.validator) | |
111 | .format(what=repr(self.validator)) |
|
|||
112 | ) |
|
267 | ) | |
113 |
|
268 | |||
114 |
|
269 | |||
@@ -120,7 +275,7 b' def optional(validator):' | |||||
120 |
|
275 | |||
121 | :param validator: A validator (or a list of validators) that is used for |
|
276 | :param validator: A validator (or a list of validators) that is used for | |
122 | non-``None`` values. |
|
277 | non-``None`` values. | |
123 |
:type validator: callable or |
|
278 | :type validator: callable or `list` of callables. | |
124 |
|
279 | |||
125 | .. versionadded:: 15.1.0 |
|
280 | .. versionadded:: 15.1.0 | |
126 | .. versionchanged:: 17.1.0 *validator* can be a list of validators. |
|
281 | .. versionchanged:: 17.1.0 *validator* can be a list of validators. | |
@@ -130,37 +285,310 b' def optional(validator):' | |||||
130 | return _OptionalValidator(validator) |
|
285 | return _OptionalValidator(validator) | |
131 |
|
286 | |||
132 |
|
287 | |||
133 |
@attr |
|
288 | @attrs(repr=False, slots=True, hash=True) | |
134 |
class _InValidator |
|
289 | class _InValidator: | |
135 | options = attr() |
|
290 | options = attrib() | |
136 |
|
291 | |||
137 | def __call__(self, inst, attr, value): |
|
292 | def __call__(self, inst, attr, value): | |
138 | if value not in self.options: |
|
293 | try: | |
|
294 | in_options = value in self.options | |||
|
295 | except TypeError: # e.g. `1 in "abc"` | |||
|
296 | in_options = False | |||
|
297 | ||||
|
298 | if not in_options: | |||
139 | raise ValueError( |
|
299 | raise ValueError( | |
140 | "'{name}' must be in {options!r} (got {value!r})" |
|
300 | "'{name}' must be in {options!r} (got {value!r})".format( | |
141 |
|
|
301 | name=attr.name, options=self.options, value=value | |
|
302 | ), | |||
|
303 | attr, | |||
|
304 | self.options, | |||
|
305 | value, | |||
142 | ) |
|
306 | ) | |
143 |
|
307 | |||
144 | def __repr__(self): |
|
308 | def __repr__(self): | |
145 | return ( |
|
309 | return "<in_ validator with options {options!r}>".format( | |
146 |
|
|
310 | options=self.options | |
147 | .format(options=self.options) |
|
|||
148 | ) |
|
311 | ) | |
149 |
|
312 | |||
150 |
|
313 | |||
151 | def in_(options): |
|
314 | def in_(options): | |
152 | """ |
|
315 | """ | |
153 |
A validator that raises a |
|
316 | A validator that raises a `ValueError` if the initializer is called | |
154 | with a value that does not belong in the options provided. The check is |
|
317 | with a value that does not belong in the options provided. The check is | |
155 | performed using ``value in options``. |
|
318 | performed using ``value in options``. | |
156 |
|
319 | |||
157 | :param options: Allowed options. |
|
320 | :param options: Allowed options. | |
158 |
:type options: list, tuple, |
|
321 | :type options: list, tuple, `enum.Enum`, ... | |
159 |
|
322 | |||
160 | :raises ValueError: With a human readable error message, the attribute (of |
|
323 | :raises ValueError: With a human readable error message, the attribute (of | |
161 |
type |
|
324 | type `attrs.Attribute`), the expected options, and the value it | |
162 | got. |
|
325 | got. | |
163 |
|
326 | |||
164 | .. versionadded:: 17.1.0 |
|
327 | .. versionadded:: 17.1.0 | |
|
328 | .. versionchanged:: 22.1.0 | |||
|
329 | The ValueError was incomplete until now and only contained the human | |||
|
330 | readable error message. Now it contains all the information that has | |||
|
331 | been promised since 17.1.0. | |||
165 | """ |
|
332 | """ | |
166 | return _InValidator(options) |
|
333 | return _InValidator(options) | |
|
334 | ||||
|
335 | ||||
|
336 | @attrs(repr=False, slots=False, hash=True) | |||
|
337 | class _IsCallableValidator: | |||
|
338 | def __call__(self, inst, attr, value): | |||
|
339 | """ | |||
|
340 | We use a callable class to be able to change the ``__repr__``. | |||
|
341 | """ | |||
|
342 | if not callable(value): | |||
|
343 | message = ( | |||
|
344 | "'{name}' must be callable " | |||
|
345 | "(got {value!r} that is a {actual!r})." | |||
|
346 | ) | |||
|
347 | raise NotCallableError( | |||
|
348 | msg=message.format( | |||
|
349 | name=attr.name, value=value, actual=value.__class__ | |||
|
350 | ), | |||
|
351 | value=value, | |||
|
352 | ) | |||
|
353 | ||||
|
354 | def __repr__(self): | |||
|
355 | return "<is_callable validator>" | |||
|
356 | ||||
|
357 | ||||
|
358 | def is_callable(): | |||
|
359 | """ | |||
|
360 | A validator that raises a `attr.exceptions.NotCallableError` if the | |||
|
361 | initializer is called with a value for this particular attribute | |||
|
362 | that is not callable. | |||
|
363 | ||||
|
364 | .. versionadded:: 19.1.0 | |||
|
365 | ||||
|
366 | :raises `attr.exceptions.NotCallableError`: With a human readable error | |||
|
367 | message containing the attribute (`attrs.Attribute`) name, | |||
|
368 | and the value it got. | |||
|
369 | """ | |||
|
370 | return _IsCallableValidator() | |||
|
371 | ||||
|
372 | ||||
|
373 | @attrs(repr=False, slots=True, hash=True) | |||
|
374 | class _DeepIterable: | |||
|
375 | member_validator = attrib(validator=is_callable()) | |||
|
376 | iterable_validator = attrib( | |||
|
377 | default=None, validator=optional(is_callable()) | |||
|
378 | ) | |||
|
379 | ||||
|
380 | def __call__(self, inst, attr, value): | |||
|
381 | """ | |||
|
382 | We use a callable class to be able to change the ``__repr__``. | |||
|
383 | """ | |||
|
384 | if self.iterable_validator is not None: | |||
|
385 | self.iterable_validator(inst, attr, value) | |||
|
386 | ||||
|
387 | for member in value: | |||
|
388 | self.member_validator(inst, attr, member) | |||
|
389 | ||||
|
390 | def __repr__(self): | |||
|
391 | iterable_identifier = ( | |||
|
392 | "" | |||
|
393 | if self.iterable_validator is None | |||
|
394 | else " {iterable!r}".format(iterable=self.iterable_validator) | |||
|
395 | ) | |||
|
396 | return ( | |||
|
397 | "<deep_iterable validator for{iterable_identifier}" | |||
|
398 | " iterables of {member!r}>" | |||
|
399 | ).format( | |||
|
400 | iterable_identifier=iterable_identifier, | |||
|
401 | member=self.member_validator, | |||
|
402 | ) | |||
|
403 | ||||
|
404 | ||||
|
405 | def deep_iterable(member_validator, iterable_validator=None): | |||
|
406 | """ | |||
|
407 | A validator that performs deep validation of an iterable. | |||
|
408 | ||||
|
409 | :param member_validator: Validator(s) to apply to iterable members | |||
|
410 | :param iterable_validator: Validator to apply to iterable itself | |||
|
411 | (optional) | |||
|
412 | ||||
|
413 | .. versionadded:: 19.1.0 | |||
|
414 | ||||
|
415 | :raises TypeError: if any sub-validators fail | |||
|
416 | """ | |||
|
417 | if isinstance(member_validator, (list, tuple)): | |||
|
418 | member_validator = and_(*member_validator) | |||
|
419 | return _DeepIterable(member_validator, iterable_validator) | |||
|
420 | ||||
|
421 | ||||
|
422 | @attrs(repr=False, slots=True, hash=True) | |||
|
423 | class _DeepMapping: | |||
|
424 | key_validator = attrib(validator=is_callable()) | |||
|
425 | value_validator = attrib(validator=is_callable()) | |||
|
426 | mapping_validator = attrib(default=None, validator=optional(is_callable())) | |||
|
427 | ||||
|
428 | def __call__(self, inst, attr, value): | |||
|
429 | """ | |||
|
430 | We use a callable class to be able to change the ``__repr__``. | |||
|
431 | """ | |||
|
432 | if self.mapping_validator is not None: | |||
|
433 | self.mapping_validator(inst, attr, value) | |||
|
434 | ||||
|
435 | for key in value: | |||
|
436 | self.key_validator(inst, attr, key) | |||
|
437 | self.value_validator(inst, attr, value[key]) | |||
|
438 | ||||
|
439 | def __repr__(self): | |||
|
440 | return ( | |||
|
441 | "<deep_mapping validator for objects mapping {key!r} to {value!r}>" | |||
|
442 | ).format(key=self.key_validator, value=self.value_validator) | |||
|
443 | ||||
|
444 | ||||
|
445 | def deep_mapping(key_validator, value_validator, mapping_validator=None): | |||
|
446 | """ | |||
|
447 | A validator that performs deep validation of a dictionary. | |||
|
448 | ||||
|
449 | :param key_validator: Validator to apply to dictionary keys | |||
|
450 | :param value_validator: Validator to apply to dictionary values | |||
|
451 | :param mapping_validator: Validator to apply to top-level mapping | |||
|
452 | attribute (optional) | |||
|
453 | ||||
|
454 | .. versionadded:: 19.1.0 | |||
|
455 | ||||
|
456 | :raises TypeError: if any sub-validators fail | |||
|
457 | """ | |||
|
458 | return _DeepMapping(key_validator, value_validator, mapping_validator) | |||
|
459 | ||||
|
460 | ||||
|
461 | @attrs(repr=False, frozen=True, slots=True) | |||
|
462 | class _NumberValidator: | |||
|
463 | bound = attrib() | |||
|
464 | compare_op = attrib() | |||
|
465 | compare_func = attrib() | |||
|
466 | ||||
|
467 | def __call__(self, inst, attr, value): | |||
|
468 | """ | |||
|
469 | We use a callable class to be able to change the ``__repr__``. | |||
|
470 | """ | |||
|
471 | if not self.compare_func(value, self.bound): | |||
|
472 | raise ValueError( | |||
|
473 | "'{name}' must be {op} {bound}: {value}".format( | |||
|
474 | name=attr.name, | |||
|
475 | op=self.compare_op, | |||
|
476 | bound=self.bound, | |||
|
477 | value=value, | |||
|
478 | ) | |||
|
479 | ) | |||
|
480 | ||||
|
481 | def __repr__(self): | |||
|
482 | return "<Validator for x {op} {bound}>".format( | |||
|
483 | op=self.compare_op, bound=self.bound | |||
|
484 | ) | |||
|
485 | ||||
|
486 | ||||
|
487 | def lt(val): | |||
|
488 | """ | |||
|
489 | A validator that raises `ValueError` if the initializer is called | |||
|
490 | with a number larger or equal to *val*. | |||
|
491 | ||||
|
492 | :param val: Exclusive upper bound for values | |||
|
493 | ||||
|
494 | .. versionadded:: 21.3.0 | |||
|
495 | """ | |||
|
496 | return _NumberValidator(val, "<", operator.lt) | |||
|
497 | ||||
|
498 | ||||
|
499 | def le(val): | |||
|
500 | """ | |||
|
501 | A validator that raises `ValueError` if the initializer is called | |||
|
502 | with a number greater than *val*. | |||
|
503 | ||||
|
504 | :param val: Inclusive upper bound for values | |||
|
505 | ||||
|
506 | .. versionadded:: 21.3.0 | |||
|
507 | """ | |||
|
508 | return _NumberValidator(val, "<=", operator.le) | |||
|
509 | ||||
|
510 | ||||
|
511 | def ge(val): | |||
|
512 | """ | |||
|
513 | A validator that raises `ValueError` if the initializer is called | |||
|
514 | with a number smaller than *val*. | |||
|
515 | ||||
|
516 | :param val: Inclusive lower bound for values | |||
|
517 | ||||
|
518 | .. versionadded:: 21.3.0 | |||
|
519 | """ | |||
|
520 | return _NumberValidator(val, ">=", operator.ge) | |||
|
521 | ||||
|
522 | ||||
|
523 | def gt(val): | |||
|
524 | """ | |||
|
525 | A validator that raises `ValueError` if the initializer is called | |||
|
526 | with a number smaller or equal to *val*. | |||
|
527 | ||||
|
528 | :param val: Exclusive lower bound for values | |||
|
529 | ||||
|
530 | .. versionadded:: 21.3.0 | |||
|
531 | """ | |||
|
532 | return _NumberValidator(val, ">", operator.gt) | |||
|
533 | ||||
|
534 | ||||
|
535 | @attrs(repr=False, frozen=True, slots=True) | |||
|
536 | class _MaxLengthValidator: | |||
|
537 | max_length = attrib() | |||
|
538 | ||||
|
539 | def __call__(self, inst, attr, value): | |||
|
540 | """ | |||
|
541 | We use a callable class to be able to change the ``__repr__``. | |||
|
542 | """ | |||
|
543 | if len(value) > self.max_length: | |||
|
544 | raise ValueError( | |||
|
545 | "Length of '{name}' must be <= {max}: {len}".format( | |||
|
546 | name=attr.name, max=self.max_length, len=len(value) | |||
|
547 | ) | |||
|
548 | ) | |||
|
549 | ||||
|
550 | def __repr__(self): | |||
|
551 | return "<max_len validator for {max}>".format(max=self.max_length) | |||
|
552 | ||||
|
553 | ||||
|
554 | def max_len(length): | |||
|
555 | """ | |||
|
556 | A validator that raises `ValueError` if the initializer is called | |||
|
557 | with a string or iterable that is longer than *length*. | |||
|
558 | ||||
|
559 | :param int length: Maximum length of the string or iterable | |||
|
560 | ||||
|
561 | .. versionadded:: 21.3.0 | |||
|
562 | """ | |||
|
563 | return _MaxLengthValidator(length) | |||
|
564 | ||||
|
565 | ||||
|
566 | @attrs(repr=False, frozen=True, slots=True) | |||
|
567 | class _MinLengthValidator: | |||
|
568 | min_length = attrib() | |||
|
569 | ||||
|
570 | def __call__(self, inst, attr, value): | |||
|
571 | """ | |||
|
572 | We use a callable class to be able to change the ``__repr__``. | |||
|
573 | """ | |||
|
574 | if len(value) < self.min_length: | |||
|
575 | raise ValueError( | |||
|
576 | "Length of '{name}' must be => {min}: {len}".format( | |||
|
577 | name=attr.name, min=self.min_length, len=len(value) | |||
|
578 | ) | |||
|
579 | ) | |||
|
580 | ||||
|
581 | def __repr__(self): | |||
|
582 | return "<min_len validator for {min}>".format(min=self.min_length) | |||
|
583 | ||||
|
584 | ||||
|
585 | def min_len(length): | |||
|
586 | """ | |||
|
587 | A validator that raises `ValueError` if the initializer is called | |||
|
588 | with a string or iterable that is shorter than *length*. | |||
|
589 | ||||
|
590 | :param int length: Minimum length of the string or iterable | |||
|
591 | ||||
|
592 | .. versionadded:: 22.1.0 | |||
|
593 | """ | |||
|
594 | return _MinLengthValidator(length) |
General Comments 0
You need to be logged in to leave comments.
Login now