I would like to propose adding a new TypedMapping structural type, which (like Mapping vs dict) functions as a read-only equivalent of TypedDict. (This is a cross-post from typing-sig@, where our original proposal got refined.) # Specification ```python class Request(TypedMapping, total=False): some_field: string | None request: Request = { "some_field": "foo" } request2: Request = {} # fine because total=False request["some_field"] = None # error del request["some_field"] # error ``` TypedMapping is a structural type that mirrors TypedDict except for: 1) instances need not be subclasses of dict; 2) no mutate methods will be generated 3) subclasses can narrow field types (consequence of 2) Rules for 3 mirror Protocols. ## Multiple inheritance and TypedDict A type that inherits from a TypedMapping subclass and from TypedDict (either directly or indirectly): 4) is the structural intersection of its parents, or invalid if no such intersection exists 5) instances must be a dict subclass 6) adds mutate methods only for fields it explicitly (re)declares ```python class A(TypedMapping): field1: int field2: str class B(A, TypedDict): field1: int b: B = { "field1": 5, "field2": "value" } b["field1"] = 6 # Fine, mutate methods added in definition of B b["field2"] = "value2" # Error, field2 mutator not declared ``` # Use case We are making increasing use of TypedDict to type hint code that uses dictionaries to hold requests to and responses from services (both client and server side). However, we run into problems whenever any constraint is loosened, e.g. passing data from a response where a field is guaranteed to a request where it is optional: ```python class Response(TypedDict): some_field: string class Request(TypedDict, total=False): some_field: string | None class Server1: def fetch_data() -> Response: ... class Server2: def accept_data(request: Request): ... def my_code(server1: Server1, server2: Server2): data: Response = server1.fetch_data() server2.accept_data(data) # error: Response and Request are incompatible ```python This last line is not type-safe, as there are mutation operations on Request that aren't valid on Response. If we could remove those mutation operations from the Request type, then it would be a valid subtype of Response. This problem seems quite common, e.g. https://github.com/python/mypy/pull/12142. Pablo Galindo has agreed to sponsor a PEP.