Repository: seandstewart/typical (language: python)

Issue:
Transmute is broken with dicts of dataclasses with default values
* typical version: 2.7.1
* Python version: 3.7.6
* Operating System: Linux

### Description

Transmute seems to be badly broken in 2.7.1 (2.6.4 works correctly) with dicts of dataclasses with default values. 

### What I Did

```
from dataclasses import dataclass
from typing import Dict

import typic

@dataclass()
class FieldOverrideSpec:
    target_name: str
    data_type: str = "default"

FieldOverrideConfig = Dict[str, FieldOverrideSpec]

typic.protocol(FieldOverrideConfig).transmute(
    {"foo": {"target_name": "foo1", "data_type": "int"}}
)
```

The result I get is `{'target_name': FieldOverrideSpec(target_name='data_type', data_type='default')}` which is obviously completely wrong. Removing the default value from the dataclass makes transmute work correctly.

Transmute is broken with dicts of dataclasses with default values
* typical version: 2.7.1
* Python version: 3.7.6
* Operating System: Linux

### Description

Transmute seems to be badly broken in 2.7.1 (2.6.4 works correctly) with dicts of dataclasses with default values. 

### What I Did

```
from dataclasses import dataclass
from typing import Dict

import typic

@dataclass()
class FieldOverrideSpec:
    target_name: str
    data_type: str = "default"

FieldOverrideConfig = Dict[str, FieldOverrideSpec]

typic.protocol(FieldOverrideConfig).transmute(
    {"foo": {"target_name": "foo1", "data_type": "int"}}
)
```

The result I get is `{'target_name': FieldOverrideSpec(target_name='data_type', data_type='default')}` which is obviously completely wrong. Removing the default value from the dataclass makes transmute work correctly.



Discussion / hints:

Fix patch:
diff --git a/pyproject.toml b/pyproject.toml
index 05d94a9b..c241454e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [tool.poetry]
 name = "typical"
 packages = [{include = "typic"}]
-version = "2.7.1"
+version = "2.7.2"
 description = "Typical: Python's Typing Toolkit."
 authors = ["Sean Stewart <sean_stewart@me.com>"]
 license = "MIT"
@@ -157,7 +157,7 @@ requires = ["poetry>=0.12"]
 build-backend = "poetry.masonry.api"
 
 [bumpver]
-current_version = "v2.6.3"
+current_version = "v2.7.2"
 version_pattern = "vMAJOR.MINOR.PATCH[-TAGNUM]"
 commit_message = "bump version to {new_version} [ci skip]"
 tag = false
@@ -165,5 +165,5 @@ push = true
 commit = true
 
 [bumpver.file_patterns]
-"pyproject.toml" = [ 'version = "{pep440_version}"' ]
+"pyproject.toml" = [ 'version = "{pep440_version}"', 'current_version = "{version}"' ]
 "typic/__init__.py" = ['__version__ = "{pep440_version}"']
diff --git a/typic/__init__.py b/typic/__init__.py
index 962f39d8..98d4cef7 100644
--- a/typic/__init__.py
+++ b/typic/__init__.py
@@ -13,4 +13,4 @@
 al = typed
 
 
-__version__ = "2.7.1"
+__version__ = "2.7.2"
diff --git a/typic/checks.py b/typic/checks.py
index 868faba7..273b7958 100644
--- a/typic/checks.py
+++ b/typic/checks.py
@@ -43,6 +43,7 @@
     TypeGuard,
     TypedDict,
     Protocol,
+    Record,
 )
 
 ObjectT = TypeVar("ObjectT")
@@ -533,7 +534,11 @@ def ismappingtype(obj: Type[ObjectT]) -> TypeGuard[Type[Mapping]]:
     True
     """
     obj = util.origin(obj)
-    return builtins.issubclass(obj, dict) or builtins.issubclass(obj, Mapping)
+    return (
+        builtins.issubclass(obj, dict)
+        or builtins.issubclass(obj, Mapping)
+        or builtins.issubclass(obj, Record)
+    )
 
 
 @lru_cache(maxsize=None)
diff --git a/typic/compat.py b/typic/compat.py
index ad2a76df..be949399 100644
--- a/typic/compat.py
+++ b/typic/compat.py
@@ -36,6 +36,12 @@ class sqla_registry:  # type: ignore
         ...
 
 
+try:  # pragma: nocover
+    from asyncpg import Record
+except (ImportError, ModuleNotFoundError):
+    Record = dict
+
+
 if sys.version_info < (3, 7):  # pragma: nocover
     if TYPE_CHECKING:
 
diff --git a/typic/ext/json.py b/typic/ext/json.py
index e1e9f6e9..5fcd6058 100644
--- a/typic/ext/json.py
+++ b/typic/ext/json.py
@@ -93,9 +93,9 @@ def tojson(
                     **kwargs,
                 )
 
-            return tojson
-
             tojson.__module__ = get_tojson.__module__
             tojson.__doc__ = json.dumps.__doc__
 
+            return tojson
+
         dumps, loads = json.dumps, json.loads
diff --git a/typic/serde/des.py b/typic/serde/des.py
index 112756db..33d7e456 100644
--- a/typic/serde/des.py
+++ b/typic/serde/des.py
@@ -41,6 +41,7 @@
     get_name,
     slotted,
 )
+from typic.checks import ismappingtype
 from typic.common import DEFAULT_ENCODING, ObjectT
 from typic.compat import TypeGuard, Literal
 from .common import (
@@ -489,16 +490,20 @@ def _build_mapping_des(self, context: BuildContext):
         else:
             self._add_type_check(func, anno_name)
         # Write the lines.
-        with func.b("try:") as b:
-            b.l(f"{self.VNAME} = {line_values}")
-        with func.b("except (TypeError, ValueError):") as b:
+        with func.b(f"if ismappingtype({self.VTYPE}):") as b:
             b.l(f"{self.VNAME} = {line}")
+        with func.b("else:") as ob:
+            with ob.b("try:") as b:
+                b.l(f"{self.VNAME} = {line_values}")
+            with ob.b("except (TypeError, ValueError):") as b:
+                b.l(f"{self.VNAME} = {line}")
         func.namespace.update(
             {
                 kd_name: key_des,
                 it_name: item_des,
                 "Mapping": abc.Mapping,
                 "iterate": self.resolver.iterate,
+                "ismappingtype": ismappingtype,
             }
         )
 


Test patch:
diff --git a/tests/test_serdes.py b/tests/test_serdes.py
index f491a06f..c648d59d 100644
--- a/tests/test_serdes.py
+++ b/tests/test_serdes.py
@@ -205,6 +205,16 @@ def test_serde_serialize(t, obj, prim):
             {objects.FooNum.bar: objects.Typic(var="foo")},
             {"bar": {"var": "foo"}},
         ),
+        (
+            Dict[str, objects.Typical],
+            {"key": objects.Typical(bar="bar", id=1)},
+            {"key": {"bar": "bar", "id": 1}},
+        ),
+        (
+            Dict[str, objects.Typical],
+            {"key": objects.Typical(bar="bar")},
+            {"key": {"bar": "bar"}},
+        ),
         (
             GenDict[str, int],
             {"foo_bar": 2},