Repository: konradhalas/dacite

Issue:
Fields of type Optional[Union[A, B]] not working with cast
### Example to reproduce faulty behavior:

```python
from dataclasses import dataclass
from typing import Optional, Union
import dacite

class A(int):
    pass

class B(str):
    pass

@dataclass
class C:
    a_or_b: Optional[Union[A, B]]

obj_from_dict = dacite.from_dict(
    data_class=C,
    data={"a_or_b": "Hello world!"},
    config=dacite.Config(cast=[A, B]),
)
assert obj_from_dict == C(a_or_b=B("Hello world!"))
```

### Issue

I would expect this to work flawlessly, since I included both `A` and `B` in cast to turn `int` to `A` and `str` to `B` so that the types match. Unfortunately this is not the case, instead I get
```
Traceback (most recent call last):
  File "problem.py", line 15, in <module>
    obj_from_dict = dacite.from_dict(
  File "[...]/lib/python3.8/site-packages/dacite/core.py", line 60, in from_dict
    transformed_value = transform_value(
  File "[...]/lib/python3.8/site-packages/dacite/types.py", line 24, in transform_value
    return transform_value(type_hooks, cast, target_type, value)
  File "[...]/lib/python3.8/site-packages/dacite/types.py", line 18, in transform_value
    value = target_type(value)
ValueError: invalid literal for int() with base 10: 'Hello world!'
```

If we inspect this more closely, we first enter `from_dict` and then `transform_value` where the target type is `Union[A, B, None]`. Since the target type is correctly identified as optional by `is_optional` and since the value is not None (but `"Hello world!"`), `transform_value` is again called. This time the target type is `extract_optional(Union[A, B, None])`. It **should** be `Union[A, B]`, but in the current implementation it **is** `A`. This causes the program to incorrectly cast `"Hello World!"` to `A` and the program crashes.


### Solution

```python
def extract_optional(optional: Type[Optional[T]]) -> T:
    for type_ in extract_generic(optional):
        if type_ is not type(None):
            return type_
    raise ValueError("can not find not-none value")
```
should be changed to something like
```python
def extract_optional(optional: Type[Optional[T]]) -> T:
    other_members = [member for member in extract_generic(optional) if member is not type(None)]
    if not other_members:
        raise ValueError("can not find not-none value")
    else:
        return Union[tuple(other_members)]
```

This way `extract_optional(Union[A, B, None]) == Union[A, B]` and no incorrect casting happens. The Union is then properly handled by `_build_value_for_union`. Note that, if `other_members` contains only a single type, say `A`, then `Union[A] == A`.

Basically, this is the same problem reported in #26 for `is_optional`, but this time for `extract_optional`. It is also probably related to #161. This issues does not appear without including the types inside the union in `type_hooks` or `cast`, because then the incorrect call to `transform_values` does not actually do anything.

Discussion:

