From 920333ea98118e9cf617f246905d7b202510941c Mon Sep 17 00:00:00 2001 From: Zanie Adkins Date: Fri, 9 Jun 2023 04:06:56 -0500 Subject: [PATCH 01/36] Always encode forward slashes as `%2F` in query parameters (#2723) * Always encode forward slashes as `%2F` in query parameters * Revert inclusion of "%" This is expected to fail tests due to double escaping * Update `urlencode` --------- Co-authored-by: Tom Christie --- httpx/_urlparse.py | 18 ++++++++++-------- tests/models/test_url.py | 4 ++-- tests/test_urlparse.py | 6 +++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/httpx/_urlparse.py b/httpx/_urlparse.py index 69ff0b4b02..e1ba8dcdb7 100644 --- a/httpx/_urlparse.py +++ b/httpx/_urlparse.py @@ -260,8 +260,10 @@ def urlparse(url: str = "", **kwargs: typing.Optional[str]) -> ParseResult: # For 'path' we need to drop ? and # from the GEN_DELIMS set. parsed_path: str = quote(path, safe=SUB_DELIMS + ":/[]@") # For 'query' we need to drop '#' from the GEN_DELIMS set. + # We also exclude '/' because it is more robust to replace it with a percent + # encoding despite it not being a requirement of the spec. parsed_query: typing.Optional[str] = ( - None if query is None else quote(query, safe=SUB_DELIMS + ":/?[]@") + None if query is None else quote(query, safe=SUB_DELIMS + ":?[]@") ) # For 'fragment' we can include all of the GEN_DELIMS set. parsed_fragment: typing.Optional[str] = ( @@ -452,11 +454,11 @@ def urlencode(items: typing.List[typing.Tuple[str, str]]) -> str: # # https://github.com/python/cpython/blob/b2f7b2ef0b5421e01efb8c7bee2ef95d3bab77eb/Lib/urllib/parse.py#L926 # - # Note that we use '%20' encoding for spaces, and treat '/' as a safe - # character. This means our query params have the same escaping as other - # characters in the URL path. This is slightly different to `requests`, - # but is the behaviour that browsers use. + # Note that we use '%20' encoding for spaces. and '%2F for '/'. + # This is slightly different than `requests`, but is the behaviour that browsers use. # - # See https://github.com/encode/httpx/issues/2536 and - # https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode - return "&".join([quote(k) + "=" + quote(v) for k, v in items]) + # See + # - https://github.com/encode/httpx/issues/2536 + # - https://github.com/encode/httpx/issues/2721 + # - https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode + return "&".join([quote(k, safe="") + "=" + quote(v, safe="") for k, v in items]) diff --git a/tests/models/test_url.py b/tests/models/test_url.py index 170066826a..a47205f97d 100644 --- a/tests/models/test_url.py +++ b/tests/models/test_url.py @@ -360,10 +360,10 @@ def test_url_query_encoding(): and https://github.com/encode/httpx/discussions/2460 """ url = httpx.URL("https://www.example.com/?a=b c&d=e/f") - assert url.raw_path == b"/?a=b%20c&d=e/f" + assert url.raw_path == b"/?a=b%20c&d=e%2Ff" url = httpx.URL("https://www.example.com/", params={"a": "b c", "d": "e/f"}) - assert url.raw_path == b"/?a=b%20c&d=e/f" + assert url.raw_path == b"/?a=b%20c&d=e%2Ff" def test_url_with_url_encoded_path(): diff --git a/tests/test_urlparse.py b/tests/test_urlparse.py index 0347d3124c..3ae9b04ce6 100644 --- a/tests/test_urlparse.py +++ b/tests/test_urlparse.py @@ -141,7 +141,7 @@ def test_param_does_not_require_encoding(): def test_param_with_existing_escape_requires_encoding(): url = httpx.URL("http://webservice", params={"u": "http://example.com?q=foo%2Fa"}) - assert str(url) == "http://webservice?u=http%3A//example.com%3Fq%3Dfoo%252Fa" + assert str(url) == "http://webservice?u=http%3A%2F%2Fexample.com%3Fq%3Dfoo%252Fa" # Tests for invalid URLs @@ -264,9 +264,9 @@ def test_path_percent_encoding(): def test_query_percent_encoding(): # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS url = httpx.URL("https://example.com/?!$&'()*+,;= abc ABC 123 :/[]@" + "?") - assert url.raw_path == b"/?!$&'()*+,;=%20abc%20ABC%20123%20:/[]@?" + assert url.raw_path == b"/?!$&'()*+,;=%20abc%20ABC%20123%20:%2F[]@?" assert url.path == "/" - assert url.query == b"!$&'()*+,;=%20abc%20ABC%20123%20:/[]@?" + assert url.query == b"!$&'()*+,;=%20abc%20ABC%20123%20:%2F[]@?" assert url.fragment == "" From 6d183a87e1f81c7132482d0ee56f44e2afdffef2 Mon Sep 17 00:00:00 2001 From: Trond Hindenes Date: Thu, 15 Jun 2023 10:20:28 +0100 Subject: [PATCH 02/36] async recommendations (#2727) * async recommendations * better * Update docs/async.md Co-authored-by: Tom Christie * added async recommendation tweak --------- Co-authored-by: Tom Christie --- docs/async.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/async.md b/docs/async.md index e22a2907e4..1138c30c56 100644 --- a/docs/async.md +++ b/docs/async.md @@ -53,6 +53,9 @@ async with httpx.AsyncClient() as client: ... ``` +!!! warning +In order to get the most benefit from connection pooling, make sure you're not instantiating multiple client instances - for example by using `async with` inside a "hot loop". This can be achieved either by having a single scoped client that's passed throughout wherever it's needed, or by having a single global client instance. + Alternatively, use `await client.aclose()` if you want to close a client explicitly: ```python From 2e2949c8ea88668f4cc5e509342b57e60fe4d86a Mon Sep 17 00:00:00 2001 From: Johnny Lim Date: Thu, 22 Jun 2023 17:44:44 +0900 Subject: [PATCH 03/36] Fix sample in quickstart.md (#2747) --- docs/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index fc0b05841e..07cbfd0865 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -367,7 +367,7 @@ If you're using streaming responses in any of these ways then the `response.cont ```pycon >>> with httpx.stream("GET", "https://www.example.com") as r: -... if r.headers['Content-Length'] < TOO_LONG: +... if int(r.headers['Content-Length']) < TOO_LONG: ... r.read() ... print(r.text) ``` From 354a6fc8dcb5c76b54f7c4de91e5b724e13818c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 09:48:29 +0100 Subject: [PATCH 04/36] Bump pytest from 7.3.1 to 7.4.0 (#2751) Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.1 to 7.4.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.3.1...7.4.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dbe8d7426d..56d10bfdb0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,7 +24,7 @@ coverage[toml]==7.2.2 cryptography==41.0.0 mypy==1.3.0 types-certifi==2021.10.8.2 -pytest==7.3.1 +pytest==7.4.0 ruff==0.0.260 trio==0.22.0 trio-typing==0.8.0 From 8a6ef6ed14b77f7aca04e1c5f99e02c1a2042fe0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 09:56:42 +0100 Subject: [PATCH 05/36] Bump mypy from 1.3.0 to 1.4.1 (#2754) Bumps [mypy](https://github.com/python/mypy) from 1.3.0 to 1.4.1. - [Commits](https://github.com/python/mypy/compare/v1.3.0...v1.4.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 56d10bfdb0..fd72ea8591 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ twine==4.0.2 black==23.3.0 coverage[toml]==7.2.2 cryptography==41.0.0 -mypy==1.3.0 +mypy==1.4.1 types-certifi==2021.10.8.2 pytest==7.4.0 ruff==0.0.260 From 9d022c0a88236adc26e3e85d34cdcc453df60796 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:13:56 +0100 Subject: [PATCH 06/36] Bump ruff from 0.0.260 to 0.0.275 (#2753) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.260 to 0.0.275. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.260...v0.0.275) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fd72ea8591..6eb14ac6ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ cryptography==41.0.0 mypy==1.4.1 types-certifi==2021.10.8.2 pytest==7.4.0 -ruff==0.0.260 +ruff==0.0.275 trio==0.22.0 trio-typing==0.8.0 trustme==1.0.0 From 353fb358eb3c3bf74ff6f24a92dc3b5452c489ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:18:52 +0100 Subject: [PATCH 07/36] Bump mkdocs-material from 9.1.15 to 9.1.17 (#2755) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.15 to 9.1.17. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.15...9.1.17) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6eb14ac6ab..758ff1b029 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ types-chardet==5.0.4.5 # Documentation mkdocs==1.4.3 mkautodoc==0.2.0 -mkdocs-material==9.1.15 +mkdocs-material==9.1.17 # Packaging build==0.10.0 From 5b156dca7f3ec637b575708198f60a062109f141 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:56:05 +0100 Subject: [PATCH 08/36] Bump coverage[toml] from 7.2.2 to 7.2.7 (#2752) Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 7.2.2 to 7.2.7. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.2.2...7.2.7) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tom Christie --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 758ff1b029..e05ee5b024 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ twine==4.0.2 # Tests & Linting black==23.3.0 -coverage[toml]==7.2.2 +coverage[toml]==7.2.7 cryptography==41.0.0 mypy==1.4.1 types-certifi==2021.10.8.2 From 2c49a151d270cc612f61c4094dbf281cbf2f63a4 Mon Sep 17 00:00:00 2001 From: Zanie Date: Wed, 12 Jul 2023 09:07:06 -0500 Subject: [PATCH 09/36] Pin CI version of `click` to resolve mypy error (#2769) * Add upper bound to click version to fix mypy error * Move pin to `requirements.txt` * Restore `pyproject.toml` --- requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/requirements.txt b/requirements.txt index e05ee5b024..325f4cb9de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,7 @@ trio==0.22.0 trio-typing==0.8.0 trustme==1.0.0 uvicorn==0.22.0 + +# The latest click fails type checks due to bug +# https://github.com/pallets/click/issues/2558 +click==8.1.3 From f115ce4e097576c0089f3169b448e26af688e85c Mon Sep 17 00:00:00 2001 From: Trim21 Date: Thu, 13 Jul 2023 20:55:41 +0800 Subject: [PATCH 10/36] docs: upload progress (#2725) * upload progress * typo * typo * Update docs/advanced.md * Update advanced.md * Update docs/advanced.md Co-authored-by: Kar Petrosyan <92274156+karosis88@users.noreply.github.com> --------- Co-authored-by: Tom Christie Co-authored-by: Kar Petrosyan <92274156+karosis88@users.noreply.github.com> --- docs/advanced.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/advanced.md b/docs/advanced.md index d01b4350c9..2a4779662e 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -426,6 +426,38 @@ with tempfile.NamedTemporaryFile() as download_file: ![rich progress bar](img/rich-progress.gif) +## Monitoring upload progress + +If you need to monitor upload progress of large responses, you can use request content generator streaming. + +For example, showing a progress bar using the [`tqdm`](https://github.com/tqdm/tqdm) library. + +```python +import io +import random + +import httpx +from tqdm import tqdm + + +def gen(): + """ + this is a complete example with generated random bytes. + you can replace `io.BytesIO` with real file object. + """ + total = 32 * 1024 * 1024 # 32m + with tqdm(ascii=True, unit_scale=True, unit='B', unit_divisor=1024, total=total) as bar: + with io.BytesIO(random.randbytes(total)) as f: + while data := f.read(1024): + yield data + bar.update(len(data)) + + +httpx.post("https://httpbin.org/post", content=gen()) +``` + +![tqdm progress bar](img/tqdm-progress.gif) + ## .netrc Support HTTPX can be configured to use [a `.netrc` config file](https://everything.curl.dev/usingcurl/netrc) for authentication. From f6866ce388266ac5c395b68ce7f664911fd09d2c Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Thu, 13 Jul 2023 20:02:01 +0200 Subject: [PATCH 11/36] Remove temporary click version pin (#2771) --- requirements.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 325f4cb9de..e05ee5b024 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,3 @@ trio==0.22.0 trio-typing==0.8.0 trustme==1.0.0 uvicorn==0.22.0 - -# The latest click fails type checks due to bug -# https://github.com/pallets/click/issues/2558 -click==8.1.3 From 18d7721c385bd06516d91b3e6e8c55c8af9ca7b6 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 13 Jul 2023 15:17:07 -0500 Subject: [PATCH 12/36] Use Mozilla documentation instead of `httpstatuses.com` for HTTP error reference (#2768) --- docs/quickstart.md | 2 +- httpx/_models.py | 4 ++-- tests/models/test_responses.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 07cbfd0865..1152a14bd3 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -285,7 +285,7 @@ Traceback (most recent call last): File "/Users/tomchristie/GitHub/encode/httpcore/httpx/models.py", line 837, in raise_for_status raise HTTPStatusError(message, response=self) httpx._exceptions.HTTPStatusError: 404 Client Error: Not Found for url: https://httpbin.org/status/404 -For more information check: https://httpstatuses.com/404 +For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 ``` Any successful response codes will simply return `None` rather than raising an exception. diff --git a/httpx/_models.py b/httpx/_models.py index e0e5278cc0..708aa2af7e 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -729,12 +729,12 @@ def raise_for_status(self) -> None: message = ( "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n" "Redirect location: '{0.headers[location]}'\n" - "For more information check: https://httpstatuses.com/{0.status_code}" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}" ) else: message = ( "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n" - "For more information check: https://httpstatuses.com/{0.status_code}" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}" ) status_class = self.status_code // 100 diff --git a/tests/models/test_responses.py b/tests/models/test_responses.py index ba46692e0d..9e65de8154 100644 --- a/tests/models/test_responses.py +++ b/tests/models/test_responses.py @@ -102,7 +102,7 @@ def test_raise_for_status(): response.raise_for_status() assert str(exc_info.value) == ( "Informational response '101 Switching Protocols' for url 'https://example.org'\n" - "For more information check: https://httpstatuses.com/101" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101" ) # 3xx status codes are redirections. @@ -114,7 +114,7 @@ def test_raise_for_status(): assert str(exc_info.value) == ( "Redirect response '303 See Other' for url 'https://example.org'\n" "Redirect location: 'https://other.org'\n" - "For more information check: https://httpstatuses.com/303" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303" ) # 4xx status codes are a client error. @@ -125,7 +125,7 @@ def test_raise_for_status(): response.raise_for_status() assert str(exc_info.value) == ( "Client error '403 Forbidden' for url 'https://example.org'\n" - "For more information check: https://httpstatuses.com/403" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403" ) # 5xx status codes are a server error. @@ -136,7 +136,7 @@ def test_raise_for_status(): response.raise_for_status() assert str(exc_info.value) == ( "Server error '500 Internal Server Error' for url 'https://example.org'\n" - "For more information check: https://httpstatuses.com/500" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500" ) # Calling .raise_for_status without setting a request instance is From 6a1841b924ab5d318d771e1a04ffb55662bf458b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jul 2023 22:38:15 -0500 Subject: [PATCH 13/36] Bump cryptography from 41.0.0 to 41.0.2 (#2773) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.0 to 41.0.2. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.0...41.0.2) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e05ee5b024..bd53167bb7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ twine==4.0.2 # Tests & Linting black==23.3.0 coverage[toml]==7.2.7 -cryptography==41.0.0 +cryptography==41.0.2 mypy==1.4.1 types-certifi==2021.10.8.2 pytest==7.4.0 From 55b8669acbedb617123409487f744810f59ebe6f Mon Sep 17 00:00:00 2001 From: Kar Petrosyan <92274156+karosis88@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:40:10 -0400 Subject: [PATCH 14/36] Add Hishel into the Third Party Packages (#2799) --- docs/third_party_packages.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/third_party_packages.md b/docs/third_party_packages.md index bad9a9bad9..2adb53ae56 100644 --- a/docs/third_party_packages.md +++ b/docs/third_party_packages.md @@ -6,6 +6,12 @@ As HTTPX usage grows, there is an expanding community of developers building too +### Hishel + +[GitHub](https://github.com/karosis88/hishel) - [Documentation](https://karosis88.github.io/hishel/) + +An elegant HTTP Cache implementation for HTTPX and HTTP Core. + ### Authlib [GitHub](https://github.com/lepture/authlib) - [Documentation](https://docs.authlib.org/en/latest/) From 9415af643f23600403740baad0a466edc5cdbec1 Mon Sep 17 00:00:00 2001 From: Trim21 Date: Tue, 1 Aug 2023 17:22:58 +0800 Subject: [PATCH 15/36] Make `raise_for_status` chainable (#2776) * merge upstream * lint * Update test_async_client.py * update docs * add example * Update docs/quickstart.md Co-authored-by: Tom Christie * Update CHANGELOG.md Co-authored-by: Tom Christie * Update docs/quickstart.md Co-authored-by: Tom Christie --------- Co-authored-by: Tom Christie --- CHANGELOG.md | 1 + docs/api.md | 2 +- docs/quickstart.md | 9 ++++++++- httpx/_models.py | 4 ++-- tests/client/test_async_client.py | 2 +- tests/client/test_client.py | 2 +- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4942c9c9a5..3d5ea6d2fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added * Add `socket_options` argument to `httpx.HTTPTransport` and `httpx.AsyncHTTPTransport` classes. (#2716) +* The `Response.raise_for_status()` method now returns the response instance. For example: `data = httpx.get('...').raise_for_status().json()`. (#2776) ### Fixed diff --git a/docs/api.md b/docs/api.md index ca5c0ba3c9..3f9878c708 100644 --- a/docs/api.md +++ b/docs/api.md @@ -70,7 +70,7 @@ * The amount of time elapsed between sending the request and calling `close()` on the corresponding response received for that request. [total_seconds()](https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds) to correctly get the total elapsed seconds. -* `def .raise_for_status()` - **None** +* `def .raise_for_status()` - **Response** * `def .json()` - **Any** * `def .read()` - **bytes** * `def .iter_raw([chunk_size])` - **bytes iterator** diff --git a/docs/quickstart.md b/docs/quickstart.md index 1152a14bd3..068547ffc9 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -288,12 +288,19 @@ httpx._exceptions.HTTPStatusError: 404 Client Error: Not Found for url: https:// For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 ``` -Any successful response codes will simply return `None` rather than raising an exception. +Any successful response codes will return the `Response` instance rather than raising an exception. ```pycon >>> r.raise_for_status() ``` +The method returns the response instance, allowing you to use it inline. For example: + +```pycon +>>> r = httpx.get('...').raise_for_status() +>>> data = httpx.get('...').raise_for_status().json() +``` + ## Response Headers The response headers are available as a dictionary-like interface. diff --git a/httpx/_models.py b/httpx/_models.py index 708aa2af7e..5af5c5d23c 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -711,7 +711,7 @@ def has_redirect_location(self) -> bool: and "Location" in self.headers ) - def raise_for_status(self) -> None: + def raise_for_status(self) -> "Response": """ Raise the `HTTPStatusError` if one occurred. """ @@ -723,7 +723,7 @@ def raise_for_status(self) -> None: ) if self.is_success: - return + return self if self.has_redirect_location: message = ( diff --git a/tests/client/test_async_client.py b/tests/client/test_async_client.py index 7fa9a77948..cc19e93d41 100644 --- a/tests/client/test_async_client.py +++ b/tests/client/test_async_client.py @@ -122,7 +122,7 @@ async def test_raise_for_status(server): response.raise_for_status() assert exc_info.value.response == response else: - assert response.raise_for_status() is None # type: ignore + assert response.raise_for_status() is response @pytest.mark.anyio diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 268cd10689..b8245288ad 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -141,7 +141,7 @@ def test_raise_for_status(server): assert exc_info.value.response == response assert exc_info.value.request.url.path == f"/status/{status_code}" else: - assert response.raise_for_status() is None # type: ignore + assert response.raise_for_status() is response def test_options(server): From e99e2948e64fac2ca498865e9742ff50a69a2155 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 06:48:15 +0100 Subject: [PATCH 16/36] Bump cryptography from 41.0.2 to 41.0.3 (#2809) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.2 to 41.0.3. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.2...41.0.3) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bd53167bb7..9050c118c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ twine==4.0.2 # Tests & Linting black==23.3.0 coverage[toml]==7.2.7 -cryptography==41.0.2 +cryptography==41.0.3 mypy==1.4.1 types-certifi==2021.10.8.2 pytest==7.4.0 From 76c9cb65f2a159adb764c2236d139f85b46e1506 Mon Sep 17 00:00:00 2001 From: Trim21 Date: Tue, 8 Aug 2023 00:47:48 +0800 Subject: [PATCH 17/36] remove unnecessary black argument (#2817) --- scripts/check | 2 +- scripts/lint | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/check b/scripts/check index 3b41a9a8b2..ef7b064f9a 100755 --- a/scripts/check +++ b/scripts/check @@ -9,6 +9,6 @@ export SOURCE_FILES="httpx tests" set -x ./scripts/sync-version -${PREFIX}black --check --diff --target-version=py37 $SOURCE_FILES +${PREFIX}black --check --diff $SOURCE_FILES ${PREFIX}mypy $SOURCE_FILES ${PREFIX}ruff check $SOURCE_FILES diff --git a/scripts/lint b/scripts/lint index 7a9e89d98b..22d12cba06 100755 --- a/scripts/lint +++ b/scripts/lint @@ -1,7 +1,7 @@ #!/bin/sh -e export PREFIX="" -if [ -d 'venv' ] ; then +if [ -d 'venv' ]; then export PREFIX="venv/bin/" fi export SOURCE_FILES="httpx tests" @@ -9,4 +9,4 @@ export SOURCE_FILES="httpx tests" set -x ${PREFIX}ruff --fix $SOURCE_FILES -${PREFIX}black --target-version=py37 $SOURCE_FILES +${PREFIX}black $SOURCE_FILES From b40c04dfa604db0a5f9b48c12268c0a0c63c71d2 Mon Sep 17 00:00:00 2001 From: Iurii Pliner Date: Wed, 9 Aug 2023 10:02:28 +0100 Subject: [PATCH 18/36] Drop support for Python 3.7 (#2813) * Drop Python 3.7 support * Fix lint * Changelog --- .github/workflows/publish.yml | 2 +- .github/workflows/test-suite.yml | 2 +- CHANGELOG.md | 4 ++++ README.md | 2 +- README_chinese.md | 2 +- docs/index.md | 2 +- httpx/_compat.py | 2 +- pyproject.toml | 3 +-- tests/test_config.py | 25 +++++++++++-------------- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 053ad289cc..3cbd4a8a2d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-python@v4" with: - python-version: 3.7 + python-version: 3.8 - name: "Install dependencies" run: "scripts/install" - name: "Build package & docs" diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index f5ad75a4e2..eb1cc7e22e 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: "actions/checkout@v3" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d5ea6d2fe..a4f49c45d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Removed + +* Drop support for Python 3.7. (#2813) + ### Added * Add `socket_options` argument to `httpx.HTTPTransport` and `httpx.AsyncHTTPTransport` classes. (#2716) diff --git a/README.md b/README.md index 4d25491a6a..62fb295d17 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Or, to include the optional HTTP/2 support, use: $ pip install httpx[http2] ``` -HTTPX requires Python 3.7+. +HTTPX requires Python 3.8+. ## Documentation diff --git a/README_chinese.md b/README_chinese.md index 0248768323..ad20c5a1bf 100644 --- a/README_chinese.md +++ b/README_chinese.md @@ -101,7 +101,7 @@ $ pip install httpx $ pip install httpx[http2] ``` -HTTPX 要求 Python 3.7+ 版本。 +HTTPX 要求 Python 3.8+ 版本。 ## 文档 diff --git a/docs/index.md b/docs/index.md index cd25ee6ca5..ec9746697d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -144,6 +144,6 @@ To include the optional brotli decoder support, use: $ pip install httpx[brotli] ``` -HTTPX requires Python 3.7+ +HTTPX requires Python 3.8+ [sync-support]: https://github.com/encode/httpx/issues/572 diff --git a/httpx/_compat.py b/httpx/_compat.py index a9b9c63072..a271c6b800 100644 --- a/httpx/_compat.py +++ b/httpx/_compat.py @@ -17,7 +17,7 @@ brotli = None if sys.version_info >= (3, 10) or ( - sys.version_info >= (3, 7) and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7) + sys.version_info >= (3, 8) and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7) ): def set_minimum_tls_version_1_2(context: ssl.SSLContext) -> None: diff --git a/pyproject.toml b/pyproject.toml index 30228c120e..acd41baf3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "httpx" description = "The next generation HTTP client." license = "BSD-3-Clause" -requires-python = ">=3.7" +requires-python = ">=3.8" authors = [ { name = "Tom Christie", email = "tom@tomchristie.com" }, ] @@ -20,7 +20,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", diff --git a/tests/test_config.py b/tests/test_config.py index d496cd4a0e..00913b2c17 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,5 @@ import os import ssl -import sys from pathlib import Path import certifi @@ -176,28 +175,26 @@ def test_timeout_repr(): not hasattr(ssl.SSLContext, "keylog_filename"), reason="requires OpenSSL 1.1.1 or higher", ) -@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher") def test_ssl_config_support_for_keylog_file(tmpdir, monkeypatch): # pragma: no cover - if sys.version_info > (3, 8): - with monkeypatch.context() as m: - m.delenv("SSLKEYLOGFILE", raising=False) + with monkeypatch.context() as m: + m.delenv("SSLKEYLOGFILE", raising=False) - context = httpx.create_ssl_context(trust_env=True) + context = httpx.create_ssl_context(trust_env=True) - assert context.keylog_filename is None + assert context.keylog_filename is None - filename = str(tmpdir.join("test.log")) + filename = str(tmpdir.join("test.log")) - with monkeypatch.context() as m: - m.setenv("SSLKEYLOGFILE", filename) + with monkeypatch.context() as m: + m.setenv("SSLKEYLOGFILE", filename) - context = httpx.create_ssl_context(trust_env=True) + context = httpx.create_ssl_context(trust_env=True) - assert context.keylog_filename == filename + assert context.keylog_filename == filename - context = httpx.create_ssl_context(trust_env=False) + context = httpx.create_ssl_context(trust_env=False) - assert context.keylog_filename is None + assert context.keylog_filename is None def test_proxy_from_url(): From 534b47ebf2700c0be8867ba887d1d890d8ad96d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:36:38 +0200 Subject: [PATCH 19/36] Bump trustme from 1.0.0 to 1.1.0 (#2804) Bumps [trustme](https://github.com/python-trio/trustme) from 1.0.0 to 1.1.0. - [Commits](https://github.com/python-trio/trustme/compare/v1.0.0...v1.1.0) --- updated-dependencies: - dependency-name: trustme dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9050c118c5..e1127efb67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,5 +28,5 @@ pytest==7.4.0 ruff==0.0.275 trio==0.22.0 trio-typing==0.8.0 -trustme==1.0.0 +trustme==1.1.0 uvicorn==0.22.0 From 304433ebaa3fb9e08af3290d11f376c1a4d2b7e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 08:52:17 -0500 Subject: [PATCH 20/36] Bump black from 23.3.0 to 23.7.0 (#2805) Bumps [black](https://github.com/psf/black) from 23.3.0 to 23.7.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/23.3.0...23.7.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e1127efb67..b58159593b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,7 +19,7 @@ build==0.10.0 twine==4.0.2 # Tests & Linting -black==23.3.0 +black==23.7.0 coverage[toml]==7.2.7 cryptography==41.0.3 mypy==1.4.1 From c20bacbf765ae3f819dd1865a7b094e8bb4ad3ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:01:00 -0500 Subject: [PATCH 21/36] Bump trio from 0.22.0 to 0.22.2 (#2807) Bumps [trio](https://github.com/python-trio/trio) from 0.22.0 to 0.22.2. - [Release notes](https://github.com/python-trio/trio/releases) - [Commits](https://github.com/python-trio/trio/compare/v0.22.0...v0.22.2) --- updated-dependencies: - dependency-name: trio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b58159593b..5b0243ac47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,7 @@ mypy==1.4.1 types-certifi==2021.10.8.2 pytest==7.4.0 ruff==0.0.275 -trio==0.22.0 +trio==0.22.2 trio-typing==0.8.0 trustme==1.1.0 uvicorn==0.22.0 From 0f61aa58d66680c239ce43c8cdd453e7dc532bfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Aug 2023 08:44:32 +0200 Subject: [PATCH 22/36] Bump mkdocs from 1.4.3 to 1.5.2 (#2818) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5b0243ac47..21b2bb9b93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ chardet==5.1.0 types-chardet==5.0.4.5 # Documentation -mkdocs==1.4.3 +mkdocs==1.5.2 mkautodoc==0.2.0 mkdocs-material==9.1.17 From 053bc57c3799801ff11273dd393cb0715e63ecf9 Mon Sep 17 00:00:00 2001 From: xzmeng Date: Tue, 29 Aug 2023 17:27:23 +0800 Subject: [PATCH 23/36] fix a typo in docs/logging.md (#2830) --- docs/logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/logging.md b/docs/logging.md index 4a32240ca8..53ae74990d 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -41,7 +41,7 @@ DEBUG [2023-03-16 14:36:21] httpcore - connection.close.started DEBUG [2023-03-16 14:36:21] httpcore - connection.close.complete ``` -Logging output includes information from both the high-level `httpx` logger, and the network-level `httpcore` logger, which can be configured seperately. +Logging output includes information from both the high-level `httpx` logger, and the network-level `httpcore` logger, which can be configured separately. For handling more complex logging configurations you might want to use the dictionary configuration style... From 3a7f6d1a5ddcad974ab6ae79b7465b4d52bb3133 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:48:11 -0500 Subject: [PATCH 24/36] Bump ruff from 0.0.275 to 0.0.286 (#2836) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.275 to 0.0.286. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.275...v0.0.286) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 21b2bb9b93..ab68fbacbe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ cryptography==41.0.3 mypy==1.4.1 types-certifi==2021.10.8.2 pytest==7.4.0 -ruff==0.0.275 +ruff==0.0.286 trio==0.22.2 trio-typing==0.8.0 trustme==1.1.0 From b95ef3e48952639eec74e412ed409eeaa1722a4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:55:09 -0500 Subject: [PATCH 25/36] Bump mypy from 1.4.1 to 1.5.1 (#2838) Bumps [mypy](https://github.com/python/mypy) from 1.4.1 to 1.5.1. - [Commits](https://github.com/python/mypy/compare/v1.4.1...v1.5.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Zanie Blue --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab68fbacbe..7f05e84656 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ twine==4.0.2 black==23.7.0 coverage[toml]==7.2.7 cryptography==41.0.3 -mypy==1.4.1 +mypy==1.5.1 types-certifi==2021.10.8.2 pytest==7.4.0 ruff==0.0.286 From 1703da870619b64bb6ba3152481a8168237903f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 14:12:56 -0500 Subject: [PATCH 26/36] Bump chardet from 5.1.0 to 5.2.0 (#2837) Bumps [chardet](https://github.com/chardet/chardet) from 5.1.0 to 5.2.0. - [Release notes](https://github.com/chardet/chardet/releases) - [Commits](https://github.com/chardet/chardet/compare/5.1.0...5.2.0) --- updated-dependencies: - dependency-name: chardet dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7f05e84656..ec720861eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ # Optional charset auto-detection # Used in our test cases -chardet==5.1.0 +chardet==5.2.0 types-chardet==5.0.4.5 # Documentation From ec4aa5e4cea10cd2d56672bc80abe20d86dcf6a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Sep 2023 08:10:53 +0100 Subject: [PATCH 27/36] Bump mkdocs-material from 9.1.17 to 9.2.6 (#2835) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ec720861eb..81cf5cb599 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ types-chardet==5.0.4.5 # Documentation mkdocs==1.5.2 mkautodoc==0.2.0 -mkdocs-material==9.1.17 +mkdocs-material==9.2.6 # Packaging build==0.10.0 From 7ecd828237cc33f80bef657e33af368e94b8546c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:19:54 +0100 Subject: [PATCH 28/36] Bump coverage[toml] from 7.2.7 to 7.3.0 (#2839) Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 7.2.7 to 7.3.0. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.2.7...7.3.0) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tom Christie --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 81cf5cb599..a884446efa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ twine==4.0.2 # Tests & Linting black==23.7.0 -coverage[toml]==7.2.7 +coverage[toml]==7.3.0 cryptography==41.0.3 mypy==1.5.1 types-certifi==2021.10.8.2 From e874351f04471029b2c5dcb2d0b50baccc7b9bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalle=20M=C3=B8ller?= Date: Thu, 7 Sep 2023 11:24:49 +0200 Subject: [PATCH 29/36] Update _models.py (#2840) To remove the unknown dict type info (variable) extensions: ResponseExtensions | dict[Unknown, Unknown] Co-authored-by: Tom Christie --- httpx/_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpx/_models.py b/httpx/_models.py index 5af5c5d23c..e1e45cf06b 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -467,7 +467,7 @@ def __init__( # the client will set `response.next_request`. self.next_request: typing.Optional[Request] = None - self.extensions = {} if extensions is None else extensions + self.extensions: ResponseExtensions = {} if extensions is None else extensions self.history = [] if history is None else list(history) self.is_closed = False From adbcd0e0e7fcb174ce60772c7d6104b8b9d329ca Mon Sep 17 00:00:00 2001 From: Kar Petrosyan <92274156+karosis88@users.noreply.github.com> Date: Mon, 11 Sep 2023 02:29:22 -0400 Subject: [PATCH 30/36] Change extensions type (#2803) * Change extensions type * Update changelog * install httpcore from the git * Revert "install httpcore from the git" This reverts commit 1813c6aff178cce3b63d7458ec9a8337542de9dd. * bump httpcore version * fix requirements --------- Co-authored-by: Tom Christie --- CHANGELOG.md | 1 + httpx/_types.py | 5 +++-- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f49c45d1..6fc53394a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added +* Change the type of `Extensions` from `Mapping[Str, Any]` to `MutableMapping[Str, Any]`. (#2803) * Add `socket_options` argument to `httpx.HTTPTransport` and `httpx.AsyncHTTPTransport` classes. (#2716) * The `Response.raise_for_status()` method now returns the response instance. For example: `data = httpx.get('...').raise_for_status().json()`. (#2776) diff --git a/httpx/_types.py b/httpx/_types.py index 6b610e1408..83cf35a32a 100644 --- a/httpx/_types.py +++ b/httpx/_types.py @@ -16,6 +16,7 @@ Iterator, List, Mapping, + MutableMapping, NamedTuple, Optional, Sequence, @@ -87,7 +88,7 @@ RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] -ResponseExtensions = Mapping[str, Any] +ResponseExtensions = MutableMapping[str, Any] RequestData = Mapping[str, Any] @@ -104,7 +105,7 @@ ] RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] -RequestExtensions = Mapping[str, Any] +RequestExtensions = MutableMapping[str, Any] class SyncByteStream: diff --git a/pyproject.toml b/pyproject.toml index acd41baf3a..753e671ebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ ] dependencies = [ "certifi", - "httpcore>=0.17.2,<0.18.0", + "httpcore>=0.18.0,<0.19.0", "idna", "sniffio", ] From a54ecccd5b7e61b29ec13df45ecc0b82a2c168bb Mon Sep 17 00:00:00 2001 From: Kar Petrosyan <92274156+karosis88@users.noreply.github.com> Date: Mon, 11 Sep 2023 05:56:01 -0400 Subject: [PATCH 31/36] HTTPS proxies support (#2845) * Add ssl_context argument to Proxy class * Changelog --- CHANGELOG.md | 1 + httpx/_config.py | 2 ++ httpx/_transports/default.py | 1 + 3 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc53394a7..5a6d927409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added +* Support HTTPS proxies. (#2845) * Change the type of `Extensions` from `Mapping[Str, Any]` to `MutableMapping[Str, Any]`. (#2803) * Add `socket_options` argument to `httpx.HTTPTransport` and `httpx.AsyncHTTPTransport` classes. (#2716) * The `Response.raise_for_status()` method now returns the response instance. For example: `data = httpx.get('...').raise_for_status().json()`. (#2776) diff --git a/httpx/_config.py b/httpx/_config.py index f46a5bfe6b..39d81a20a0 100644 --- a/httpx/_config.py +++ b/httpx/_config.py @@ -326,6 +326,7 @@ def __init__( self, url: URLTypes, *, + ssl_context: typing.Optional[ssl.SSLContext] = None, auth: typing.Optional[typing.Tuple[str, str]] = None, headers: typing.Optional[HeaderTypes] = None, ): @@ -343,6 +344,7 @@ def __init__( self.url = url self.auth = auth self.headers = headers + self.ssl_context = ssl_context @property def raw_auth(self) -> typing.Optional[typing.Tuple[bytes, bytes]]: diff --git a/httpx/_transports/default.py b/httpx/_transports/default.py index 1aebd5c5a5..7dba5b8208 100644 --- a/httpx/_transports/default.py +++ b/httpx/_transports/default.py @@ -156,6 +156,7 @@ def __init__( proxy_auth=proxy.raw_auth, proxy_headers=proxy.headers.raw, ssl_context=ssl_context, + proxy_ssl_context=proxy.ssl_context, max_connections=limits.max_connections, max_keepalive_connections=limits.max_keepalive_connections, keepalive_expiry=limits.keepalive_expiry, From c3585a5ccfa57bec653f3846b8625a27d11dcd5e Mon Sep 17 00:00:00 2001 From: Trim21 Date: Mon, 11 Sep 2023 18:13:24 +0800 Subject: [PATCH 32/36] Version 0.25.0 (#2801) * bump * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Kar Petrosyan <92274156+karosis88@users.noreply.github.com> * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: Kar Petrosyan <92274156+karosis88@users.noreply.github.com> Co-authored-by: Tom Christie --- CHANGELOG.md | 6 ++++-- httpx/__version__.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a6d927409..66d2b080cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## Unreleased +## 0.25.0 (11th Sep, 2023) ### Removed @@ -20,7 +20,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed * Return `500` error response instead of exceptions when `raise_app_exceptions=False` is set on `ASGITransport`. (#2669) -* Ensure all WSGITransport environs have a SERVER_PROTOCOL. (#2708) +* Ensure all `WSGITransport` environs have a `SERVER_PROTOCOL`. (#2708) +* Always encode forward slashes as `%2F` in query parameters (#2723) +* Use Mozilla documentation instead of `httpstatuses.com` for HTTP error reference (#2768) ## 0.24.1 (17th May, 2023) diff --git a/httpx/__version__.py b/httpx/__version__.py index 6a8e63c602..bfa421ad60 100644 --- a/httpx/__version__.py +++ b/httpx/__version__.py @@ -1,3 +1,3 @@ __title__ = "httpx" __description__ = "A next generation HTTP client, for Python 3." -__version__ = "0.24.1" +__version__ = "0.25.0" From 88e84314378b31336027363af862619c519a4a3a Mon Sep 17 00:00:00 2001 From: Musale Martin Date: Fri, 15 Sep 2023 12:52:11 +0300 Subject: [PATCH 33/36] Add cookies to the retried request when performing digest authentication. (#2846) * Add cookies from the response to the retried request * Conditionally add cookies from the response * Fix failing auth module tests * Fix linting error * Add tests to check set cookies from server --- httpx/_auth.py | 4 +++- tests/test_auth.py | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/httpx/_auth.py b/httpx/_auth.py index 1d7385d573..27dc7f743b 100644 --- a/httpx/_auth.py +++ b/httpx/_auth.py @@ -8,7 +8,7 @@ from urllib.request import parse_http_list from ._exceptions import ProtocolError -from ._models import Request, Response +from ._models import Cookies, Request, Response from ._utils import to_bytes, to_str, unquote if typing.TYPE_CHECKING: # pragma: no cover @@ -217,6 +217,8 @@ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, Non request.headers["Authorization"] = self._build_auth_header( request, self._last_challenge ) + if response.cookies: + Cookies(response.cookies).set_cookie_header(request=request) yield request def _parse_challenge( diff --git a/tests/test_auth.py b/tests/test_auth.py index a1997c2fe2..563256954d 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -54,7 +54,7 @@ def test_digest_auth_with_401(): "WWW-Authenticate": 'Digest realm="...", qop="auth", nonce="...", opaque="..."' } response = httpx.Response( - content=b"Auth required", status_code=401, headers=headers + content=b"Auth required", status_code=401, headers=headers, request=request ) request = flow.send(response) assert request.headers["Authorization"].startswith("Digest") @@ -79,7 +79,7 @@ def test_digest_auth_with_401_nonce_counting(): "WWW-Authenticate": 'Digest realm="...", qop="auth", nonce="...", opaque="..."' } response = httpx.Response( - content=b"Auth required", status_code=401, headers=headers + content=b"Auth required", status_code=401, headers=headers, request=request ) first_request = flow.send(response) assert first_request.headers["Authorization"].startswith("Digest") @@ -101,3 +101,42 @@ def test_digest_auth_with_401_nonce_counting(): response = httpx.Response(content=b"Hello, world!", status_code=200) with pytest.raises(StopIteration): flow.send(response) + + +def set_cookies(request: httpx.Request) -> httpx.Response: + headers = { + "Set-Cookie": "session=.session_value...", + "WWW-Authenticate": 'Digest realm="...", qop="auth", nonce="...", opaque="..."', + } + if request.url.path == "/auth": + return httpx.Response( + content=b"Auth required", status_code=401, headers=headers + ) + else: + raise NotImplementedError() # pragma: no cover + + +def test_digest_auth_setting_cookie_in_request(): + url = "https://www.example.com/auth" + client = httpx.Client(transport=httpx.MockTransport(set_cookies)) + request = client.build_request("GET", url) + + auth = httpx.DigestAuth(username="user", password="pass") + flow = auth.sync_auth_flow(request) + request = next(flow) + assert "Authorization" not in request.headers + + response = client.get(url) + assert len(response.cookies) > 0 + assert response.cookies["session"] == ".session_value..." + + request = flow.send(response) + assert request.headers["Authorization"].startswith("Digest") + assert request.headers["Cookie"] == "session=.session_value..." + + # No other requests are made. + response = httpx.Response( + content=b"Hello, world!", status_code=200, request=request + ) + with pytest.raises(StopIteration): + flow.send(response) From e4241c6155c8a85789566b8fe10fa2291eea20f4 Mon Sep 17 00:00:00 2001 From: "Y.D.X" <73375426+YDX-2147483647@users.noreply.github.com> Date: Sun, 17 Sep 2023 04:58:56 +0800 Subject: [PATCH 34/36] Drop private imports from test_proxies.py (#2850) --- tests/client/test_proxies.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/client/test_proxies.py b/tests/client/test_proxies.py index 3070022fa1..62ffc380bf 100644 --- a/tests/client/test_proxies.py +++ b/tests/client/test_proxies.py @@ -2,7 +2,6 @@ import pytest import httpx -from httpx._utils import URLPattern def url_to_origin(url: str) -> httpcore.URL: @@ -35,11 +34,12 @@ def url_to_origin(url: str) -> httpcore.URL: ) def test_proxies_parameter(proxies, expected_proxies): client = httpx.Client(proxies=proxies) + client_patterns = [p.pattern for p in client._mounts.keys()] + client_proxies = list(client._mounts.values()) for proxy_key, url in expected_proxies: - pattern = URLPattern(proxy_key) - assert pattern in client._mounts - proxy = client._mounts[pattern] + assert proxy_key in client_patterns + proxy = client_proxies[client_patterns.index(proxy_key)] assert isinstance(proxy, httpx.HTTPTransport) assert isinstance(proxy._pool, httpcore.HTTPProxy) assert proxy._pool._proxy_url == url_to_origin(url) From 59df8190a4c72c58238dda315ad446f1c132dcdc Mon Sep 17 00:00:00 2001 From: xzmeng Date: Tue, 19 Sep 2023 15:54:32 +0800 Subject: [PATCH 35/36] Raise ValueError on `Response.encoding` being set after `Response.text` has been accessed (#2852) * Raise ValueError on change encoding * Always raise ValueError for simplicity * update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ httpx/_models.py | 10 ++++++++++ tests/models/test_responses.py | 17 +++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d2b080cd..4842dfff82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## Unreleased + +### Fixed + +* Raise `ValueError` on `Response.encoding` being set after `Response.text` has been accessed. (#2852) + ## 0.25.0 (11th Sep, 2023) ### Removed diff --git a/httpx/_models.py b/httpx/_models.py index e1e45cf06b..8a6bda04bb 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -603,6 +603,16 @@ def encoding(self) -> typing.Optional[str]: @encoding.setter def encoding(self, value: str) -> None: + """ + Set the encoding to use for decoding the byte content into text. + + If the `text` attribute has been accessed, attempting to set the + encoding will throw a ValueError. + """ + if hasattr(self, "_text"): + raise ValueError( + "Setting encoding after `text` has been accessed is not allowed." + ) self._encoding = value @property diff --git a/tests/models/test_responses.py b/tests/models/test_responses.py index 9e65de8154..9177773a50 100644 --- a/tests/models/test_responses.py +++ b/tests/models/test_responses.py @@ -298,6 +298,23 @@ def test_response_force_encoding(): assert response.encoding == "iso-8859-1" +def test_response_force_encoding_after_text_accessed(): + response = httpx.Response( + 200, + content=b"Hello, world!", + ) + assert response.status_code == 200 + assert response.reason_phrase == "OK" + assert response.text == "Hello, world!" + assert response.encoding == "utf-8" + + with pytest.raises(ValueError): + response.encoding = "UTF8" + + with pytest.raises(ValueError): + response.encoding = "iso-8859-1" + + def test_read(): response = httpx.Response( 200, From 7c9db49f0c6c454fa6d0d7bdbd0d71c0a0cbacfd Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 21 Sep 2023 08:35:56 -0600 Subject: [PATCH 36/36] Add support for Python 3.12 (#2854) * Add support for Python 3.12 * Bump GitHub Actions * Remove redundant version checks * Add CHANGELOG entry --- .github/workflows/publish.yml | 2 +- .github/workflows/test-suite.yml | 5 +++-- CHANGELOG.md | 4 ++++ httpx/_compat.py | 4 +--- httpx/_config.py | 17 +++++++---------- pyproject.toml | 1 + 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3cbd4a8a2d..4ceb8c69e0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: name: deploy steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" - uses: "actions/setup-python@v4" with: python-version: 3.8 diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index eb1cc7e22e..c3ad08f145 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -14,13 +14,14 @@ jobs: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" - uses: "actions/setup-python@v4" with: python-version: "${{ matrix.python-version }}" + allow-prereleases: true - name: "Install dependencies" run: "scripts/install" - name: "Run linting checks" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4842dfff82..73b99c6b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Added + +* Add support for Python 3.12. (#2854) + ### Fixed * Raise `ValueError` on `Response.encoding` being set after `Response.text` has been accessed. (#2852) diff --git a/httpx/_compat.py b/httpx/_compat.py index a271c6b800..493e621087 100644 --- a/httpx/_compat.py +++ b/httpx/_compat.py @@ -16,9 +16,7 @@ except ImportError: brotli = None -if sys.version_info >= (3, 10) or ( - sys.version_info >= (3, 8) and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7) -): +if sys.version_info >= (3, 10) or ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7): def set_minimum_tls_version_1_2(context: ssl.SSLContext) -> None: # The OP_NO_SSL* and OP_NO_TLS* become deprecated in favor of diff --git a/httpx/_config.py b/httpx/_config.py index 39d81a20a0..45ed29ed70 100644 --- a/httpx/_config.py +++ b/httpx/_config.py @@ -1,7 +1,6 @@ import logging import os import ssl -import sys import typing from pathlib import Path @@ -128,11 +127,10 @@ def load_ssl_context_verify(self) -> ssl.SSLContext: # Signal to server support for PHA in TLS 1.3. Raises an # AttributeError if only read-only access is implemented. - if sys.version_info >= (3, 8): # pragma: no cover - try: - context.post_handshake_auth = True - except AttributeError: # pragma: no cover - pass + try: + context.post_handshake_auth = True + except AttributeError: # pragma: no cover + pass # Disable using 'commonName' for SSLContext.check_hostname # when the 'subjectAltName' extension isn't available. @@ -168,10 +166,9 @@ def _create_default_ssl_context(self) -> ssl.SSLContext: alpn_idents = ["http/1.1", "h2"] if self.http2 else ["http/1.1"] context.set_alpn_protocols(alpn_idents) - if sys.version_info >= (3, 8): # pragma: no cover - keylogfile = os.environ.get("SSLKEYLOGFILE") - if keylogfile and self.trust_env: - context.keylog_filename = keylogfile + keylogfile = os.environ.get("SSLKEYLOGFILE") + if keylogfile and self.trust_env: + context.keylog_filename = keylogfile return context diff --git a/pyproject.toml b/pyproject.toml index 753e671ebc..baa92e9a43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Internet :: WWW/HTTP", ] dependencies = [