Skip to content

Commit 17f4680

Browse files
authored
Fix structuring Final lists (#413)
1 parent c8aeafc commit 17f4680

3 files changed

Lines changed: 51 additions & 48 deletions

File tree

HISTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
([#400](https://github.com/python-attrs/cattrs/pull/400))
3333
- {py:class}`AttributeValidationNote <cattrs.AttributeValidationNote>` and {py:class}`IterableValidationNote <cattrs.IterableValidationNote>` are now picklable.
3434
([#408](https://github.com/python-attrs/cattrs/pull/408))
35+
- Fix structuring `Final` lists.
36+
([#412](https://github.com/python-attrs/cattrs/issues/412))
3537

3638

3739
## 23.1.2 (2023-06-02)

src/cattrs/converters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ def _structure_final_factory(self, type):
469469
if res == self._structure_call:
470470
# It's not really `structure_call` for Finals (can't call Final())
471471
return lambda v, _: self._structure_call(v, base)
472-
return res
472+
return lambda v, _: res(v, base)
473473

474474
# Attrs classes.
475475

tests/typed.py

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from collections.abc import MutableSet as AbcMutableSet
66
from collections.abc import Sequence as AbcSequence
77
from collections.abc import Set as AbcSet
8-
from dataclasses import field, make_dataclass
8+
from dataclasses import field as dc_field
9+
from dataclasses import make_dataclass
910
from functools import partial
1011
from pathlib import Path
1112
from typing import (
@@ -24,9 +25,8 @@
2425
TypeVar,
2526
)
2627

27-
import attr
28-
from attr import NOTHING, Factory, frozen
2928
from attr._make import _CountingAttr
29+
from attrs import NOTHING, Factory, field, frozen
3030
from hypothesis import note
3131
from hypothesis.strategies import (
3232
DrawFn,
@@ -46,6 +46,7 @@
4646
text,
4747
tuples,
4848
)
49+
from typing_extensions import Final
4950

5051
from .untyped import gen_attr_names, make_class
5152

@@ -212,7 +213,7 @@ def _create_hyp_class(
212213
"""
213214

214215
def key(t):
215-
return (t[0]._default is not attr.NOTHING, t[0].kw_only)
216+
return (t[0]._default is not NOTHING, t[0].kw_only)
216217

217218
attrs_and_strat = sorted(attrs_and_strategy, key=key)
218219
attrs = [a[0] for a in attrs_and_strat]
@@ -261,7 +262,7 @@ def _create_dataclass(
261262
"""
262263

263264
def key(t):
264-
return t[0]._default is not attr.NOTHING
265+
return t[0]._default is not NOTHING
265266

266267
attrs_and_strat = sorted(attrs_and_strategy, key=key)
267268
attrs = [a[0] for a in attrs_and_strat]
@@ -276,9 +277,9 @@ def key(t):
276277
(n, a.type)
277278
if a._default is NOTHING
278279
else (
279-
(n, a.type, field(default=a._default))
280+
(n, a.type, dc_field(default=a._default))
280281
if not isinstance(a._default, Factory)
281-
else (n, a.type, field(default_factory=a._default.factory))
282+
else (n, a.type, dc_field(default_factory=a._default.factory))
282283
)
283284
for n, a in zip(gen_attr_names(), attrs)
284285
],
@@ -294,7 +295,7 @@ def _create_hyp_class_and_strat(
294295
attrs_and_strategy: List[Tuple[_CountingAttr, SearchStrategy[PosArg]]]
295296
) -> SearchStrategy[Tuple[Type, SearchStrategy[PosArgs], SearchStrategy[KwArgs]]]:
296297
def key(t):
297-
return (t[0].default is not attr.NOTHING, t[0].kw_only)
298+
return (t[0].default is not NOTHING, t[0].kw_only)
298299

299300
attrs_and_strat = sorted(attrs_and_strategy, key=key)
300301
attrs = [a[0] for a in attrs_and_strat]
@@ -320,11 +321,11 @@ def bare_typed_attrs(draw, defaults=None, kw_only=None):
320321
Generate a tuple of an attribute and a strategy that yields values
321322
appropriate for that attribute.
322323
"""
323-
default = attr.NOTHING
324+
default = NOTHING
324325
if defaults is True or (defaults is None and draw(booleans())):
325326
default = None
326327
return (
327-
attr.ib(
328+
field(
328329
type=Any,
329330
default=default,
330331
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -339,11 +340,11 @@ def int_typed_attrs(draw, defaults=None, kw_only=None):
339340
Generate a tuple of an attribute and a strategy that yields ints for that
340341
attribute.
341342
"""
342-
default = attr.NOTHING
343+
default = NOTHING
343344
if defaults is True or (defaults is None and draw(booleans())):
344345
default = draw(integers())
345346
return (
346-
attr.ib(
347+
field(
347348
type=int,
348349
default=default,
349350
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -362,7 +363,7 @@ def str_typed_attrs(draw, defaults=None, kw_only=None):
362363
if defaults is True or (defaults is None and draw(booleans())):
363364
default = draw(text())
364365
return (
365-
attr.ib(
366+
field(
366367
type=str,
367368
default=default,
368369
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -377,11 +378,11 @@ def float_typed_attrs(draw, defaults=None, kw_only=None):
377378
Generate a tuple of an attribute and a strategy that yields floats for that
378379
attribute.
379380
"""
380-
default = attr.NOTHING
381+
default = NOTHING
381382
if defaults is True or (defaults is None and draw(booleans())):
382383
default = draw(floats())
383384
return (
384-
attr.ib(
385+
field(
385386
type=float,
386387
default=default,
387388
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -400,11 +401,11 @@ def path_typed_attrs(
400401
"""
401402
from string import ascii_lowercase
402403

403-
default = attr.NOTHING
404+
default = NOTHING
404405
if defaults is True or (defaults is None and draw(booleans())):
405406
default = Path(draw(text(ascii_lowercase, min_size=1)))
406407
return (
407-
attr.ib(
408+
field(
408409
type=Path,
409410
default=default,
410411
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -422,7 +423,7 @@ def dict_typed_attrs(
422423
for that attribute. The dictionaries map strings to integers.
423424
The generated dict types are what's expected to be used on pre-3.9 Pythons.
424425
"""
425-
default = attr.NOTHING
426+
default = NOTHING
426427
val_strat = dictionaries(keys=text(), values=integers())
427428
if defaults is True or (defaults is None and draw(booleans())):
428429
default_val = draw(val_strat)
@@ -432,7 +433,7 @@ def dict_typed_attrs(
432433
default = default_val
433434
type = draw(sampled_from([Dict[str, int], Dict, dict]))
434435
return (
435-
attr.ib(
436+
field(
436437
type=type,
437438
default=default,
438439
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -451,7 +452,7 @@ def new_dict_typed_attrs(
451452
452453
Uses the new 3.9 dict annotation.
453454
"""
454-
default_val = attr.NOTHING
455+
default_val = NOTHING
455456
val_strat = dictionaries(keys=text(), values=integers())
456457
if defaults is True or (defaults is None and draw(booleans())):
457458
default_val = draw(val_strat)
@@ -463,7 +464,7 @@ def new_dict_typed_attrs(
463464
default = default_val
464465

465466
return (
466-
attr.ib(
467+
field(
467468
type=dict[str, int],
468469
default=default,
469470
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -484,7 +485,7 @@ def set_typed_attrs(
484485
Generate a tuple of an attribute and a strategy that yields sets
485486
for that attribute. The sets contain integers.
486487
"""
487-
default_val = attr.NOTHING
488+
default_val = NOTHING
488489
val_strat = sets(integers())
489490
if defaults is True or (defaults is None and draw(booleans())):
490491
default_val = draw(val_strat)
@@ -503,7 +504,7 @@ def set_typed_attrs(
503504
)
504505
)
505506
return (
506-
attr.ib(
507+
field(
507508
type=type,
508509
default=default,
509510
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -520,7 +521,7 @@ def frozenset_typed_attrs(
520521
Generate a tuple of an attribute and a strategy that yields frozensets
521522
for that attribute. The frozensets contain integers.
522523
"""
523-
default = attr.NOTHING
524+
default = NOTHING
524525
val_strat = frozensets(integers())
525526
if defaults is True or (defaults is None and draw(booleans())):
526527
default = draw(val_strat)
@@ -532,7 +533,7 @@ def frozenset_typed_attrs(
532533
)
533534
)
534535
return (
535-
attr.ib(
536+
field(
536537
type=type,
537538
default=default,
538539
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -548,12 +549,12 @@ def list_typed_attrs(
548549
allow_mutable_defaults=True,
549550
legacy_types_only=False,
550551
kw_only=None,
551-
):
552+
) -> Tuple[_CountingAttr, SearchStrategy[List[float]]]:
552553
"""
553554
Generate a tuple of an attribute and a strategy that yields lists
554555
for that attribute. The lists contain floats.
555556
"""
556-
default_val = attr.NOTHING
557+
default_val = NOTHING
557558
val_strat = lists(floats(allow_infinity=False, allow_nan=False))
558559
if defaults is True or (defaults is None and draw(booleans())):
559560
default_val = draw(val_strat)
@@ -564,10 +565,10 @@ def list_typed_attrs(
564565
else:
565566
default = default_val
566567
return (
567-
attr.ib(
568+
field(
568569
type=draw(
569570
sampled_from(
570-
[list[float], list, List[float], List]
571+
[list[float], list, List[float], List, Final[list[float]]]
571572
if not legacy_types_only
572573
else [List, List[float], list]
573574
)
@@ -591,7 +592,7 @@ def seq_typed_attrs(
591592
Generate a tuple of an attribute and a strategy that yields lists
592593
for that attribute. The lists contain integers.
593594
"""
594-
default_val = attr.NOTHING
595+
default_val = NOTHING
595596
val_strat = lists(integers())
596597
if defaults is True or (defaults is None and draw(booleans())):
597598
default_val = draw(val_strat)
@@ -603,7 +604,7 @@ def seq_typed_attrs(
603604
default = default_val
604605

605606
return (
606-
attr.ib(
607+
field(
607608
type=AbcSequence[int] if not legacy_types_only else Sequence[int],
608609
default=default,
609610
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -624,7 +625,7 @@ def mutable_seq_typed_attrs(
624625
Generate a tuple of an attribute and a strategy that yields lists
625626
for that attribute. The lists contain floats.
626627
"""
627-
default_val = attr.NOTHING
628+
default_val = NOTHING
628629
val_strat = lists(floats(allow_infinity=False, allow_nan=False))
629630
if defaults is True or (defaults is None and draw(booleans())):
630631
default_val = draw(val_strat)
@@ -636,7 +637,7 @@ def mutable_seq_typed_attrs(
636637
default = default_val
637638

638639
return (
639-
attr.ib(
640+
field(
640641
type=AbcMutableSequence[float]
641642
if not legacy_types_only
642643
else MutableSequence[float],
@@ -653,12 +654,12 @@ def homo_tuple_typed_attrs(draw, defaults=None, legacy_types_only=False, kw_only
653654
Generate a tuple of an attribute and a strategy that yields homogenous
654655
tuples for that attribute. The tuples contain strings.
655656
"""
656-
default = attr.NOTHING
657+
default = NOTHING
657658
val_strat = tuples(text(), text(), text())
658659
if defaults is True or (defaults is None and draw(booleans())):
659660
default = draw(val_strat)
660661
return (
661-
attr.ib(
662+
field(
662663
type=draw(
663664
sampled_from(
664665
[tuple[str, ...], tuple, Tuple, Tuple[str, ...]]
@@ -679,12 +680,12 @@ def newtype_int_typed_attrs(draw: DrawFn, defaults=None, kw_only=None):
679680
Generate a tuple of an attribute and a strategy that yields ints for that
680681
attribute.
681682
"""
682-
default = attr.NOTHING
683+
default = NOTHING
683684
if defaults is True or (defaults is None and draw(booleans())):
684685
default = draw(integers())
685686
NewInt = NewType("NewInt", int)
686687
return (
687-
attr.ib(
688+
field(
688689
type=NewInt,
689690
default=default,
690691
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -699,7 +700,7 @@ def newtype_attrs_typed_attrs(draw: DrawFn, defaults=None, kw_only=None):
699700
Generate a tuple of an attribute and a strategy that yields values for that
700701
attribute.
701702
"""
702-
default = attr.NOTHING
703+
default = NOTHING
703704

704705
@frozen
705706
class NewTypeAttrs:
@@ -710,7 +711,7 @@ class NewTypeAttrs:
710711

711712
NewAttrs = NewType("NewAttrs", NewTypeAttrs)
712713
return (
713-
attr.ib(
714+
field(
714715
type=NewAttrs,
715716
default=default,
716717
kw_only=draw(booleans()) if kw_only is None else kw_only,
@@ -728,11 +729,11 @@ def just_class(
728729
nested_cl = tup[1][0]
729730
nested_cl_args = tup[1][1]
730731
nested_cl_kwargs = tup[1][2]
731-
default = attr.Factory(lambda: nested_cl(*defaults[0], **defaults[1]))
732+
default = Factory(lambda: nested_cl(*defaults[0], **defaults[1]))
732733
combined_attrs = list(tup[0])
733734
combined_attrs.append(
734735
(
735-
attr.ib(type=nested_cl, default=default),
736+
field(type=nested_cl, default=default),
736737
just(nested_cl(*nested_cl_args, **nested_cl_kwargs)),
737738
)
738739
)
@@ -748,11 +749,11 @@ def list_of_class(
748749
nested_cl = tup[1][0]
749750
nested_cl_args = tup[1][1]
750751
nested_cl_kwargs = tup[1][2]
751-
default = attr.Factory(lambda: [nested_cl(*defaults[0], **defaults[1])])
752+
default = Factory(lambda: [nested_cl(*defaults[0], **defaults[1])])
752753
combined_attrs = list(tup[0])
753754
combined_attrs.append(
754755
(
755-
attr.ib(type=List[nested_cl], default=default),
756+
field(type=List[nested_cl], default=default),
756757
just([nested_cl(*nested_cl_args, **nested_cl_kwargs)]),
757758
)
758759
)
@@ -769,11 +770,11 @@ def new_list_of_class(
769770
nested_cl = tup[1][0]
770771
nested_cl_args = tup[1][1]
771772
nested_cl_kwargs = tup[1][2]
772-
default = attr.Factory(lambda: [nested_cl(*defaults[0], **defaults[1])])
773+
default = Factory(lambda: [nested_cl(*defaults[0], **defaults[1])])
773774
combined_attrs = list(tup[0])
774775
combined_attrs.append(
775776
(
776-
attr.ib(type=list[nested_cl], default=default),
777+
field(type=list[nested_cl], default=default),
777778
just([nested_cl(*nested_cl_args, **nested_cl_kwargs)]),
778779
)
779780
)
@@ -789,11 +790,11 @@ def dict_of_class(
789790
nested_cl = tup[1][0]
790791
nested_cl_args = tup[1][1]
791792
nested_cl_kwargs = tup[1][2]
792-
default = attr.Factory(lambda: {"cls": nested_cl(*defaults[0], **defaults[1])})
793+
default = Factory(lambda: {"cls": nested_cl(*defaults[0], **defaults[1])})
793794
combined_attrs = list(tup[0])
794795
combined_attrs.append(
795796
(
796-
attr.ib(type=Dict[str, nested_cl], default=default),
797+
field(type=Dict[str, nested_cl], default=default),
797798
just({"cls": nested_cl(*nested_cl_args, **nested_cl_kwargs)}),
798799
)
799800
)

0 commit comments

Comments
 (0)