proxylib
Which proxy should this request use? proxylib answers it the same way on every
OS — from the proxy environment variables, the OS's own resolver and settings, or a
PAC file — and gets the answer into requests, urllib, httpx and aiohttp, with
failover along the list a PAC returns. Documentation:
https://jose-pr.github.io/proxylib/.
Features
- One precedence on every OS —
auto_proxy()uses the proxy environment variables when any is set (asurllib.request.getproxies()does), else the OS's own resolver (WinHTTP on Windows, CFNetwork on macOS, libproxy on Linux when installed), backed by the OS settings read directly. - OS settings as data —
system_config()returns aProxyConfigfrom the Windows Internet Options, macOS System Configuration, or GNOME, MATE, KDE and NetworkManager: per-scheme proxies, SOCKS, the PAC URL, auto-detection and the bypass list in that OS's own syntax, with "the OS says no proxy" told apart from "no settings". - WPAD only when asked — auto-detection runs when the OS has it switched on, or
with
wpad=Trueon a machine with no settings; never because nothing else was found. - PAC scripts that cannot hang you — the full Netscape and Microsoft PAC builtins,
run as real JavaScript (quickjs or dukpy) in a helper process with a time limit; a
Python function can stand in for
FindProxyForURL. - Clients with failover —
requests,httpx,aiohttpandurllibresolve the proxy from the real request URL; aPROXY a; PROXY b; DIRECTanswer is a failover list, and a proxy that refuses or times out connecting is skipped for 5 minutes. - Proxy headers and URL-prefix gateways — a
Proxycan carry headers sent to that proxy only (a Bearer token, or a credential under another header name), andprefix+https://gw/fetch/(PREFIX https://gw/fetch/in a PAC result) names a gateway fetching<gateway><URL>. A client skips the entries it cannot use and raisesUnsupportedProxyErrorwhen none is left, instead of leaking a header or going elsewhere. - Patched clients that behave as before —
requestsandurllibget proxylib's integration as they are imported, choosing exactly the proxy they always did until youpatch()a map in (nestable) or name them inPROXYLIB_IMPORTPATCH. - Bypass rules in each OS's syntax —
NO_PROXY(curl), WindowsProxyOverride, macOSExceptionsList, GNOMEignore-hosts, KDENoProxyFor: suffixes, globs, IPv4 and IPv6 CIDR, ports,<local>,<subnet>. - Composable — any mapping is a proxy map;
ChainProxyMap,Bypass,Cached,RoundRobinandProbedcompose in the order you write them;alookup()for asyncio.
Installation
pip install proxylib
Python 3.9 to 3.14. Dependencies: netimps and importpatch, neither with dependencies of its own.
| Extra | Adds | Needed for |
|---|---|---|
proxylib[pac] |
quickjs-ng (Python 3.10+) / quickjs (3.9) |
Running PAC files (preferred engine) |
proxylib[dukpy] |
dukpy |
The dukpy engine, for platforms without a quickjs wheel (Windows ARM64, Intel macOS) |
The requests, httpx and aiohttp integrations need no extra: each works when its
client is installed. Without a JS engine, loading a PAC raises MissingExtraError
naming the extra; on_missing_engine="direct" gives a warning and DIRECT instead.
Quick start
Ask the machine
from proxylib import auto_proxy, lookup
proxymap = auto_proxy() # proxy variables if set, else the OS resolver and settings
print(lookup(proxymap, "https://example.com/")) # (Proxy(...), ...) or (None,) for DIRECT
proxymap.close()
requests
import requests
from proxylib import ProxyMapAdapter, auto_proxy
session = requests.Session()
adapter = ProxyMapAdapter(auto_proxy())
session.mount("http://", adapter)
session.mount("https://", adapter)
response = session.get("https://example.com/")
ProxyMapAdapter sees the real request URL — scheme, host and path, what a PAC's
FindProxyForURL expects — and fails over along the answer. A chain also works where
requests takes its proxies:
import requests
requests.get("https://example.com/", proxies={"https": "PROXY proxy.invalid:3128; DIRECT"})
(proxylib's integration, installed whenever requests is imported, reads that value; a plain proxy URL there stays requests' own).
httpx and aiohttp
import httpx
from proxylib import auto_proxy
from proxylib.integrations.httpx import ProxyTransport
with httpx.Client(transport=ProxyTransport(auto_proxy())) as client:
client.get("https://example.com/")
AsyncProxyTransport does the same for httpx.AsyncClient. aiohttp has no transport
hook, so its session is wrapped:
import asyncio
from proxylib import auto_proxy
from proxylib.integrations.aiohttp import ProxySession
async def main():
async with ProxySession(auto_proxy()) as session:
async with session.get("https://example.com/") as response:
return await response.text()
asyncio.run(main())
urllib
import urllib.request
from proxylib import ProxyMapHandler, auto_proxy
opener = urllib.request.build_opener(ProxyMapHandler(auto_proxy()))
opener.open("https://example.com/").read()
Patch every client at once
import requests
import proxylib
with proxylib.patch(proxylib.auto_proxy()):
requests.get("https://example.com/") # resolved by auto_proxy()
# back to requests' own choice here; proxylib.unpatch() ends every patch
Patches nest (the newest wins); targets=["requests"] limits one to a client. Without
code, PROXYLIB_IMPORTPATCH=1 (or client names: requests:httpx) makes those clients
use auto_proxy() in every program that imports them, through importpatch;
PROXYLIB_IMPORTPATCH=0 turns the integration off.
PAC files
from proxylib import load_pac, loads_pac, lookup
script = 'function FindProxyForURL(url, host) { return "PROXY proxy.invalid:3128; DIRECT"; }'
from_text = loads_pac(script)
from_url = load_pac("https://example.com/proxy.pac") # a URL, or a path
print(lookup(from_text, "https://example.com/"))
from_text.close()
from_url.close()
Each call gets 5 seconds (script_timeout=): an overrun raises PACError and the
script fails fast for 30 seconds before it is restarted.
Any mapping is a proxy map
from proxylib import Bypass, ChainProxyMap, EnvProxyConfig, SimpleProxyMap, lookup
proxymap = Bypass(
ChainProxyMap({"https": "http://corp-proxy:3128"}, EnvProxyConfig(), SimpleProxyMap("DIRECT")),
"*.internal, 10.0.0.0/8",
)
print(lookup(proxymap, "https://example.com/")) # (Proxy(scheme='http', host='corp-proxy', port=3128),)
print(lookup(proxymap, "https://app.internal/")) # (None,)
API overview
| Module | Purpose |
|---|---|
proxylib.proxy |
Proxy, the ProxyMap protocol and factory, SimpleProxyMap, ChainProxyMap, lookup(), alookup() |
proxylib.wrappers |
Bypass, Cached, RoundRobin, Probed, configure() |
proxylib.bypass |
BypassRules and its dialects |
proxylib.env |
EnvProxyConfig — the proxy environment variables |
proxylib.system |
auto_proxy(), system_config(), ProxyConfig; the OS resolvers WinHTTPProxyMap, CFNetworkProxyMap, LibProxyMap |
proxylib.pac |
load(), loads(), PacScript, PacProxyMap, functions; .wpad.discover(); .engines |
proxylib.patching |
patch(), Patch, unpatch(), register_patcher(), active(), targets() |
proxylib.integrations |
.requests.ProxyMapAdapter, .urllib.ProxyMapHandler, .httpx.ProxyTransport/AsyncProxyTransport, .aiohttp.ProxySession, .dict.ProxyDict |
proxylib.exceptions |
ProxylibError and its subclasses |
The full contract, with every signature, is the API header shipped in the package
(proxylib/AGENTS.md) and the documentation site.
Moving from 1.x: migration guide.
Known limitations
- DHCP-based WPAD is not implemented — only DNS+HTTP (
wpad.<domain>/wpad.dat). - Linux desktops are read without session detection: GNOME/MATE (
gsettings), KDE (kioslaverc) and NetworkManager (nmcli, ordbus-send); the first with a proxy configured wins. Other desktops are covered only through libproxy, if installed. Thedbus-sendpath reads its debug output and has not been checked against a live NetworkManager. - urllib does not fail over —
ProxyMapHandleruses the healthiest entry but cannot re-send a request through the next one; useProbed(proxymap), or requests, httpx or aiohttp. Failover happens only for errors before the request is sent. - SOCKS proxies need the client's SOCKS support:
requests[socks]for requests,httpx[socks]for httpx; aiohttp and urllib speak HTTP proxies only. - aiohttp skips gateways (requests, httpx and urllib take them). urllib
sends only a
Proxy-Authorizationproxy header for https URLs (it would send any other through the tunnel), so it skips a proxy with other headers there.
Development
python -m venv .venv/3.14
.venv/3.14/Scripts/python -m pip install -e ".[dev,dukpy]"
.venv/3.14/Scripts/python -m pytest -q -W error::UserWarning
(bin/ instead of Scripts/ outside Windows.) The quickjs tests need proxylib[pac],
which has no Windows ARM64 wheel; they skip, visibly, without it. The docs build with
pip install -e ".[docs]" and mkdocs build --strict.
Releasing
This project follows Semantic Versioning and keeps a
CHANGELOG.md. Pushing a
tag matching v* triggers the release workflow: test gate → build (checking the tag
names the version built) → a strict docs build as a gate → GitHub release → publish to
PyPI with trusted publishing. The release workflow never deploys the docs site itself:
for a final release its last job dispatches the docs workflow at the tag, which owns
every Pages deploy.
License
MIT — see LICENSE.
Metadata
Release files for proxylib 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| proxylib-2.0.0.tar.gz | 139.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| proxylib-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 237.7 kB
Release files / proxylib-2.0.0.tar.gz
| Download URL | proxylib-2.0.0.tar.gz |
|---|---|
| Size | 139.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a01ba8871a2751ac15f53322252afea915cce6ed4c7d4ae9b5fd0880327805be
|
|
BLAKE2b-256 checksum How to use checksums |
e264560833e1dbc8745094ef14551cb6a556336ea706012fa38f74b5b8b7bbac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logRelease files / proxylib-2.0.0-py3-none-any.whl
| Download URL | proxylib-2.0.0-py3-none-any.whl |
|---|---|
| Size | 98.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
01e6cf9005f081355ccecaf4d38c2b20b79742e21fa24003970950c32831323d
|
|
BLAKE2b-256 checksum How to use checksums |
d23e9cb83139f42fb9b5084dbdbe9af1681797aa6b5b0903b6213fba6ce90adb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logSecurity for proxylib 2.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for proxylib-2.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.