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

Header refinements #1248

Merged
merged 2 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions httpx/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import httpcore

from .__version__ import __version__
from ._auth import Auth, BasicAuth, FunctionAuth
from ._config import (
DEFAULT_LIMITS,
Expand All @@ -18,6 +19,7 @@
create_ssl_context,
)
from ._content_streams import ContentStream
from ._decoders import SUPPORTED_DECODERS
from ._exceptions import (
HTTPCORE_EXC_MAP,
InvalidURL,
Expand Down Expand Up @@ -55,6 +57,10 @@
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:
Expand All @@ -74,7 +80,7 @@ def __init__(

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
Expand Down Expand Up @@ -161,7 +167,16 @@ def headers(self) -> Headers:

@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:
Expand Down Expand Up @@ -299,11 +314,9 @@ def _merge_headers(
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
Expand Down
35 changes: 6 additions & 29 deletions httpx/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import rfc3986
import rfc3986.exceptions

from .__version__ import __version__
from ._content_streams import ByteStream, ContentStream, encode
from ._decoders import (
SUPPORTED_DECODERS,
Expand Down Expand Up @@ -103,13 +102,11 @@ def userinfo(self) -> str:

@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:
Expand Down Expand Up @@ -580,12 +577,6 @@ def getlist(self, key: str, split_commas: bool = False) -> typing.List[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,
Expand Down Expand Up @@ -627,26 +618,12 @@ def prepare(self) -> None:
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)

Expand Down
22 changes: 22 additions & 0 deletions tests/client/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 12 additions & 0 deletions tests/models/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down