Skip to content

Commit 644f11b

Browse files
authored
Fix TypedDict values not validated when types are forward references (#722)
1 parent df3050d commit 644f11b

File tree

3 files changed

+26
-5
lines changed

3 files changed

+26
-5
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ Fixed
2525
^^^^^
2626
- ``set_parsing_settings(validate_defaults=True)`` fails when the parser has a
2727
config action (`#718 <https://github.com/omni-us/jsonargparse/pull/718>`__).
28-
- Regression causing dump/save to fail when ``skip_link_targets=True`` and target
29-
being an entire required dataclass (`#717
28+
- Regression causing dump/save to fail when ``skip_link_targets=True`` and
29+
target being an entire required dataclass (`#717
3030
<https://github.com/omni-us/jsonargparse/pull/717>`__).
31+
- ``TypedDict`` values not validated when types are forward references (`#722
32+
<https://github.com/omni-us/jsonargparse/pull/722>`__).
3133

3234
Changed
3335
^^^^^^^

jsonargparse/_typehints.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
from copy import deepcopy
1212
from enum import Enum
1313
from functools import partial
14+
from importlib import import_module
1415
from types import FunctionType, MappingProxyType
1516
from typing import (
1617
Any,
1718
Callable,
1819
Dict,
20+
ForwardRef,
1921
Iterable,
2022
List,
2123
Literal,
@@ -719,6 +721,15 @@ def raise_union_unexpected_value(subtypes, val: Any, exceptions: List[Exception]
719721
) from exceptions[0]
720722

721723

724+
def resolve_forward_ref(ref):
725+
if not isinstance(ref, ForwardRef) or not ref.__forward_module__:
726+
return ref
727+
728+
aliases = __builtins__.copy()
729+
aliases.update(vars(import_module(ref.__forward_module__)))
730+
return aliases.get(ref.__forward_arg__, ref)
731+
732+
722733
def adapt_typehints(
723734
val,
724735
typehint,
@@ -954,7 +965,8 @@ def adapt_typehints(
954965
if extra_keys:
955966
raise_unexpected_value(f"Unexpected keys: {extra_keys}", val)
956967
for k, v in val.items():
957-
val[k] = adapt_typehints(v, typehint.__annotations__[k], **adapt_kwargs)
968+
subtypehint = resolve_forward_ref(typehint.__annotations__[k])
969+
val[k] = adapt_typehints(v, subtypehint, **adapt_kwargs)
958970
if typehint_origin is MappingProxyType and not serialize:
959971
val = MappingProxyType(val)
960972
elif typehint_origin is OrderedDict:
@@ -1100,6 +1112,11 @@ def adapt_typehints(
11001112
elif is_alias_type(typehint):
11011113
return adapt_typehints(val, get_alias_target(typehint), **adapt_kwargs)
11021114

1115+
else:
1116+
if str(typehint) == "+VT_co":
1117+
return val # required for typing.Mapping in python 3.8
1118+
raise RuntimeError(f"The code should never reach here: typehint={typehint}") # pragma: no cover
1119+
11031120
return val
11041121

11051122

jsonargparse_tests/test_typehints.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,8 +753,7 @@ def test_invalid_inherited_unpack_typeddict(parser, init_args):
753753
parser.parse_args([f"--testclass={json.dumps(test_config)}"])
754754

755755

756-
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Python 3.8 lacked runtime inspection of TypedDict required keys")
757-
def test_typeddict_totality_inheritance(parser):
756+
if sys.version_info >= (3, 9):
758757

759758
class BottomDict(TypedDict, total=True):
760759
a: int
@@ -765,6 +764,9 @@ class MiddleDict(BottomDict, total=False):
765764
class TopDict(MiddleDict, total=True):
766765
c: int
767766

767+
768+
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Python 3.8 lacked runtime inspection of TypedDict required keys")
769+
def test_typeddict_totality_inheritance(parser):
768770
parser.add_argument("--middledict", type=MiddleDict, required=False)
769771
parser.add_argument("--topdict", type=TopDict, required=False)
770772
assert {"a": 1} == parser.parse_args(['--middledict={"a": 1}'])["middledict"]

0 commit comments

Comments
 (0)