Don't crash on a bracketed IPv6 entry in NO_PROXY
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, becauseis_ipv6_hostnameignores 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]:8080does not bypass another port, and that[::1]does not bypass another IPv6 address.
Verification
- Reverting only the
_utils.pychange fails exactly the three newtest_utils.pycases and all twelvetest_proxies.pycases; nothing else changes. scripts/checkexits 0; full suite2026 passed, 1 skipped.- Local note: this was run on macOS with the system proxy enabled, where
urllib.request.getproxies()falls back toscutiland makes the pre-existingtest_get_environment_proxiesparametrizations fail regardless of this change. To match Linux CI I unset the proxy environment variables and neutralizedgetproxies_macosx_sysconfviasitecustomize.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.