Repository: wyfo/apischema

Issue:
Not informative exception
Hi @wyfo ,

First of all, the project is impressive! We get a boost of 25% compared to well-tuned Pydantic serialization, which was like the following one when all non-BaseModel nested attributes were on the shoulders of orjson's SIMD.:

```
def default(obj: Any) -> Any:
    if isinstance(obj, BaseModel):
        return dict(obj)
    raise TypeError

orjson.dumps(content, default=default)
```

I get these results on not-Cythonized version. So the main benchmark will occur shortly after integration, and I strongly believe that I'll get superior results.

But lets' get closer to the issue topic

```Python
from typing import Optional, List
import dataclasses
from apischema import serialize


@dataclasses.dataclass
class Item:
    name: str
    price: Optional[float] = None
    owner_ids: Optional[List[int]] = None


value = Item(name="coerce", price="1.0")
serialize(Item, value, check_type=True)
```

I'm getting the following exception. 

```
Traceback (most recent call last):
  File "C:\Users\sdubr\PycharmProjects\squall\report.py", line 14, in <module>
    serialize(Item, value, check_type=True)
  File "C:\Users\sdubr\PycharmProjects\squall\venv\lib\site-packages\apischema\utils.py", line 424, in wrapper
    return wrapped(*args, **kwargs)
  File "C:\Users\sdubr\PycharmProjects\squall\venv\lib\site-packages\apischema\serialization\__init__.py", line 577, in serialize
    return serialization_method(
  File "C:\Users\sdubr\PycharmProjects\squall\venv\lib\site-packages\apischema\serialization\__init__.py", line 185, in wrapper
    return fallback(obj)
  File "C:\Users\sdubr\PycharmProjects\squall\venv\lib\site-packages\apischema\serialization\__init__.py", line 169, in method
    raise TypeError(f"Expected {tp}, found {obj.__class__}")
TypeError: Expected <class '__main__.Item'>, found <class '__main__.Item'>
```

The fact that exception has arrived is proper behavior that I expect. But, the exception doesn't provide any useful information.
It will be great to see something similar to exceptions we can get from the deserialization phase.




Discussion:

Patch:
diff --git a/apischema/conversions/utils.py b/apischema/conversions/utils.py
index 505c872..cd8d8e1 100644
--- a/apischema/conversions/utils.py
+++ b/apischema/conversions/utils.py
@@ -1,4 +1,4 @@
-from inspect import Parameter, isclass, signature
+from inspect import Parameter, signature
 from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, cast
 
 from apischema.types import AnyType
@@ -32,7 +32,7 @@ def converter_types(
         else:
             parameters = list(signature(converter).parameters.values())
     except ValueError:  # builtin types
-        if target is None and isclass(converter):
+        if target is None and is_type(converter):
             target = cast(Type[Any], converter)
         if source is None:
             raise TypeError("Converter source is unknown") from None
@@ -51,7 +51,7 @@ def converter_types(
         if source is not None and target is not None:
             return source, target
         types = get_type_hints(converter, None, namespace, include_extras=True)
-        if not types and isclass(converter):
+        if not types and is_type(converter):
             types = get_type_hints(
                 converter.__new__, None, namespace, include_extras=True
             ) or get_type_hints(
@@ -63,12 +63,12 @@ def converter_types(
             except KeyError:
                 raise TypeError("converter source is unknown") from None
         if target is None:
-            try:
-                target = types.pop("return")
-            except KeyError:
-                if isclass(converter):
-                    target = cast(Type, converter)
-                else:
+            if is_type(converter):
+                target = cast(Type, converter)
+            else:
+                try:
+                    target = types.pop("return")
+                except KeyError:
                     raise TypeError("converter target is unknown") from None
     return source, target
 
diff --git a/apischema/serialization/__init__.py b/apischema/serialization/__init__.py
index 73b7058..c2fd7cc 100644
--- a/apischema/serialization/__init__.py
+++ b/apischema/serialization/__init__.py
@@ -177,12 +177,7 @@ class SerializationMethodVisitor(
         cls_to_check = Mapping if is_typed_dict(cls) else cls
 
         def wrapper(obj: Any) -> Any:
-            if isinstance(obj, cls_to_check):
-                try:
-                    return method(obj)
-                except Exception:
-                    pass
-            return fallback(obj)
+            return method(obj) if isinstance(obj, cls_to_check) else fallback(obj)
 
         return wrapper
 
diff --git a/apischema/serialization/serialized_methods.py b/apischema/serialization/serialized_methods.py
index 0ba02d4..80e5726 100644
--- a/apischema/serialization/serialized_methods.py
+++ b/apischema/serialization/serialized_methods.py
@@ -1,7 +1,7 @@
 from collections import defaultdict
 from dataclasses import dataclass
 from functools import wraps
-from inspect import Parameter, isclass, signature
+from inspect import Parameter, signature
 from typing import (
     Any,
     Callable,
@@ -24,7 +24,7 @@ from apischema.methods import method_registerer
 from apischema.ordering import Ordering
 from apischema.schemas import Schema
 from apischema.types import AnyType, Undefined, UndefinedType
-from apischema.typing import generic_mro, get_type_hints
+from apischema.typing import generic_mro, get_type_hints, is_type
 from apischema.utils import (
     deprecate_kwargs,
     get_args2,
@@ -61,7 +61,7 @@ class SerializedMethod:
     def types(self, owner: AnyType = None) -> Mapping[str, AnyType]:
         types = get_type_hints(self.func, include_extras=True)
         if "return" not in types:
-            if isclass(self.func):
+            if is_type(self.func):
                 types["return"] = self.func
             else:
                 raise TypeError("Function must be typed")


Test patch:
diff --git a/tests/integration/test_type_converter.py b/tests/integration/test_type_converter.py
new file mode 100644
index 0000000..cbd1433
--- /dev/null
+++ b/tests/integration/test_type_converter.py
@@ -0,0 +1,12 @@
+from apischema import deserialize, deserializer
+
+
+@deserializer
+class Foo:
+    def __init__(self, bar: int) -> None:
+        self.bar = bar
+
+
+def test_type_converter():
+    foo = deserialize(Foo, 42)
+    assert isinstance(foo, Foo) and foo.bar == 42