Patch:
diff --git a/.github/workflows/code_check.yaml b/.github/workflows/code_check.yaml
new file mode 100644
index 0000000..a69ae45
--- /dev/null
+++ b/.github/workflows/code_check.yaml
@@ -0,0 +1,34 @@
+name: Code Check
+
+on:
+  pull_request:
+    types: [ opened, reopened, synchronize, edited, closed ]
+  push:
+    branches:
+      - master
+
+jobs:
+  check:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11']
+
+    name: Python ${{ matrix.python-version }}
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+          architecture: x64
+      - name: Install dependencies
+        run: pip install -e ".[dev]"
+      - name: Testing Check
+        run: pytest --cov=dacite
+      - name: Formatting Check
+        run: black --check .
+      - name: Typing Check
+        run: mypy dacite
+      - name: Linting Check
+        run: pylint dacite
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 0000000..e6b39d6
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,21 @@
+name: publish
+on: release
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v3
+        with:
+          python-version: '3.11'
+          architecture: x64
+      - name: Build package
+        run: |
+          python -m pip install --upgrade build
+          python -m build
+      - name: Publish package to PyPI
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          user: __token__
+          password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e26f73a..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-dist: xenial
-language: python
-python:
-- 3.6
-- 3.7
-- 3.8
-- 3.9-dev
-install:
-- pip install -e .[dev]
-script:
-- pytest --cov=dacite
-- black --check .
-- mypy dacite
-- pylint dacite
-after_success:
-  coveralls
-stages:
-- test
-- deploy
-jobs:
-  include:
-    - stage: deploy
-      script: skip
-      python: 3.8
-      deploy:
-        provider: pypi
-        user: khalas
-        password:
-          secure: AsRZXKbagKofHu1JhqgKziomF/eIQZsGgp0scdAZxd1XwsDOwGvvzEJdgMmzxo3Wgvwa5rHBmZAjN8qYDyHLESCPUlVaE6bFztM5q2icZ7z6ReaY9807C5wb9xbDVwQ74GBLP3x1CAR0o2KcO2FS4Xc9TIGmHTY0yyrYzsose0Qn6hAEOpNcoAPR6eRUkIakyqVwj8JAdFBsB1EuqFIpgEwjmgQrv3ZUwTe+a7ERJE6Su3Q5qKU1tJFEbzMvhdBpTOfUujUdtNVx6A8FSivCBcGE1FgRz+ZEFcLxsnYNyw+cNgWjD7thyAE6RrSzNSn3ddMGwqYcXQkfOLICbdazDrcAAAhl+DJGPDilkbAo9cX8zoKY4v12bKAs5PFh6L/qArFAT6X7rMBDkqZ/i8O7Pxbvsm1G1yfupUzwBn+qlc4WxhrroqFa2/kLbPADrihiercyueoXU66+CzKHzmfvDhv7Yoz9bE1ZzgW0go7VsJXy93ZAuveB8i9+OMUsRBY8dTrIdBhd8IDpBet667ugStv+0yg2rhEQxw8q7mhLvwk2AAt2qX6dekhyrqKGrBVgxfWWqX0iyf/whIcUuKHZ6VBKFgwZ8DhCfvNw5FaRrKEbRc5TPhlNk0TyNNQh0rsSi7F+6hCwLcLl59MgWXoJkbpoK5yJZqA2YcSYXQAixFY=
-        on:
-          tags: true
-        distributions: "sdist bdist_wheel"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 763130d..d965f3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,10 +10,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 - Add explicit `__all__` configuration
+- Add Python 3.10 and 3.11 support
+- Support [PEP 604] unions through `types.UnionType`
+
+[PEP 604]: https://peps.python.org/pep-0604/
 
 ### Fixed
 
 - Do not suppress `KeyError` in a type hook
+- Handle tuple as a `Sequence`
+- Handle a default value for a non-init field within a frozen dataclass
+- Run type hooks / cast for `InitVar` fields
 
 ## [1.6.0] - 2020-11-30
 
diff --git a/dacite/core.py b/dacite/core.py
index c82212d..7e983af 100644
--- a/dacite/core.py
+++ b/dacite/core.py
@@ -5,7 +5,13 @@ from typing import TypeVar, Type, Optional, get_type_hints, Mapping, Any
 
 from dacite.config import Config
 from dacite.data import Data
