Repository: wyfo/apischema

Issue:
Fix frozenset deserialization
AbstractSet and FrozenSet are not deserialized in frozensets.
```python
from typing import AbstractSet, FrozenSet
from apischema import deserialize
assert type(deserialize(AbstractSet[int], [1, 2, 3])) is set
assert type(deserialize(FrozenSet[int], [1, 2, 3])) is set
```
PR to follow

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/wyfo/apischema/pull/342?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez) Report
> Merging [#342](https://codecov.io/gh/wyfo/apischema/pull/342?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez) (7c47d6d) into [master](https://codecov.io/gh/wyfo/apischema/commit/af1bf4ad1a04950ea3cdc7497b800cd268b0ea42?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez) (af1bf4a) will **decrease** coverage by `0.02%`.
> The diff coverage is `80.95%`.

[![Impacted file tree graph](https://codecov.io/gh/wyfo/apischema/pull/342/graphs/tree.svg?width=650&height=150&src=pr&token=GTGPD4DLE5&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez)](https://codecov.io/gh/wyfo/apischema/pull/342?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez)

```diff
@@            Coverage Diff             @@
##           master     #342      +/-   ##
==========================================
- Coverage   86.13%   86.11%   -0.03%     
==========================================
  Files          67       67              
  Lines        5879     5898      +19     
  Branches     1218     1222       +4     
==========================================
+ Hits         5064     5079      +15     
- Misses        596      599       +3     
- Partials      219      220       +1     
```


| [Impacted Files](https://codecov.io/gh/wyfo/apischema/pull/342?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez) | Coverage Δ | |
|---|---|---|
| [apischema/deserialization/methods.py](https://codecov.io/gh/wyfo/apischema/pull/342/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez#diff-YXBpc2NoZW1hL2Rlc2VyaWFsaXphdGlvbi9tZXRob2RzLnB5) | `84.51% <76.47%> (-0.21%)` | :arrow_down: |
| [apischema/deserialization/\_\_init\_\_.py](https://codecov.io/gh/wyfo/apischema/pull/342/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez#diff-YXBpc2NoZW1hL2Rlc2VyaWFsaXphdGlvbi9fX2luaXRfXy5weQ==) | `98.98% <100.00%> (+<0.01%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/wyfo/apischema/pull/342?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/wyfo/apischema/pull/342?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez). Last update [af1bf4a...7c47d6d](https://codecov.io/gh/wyfo/apischema/pull/342?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Joseph+Perez).

wyfo: @pchanial 
Actually, I can merge this PR like this, and do the small refactoring myself if you're ok with that. I would like to release the patch tomorrow morning (I don't like having a bug more than 24h in the issues).
If you want to make the change yourself, I will wait, no worry.
pchanial: I had made the change before your last comment, so I just pushed it. I have started looking at ways to refactor this part but the tests fail with cython. I'll make another PR so we can discuss it.

Patch:
diff --git a/apischema/deserialization/__init__.py b/apischema/deserialization/__init__.py
index eed26ac..240efaf 100644
--- a/apischema/deserialization/__init__.py
+++ b/apischema/deserialization/__init__.py
@@ -58,6 +58,7 @@ from apischema.deserialization.methods import (
     FieldsConstructor,
     FlattenedField,
     FloatMethod,
+    FrozenSetMethod,
     IntMethod,
     ListCheckOnlyMethod,
     ListMethod,
@@ -360,13 +361,19 @@ class DeserializationMethodVisitor(
             value_method = value_factory.method
             list_constraints = constraints_validators(constraints)[list]
             method: DeserializationMethod
-            if issubclass(cls, collections.abc.Set):
+            if issubclass(cls, collections.abc.Set) and not issubclass(cls, frozenset):
                 return SetMethod(list_constraints, value_method)
-            elif self.no_copy and check_only(value_method):
+
+            if self.no_copy and check_only(value_method):
                 method = ListCheckOnlyMethod(list_constraints, value_method)
             else:
                 method = ListMethod(list_constraints, value_method)
-            return VariadicTupleMethod(method) if issubclass(cls, tuple) else method
+
+            if issubclass(cls, tuple):
+                return VariadicTupleMethod(method)
+            if issubclass(cls, frozenset):
+                return FrozenSetMethod(method)
+            return method
 
         return self._factory(factory, list)
 
diff --git a/apischema/deserialization/methods.py b/apischema/deserialization/methods.py
index 5cc1e97..a1d4372 100644
--- a/apischema/deserialization/methods.py
+++ b/apischema/deserialization/methods.py
@@ -308,6 +308,14 @@ class SetMethod(DeserializationMethod):
         return values
 
 
+@dataclass
+class FrozenSetMethod(DeserializationMethod):
+    method: DeserializationMethod
+
+    def deserialize(self, data: Any) -> Any:
+        return frozenset(self.method.deserialize(data))
+
+
 @dataclass
 class VariadicTupleMethod(DeserializationMethod):
     method: DeserializationMethod


Test patch:
diff --git a/tests/unit/test_deserialization_serialization.py b/tests/unit/test_deserialization_serialization.py
index 7d6d0de..a076691 100644
--- a/tests/unit/test_deserialization_serialization.py
+++ b/tests/unit/test_deserialization_serialization.py
@@ -4,6 +4,7 @@ from enum import Enum
 from typing import (
     AbstractSet,
     Any,
+    FrozenSet,
     List,
     Mapping,
     Optional,
@@ -30,6 +31,7 @@ uuid = str(uuid4())
 def bijection(cls, data, expected):
     obj = deserialize(cls, data)
     assert obj == expected
+    assert type(obj) is type(expected)
     assert serialize(cls, obj) == data
 
 
@@ -97,7 +99,8 @@ def test_primitive_error(data):
         (List, [0, SimpleDataclass(0)]),
         (Set, {0, SimpleDataclass(0)}),
         (Sequence, [0, SimpleDataclass(0)]),
-        (AbstractSet, frozenset([0, SimpleDataclass(0)])),
+        (AbstractSet, {0, SimpleDataclass(0)}),
+        (FrozenSet, frozenset([0, SimpleDataclass(0)])),
     ],
 )
 def test_collection(cls, expected):
@@ -188,7 +191,7 @@ def test_with_class_context():
     class BigInt(int):
         pass
 
-    bijection(BigInt, 100, 100)
+    bijection(BigInt, 100, BigInt(100))
 
 
 def test_properties():