Repository: seandstewart/typical (language: python)

Issue:
Support Types Defined in Local Namespaces
### Expected Behavior

```
from __future__ import annotations

import dataclasses
import typic

def foo():

    @dataclasses.dataclass
    class Inner:
        field: str

    @dataclasses.dataclass
    class Outer:
        inner: Inner

    proto = typic.protocol(Outer)

    return proto.transmute({"inner": {"field": "value"}})


if __name__ == '__main__':
    print(foo())


#> foo.<locals>.Outer(inner=foo.<locals>.Inner(field='value'))
```



Discussion / hints:

Fix patch:
diff --git a/pyproject.toml b/pyproject.toml
index f82e9e40..6f3b3732 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [tool.poetry]
 name = "typical"
 packages = [{include = "typic"}]
-version = "2.1.2"
+version = "2.1.3"
 description = "Typical: Python's Typing Toolkit."
 authors = ["Sean Stewart <sean_stewart@me.com>"]
 license = "MIT"
diff --git a/requirements.txt b/requirements.txt
index 934767f7..304480cb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -19,7 +19,8 @@ click==7.1.2 \
     --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \
     --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a
 colorama==0.4.4; sys_platform == "win32" \
-    --hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2
+    --hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 \
+    --hash=sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b
 coverage==5.3 \
     --hash=sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270 \
     --hash=sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4 \
@@ -58,11 +59,12 @@ coverage==5.3 \
 distlib==0.3.1 \
     --hash=sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb \
     --hash=sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1
-django==2.2.17 \
-    --hash=sha256:558cb27930defd9a6042133258caf797b2d1dee233959f537e3dc475cb49bd7c \
-    --hash=sha256:cf5370a4d7765a9dd6d42a7b96b53c74f9446cd38209211304b210fe0404b861
+django==2.2.20 \
+    --hash=sha256:2484f115891ab1a0e9ae153602a641fbc15d7894c036d79fb78662c0965d7954 \
+    --hash=sha256:2569f9dc5f8e458a5e988b03d6b7a02bda59b006d6782f4ea0fd590ed7336a64
 djangorestframework==3.12.2 \
-    --hash=sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7
+    --hash=sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7 \
+    --hash=sha256:0898182b4737a7b584a2c73735d89816343369f259fea932d90dc78e35d8ac33
 dnspython==2.0.0 \
     --hash=sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d \
     --hash=sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7
@@ -91,9 +93,9 @@ importlib-metadata==3.1.1; python_version < "3.8" \
     --hash=sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170
 inflection==0.3.1 \
     --hash=sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca
-jinja2==2.11.2 \
-    --hash=sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035 \
-    --hash=sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0
+jinja2==2.11.3 \
+    --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \
+    --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6
 joblib==0.17.0 \
     --hash=sha256:698c311779f347cf6b7e6b8a39bb682277b8ee4aba8cf9507bc0cf4cd4737b72 \
     --hash=sha256:9e284edd6be6b71883a63c9b7f124738a3c16195513ad940eae7e3438de885d5
@@ -124,20 +126,39 @@ markupsafe==1.1.1 \
     --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \
     --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \
     --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \
+    --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \
     --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
     --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \
+    --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \
+    --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \
+    --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \
     --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \
     --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \
     --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \
+    --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \
     --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \
     --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \
+    --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \
+    --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \
+    --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \
     --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \
     --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \
     --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \
     --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \
     --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \
+    --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \
+    --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \
+    --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \
     --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \
     --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \
+    --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \
+    --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \
+    --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \
+    --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \
+    --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \
+    --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 \
+    --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \
+    --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \
     --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b
 marshmallow==3.9.1 \
     --hash=sha256:e26763201474b588d144dae9a32bdd945cd26a06c943bc746a6882e850475378 \
@@ -279,9 +300,9 @@ pre-commit==1.21.0 \
     --hash=sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850
 py-cpuinfo==7.0.0 \
     --hash=sha256:9aa2e49675114959697d25cf57fec41c29b55887bff3bc4809b44ac6f5730097
-py==1.9.0 \
-    --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \
-    --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342
+py==1.10.0 \
+    --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a \
+    --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3
 pycodestyle==2.6.0 \
     --hash=sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367 \
     --hash=sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e
@@ -317,9 +338,9 @@ pygal==2.4.0 \
 pygaljs==1.0.2 \
     --hash=sha256:d75e18cb21cc2cda40c45c3ee690771e5e3d4652bf57206f20137cf475c0dbe8 \
     --hash=sha256:0b71ee32495dcba5fbb4a0476ddbba07658ad65f5675e4ad409baf154dec5111
