Header refinements (#1248)

Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
This commit is contained in:
Tom Christie 2020-09-02 21:32:48 +01:00 committed by GitHub
parent def9f1c320
commit 19b863af40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 35 deletions

View File

@ -5,6 +5,7 @@ from types import TracebackType
import httpcore
from .__version__ import __version__
from ._auth import Auth, BasicAuth, FunctionAuth
from ._config import (
DEFAULT_LIMITS,
@ -18,6 +19,7 @@ from ._config import (
create_ssl_context,
)
from ._content_streams import ContentStream
from ._decoders import SUPPORTED_DECODERS
from ._exceptions import (
HTTPCORE_EXC_MAP,
InvalidURL,
@ -55,6 +57,10 @@ from ._utils import (
logger = get_logger(__name__)
KEEPALIVE_EXPIRY = 5.0
USER_AGENT = f"python-httpx/{__version__}"
ACCEPT_ENCODING = ", ".join(
[key for key in SUPPORTED_DECODERS.keys() if key != "identity"]
)
class BaseClient:
@ -74,7 +80,7 @@ class BaseClient:
self._auth = self._build_auth(auth)
self._params = QueryParams(params)
self._headers = Headers(headers)
self.headers = Headers(headers)
self._cookies = Cookies(cookies)
self._timeout = Timeout(timeout)
self.max_redirects = max_redirects
@ -161,7 +167,16 @@ class BaseClient:
@headers.setter
def headers(self, headers: HeaderTypes) -> None:
self._headers = Headers(headers)
client_headers = Headers(
{
b"Accept": b"*/*",
b"Accept-Encoding": ACCEPT_ENCODING.encode("ascii"),
b"Connection": b"keep-alive",
b"User-Agent": USER_AGENT.encode("ascii"),
}
)
client_headers.update(headers)
self._headers = client_headers
@property
def cookies(self) -> Cookies:
@ -299,11 +314,9 @@ class BaseClient:
Merge a headers argument together with any headers on the client,
to create the headers used for the outgoing request.
"""
if headers or self.headers:
merged_headers = Headers(self.headers)
merged_headers.update(headers)
return merged_headers
return headers
merged_headers = Headers(self.headers)
merged_headers.update(headers)
return merged_headers
def _merge_queryparams(
self, params: QueryParamTypes = None

View File

@ -14,7 +14,6 @@ import chardet
import rfc3986
import rfc3986.exceptions
from .__version__ import __version__
from ._content_streams import ByteStream, ContentStream, encode
from ._decoders import (
SUPPORTED_DECODERS,
@ -103,13 +102,11 @@ class URL:
@property
def username(self) -> str:
userinfo = self._uri_reference.userinfo or ""
return unquote(userinfo.partition(":")[0])
return unquote(self.userinfo.partition(":")[0])
@property
def password(self) -> str:
userinfo = self._uri_reference.userinfo or ""
return unquote(userinfo.partition(":")[2])
return unquote(self.userinfo.partition(":")[2])
@property
def host(self) -> str:
@ -580,12 +577,6 @@ class Headers(typing.MutableMapping[str, str]):
return self.get_list(key, split_commas=split_commas)
USER_AGENT = f"python-httpx/{__version__}"
ACCEPT_ENCODING = ", ".join(
[key for key in SUPPORTED_DECODERS.keys() if key != "identity"]
)
class Request:
def __init__(
self,
@ -627,26 +618,12 @@ class Request:
has_content_length = (
"content-length" in self.headers or "transfer-encoding" in self.headers
)
has_user_agent = "user-agent" in self.headers
has_accept = "accept" in self.headers
has_accept_encoding = "accept-encoding" in self.headers
has_connection = "connection" in self.headers
if not has_host:
url = self.url
if url.userinfo:
url = url.copy_with(username=None, password=None)
auto_headers.append((b"host", url.authority.encode("ascii")))
if not has_host and self.url.authority:
host = self.url.copy_with(username=None, password=None).authority
auto_headers.append((b"host", host.encode("ascii")))
if not has_content_length and self.method in ("POST", "PUT", "PATCH"):
auto_headers.append((b"content-length", b"0"))
if not has_user_agent:
auto_headers.append((b"user-agent", USER_AGENT.encode("ascii")))
if not has_accept:
auto_headers.append((b"accept", b"*/*"))
if not has_accept_encoding:
auto_headers.append((b"accept-encoding", ACCEPT_ENCODING.encode()))
if not has_connection:
auto_headers.append((b"connection", b"keep-alive"))
self.headers = Headers(auto_headers + self.headers.raw)

View File

@ -124,6 +124,28 @@ def test_header_update():
}
def test_remove_default_header():
"""
Remove a default header from the Client.
"""
url = "http://example.org/echo_headers"
client = httpx.Client(transport=MockTransport())
del client.headers["User-Agent"]
response = client.get(url)
assert response.status_code == 200
assert response.json() == {
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"host": "example.org",
}
}
def test_header_does_not_exist():
headers = httpx.Headers({"foo": "bar"})
with pytest.raises(KeyError):

View File

@ -34,6 +34,18 @@ def test_json_encoded_data():
assert request.content == b'{"test": 123}'
def test_headers():
request = httpx.Request("POST", "http://example.org", json={"test": 123})
assert request.headers == httpx.Headers(
{
"Host": "example.org",
"Content-Type": "application/json",
"Content-Length": "13",
}
)
def test_read_and_stream_data():
# Ensure a request may still be streamed if it has been read.
# Needed for cases such as authentication classes that read the request body.