Don't crash on a bracketed IPv6 entry in NO_PROXY

Closed
NA
7d ago
+46-0

Summary

Recognize bracketed IPv6 literals in NO_PROXY before the hostname-wildcard fallback, preventing valid entries such as [::1] and [::1]:8080 from aborting client construction. all://*[::1] is not a parseable URL pattern, so Client() and AsyncClient() raised InvalidURL: Invalid port: ':1]' before any request was made.

This change does not add general handling for malformed entries, or CIDR/network matching — URLPattern.matches does not consider a prefix length, which is what #969 covers.

Raised in discussion https://github.com/pydantic/httpx2/discussions/1241. That thread and this PR were posted together; I am happy to hold the implementation until the thread settles, or to adjust it based on the feedback there.

What changed

  • src/httpx2/httpx2/_utils.py: a bracketed entry whose bracketed part is a bare IPv6 address is recognized before the dispatch, producing the same mount as the unbracketed spelling. The check requires the bracketed part to be the whole address, because is_ipv6_hostname ignores anything after a /.
  • tests/httpx2/test_utils.py: three cases covering [::1], [::1]:8080, [::1]/128.
  • tests/httpx2/client/test_proxies.py: client-construction and transport-selection cases for both client classes, including that [::1]:8080 does not bypass another port, and that [::1] does not bypass another IPv6 address.

Verification

  • Reverting only the _utils.py change fails exactly the three new test_utils.py cases and all twelve test_proxies.py cases; nothing else changes.
  • scripts/check exits 0; full suite 2026 passed, 1 skipped.
  • Local note: this was run on macOS with the system proxy enabled, where urllib.request.getproxies() falls back to scutil and makes the pre-existing test_get_environment_proxies parametrizations fail regardless of this change. To match Linux CI I unset the proxy environment variables and neutralized getproxies_macosx_sysconf via sitecustomize.py. This is not the same as having run on Linux CI.

Separate issue found while testing

Pre-existing, not addressed here: a NO_PROXY entry that names a scheme's default port never matches, because the request URL normalizes that port to None while the pattern keeps it. This affects IPv4 and domain entries as well, and it can also over-match from the other direction. Happy to file it separately.

Checklist

  • I understand that this PR may be closed in case there was no previous discussion.
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.

The documentation box is left unticked: this changes no public API and no documented behaviour contract — it fixes the parsing of an existing configuration. Happy to add a changelog entry or a docs note if the project requires one.