-from dacite.dataclasses import get_default_value_for_field, create_instance, DefaultValueNotFoundError, get_fields
+from dacite.dataclasses import (
+    get_default_value_for_field,
+    create_instance,
+    DefaultValueNotFoundError,
+    get_fields,
+    is_frozen,
+)
 from dacite.exceptions import (
     ForwardReferenceError,
     WrongTypeError,
@@ -26,6 +32,7 @@ from dacite.types import (
     extract_origin_collection,
     is_init_var,
     extract_init_var,
+    is_set,
 )
 
 T = TypeVar("T")
@@ -75,7 +82,7 @@ def from_dict(data_class: Type[T], data: Data, config: Optional[Config] = None)
                 raise MissingValueError(field.name)
         if field.init:
             init_values[field.name] = value
-        else:
+        elif not is_frozen(data_class):
             post_init_values[field.name] = value
 
     return create_instance(data_class=data_class, init_values=init_values, post_init_values=post_init_values)
@@ -86,8 +93,14 @@ def _build_value(type_: Type, data: Any, config: Config) -> Any:
         type_ = extract_init_var(type_)
     if is_union(type_):
         return _build_value_for_union(union=type_, data=data, config=config)
-    elif is_generic_collection(type_) and is_instance(data, extract_origin_collection(type_)):
-        return _build_value_for_collection(collection=type_, data=data, config=config)
+    elif is_generic_collection(type_):
+        origin = extract_origin_collection(type_)
+        if is_instance(data, origin):
+            return _build_value_for_collection(collection=type_, data=data, config=config)
+        if is_set(origin):
+            return origin(
+                _build_value(type_=extract_generic(type_)[0], data=single_val, config=config) for single_val in data
+            )
     elif is_dataclass(type_) and is_instance(data, Data):
         return from_dict(data_class=type_, data=data, config=config)
     return data
@@ -130,6 +143,8 @@ def _build_value_for_collection(collection: Type, data: Any, config: Config) ->
         item_type = extract_generic(collection, defaults=(Any, Any))[1]
         return data_type((key, _build_value(type_=item_type, data=value, config=config)) for key, value in data.items())
     elif is_instance(data, tuple):
+        if not data:
+            return data_type()
         types = extract_generic(collection)
         if len(types) == 2 and types[1] == Ellipsis:
             return data_type(_build_value(type_=types[0], data=item, config=config) for item in data)
diff --git a/dacite/dataclasses.py b/dacite/dataclasses.py
index 4f48736..4824520 100644
--- a/dacite/dataclasses.py
+++ b/dacite/dataclasses.py
@@ -31,3 +31,7 @@ def create_instance(data_class: Type[T], init_values: Data, post_init_values: Da
 def get_fields(data_class: Type[T]) -> List[Field]:
     fields = getattr(data_class, _FIELDS)
     return [f for f in fields.values() if f._field_type is _FIELD or f._field_type is _FIELD_INITVAR]
+
+
+def is_frozen(data_class: Type[T]) -> bool:
+    return data_class.__dataclass_params__.frozen
diff --git a/dacite/exceptions.py b/dacite/exceptions.py
index 871e73f..4e44f68 100644
--- a/dacite/exceptions.py
+++ b/dacite/exceptions.py
@@ -1,8 +1,9 @@
 from typing import Any, Type, Optional, Set, Dict
+from dacite.types import is_union
 
 
 def _name(type_: Type) -> str:
-    return type_.__name__ if hasattr(type_, "__name__") else str(type_)
+    return type_.__name__ if hasattr(type_, "__name__") and not is_union(type_) else str(type_)
 
 
 class DaciteError(Exception):
diff --git a/dacite/types.py b/dacite/types.py
index 1d4dfea..0abb551 100644
--- a/dacite/types.py
+++ b/dacite/types.py
@@ -7,13 +7,18 @@ T = TypeVar("T", bound=Any)
 def transform_value(
     type_hooks: Dict[Type, Callable[[Any], Any]], cast: List[Type], target_type: Type, value: Any
 ) -> Any:
+    if is_init_var(target_type):
+        target_type = extract_init_var(target_type)
     if target_type in type_hooks:
         value = type_hooks[target_type](value)
     else:
         for cast_type in cast:
             if is_subclass(target_type, cast_type):
                 if is_generic_collection(target_type):
-                    value = extract_origin_collection(target_type)(value)
+                    origin_collection = extract_origin_collection(target_type)
+                    if is_set(origin_collection):
+                        return list(value)
+                    value = origin_collection(value)
                 else:
                     value = target_type(value)
                 break
@@ -49,10 +54,11 @@ def is_optional(type_: Type) -> bool:
 
 
 def extract_optional(optional: Type[Optional[T]]) -> T:
-    for type_ in extract_generic(optional):
-        if type_ is not type(None):
-            return type_
-    raise ValueError("can not find not-none value")
+    other_members = [member for member in extract_generic(optional) if member is not type(None)]
+    if other_members:
+        return Union[tuple(other_members)]
+    else:
+        raise ValueError("can not find not-none value")
 
 
 def is_generic(type_: Type) -> bool:
@@ -60,7 +66,19 @@ def is_generic(type_: Type) -> bool:
 
 
 def is_union(type_: Type) -> bool:
-    return is_generic(type_) and type_.__origin__ == Union
+    if is_generic(type_) and type_.__origin__ == Union:
+        return True
+
+    try:
+        from types import UnionType  # type: ignore
+
+        return isinstance(type_, UnionType)
+    except ImportError:
+        return False
+
+
+def is_tuple(type_: Type) -> bool:
+    return is_subclass(type_, tuple)
 
 
 def is_literal(type_: Type) -> bool:
@@ -84,6 +102,10 @@ def is_init_var(type_: Type) -> bool:
     return isinstance(type_, InitVar) or type_ is InitVar
 
 
+def is_set(type_: Type) -> bool:
+    return type_ in (set, frozenset) or isinstance(type_, (frozenset, set))
+
+
 def extract_init_var(type_: Type) -> Union[Type, Any]:
     try:
         return type_.type
@@ -102,7 +124,7 @@ def is_instance(value: Any, type_: Type) -> bool:
             return False
         if not extract_generic(type_):
             return True
-        if isinstance(value, tuple):
+        if isinstance(value, tuple) and is_tuple(type_):
             tuple_types = extract_generic(type_)
             if len(tuple_types) == 1 and tuple_types[0] == ():
                 return len(value) == 0
@@ -151,6 +173,8 @@ def extract_generic(type_: Type, defaults: Tuple = ()) -> tuple:
     try:
         if hasattr(type_, "_special") and type_._special:
             return defaults
+        if type_.__args__ == ():
+            return (type_.__args__,)
         return type_.__args__ or defaults  # type: ignore
     except AttributeError:
         return defaults
diff --git a/setup.py b/setup.py
index 89c86c5..19b3619 100644
--- a/setup.py
+++ b/setup.py
@@ -20,6 +20,8 @@ setup(
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
         "Topic :: Software Development :: Libraries :: Python Modules",
     ],
     python_requires=">=3.6",


Test patch:
diff --git a/tests/common.py b/tests/common.py
index 71a557a..bfb7c81 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -3,3 +3,7 @@ import sys
 import pytest
 
 literal_support = init_var_type_support = pytest.mark.skipif(sys.version_info < (3, 8), reason="requires Python 3.8")
+pep_604_support = pytest.mark.skipif(sys.version_info < (3, 10), reason="requires Python 3.10")
+type_hints_with_generic_collections_support = pytest.mark.skipif(
+    sys.version_info < (3, 9), reason="requires Python 3.9"
+)
diff --git a/tests/core/test_base.py b/tests/core/test_base.py
index fadf2a4..8d82788 100644
--- a/tests/core/test_base.py
+++ b/tests/core/test_base.py
@@ -1,5 +1,5 @@
 from dataclasses import dataclass, field
-from typing import Any, NewType
+from typing import Any, NewType, Optional
 
 import pytest
 
@@ -158,6 +158,29 @@ def test_from_dict_with_post_init_missing_value():
     assert not hasattr(result, "s")
 
 
+def test_from_dict_with_optional_non_init_field():
+    @dataclass
+    class X:
+        s: Optional[str] = field(init=False)
+
+    x = X()
+    x.s = None
+
+    result = from_dict(X, {})
+
+    assert result == x
+
+
+def test_from_dict_with_non_init_field_with_default_value_and_frozen_dataclass():
+    @dataclass(frozen=True)
+    class X:
+        s: str = field(init=False, default="test")
+
+    result = from_dict(X, {})
+
+    assert result == X()
+
+
 def test_from_dict_with_new_type():
     MyStr = NewType("MyStr", str)
 
diff --git a/tests/core/test_collection.py b/tests/core/test_collection.py
index a990be7..37f8f19 100644
--- a/tests/core/test_collection.py
+++ b/tests/core/test_collection.py
@@ -1,9 +1,9 @@
 from dataclasses import dataclass
-from typing import List, Set, Union, Dict, Collection, Tuple
+from typing import List, Set, Union, Dict, Collection, Tuple, Sequence
 
 import pytest
 
-from dacite import from_dict, WrongTypeError
+from dacite import from_dict, WrongTypeError, Config
 
 
 def test_from_dict_with_generic_collection():
@@ -68,6 +68,54 @@ def test_from_dict_with_set():
     assert result == X(i_set={1, 2})
 
 
+def test_from_dict_with_set_strings():
+    @dataclass
+    class X:
+        i_set: Set[str]
+
+    result = from_dict(X, {"i_set": {"a", "b"}})
+
+    assert result == X(i_set={"a", "b"})
+
+
+def test_from_dict_with_set_classes():
+    @dataclass(frozen=True)
+    class A:
+        x: int
+
+    @dataclass
+    class SetOfA:
+        set_a: Set[A]
+
+    data = {"set_a": [{"x": 1}, {"x": 2}]}
+
+    result = from_dict(data_class=SetOfA, data=data, config=Config(cast=[set]))
+
+    assert result == SetOfA({A(1), A(2)})
+
+
+def test_from_dict_with_nested_set_classes():
+    @dataclass(frozen=True)
+    class A:
+        x: int
+
+    @dataclass
+    class SetOfA:
+        set_a: Set[A]
+
+        def __hash__(self) -> int:
+            return hash(sum(ob.x for ob in self.set_a))
+
+    @dataclass
+    class SetOfSetOfA:
+        set_set_a: Set[SetOfA]
+
+    data = {"set_set_a": [{"set_a": [{"x": 1}, {"x": 2}]}]}
+    result = from_dict(data_class=SetOfSetOfA, data=data, config=Config(cast=[set]))
+
+    assert result == SetOfSetOfA(set_set_a={SetOfA(set_a={A(x=1), A(x=2)})})
+
+
 def test_from_dict_with_dict():
     @dataclass
     class X:
@@ -221,3 +269,23 @@ def test_from_dict_with_tuple_and_implicit_any_types():
     result = from_dict(X, {"t": (1, 2, 3)})
 
     assert result == X(t=(1, 2, 3))
+
+
+def test_from_dict_with_sequence_and_tuple():
+    @dataclass
+    class X:
+        s: Sequence[int]
+
+    result = from_dict(X, {"s": (1, 2, 3)})
+
+    assert result == X(s=(1, 2, 3))
+
+
+def test_from_dict_with_sequence_and_empty_tuple():
+    @dataclass
+    class X:
+        s: Sequence[int]
+
+    result = from_dict(X, {"s": ()})
+
+    assert result == X(s=())
diff --git a/tests/core/test_init_var.py b/tests/core/test_init_var.py
index 0ef62b9..a496212 100644
--- a/tests/core/test_init_var.py
+++ b/tests/core/test_init_var.py
@@ -1,6 +1,6 @@
 from dataclasses import dataclass, InitVar, field
 
-from dacite import from_dict
+from dacite import from_dict, Config
 from tests.common import init_var_type_support
 
 
@@ -35,3 +35,18 @@ def test_from_dict_with_init_var_and_data_class():
     result = from_dict(Y, {"a": {"i": 2}})
 
     assert result.b == X(i=4)
+
+
+@init_var_type_support
+def test_from_dict_with_init_var_and_cast():
+    @dataclass
+    class X:
+        a: InitVar[int]
+        b: int = field(init=False)
+
+        def __post_init__(self, a: int) -> None:
+            self.b = 2 * a
+
+    result = from_dict(X, {"a": "2"}, config=Config(cast=[int]))
+
+    assert result.b == 4
diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py
index 300b40d..2ae3559 100644
--- a/tests/test_dataclasses.py
+++ b/tests/test_dataclasses.py
@@ -3,7 +3,7 @@ from typing import Optional
 
 import pytest
 
-from dacite.dataclasses import get_default_value_for_field, create_instance, DefaultValueNotFoundError
+from dacite.dataclasses import get_default_value_for_field, create_instance, DefaultValueNotFoundError, is_frozen
 
 
 def test_get_default_value_for_field_with_default_value():
@@ -65,3 +65,19 @@ def test_create_instance_with_post_init_values():
 
     assert instance.i == 1
     assert instance.j == 2
+
+
+def test_is_frozen_with_frozen_dataclass():
+    @dataclass(frozen=True)
+    class X:
+        pass
+
+    assert is_frozen(X)
+
+
+def test_is_frozen_with_non_frozen_dataclass():
+    @dataclass(frozen=False)
+    class X:
+        pass
+
+    assert not is_frozen(X)
diff --git a/tests/test_types.py b/tests/test_types.py
index 948ff16..181115e 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -19,8 +19,15 @@ from dacite.types import (
     is_init_var,
     extract_init_var,
     is_type_generic,
+    is_set,
+    is_tuple,
+)
+from tests.common import (
+    literal_support,
+    init_var_type_support,
+    pep_604_support,
+    type_hints_with_generic_collections_support,
 )
-from tests.common import literal_support, init_var_type_support
 
 
 def test_is_union_with_union():
@@ -31,6 +38,41 @@ def test_is_union_with_non_union():
     assert not is_union(int)
 
 
+def test_is_tuple_with_tuple():
+    assert is_tuple(Tuple[int, float, str])
+
+
+def test_is_tuple_with_variable_length_tuple():
+    assert is_tuple(Tuple[int, ...])
+
+
+def test_is_tuple_with_not_parametrized_tuple():
+    assert is_tuple(Tuple)
+
+
+def test_is_tuple_with_tuple_class_object():
+    assert is_tuple(tuple)
+
+
+@type_hints_with_generic_collections_support
+def test_is_tuple_with_tuple_generic():
+    assert is_tuple(tuple[int, float, str])
+
+
+@type_hints_with_generic_collections_support
+def test_is_tuple_with_variable_length_tuple_generic():
+    assert is_tuple(tuple[int, ...])
+
+
+def test_is_tuple_with_non_tuple():
+    assert not is_tuple(int)
+
+
+@pep_604_support
+def test_is_union_with_pep_604_union():
+    assert is_union(int | float)
+
+
 @literal_support
 def test_is_literal_with_literal():
     from typing import Literal
@@ -62,6 +104,16 @@ def test_is_optional_with_optional_of_union():
     assert is_optional(Optional[Union[int, float]])
 
 
+@pep_604_support
+def test_is_optional_with_pep_604_union():
+    assert is_optional(int | float | None)
+
+
+@pep_604_support
+def test_is_optional_with_non_optional_pep_604_union():
+    assert not is_optional(int | float)
+
+
 def test_extract_optional():
     assert extract_optional(Optional[int]) == int
 
@@ -71,6 +123,10 @@ def test_extract_optional_with_wrong_type():
         extract_optional(List[None])
 
 
+def test_extract_optional_with_optional_of_union():
+    assert extract_optional(Optional[Union[int, str]]) == Union[int, str]
+
+
 def test_is_generic_with_generic():
     assert is_generic(Optional[int])
 
@@ -385,3 +441,33 @@ def test_is_type_generic_with_matching_value():
 
 def test_is_type_generic_with_not_matching_value():
     assert not is_type_generic(int)
+
+
+def test_is_set_set_class():
+    assert is_set(set)
+
+
+def test_is_set_frozentset_class():
+    assert is_set(frozenset)
+
+
+def test_is_set_set_object():
+    obj = {1, 2, 3}
+    assert is_set(obj)
+
+
+def test_is_set_frozentset_object():
+    obj = frozenset({1, 2, 3})
+    assert is_set(obj)
+
+
+def test_is_set_list_class():
+    assert not is_set(list)
+
+
+def test_is_set_int_class():
+    assert not is_set(int)
+
+
+def test_is_set_union():
+    assert not is_set(Union[int, float])