Skip to content

Commit

Permalink
Fix collections.abc.Seqs on 3.9+, where they should be used
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche committed Mar 9, 2021
1 parent 618de82 commit da63135
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 7 deletions.
4 changes: 3 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ History

1.4.0 (UNRELEASED)
------------------
* Fix an issue with `GenConverter` un/structuring hooks when a function hook is registered after the converter has already been used.
* Fix an issue with ``GenConverter`` un/structuring hooks when a function hook is registered after the converter has already been used.
* Add support for ``collections.abc.{Sequence, MutableSequence, Set, MutableSet}``. These should be used on 3.9+ instead of their ``typing`` alternatives, which are deprecated.
(`#128 <https://github.com/Tinche/cattrs/issues/128>`_)

1.3.0 (2021-02-25)
------------------
Expand Down
31 changes: 27 additions & 4 deletions src/cattr/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ def is_bare(type):
_SpecialGenericAlias,
_UnionGenericAlias,
)
from collections.abc import (
MutableSequence as AbcMutableSequence,
Sequence as AbcSequence,
MutableSet as AbcMutableSet,
Set as AbcSet,
)

def is_tuple(type):
return (
Expand All @@ -117,12 +123,26 @@ def is_union_type(obj):

def is_sequence(type: Any) -> bool:
return (
type in (List, list, Sequence, MutableSequence, tuple)
type
in (
List,
list,
Sequence,
MutableSequence,
AbcMutableSequence,
tuple,
)
or (
type.__class__ is _GenericAlias
and issubclass(type.__origin__, Sequence)
and issubclass(
type.__origin__,
Sequence,
)
)
or (
getattr(type, "__origin__", None)
in (list, tuple, AbcMutableSequence, AbcSequence)
)
or (getattr(type, "__origin__", None) in (list, tuple))
)

def is_mutable_set(type):
Expand All @@ -132,7 +152,10 @@ def is_mutable_set(type):
type.__class__ is _GenericAlias
and issubclass(type.__origin__, MutableSet)
)
or (getattr(type, "__origin__", None) is set)
or (
getattr(type, "__origin__", None)
in (set, AbcMutableSet, AbcSet)
)
)

def is_frozenset(type):
Expand Down
74 changes: 72 additions & 2 deletions tests/metadata/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
"""Tests for metadata functionality."""
import sys
from collections import OrderedDict
from typing import Any, Callable, Dict, List, Tuple, Type, TypeVar
from collections.abc import (
MutableSequence as AbcMutableSequence,
Sequence as AbcSequence,
Set as AbcSet,
MutableSet as AbcMutableSet,
)
from typing import (
Any,
Callable,
Dict,
List,
MutableSequence,
Sequence,
Tuple,
Type,
TypeVar,
)

import attr
from attr import NOTHING
Expand Down Expand Up @@ -65,6 +81,8 @@ def simple_typed_attrs(
| str_typed_attrs(defaults)
| float_typed_attrs(defaults)
| dict_typed_attrs(defaults)
| mutable_seq_typed_attrs(defaults)
| seq_typed_attrs(defaults)
)
else:
return (
Expand All @@ -78,6 +96,8 @@ def simple_typed_attrs(
| list_typed_attrs(defaults)
| frozenset_typed_attrs(defaults)
| homo_tuple_typed_attrs(defaults)
| mutable_seq_typed_attrs(defaults)
| seq_typed_attrs(defaults)
)


Expand Down Expand Up @@ -212,7 +232,15 @@ def set_typed_attrs(draw, defaults=None):
val_strat = sets(integers())
if defaults is True or (defaults is None and draw(booleans())):
default = draw(val_strat)
return (attr.ib(type=set[int], default=default), val_strat)
return (
attr.ib(
type=set[int]
if draw(booleans())
else (AbcSet[int] if draw(booleans()) else AbcMutableSet[int]),
default=default,
),
val_strat,
)


@composite
Expand Down Expand Up @@ -253,6 +281,48 @@ def list_typed_attrs(draw, defaults=None):
)


@composite
def mutable_seq_typed_attrs(draw, defaults=None):
"""
Generate a tuple of an attribute and a strategy that yields lists
for that attribute. The lists contain floats.
"""
default = attr.NOTHING
val_strat = lists(integers())
if defaults is True or (defaults is None and draw(booleans())):
default = draw(val_strat)
return (
attr.ib(
type=Sequence[int]
if not is_39_or_later or draw(booleans())
else AbcSequence[int],
default=default,
),
val_strat,
)


@composite
def seq_typed_attrs(draw, defaults=None):
"""
Generate a tuple of an attribute and a strategy that yields lists
for that attribute. The lists contain floats.
"""
default = attr.NOTHING
val_strat = lists(floats(allow_infinity=False, allow_nan=False))
if defaults is True or (defaults is None and draw(booleans())):
default = draw(val_strat)
return (
attr.ib(
type=MutableSequence[float]
if not is_39_or_later
else AbcMutableSequence[float],
default=default,
),
val_strat,
)


@composite
def homo_tuple_typed_attrs(draw, defaults=None):
"""
Expand Down

0 comments on commit da63135

Please sign in to comment.