Skip to content

Commit c8e5fc0

Browse files
codewizdaveclaude
andcommitted
feat: add ReadOnly qualifier support for NewTypedDict
- Handle ReadOnly qualifier in addition to NotRequired - Support multiple qualifiers (ReadOnly + NotRequired) - Add tests for ReadOnly functionality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 64992f9 commit c8e5fc0

2 files changed

Lines changed: 36 additions & 4 deletions

File tree

packages/typemap/src/typemap/type_eval/_eval_operators.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,11 +1304,15 @@ def _eval_NewTypedDict(*etyps: Member, ctx):
13041304
typ = _eval_types(typ, ctx)
13051305
tquals = _eval_types(quals, ctx)
13061306

1307-
# Handle NotRequired qualifier for TypedDict
1307+
# Handle qualifiers for TypedDict fields
1308+
annotations = []
13081309
if type_eval.issubtype(typing.Literal["NotRequired"], tquals):
1309-
# NotRequired means the field is optional
1310-
# In TypedDict, this is handled differently
1311-
annos[name] = typing.Annotated[typ, typing.NotRequired]
1310+
annotations.append(typing.NotRequired)
1311+
if type_eval.issubtype(typing.Literal["ReadOnly"], tquals):
1312+
annotations.append(typing.ReadOnly)
1313+
1314+
if annotations:
1315+
annos[name] = typing.Annotated[typ, *annotations]
13121316
else:
13131317
annos[name] = typ
13141318

packages/typemap/tests/test_type_eval.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3306,6 +3306,34 @@ def test_newtypeddict_optional_field():
33063306
assert hasattr(result.__annotations__["optional_field"], "__metadata__")
33073307

33083308

3309+
def test_newtypeddict_readonly_field():
3310+
"""Test NewTypedDict with ReadOnly qualifier marks field as read-only."""
3311+
3312+
result = eval_typing(
3313+
NewTypedDict[
3314+
Member[Literal["id"], int],
3315+
Member[Literal["name"], str, Literal["ReadOnly"]],
3316+
]
3317+
)
3318+
assert result.__annotations__["id"] is int
3319+
# ReadOnly fields should be wrapped with Annotated
3320+
assert hasattr(result.__annotations__["name"], "__metadata__")
3321+
3322+
3323+
def test_newtypeddict_multiple_qualifiers():
3324+
"""Test NewTypedDict with both NotRequired and ReadOnly qualifiers."""
3325+
3326+
result = eval_typing(
3327+
NewTypedDict[
3328+
Member[Literal["id"], int, Literal["ReadOnly"]],
3329+
Member[Literal["optional_name"], str, Literal["NotRequired", "ReadOnly"]],
3330+
]
3331+
)
3332+
# Both should have metadata
3333+
assert hasattr(result.__annotations__["id"], "__metadata__")
3334+
assert hasattr(result.__annotations__["optional_name"], "__metadata__")
3335+
3336+
33093337
def test_newtypeddict_with_iter_attrs():
33103338
"""Test NewTypedDict using Iter and Attrs to create from existing class."""
33113339

0 commit comments

Comments
 (0)