-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to accept dicts with extra keys? #4617
Comments
There is a cool new feature called |
This looks like a feature request for TypedDict. |
@gvanrossum Actually I am quite sure |
Hm, I see one problem: potentially, the list of optional keys can be very long (since one still needs to list them). |
Sadly no -- # ...imports...
A = TypedDict('A', {'x': int}, total=False)
def f(a: A) -> None:
print(a['x'])
b: A = {'x': 0, 'y': 0} # E: Extra key 'y' for TypedDict "A" |
Yes, I think this is a valid feature request, as enumerating keys in the |
This is already supported for function arguments: from mypy_extensions import TypedDict
A = TypedDict('A', {'x': int})
B = TypedDict('B', {'x': int, 'y': str})
def f(x: A) -> None: ...
b: B = {'x': 1, 'y': 'foo'}
f(b) # Ok The issues seems specific to creating a typed dict -- mypy makes sure that no extra keys are provided (as these could be accidental typos or obsolete key names, for example). I don't think that changing the semantics of
These semantics seem kind of arbitrary to me. To seriously consider such as feature I'd like to see more concrete examples where the current behavior is not sufficient. If the structure of a typed dict object is not well specified, the current recommendation is to use If we had intersection types, a similar effect to my proposed |
Thanks. I think that I can work with the current behavior. JukkaL's example is what I am trying to do. Something like: from typing import Iterable
from mypy_extensions import TypedDict
NamedThing = TypedDict('NamedThing', {'name': str})
Movie = TypedDict('Movie', {'name': str, 'year': int})
Replicant = TypedDict('Replicant', {'name': str, 'model': str})
def slug(x: NamedThing) -> str:
return x['name'].lower().replace(' ', '-')
blade_runner: Movie = {'name': 'Blade Runner', 'year': 1982}
roy: Replicant = {'name': 'Roy', 'model': 'Nexus-6'}
things: Iterable[NamedThing] = [blade_runner, roy]
for thing in things:
print(slug(thing)) When trying Mypy I was directly assigning the values or using variables without annotations, like: blade_runner: NamedThing = {'name': 'Blade Runner', 'year': 1982}
slug({'name': 'Blade Runner', 'year': 1982})
blade_runner = {'name': 'Blade Runner', 'year': 1982}
slug(blade_runner) |
I'm closing this since it seems that the current behavior is mostly good enough, and the solution to the larger issue would be messy. |
* Now we use TypedDict to validate JSON-based dictionaries at the time of code writing. * Currently TypedDict has some limitations, but they are the issues only during "static" type checks, not in the runtime. - It does not allow extra keys (in contrast to trafaret.Dict) ref: python/mypy#4617 - dict is not a subtype of TypedDict; instead we should use Mapping and MutableMapping.
I would like this issue to be re-opened. I think The current solution of Typescript example: // Simple example
interface ElasticsearchDocument {
_id: string
_type: string
_index: string
@timestamp: number
[key: string]: string|number|boolean;
}
// Complex example: Nested dict
interface SubObject {
[key: string]: string|number|boolean|SubObject
}
interface DynamoDBDocument {
index_id: number
secondary_index_id: number
[key: string]: string|number|boolean|SubObject
} The above suggestion of I don't have a good sense of the restrictions on syntax that MyPy has to deal with. So this proposal might be totally unreasonable. But the syntax could be: TypedDict("ElasticsearchDocument",
{"_id": str},
extra_keys={str: Union[str,int,float,bool]}) This says: "this is a dictionary with Theoretically you could spec something like this: TypedDict("ComplexDict",
{1: str, "a": int},
extra_keys={int: bool, str: str}) |
I would also like to see extra keys allowed. My use case is in a HTTP/REST client: class SomeApiClient:
class RegisterRequest(TypedDict):
...
class RegisterResponse(TypedDict):
...
def register(self, payload: RegisterRequest) -> RegisterResponse
...
return resp_json
I would happily risk typoing (as explained in #6223 (comment)) just to have extra keys (it would be optional feature, anyway). |
This might be solveable using Protocols, Literals and |
Can this issue be reopened? |
@JukkaL As @alexjurkiewicz mentions, the advantage of allowing extra keys is that then From a practical perspective: Python is a language that integrates many different systems, protocols, etc., it's not infrequent for various protocols to have slightly different extensions—the point of standardizations is to try to avoid that, but it's seemed inevitable. Since a lot of protocols communicate in JSON, having TypedDict's that expect several fields for sure but allow any other fields is useful. This is also the case when you have several versions of a protocol—you make the never one a superset of the older one to be backwards compatible. This is not a duplication of the So you could have From a programming language theoretical perspective: The topic of typed extensible records is not new, but dates to Mitchell & Wand in the 70s. A recent paper about this can be found here:
You may be amused to know the paper begins this way 😊🤗:
It then goes on:
@gvanrossum I suspect having the field |
A use case that I I think I have with this is integrating with a legacy MongoDB API where a given record could have a bunch of key/value pairs, but I'm really only concerned with say 3-4 of them in the codebase I'm working with. I want to strongly type the 3-4 that I'm concerned with, and not worry about the other ones but rather just pass them around. |
Linking this discussion on typing-sig https://mail.python.org/archives/list/[email protected]/thread/66RITIHDQHVTUMJHH2ORSNWZ6DOPM367/#QYOBBLTWVSEWMFRRHBA2OPR5QQ4IMWOL |
As mentioned in the typing-sig thread, this probably requires a PEP. The author of the PEP should figure out how the subtyping rules would need to be extended and some other technical details. I'm not against making TypedDicts more flexible with a flag that allows arbitrary keys with |
This is really, really, annoying. What we really want is (psuedocode): ```python class SubValues(TypedDict[str, str], total=False): @input@: T.List[str] @output@: T.List[str] ``` Which would specifiy that `@INPUT@` and `@OUTPUT@` *may* be present and if they are, then they are lists. There may be additional keys, which have a single string as a value. Of course there is currently no way to do that with typing, so we have to instead take a union type and then use asserts to help the type checker unerstand this. More info: python/mypy#4617
This is really, really, annoying. What we really want is (psuedocode): ```python class SubValues(TypedDict[str, str], total=False): @input@: T.List[str] @output@: T.List[str] ``` Which would specifiy that `@INPUT@` and `@OUTPUT@` *may* be present and if they are, then they are lists. There may be additional keys, which have a single string as a value. Of course there is currently no way to do that with typing, so we have to instead take a union type and then use asserts to help the type checker unerstand this. More info: python/mypy#4617
@JukkaL I am unfamiliar with all the places requests etc. go - Are you aware of this being discussed/worked on more recently? I'm looking for the same functionality, TypedDict where it allows arbitrary other keys. Thanks! |
Anyone has a good solution for that? |
For the time being, if you know what these extra keys are that may or may not be in the dictionary ahead of time, you can include them in the definition but mark them as |
I would also find this very useful. TypedDicts are very limiting without this. I would have to go back to using a generic |
I second the notion that this issue be re-opened. Coming over from Typescript this has been my biggest pain point with mypy and Python's typing in general. Discovering this was a helpful surprise, but it's still not good enough - I'm forced to cast dictionaries I'm quite sure fullfill the TypedDict's conditions, which feels like an admission of defeat. To bring it back into the scope of mypy (instead of delegating it to PEP) I'd suggest adding a new configuration flag.
|
I don't think we need a new flag for this, I think we can simply:
Then people will be able to simply say |
See: [python#4617](python#4617) This allows the following code to trigger the error `typeddict-unknown-key` ```python A = T.TypedDict("A", {"x": int}) def f(x: A) -> None: ... f({"x": 1, "y": "foo"}) ``` The user can then safely ignore this specific error at their disgression.
@ilevkivskyi I submitted a small PR for this. Feel free to (Apologies if pinging is considered rude) |
Fixes #4617 This allows the following code to trigger the error `typeddict-unknown-key` ```python A = T.TypedDict("A", {"x": int}) def f(x: A) -> None: ... f({"x": 1, "y": "foo"}) # err: typeddict-unknown-key f({"y": "foo"}) # err: typeddict-unknown-key & typeddict-item f({"x": 'err', "y": "foo"}) # err: typeddict-unknown-key & typeddict-item a: A = { 'x': 1 } # You can set extra attributes a['extra'] = 'extra' # err: typeddict-unknown-key # Reading them produces the normal item error err = a['does not exist'] # err: typeddict-item ``` The user can then safely ignore this specific error at their disgression. Co-authored-by: Ivan Levkivskyi <[email protected]>
For posterity, if you want mypy to not complain about unknown/extra keys in a TypedDict you can use |
For Toblerity/Fiona#1125 we are trying to mimic the GeoJSON specification. While there are some reserved keys like
I think this actually does not work. Are there working approaches different from the two below? Two examples: 1. should pass, fails, playgroundfrom typing import Any, Protocol, Literal, overload
class SomeSpecificKeysDict(Protocol):
@overload
def __getitem__(self, key: Literal["foo"]) -> str:
...
@overload
def __getitem__(self, key: Literal["bar"]) -> int:
...
my_dict: SomeSpecificKeysDict = { "foo": "a string", "bar": 2 } This should pass, but it errors with
2. Should fail, passes, playgroundfrom typing import Any, Protocol, Literal, overload
class SomeSpecificKeysDict(Protocol):
@overload
def __getitem__(self, key: Literal["foo"]) -> str:
...
@overload
def __getitem__(self, key: Literal["bar"]) -> int:
...
@overload
def __getitem__(self, key) -> Any:
...
my_dict: SomeSpecificKeysDict = { "foo": "a string", "bar": "another_string" }
|
The The { [string]: any; } The problems with the "ignore errors" approach are obvious:
Thus I believe this issue should be reopened; the proposed solution is just an unacceptable hack. |
@alecov, if you'd like to propose or endorse changes to the Python static type system, the python/typing discussion forum would be a better place than the mypy issue tracker. Mypy is correctly implementing support for TypedDict as it is currently spec'ed. (BTW, I agree with many of your points above.) |
I also miss this a lot in the current TypedDict functionality, which makes it unsuitable to use in old code bases where I have to deal with large dicts with only a few keys that are of interest for the code that I am refactoring at that moment. Covering all keys that are not of direct importance would add way too much boilerplate to the code base. The same for using For new code we avoid dicts between (public) functions anyway in favor of dataclasses. Allowing unknown keys is largely only a problem because of mutability (invariance). When I have that problem with ordinary dicts, I solve it with the Mapping[str, object] type, which disallows the mutable behaviour. |
This opens up a hole like this: interface A {
value: string;
[key: string]: string | number;
}
interface B extends A {
foo: number;
}
const x: B = {value: "asd", foo: 12}
function mut(v: A) {
v.foo = "asd"
}
mut(x)
const y: B = x
console.log(y) // {"value": "asd", "foo": "asd"} If we are bringing this in Python, do we actually want it to be a sound system, if that's actually possible? |
@PIG208 TypedDicts are, arguably, structural types and allow behaviors that, if you view TypedDicts as nominal types, would appear unsound (e.g. if you think of |
For those wanting this ticket reopened, I just opened a feature request which may be of interest: |
A lot of times I pass data around using dictionaries. When I receive the data, for example as a function argument, what I want is a dict with that contains some specifics keys. I don't mind if the dict has more data.
So, I tried typed dicts:
This works:
But this throws the error
Extra key 'has_sequel' for TypedDict "Movie"
:I can understand that you can't replace for the first value, because the result of
keys
oritems
is different.But if I am only interested in having those keys, not iterating or other stuff, what are my options (if any)?
The text was updated successfully, but these errors were encountered: