You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Thanks all — a new version of types-requests should be uploaded to PyPI shortly. Feel free to leave a comment here or open a new issue if there are still problems with the new release :)
I think this issue should be reopened. The problem here I believe is the usage of MutableMapping which is invariant. Mypy recommends using immutable collections as annotations when possible so I believe Mapping should be used here (if it's true that it is not mutated), otherwise we might be stuck.
Edit: sorry, didn't see the new issue opened, which properly describes the problem.
I am not really sure if this bug should be closed: Using MutableMapping instead of Mapping seems to be wrong at several other places, too: _TextMapping, which is mutable, is e.g. used for headers or cookies kwargs, but I somehow doubt that those arguments are actually mutated.
This incorrect(?) variance leads to quite a few false positives on our SW, and I guess we're not alone with this...
@svenpanne_TextMapping is now the only MutableMapping left that's used in a function argument. I also doubt that it's mutated, I'll do some analysis later to see if it can be changed.
Out of curiosity, are you getting these false positives because of the invariance of MutableMapping (i.e. you're passing a MutableMapping with a subtype of str as the value), or because you're passing a non-mutable Mapping?
It's mainly because we pass Mappings as parameters for cookies/headers/proxies IIRC, I would need to take a closer look if we make use of covariance in the values.
As a temporary measure, we just copy the Mappings until this bug here is fixed: Checkmk/checkmk@0447c08
This would also make me a bit hesitant to change headers and cookies to Mapping. In the end requests documentation asks for a dictionary for all three.
Another thing worth considering is that to keep things internally consistent, you'd have to make Request.headers etc. also a Mapping. Request.headers is mutated (internally), and it would also break type checking on code like:
r = Request()
r.headers['..'] = '..'
I can imagine that more people use Mapping for these parameters though, so could be worth it, but it's a balancing act.
I don't think that this is a balancing act, regarding typing things are always crystal clear: If a parameter gets mutated by a function/method it must be a MutableMapping, using just a Mapping would not be sound (in other words: totally wrong). If it does not get mutated, using a Mapping would be the much better choice: Although a MutableMapping would be sound, too (every MutableMapping is a Mapping after all), this would unnecessarily restrict the function/method (it would require more than is needed). In other words: Parameters should be typed as unspecific as possible (while still being sound) and return values as specific as possible. This will ensure maximum flexibilty.
request's documentation is a bit crappy here IMHO, and the API is more than surprising: Even though it talks about dictionaries, I would have never expected that those input parameters are mutated! Looking at your proxies example, it's clear that this kwarg is mutated, but this doesn't necessarily mean that headers and cookies are mutated, too. One has to figure that out...
Regarding;
r = Request()
r.headers['..'] = '..'
It is far from clear that the headers property returns the same object which has been passed via the headers kwarg. Quite the opposite: It's an anti-pattern to return a reference to a mutable part of an object, so it might be a copy, I don't know. And if you get a mutable argument, it's more often than not a good idea to keep a copy of it, protecting you against mutation behind your back. But I don't know the internals of requests enough to say what is actually being done there...
In any case: It's already very helpful to know that proxies are mutated, I'll definitely need to have a closer look at our SW, I am not sure if it's really prepared for that.
It is far from clear that the headers property returns the same object which has been passed via the headers kwarg.
That is what is happening internally. So making headers/cookies a Mapping in Session.request but MutableMapping in the attributes of Request would internally be inconsistent. From an API perspective it would still be correct (you can't access the Request made by Session.request).
The problem is these are stubs for a third party. Typeshed can decide to make the parameters as permissive as the current implementation allows, but we don't know what the authors actually intended. Following documentation would be safer, but also leads to false positives. So that's where the balacing lies.
However it's also possible that requests' authors didn't intend the input argument to proxies to be mutated, since it's kind of dangerous, so it's also an option to get this changed in requests.
When "balancing", we usually consider false negatives (no errors for wrong code) less bad than false positives (errors for correct code).
So making headers/cookies a Mapping in Session.request but MutableMapping in the attributes of Request
In general, Mapping in an argument and MutableMapping in the corresponding attribute is fine. But I don't think it makes sense here:
If I understood correctly, you will get a runtime error if you pass in an immutable mapping. Type checking is supposed to prevent things like this.
MutableMapping[str, str] doesn't have variance issues (unless you have a custom str subclass which is rare), so we don't need Mapping just to work around that. This is different from files, which uses a union type inside the MutableMapping.
If people are still getting what they believe to be false-positive errors following #7696, it would be helpful if they could post a minimal repro and the exact error message they're getting 🙂 otherwise this is all purely theoretical
When "balancing", we usually consider false negatives (no errors for wrong code) less bad than false positives (errors for correct code).
I am not exactly sure who "we" is, but type checkers for other programming languages actually do the exact opposite thing and issue an error if code can not be proven correct. In other words, they err on the conservative side, because having the guarantee that things can't go wrong at runtime if the type checker is silent is far more important than any "inconvenience" caused by this conservatism. Due to Rice's theorem (all non-trivial, semantic properties of programs are undecidable) there will always be programs which don't go wrong, but can't be proven safe by any type checker. This is not as bad as it actually sounds, there are basically always ways to make your intentions clear to a type checker by rewriting things slightly.
If you do not err on the conservative side, type checkers are relatively useless.
[...] In general, Mapping in an argument and MutableMapping in the corresponding attribute is fine. [...]
Huh? Perhaps I'm misunderstanding things, but this doesn't sound fine at all: If you pass an immutable data structure into some class, which remembers the same data structure internally and makes the very same data structure available as a property, and then claim that the data structure returned is mutable, something has gone seriously wrong.
In other words: In such a scenario as above (argument remembered internally and exposed as a property, no copying/cloning involved), then the argument and the property must have the same type. From a variance POV: The class is a consumer and a producer, so we have invariance.
Coming back to our proxies kwarg and its related property, things are quite clear now: Both have to have the same type, so both have to be either Mapping or MutableMapping. But the former would be a lie, because requests seems to mutate the argument, so this leaves us with: Both kwarg and property have to be a MutableMapping.
This will cause some type errors, e.g. in our own project, but this is a good thing: We weren't aware of the fact that the argument can be mutated, so we have to fix things on our side! This is what type checking is all about...
Regarding cookies and headers: Are these handled the same way in requests, i.e. no copying + exposed as a property + mutated internally? If yes, the same reasoning applies.
If people are still getting what they believe to be false-positive errors following #7696, it would be helpful if they could post a minimal repro and the exact error message they're getting 🙂 otherwise this is all purely theoretical
As mentioned above, the situation regarding proxies is clear: Having a mutable mapping for kwarg & property (as we currently have with #7696) is correct.
Regading cookies and headers: With #7696 you now suddenly get errors if you pass a Mapping (trivial repro), and it remains to be clarified if this a false positive or not. This depends on the fact what requests is doing with those kwargs internally.
Typeshed maintainers. This is documented in our CONTRIBUTING.md.
the guarantee that things can't go wrong at runtime if the type checker is silent
This isn't how type checking works in Python. It wouldn't even be possible, because you can always do something very dynamic, e.g. getattr(foo, arbitrary_string), or use argparse without specifying a custom class for the type of the object that parse_args() returns. The goal of type checking is to detect common mistakes while not getting in the way too much, and in my opinion it works very well.
This will cause some type errors, e.g. in our own project, but this is a good thing:
Indeed. If a mapping is mutated, it should be a MutableMapping :)
We all want errors for code that is most likely wrong, such as passing in an immutable mapping for code that will mutate it. The question is whether we want errors for code that might be correct or wrong. I'm saying that it's sometimes fine to not error in such cases, but I don't mean that we should just apply that blindly. It really has to be considered case by case.
Huh? Perhaps I'm misunderstanding things, but this doesn't sound fine at all:
This was my first reaction too. But some classes are used in two kinds of ways:
Construct with immutable mapping, never mutate afterwards through the attribute
Construct with mutable mapping, mutate afterwards through the attribute
If you want both of these to "just work", without forcing users to assert isinstance(foo.bar, MutableMapping) or similar, you have two options: either make the class generic over the mapping type (which may or may not be a good idea), or use Mapping for the argument and MutableMapping for the attribute. There was a similar situation with logging, but I couldn't find the PR.
For the record, headers and cookies are not mutated as far as I can see. The reasons I was hesitant to change these were:
It would cause issues with Request's mutable attribute later on. But I understand from Akuli's comment that this is acceptable, so we can dismiss this :)
headers, cookies and proxies are all used for the same purpose (passing some config) and are documented in the same way ("dictionary" of strings). If proxies is mutated, then it may indicate that a mutable mapping is also expected for the other two.
I opened psf/requests#6118 for requests. I think there is a good chance that they never intended this.
Looks like it won't be changed any time soon, see psf/requests#6118 . From the response I also gather that they really do expect a mutable argument here.
Activity
[-]requests: Allow files={"example": bytes}[/-][+]requests: Allow files={"example": ("filename", bytes)}[/+]bersbersbers commentedon Apr 27, 2022
I see a similar issue for the example from https://docs.python-requests.org/en/latest/user/quickstart/#post-a-multipart-encoded-file:
This issue reproduces with
mypy==0.942andtypes-requests==2.27.21, but not withtypes-requests==2.27.20.bersbersbers commentedon Apr 27, 2022
I'd say the issue is in #7696, specifically, https://github.com/python/typeshed/pull/7696/files#diff-c7fb924dc4d234c3cc1a615ee40522b3a0bb8641f0662ecb1a89762a090b8db9R32 and https://github.com/python/typeshed/pull/7696/files#diff-bfcfcf0d08b5e5c2965394517079c5cfb2b2a11b2e27cf9d08fb07090cd9a4ceR51.
shawnwall commentedon Apr 27, 2022
I'm also seeing this issue when utilizing
StringIOin a test:AlexWaygood commentedon Apr 28, 2022
Thanks all — a new version of types-requests should be uploaded to PyPI shortly. Feel free to leave a comment here or open a new issue if there are still problems with the new release :)
bersbersbers commentedon Apr 28, 2022
I have tried 2.27.23 and while the error message has changed, it is still there:
This is with the code example from #7724 (comment)
etiennecrb commentedon Apr 28, 2022
I think this issue should be reopened. The problem here I believe is the usage of
MutableMappingwhich is invariant. Mypy recommends using immutable collections as annotations when possible so I believeMappingshould be used here (if it's true that it is not mutated), otherwise we might be stuck.Edit: sorry, didn't see the new issue opened, which properly describes the problem.
AlexWaygood commentedon Apr 28, 2022
Okay, #7732 has just been merged, and a new release will be uploaded shortly — please let us know if there are still problems with the new release!
bersbersbers commentedon Apr 28, 2022
2.27.24 works fine for me, thanks for the quick fix!
svenpanne commentedon Apr 29, 2022
I am not really sure if this bug should be closed: Using
MutableMappinginstead ofMappingseems to be wrong at several other places, too:_TextMapping, which is mutable, is e.g. used forheadersorcookieskwargs, but I somehow doubt that those arguments are actually mutated.This incorrect(?) variance leads to quite a few false positives on our SW, and I guess we're not alone with this...
milanboers commentedon Apr 30, 2022
@svenpanne
_TextMappingis now the onlyMutableMappingleft that's used in a function argument. I also doubt that it's mutated, I'll do some analysis later to see if it can be changed.Out of curiosity, are you getting these false positives because of the invariance of
MutableMapping(i.e. you're passing aMutableMappingwith a subtype ofstras the value), or because you're passing a non-mutableMapping?svenpanne commentedon Apr 30, 2022
It's mainly because we pass
Mappings as parameters for cookies/headers/proxies IIRC, I would need to take a closer look if we make use of covariance in the values.As a temporary measure, we just copy the
Mappings until this bug here is fixed: Checkmk/checkmk@0447c08milanboers commentedon Apr 30, 2022
proxiesseems to be mutated:This would also make me a bit hesitant to change
headersandcookiestoMapping. In the end requests documentation asks for a dictionary for all three.Another thing worth considering is that to keep things internally consistent, you'd have to make
Request.headersetc. also aMapping.Request.headersis mutated (internally), and it would also break type checking on code like:I can imagine that more people use
Mappingfor these parameters though, so could be worth it, but it's a balancing act.svenpanne commentedon Apr 30, 2022
I don't think that this is a balancing act, regarding typing things are always crystal clear: If a parameter gets mutated by a function/method it must be a
MutableMapping, using just aMappingwould not be sound (in other words: totally wrong). If it does not get mutated, using aMappingwould be the much better choice: Although aMutableMappingwould be sound, too (everyMutableMappingis aMappingafter all), this would unnecessarily restrict the function/method (it would require more than is needed). In other words: Parameters should be typed as unspecific as possible (while still being sound) and return values as specific as possible. This will ensure maximum flexibilty.request's documentation is a bit crappy here IMHO, and the API is more than surprising: Even though it talks about dictionaries, I would have never expected that those input parameters are mutated! Looking at your
proxiesexample, it's clear that this kwarg is mutated, but this doesn't necessarily mean thatheadersandcookiesare mutated, too. One has to figure that out...Regarding;
It is far from clear that the
headersproperty returns the same object which has been passed via theheaderskwarg. Quite the opposite: It's an anti-pattern to return a reference to a mutable part of an object, so it might be a copy, I don't know. And if you get a mutable argument, it's more often than not a good idea to keep a copy of it, protecting you against mutation behind your back. But I don't know the internals of requests enough to say what is actually being done there...In any case: It's already very helpful to know that
proxiesare mutated, I'll definitely need to have a closer look at our SW, I am not sure if it's really prepared for that.milanboers commentedon Apr 30, 2022
That is what is happening internally. So making headers/cookies a
MappinginSession.requestbutMutableMappingin the attributes ofRequestwould internally be inconsistent. From an API perspective it would still be correct (you can't access theRequestmade bySession.request).The problem is these are stubs for a third party. Typeshed can decide to make the parameters as permissive as the current implementation allows, but we don't know what the authors actually intended. Following documentation would be safer, but also leads to false positives. So that's where the balacing lies.
However it's also possible that requests' authors didn't intend the input argument to
proxiesto be mutated, since it's kind of dangerous, so it's also an option to get this changed in requests.Akuli commentedon May 1, 2022
When "balancing", we usually consider false negatives (no errors for wrong code) less bad than false positives (errors for correct code).
In general,
Mappingin an argument andMutableMappingin the corresponding attribute is fine. But I don't think it makes sense here:MutableMapping[str, str]doesn't have variance issues (unless you have a customstrsubclass which is rare), so we don't needMappingjust to work around that. This is different fromfiles, which uses a union type inside theMutableMapping.AlexWaygood commentedon May 1, 2022
If people are still getting what they believe to be false-positive errors following #7696, it would be helpful if they could post a minimal repro and the exact error message they're getting 🙂 otherwise this is all purely theoretical
svenpanne commentedon May 1, 2022
I am not exactly sure who "we" is, but type checkers for other programming languages actually do the exact opposite thing and issue an error if code can not be proven correct. In other words, they err on the conservative side, because having the guarantee that things can't go wrong at runtime if the type checker is silent is far more important than any "inconvenience" caused by this conservatism. Due to Rice's theorem (all non-trivial, semantic properties of programs are undecidable) there will always be programs which don't go wrong, but can't be proven safe by any type checker. This is not as bad as it actually sounds, there are basically always ways to make your intentions clear to a type checker by rewriting things slightly.
If you do not err on the conservative side, type checkers are relatively useless.
Huh? Perhaps I'm misunderstanding things, but this doesn't sound fine at all: If you pass an immutable data structure into some class, which remembers the same data structure internally and makes the very same data structure available as a property, and then claim that the data structure returned is mutable, something has gone seriously wrong.
In other words: In such a scenario as above (argument remembered internally and exposed as a property, no copying/cloning involved), then the argument and the property must have the same type. From a variance POV: The class is a consumer and a producer, so we have invariance.
Coming back to our
proxieskwarg and its related property, things are quite clear now: Both have to have the same type, so both have to be eitherMappingorMutableMapping. But the former would be a lie, because requests seems to mutate the argument, so this leaves us with: Both kwarg and property have to be aMutableMapping.This will cause some type errors, e.g. in our own project, but this is a good thing: We weren't aware of the fact that the argument can be mutated, so we have to fix things on our side! This is what type checking is all about...
Regarding
cookiesandheaders: Are these handled the same way in requests, i.e. no copying + exposed as a property + mutated internally? If yes, the same reasoning applies.svenpanne commentedon May 1, 2022
As mentioned above, the situation regarding
proxiesis clear: Having a mutable mapping for kwarg & property (as we currently have with #7696) is correct.Regading
cookiesandheaders: With #7696 you now suddenly get errors if you pass aMapping(trivial repro), and it remains to be clarified if this a false positive or not. This depends on the fact what requests is doing with those kwargs internally.Akuli commentedon May 1, 2022
Typeshed maintainers. This is documented in our CONTRIBUTING.md.
This isn't how type checking works in Python. It wouldn't even be possible, because you can always do something very dynamic, e.g.
getattr(foo, arbitrary_string), or useargparsewithout specifying a custom class for the type of the object thatparse_args()returns. The goal of type checking is to detect common mistakes while not getting in the way too much, and in my opinion it works very well.Indeed. If a mapping is mutated, it should be a
MutableMapping:)We all want errors for code that is most likely wrong, such as passing in an immutable mapping for code that will mutate it. The question is whether we want errors for code that might be correct or wrong. I'm saying that it's sometimes fine to not error in such cases, but I don't mean that we should just apply that blindly. It really has to be considered case by case.
This was my first reaction too. But some classes are used in two kinds of ways:
If you want both of these to "just work", without forcing users to
assert isinstance(foo.bar, MutableMapping)or similar, you have two options: either make the class generic over the mapping type (which may or may not be a good idea), or useMappingfor the argument andMutableMappingfor the attribute. There was a similar situation withlogging, but I couldn't find the PR.milanboers commentedon May 3, 2022
For the record,
headersandcookiesare not mutated as far as I can see. The reasons I was hesitant to change these were:Request's mutable attribute later on. But I understand from Akuli's comment that this is acceptable, so we can dismiss this :)headers,cookiesandproxiesare all used for the same purpose (passing some config) and are documented in the same way ("dictionary" of strings). Ifproxiesis mutated, then it may indicate that a mutable mapping is also expected for the other two.I opened psf/requests#6118 for requests. I think there is a good chance that they never intended this.
milanboers commentedon May 4, 2022
Looks like it won't be changed any time soon, see psf/requests#6118 . From the response I also gather that they really do expect a mutable argument here.
Remove workaround for typeshed issue 7724
prauscher commentedon Mar 11, 2024
PR was merged, so this can be closed now, thanks @milanboers :)