Skip to content
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

requests: Allow session headers to be of type None #7773

Merged
merged 14 commits into from
May 16, 2022

Conversation

janrito
Copy link
Contributor

@janrito janrito commented May 3, 2022

@AlexWaygood AlexWaygood changed the title Allow session headers to be of type None requests: Allow session headers to be of type None May 3, 2022
@Akuli
Copy link
Collaborator

Akuli commented May 3, 2022

Currently many functions accept headers: _TextMapping, where _TextMapping is defined to be MutableMapping[str, str]. Can you fix that too? The correct type is probably MutableMapping[str, str] | MutableMapping[str, str | None], because MutableMapping is invariant and so a dict[str, str] is not a valid MutableMapping[str, str | None].

@janrito
Copy link
Contributor Author

janrito commented May 3, 2022

@Akuli does it make sense for headers to be None?

in a session, None is used to remove headers from a specific request. Empty headers should be invalid I think?

@janrito
Copy link
Contributor Author

janrito commented May 3, 2022

session = requests.Session()
session.headers.update({"Content-Type": "application/json"})

# request with content type
session.get("http://api.com")

# request with no content type
session.get("http://api.com", headers={"Content-Type": None})

@@ -72,7 +72,7 @@ _Verify: TypeAlias = bool | str

class Session(SessionRedirectMixin):
__attrs__: Any
headers: CaseInsensitiveDict[str]
headers: CaseInsensitiveDict[str | None]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in https://docs.python-requests.org/en/latest/user/quickstart/#custom-headers , header values can also be bytestring.

Suggested change
headers: CaseInsensitiveDict[str | None]
headers: CaseInsensitiveDict[str | bytes | None]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I think I added None to the wrong place. We want to allow str or bytes on actual headers, but None only on updates

@@ -67,12 +67,14 @@ _Params: TypeAlias = Union[
str | bytes,
]
_TextMapping: TypeAlias = MutableMapping[str, str]
_HeadersMapping = TypeAlias = MutableMapping[str, str | bytes]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inconvenient because MutableMapping is invariant in its value type, so a dict[str, str] would not be acceptable.

Copy link
Contributor Author

@janrito janrito May 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about

MutableMapping[str, str] | MutableMapping[str, bytes]

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -67,12 +67,14 @@ _Params: TypeAlias = Union[
str | bytes,
]
_TextMapping: TypeAlias = MutableMapping[str, str]
_HeadersMapping: TypeAlias = MutableMapping[str, str] | MutableMapping[str, bytes]
_HeadersUpdateMapping: TypeAlias = MutableMapping[str, str] | MutableMapping[str, bytes] | MutableMapping[str, None]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that's correct, header values can be some str and some bytes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm I see - would we have to specify all combinations of mutable mappings for str, bytes and None?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Akuli Akuli closed this May 7, 2022
@Akuli Akuli reopened this May 7, 2022
@github-actions

This comment has been minimized.

@@ -95,7 +95,7 @@ class Response:
__attrs__: Any
_content: bytes | None # undocumented
status_code: int
headers: CaseInsensitiveDict[str]
headers: CaseInsensitiveDict[str | bytes]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this is correct? These headers are from the HTTP response, and I'd expect requests to return the same type consistently.

This seems to be the cause of a false positive on this line found by mypy-primer: https://github.com/yurijmikhalevich/rclip/blob/98a2454b988986f6ed67ae6e9fe3342ea82e0f92/rclip/utils.py#L92

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions

This comment has been minimized.

@janrito
Copy link
Contributor Author

janrito commented May 13, 2022

I'm unsure what the next steps are

@Akuli
Copy link
Collaborator

Akuli commented May 13, 2022

You removed | bytes from the wrong place. It's fine to have it in session headers, but not in response headers.

@janrito
Copy link
Contributor Author

janrito commented May 16, 2022

You removed | bytes from the wrong place. It's fine to have it in session headers, but not in response headers.

whelp f9aaf0c, 0d80bdf

@github-actions

This comment has been minimized.

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CaseInsensitiveDict[str | bytes] has the same issues that MutableMapping[x, str | bytes] has (invariance) — CaseInsensitiveDict[str] | CaseInsensitiveDict[bytes] | CaseInsensitiveDict[str | bytes] would be better

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@AlexWaygood
Copy link
Member

Well, my suggested change to work around the invariance of CaseInsensitiveDict appeared to introduce a lot of new false-positives, so, uh... maybe ignore my remarks there and revert the last commit? 🙃

@janrito
Copy link
Contributor Author

janrito commented May 16, 2022

I find it quite challenging to have a local workflow for this. Not even testing other projects, but my own. I'd love some pointers if anyone has some

@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