-pygments==2.7.2 \
-    --hash=sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773 \
-    --hash=sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0
+pygments==2.7.4 \
+    --hash=sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435 \
+    --hash=sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337
 pymdown-extensions==7.1 \
     --hash=sha256:5bf93d1ccd8281948cd7c559eb363e59b179b5373478e8a7195cf4b78e3c11b6 \
     --hash=sha256:8f415b21ee86d80bb2c3676f4478b274d0a8ccb13af672a4c86b9ffd22bd005c
@@ -344,18 +365,28 @@ pytz==2020.4 \
 pytzdata==2020.1 \
     --hash=sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f \
     --hash=sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540
-pyyaml==5.3.1 \
-    --hash=sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f \
-    --hash=sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76 \
-    --hash=sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2 \
-    --hash=sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c \
-    --hash=sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2 \
-    --hash=sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648 \
-    --hash=sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a \
-    --hash=sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf \
-    --hash=sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97 \
-    --hash=sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee \
-    --hash=sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d
+pyyaml==5.4 \
+    --hash=sha256:f7a21e3d99aa3095ef0553e7ceba36fb693998fbb1226f1392ce33681047465f \
+    --hash=sha256:52bf0930903818e600ae6c2901f748bc4869c0c406056f679ab9614e5d21a166 \
+    --hash=sha256:a36a48a51e5471513a5aea920cdad84cbd56d70a5057cca3499a637496ea379c \
+    --hash=sha256:5e7ac4e0e79a53451dc2814f6876c2fa6f71452de1498bbe29c0b54b69a986f4 \
+    --hash=sha256:cc552b6434b90d9dbed6a4f13339625dc466fd82597119897e9489c953acbc22 \
+    --hash=sha256:0dc9f2eb2e3c97640928dec63fd8dc1dd91e6b6ed236bd5ac00332b99b5c2ff9 \
+    --hash=sha256:5a3f345acff76cad4aa9cb171ee76c590f37394186325d53d1aa25318b0d4a09 \
+    --hash=sha256:f3790156c606299ff499ec44db422f66f05a7363b39eb9d5b064f17bd7d7c47b \
+    --hash=sha256:124fd7c7bc1e95b1eafc60825f2daf67c73ce7b33f1194731240d24b0d1bf628 \
+    --hash=sha256:8b818b6c5a920cbe4203b5a6b14256f0e5244338244560da89b7b0f1313ea4b6 \
+    --hash=sha256:737bd70e454a284d456aa1fa71a0b429dd527bcbf52c5c33f7c8eee81ac16b89 \
+    --hash=sha256:7242790ab6c20316b8e7bb545be48d7ed36e26bbe279fd56f2c4a12510e60b4b \
+    --hash=sha256:cc547d3ead3754712223abb7b403f0a184e4c3eae18c9bb7fd15adef1597cc4b \
+    --hash=sha256:8635d53223b1f561b081ff4adecb828fd484b8efffe542edcfdff471997f7c39 \
+    --hash=sha256:26fcb33776857f4072601502d93e1a619f166c9c00befb52826e7b774efaa9db \
+    --hash=sha256:b2243dd033fd02c01212ad5c601dafb44fbb293065f430b0d3dbf03f3254d615 \
+    --hash=sha256:31ba07c54ef4a897758563e3a0fcc60077698df10180abe4b8165d9895c00ebf \
+    --hash=sha256:02c78d77281d8f8d07a255e57abdbf43b02257f59f50cc6b636937d68efa5dd0 \
+    --hash=sha256:fdc6b2cb4b19e431994f25a9160695cc59a4e861710cc6fc97161c5e845fc579 \
+    --hash=sha256:8bf38641b4713d77da19e91f8b5296b832e4db87338d6aeffe422d42f1ca896d \
+    --hash=sha256:3c49e39ac034fd64fd576d63bb4db53cda89b362768a67f07749d55f128ac18a
 regex==2020.11.13 \
     --hash=sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85 \
     --hash=sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70 \
@@ -546,19 +577,28 @@ typed-ast==1.4.1 \
     --hash=sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75 \
     --hash=sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652 \
     --hash=sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7 \
+    --hash=sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f \
     --hash=sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1 \
     --hash=sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa \
     --hash=sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614 \
     --hash=sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41 \
     --hash=sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b \
+    --hash=sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298 \
     --hash=sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe \
     --hash=sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355 \
     --hash=sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6 \
     --hash=sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907 \
     --hash=sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d \
+    --hash=sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d \
     --hash=sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c \
     --hash=sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4 \
     --hash=sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34 \
+    --hash=sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c \
+    --hash=sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072 \
+    --hash=sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91 \
+    --hash=sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d \
+    --hash=sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395 \
+    --hash=sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c \
     --hash=sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b
 typing-extensions==3.7.4.3 \
     --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f \
diff --git a/typic/serde/common.py b/typic/serde/common.py
index e822c28d..94f264d5 100644
--- a/typic/serde/common.py
+++ b/typic/serde/common.py
@@ -3,6 +3,7 @@
 import reprlib
 import sys
 import warnings
+from types import FrameType
 from typing import (
     Callable,
     Union,
@@ -229,7 +230,7 @@ class ForwardDelayedAnnotation:
     is_strict: Optional[st.StrictModeT] = None
     flags: Optional["SerdeFlags"] = None
     default: Any = _empty
-    localns: Optional[Mapping] = dataclasses.field(hash=False, default=None)
+    frame: Optional[FrameType] = dataclasses.field(default=None, hash=False)
     _name: Optional[str] = None
     _resolved: Optional["SerdeProtocol"] = dataclasses.field(default=None)
 
@@ -251,13 +252,16 @@ def resolved(self):
         if self._resolved is None:
             globalns = sys.modules[self.module].__dict__.copy()
             try:
-                type = evaluate_forwardref(self.ref, globalns or {}, self.localns or {})
+                type = evaluate_forwardref(self.ref, globalns, globalns)
             except NameError as e:
-                warnings.warn(
-                    f"Couldn't resolve forward reference: {e}. "
-                    f"Make sure this type is available in {self.module}."
-                )
-                type = Any
+                name = self.ref.__forward_arg__
+                type = util.extract(name, frame=self.frame)
+                if not type:
+                    warnings.warn(
+                        f"Couldn't resolve forward reference: {e}. "
+                        f"Make sure this type is available in {self.module}."
+                    )
+                    type = Any
             anno = self.resolver.annotation(
                 type,
                 name=self._name,
diff --git a/typic/serde/resolver.py b/typic/serde/resolver.py
index 07485718..cc9c0758 100644
--- a/typic/serde/resolver.py
+++ b/typic/serde/resolver.py
@@ -12,7 +12,6 @@
     Optional,
     ClassVar,
     Tuple,
-    Iterable,
     cast,
     Union,
     TypeVar,
@@ -24,7 +23,6 @@
 from typic.common import (
     EMPTY,
     ORIG_SETTER_NAME,
-    SERDE_ATTR,
     SERDE_FLAGS_ATTR,
     TYPIC_ANNOS_NAME,
     ObjectT,
@@ -183,46 +181,6 @@ def known(self, t: Type) -> bool:
     def delayed(self, t: Type) -> bool:
         return getattr(t, "__delayed__", False)
 
-    @lru_cache(maxsize=None)
-    def _get_serializer_proto(self, t: Type) -> SerdeProtocol:
-        tname = util.get_name(t)
-        if hasattr(t, SERDE_ATTR):
-            return getattr(t, SERDE_ATTR)
-        for attr, caller in self._DICT_FACTORY_METHODS:
-            if hasattr(t, attr):
-
-                def serializer(
-                    val,
-                    lazy: bool = False,
-                    name: util.ReprT = None,
-                    *,
-                    __prim=self.primitive,
-                    __call=caller,
-                    __tname=tname,
-                    __repr=util.joinedrepr,
-                ):
-                    name = name or __tname
-                    return {
-                        __prim(x): __prim(y, lazy=lazy, name=__repr(name, x))
-                        for x, y in __call(val).items()
-                    }
-
-                return SerdeProtocol(
-                    self.annotation(t),  # type: ignore
-                    deserializer=None,
-                    serializer=serializer,
-                    constraints=None,
-                    validator=None,
-                )
-
-        if checks.ismappingtype(t):
-            t = Mapping[Any, Any]
-        elif checks.iscollectiontype(t) and not issubclass(t, (str, bytes, bytearray)):
-            t = Iterable[Any]
-        settings = getattr(t, SERDE_FLAGS_ATTR, None)
-        serde: SerdeProtocol = self.resolve(t, flags=settings, _des=False)
-        return serde
-
     def primitive(self, obj: Any, lazy: bool = False, name: util.ReprT = None) -> Any:
         """A method for converting an object to its primitive equivalent.
 
@@ -261,7 +219,7 @@ def primitive(self, obj: Any, lazy: bool = False, name: util.ReprT = None) -> An
         if checks.isenumtype(t):
             obj = obj.value
             t = obj.__class__
-        proto: SerdeProtocol = self._get_serializer_proto(t)
+        proto: SerdeProtocol = self.resolve(t)
         return proto.primitive(obj, lazy=lazy, name=name)  # type: ignore
 
     def tojson(
@@ -314,7 +272,7 @@ def tojson(
         if checks.isenumtype(t):
             obj = obj.value
             t = obj.__class__
-        proto: SerdeProtocol = self._get_serializer_proto(t)
+        proto: SerdeProtocol = self.resolve(t)
         return proto.tojson(obj, indent=indent, ensure_ascii=ensure_ascii, **kwargs)
 
     @lru_cache(maxsize=None)
@@ -465,11 +423,10 @@ def annotation(
                 )
         # The type definition doesn't exist yet.
         if use.__class__ is ForwardRef:
-            module, localns = self.__module__, {}
+            module = self.__module__
             # Ideally we have a namespace from a parent class/function to the field
             if namespace:
                 module = namespace.__module__
-                localns = getattr(namespace, "__dict__", {})
 
             return ForwardDelayedAnnotation(
                 ref=use,
@@ -481,7 +438,7 @@ def annotation(
                 flags=flags,
                 default=default,
                 module=module,
-                localns=localns,
+                frame=inspect.currentframe(),
             )
         # The type definition is recursive or within a recursive loop.
         elif use is namespace or use in self.__stack:
diff --git a/typic/types/frozendict.py b/typic/types/frozendict.py
index ed89d1e4..e71fa62f 100644
--- a/typic/types/frozendict.py
+++ b/typic/types/frozendict.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: UTF-8 -*-
 import copy
+import inspect
 from operator import attrgetter
 from typing import Union, Tuple, List, Any, TypeVar, Mapping, Generic, FrozenSet
 from collections.abc import Hashable
@@ -136,7 +137,7 @@ def mutate(self, other: Mapping = None, **kwargs) -> "FrozenDict":
 
 
 def freeze(o: Any, *, __hashgetter=_hashgetter) -> FrozenT:
-    if __hashgetter(o):
+    if __hashgetter(o) or inspect.isclass(o) or inspect.isfunction(o):
         return o
 
     if isinstance(o, set):
diff --git a/typic/util.py b/typic/util.py
index 0ed6b223..4cd837a3 100644
--- a/typic/util.py
+++ b/typic/util.py
@@ -8,6 +8,7 @@
 import functools
 import inspect
 import sys
+import types
 from datetime import date, datetime, timedelta, time
 from threading import RLock
 from types import MappingProxyType, MemberDescriptorType
@@ -46,6 +47,7 @@
     "cached_simple_attributes",
     "cached_type_hints",
     "cachedmethod",
+    "extract",
     "fastcachedmethod",
     "filtered_repr",
     "guard_recursion",
@@ -800,3 +802,21 @@ def get_tag_for_types(types: Tuple[Type, ...]) -> Optional[TaggedUnion]:
                 tag=tag, types=types, isliteral=literal, types_by_values=tbv
             )
     return None
+
+
+def extract(name: str, *, frame: types.FrameType = None) -> Optional[Any]:
+    """Extract `name` from the stacktrace of `frame`.
+
+    If `frame` is not provided, this function will use the current frame.
+    """
+    frame = frame or inspect.currentframe()
+    seen = set()
+    while frame and frame not in seen:
+        if name in frame.f_globals:
+            return frame.f_globals[name]
+        if name in frame.f_locals:
+            return frame.f_locals[name]
+        seen.add(frame)
+        frame = frame.f_back
+
+    return None


Test patch:
diff --git a/tests/test_typed.py b/tests/test_typed.py
index 1cc2edde..60ecc4ea 100644
--- a/tests/test_typed.py
+++ b/tests/test_typed.py
@@ -1093,3 +1093,18 @@ def test_tagged_union_validate(annotation, value):
 def test_tagged_union_validate_invalid(annotation, value):
     with pytest.raises(ConstraintValueError):
         validate(annotation, value)
+
+
+def test_local_namespace():
+    @dataclasses.dataclass
+    class Inner:
+        field: str
+
+    @dataclasses.dataclass
+    class Outer:
+        inner: Inner
+
+    proto = resolver.resolve(Outer)
+
+    obj = proto.transmute({"inner": {"field": "value"}})
+    assert isinstance(obj.inner, Inner)