rotki (https://github.com/rotki/rotki)
+ rotkehlchen/premium/premium.py:219: error: Unused "type: ignore" comment
+ rotkehlchen/premium/premium.py:245: error: Unused "type: ignore" comment
+ rotkehlchen/premium/premium.py:270: error: Unused "type: ignore" comment
+ rotkehlchen/premium/premium.py:301: error: Unused "type: ignore" comment
+ rotkehlchen/premium/premium.py:328: error: Unused "type: ignore" comment
+ rotkehlchen/exchanges/kraken.py:546: error: Unused "type: ignore" comment

@JelleZijlstra
Copy link
Member

I find it quite challenging to have a local workflow for this. Not even testing other projects, but my own. I'd love some pointers if anyone has some

One quick-and-dirty way is to just edit the stub files used by your type checker (somewhere in site-packages) in place. You can also use $MYPYPATH.

@Akuli
Copy link
Collaborator

Akuli commented May 16, 2022

To clarify, here's how you would use MYPYPATH:

$ MYPYPATH=/home/yourname/typeshed/stubs/requests mypy project_using_requests

This lets mypy import from /home/yourname/typeshed/stubs/requests. There's also --custom-typeshed-dir, but it unfortunately affects stdlib stubs only.

Copy link
Collaborator

@Akuli Akuli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Primer is happy and the code makes sense to me (None can be used to override whatever was configured in the session). Thanks for contributing! This one was trickier than expected.

@Akuli Akuli merged commit 87abd8a into python:master May 16, 2022
@janrito janrito deleted the requests-session-header-allow-none branch May 17, 2022 09:00
@rpdelaney
Copy link

rpdelaney commented May 23, 2022

After this change, my type checker is throwing an error:

error: Argument "headers" to "get" has incompatible type "MappingProxyType[str, str]"; expected "Optional[MutableMapping[str, str]]" [arg-type]

Do headers have to be mutable? Can we also accept MappingProxyType?

rpdelaney pushed a commit to rpdelaney/downforeveryone that referenced this pull request May 23, 2022
Due to recent typeshed change:
python/typeshed#7773
cjwatson added a commit to cjwatson/typeshed that referenced this pull request Feb 6, 2024
python#7773 changed
`requests.session.Session` methods to accept None for header values, but
didn't do quite the same for the functions in `requests.api`.  I think
this was a mistake.  The functions in `requests.api` just pass through
the `headers` argument without doing anything in particular to it.

Furthermore, it's useful to be able to pass None as a header value:
because `requests.utils.default_headers` sets an `Accept-Encoding`
header by default, the easiest way to send a request with no
`Accept-Encoding` header is something like `requests.get(url,
headers={"Accept-Encoding": None})`.  It's annoying to have to construct
a `Session` just to pass type-checking.

It's a little confusing for the type alias to be called
`_HeadersUpdateMapping` in `requests.sessions` but `_HeadersMapping` in
`requests.api`; this is because the latter name was already used in
other type stubs (`tensorflow.keras.callbacks`), so it seemed best to
avoid breaking API.
cjwatson added a commit to cjwatson/typeshed that referenced this pull request Feb 6, 2024
python#7773 changed
`requests.session.Session` methods to accept None for header values, but
didn't do quite the same for the functions in `requests.api`.  I think
this was a mistake.  The functions in `requests.api` just pass through
the `headers` argument without doing anything in particular to it.

Furthermore, it's useful to be able to pass None as a header value:
because `requests.utils.default_headers` sets an `Accept-Encoding`
header by default, the easiest way to send a request with no
`Accept-Encoding` header is something like `requests.get(url,
headers={"Accept-Encoding": None})`.  It's annoying to have to construct
a `Session` just to pass type-checking.

It's a little confusing for the type alias to be called
`_HeadersUpdateMapping` in `requests.sessions` but `_HeadersMapping` in
`requests.api`; this is because the latter name was already used in
other type stubs (`tensorflow.keras.callbacks`), so it seemed best to
avoid breaking API.
JelleZijlstra pushed a commit that referenced this pull request Feb 17, 2024
#7773 changed
`requests.session.Session` methods to accept None for header values, but
didn't do quite the same for the functions in `requests.api`.  I think
this was a mistake.  The functions in `requests.api` just pass through
the `headers` argument without doing anything in particular to it.

Furthermore, it's useful to be able to pass None as a header value:
because `requests.utils.default_headers` sets an `Accept-Encoding`
header by default, the easiest way to send a request with no
`Accept-Encoding` header is something like `requests.get(url,
headers={"Accept-Encoding": None})`.  It's annoying to have to construct
a `Session` just to pass type-checking.

It's a little confusing for the type alias to be called
`_HeadersUpdateMapping` in `requests.sessions` but `_HeadersMapping` in
`requests.api`; this is because the latter name was already used in
other type stubs (`tensorflow.keras.callbacks`), so it seemed best to
avoid breaking API.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants