proxylib
Platform-agnostic proxy configuration for Python: detect the system/environment proxy
settings, evaluate PAC (Proxy Auto-Config) files — including WPAD auto-discovery — and
wire the result straight into requests or
urllib.
Zero required runtime dependencies; a couple of optional extras unlock more (see below).
Features
- Proxy string parsing —
PROXY host:port; DIRECT-style PAC strings,scheme://host:portURLs, and env-var-style values, all normalized into a commonProxytype. - System proxy detection — Windows (via the WinHTTP API), macOS (
scutil --proxy), and Linux (env vars, plus best-effort libproxy, GNOME, MATE, KDE, and NetworkManager checks — tried in that order, first match wins).system_proxy()respects each OS/desktop's own auto-detect (WPAD) signal — "Automatically detect settings" on Windows, "Auto Proxy Discovery" on macOS, modeautowith a blank Configuration URL on GNOME/MATE,ProxyType=3in KDE'skioslaverc, or NetworkManager'sproxy.method=autowith no PAC set — and tries WPAD itself first, in that platform's own precedence order (auto-detect → PAC script → manual proxy → DIRECT). NetworkManager is read vianmcliif installed, elsedbus-senddirectly.LibProxyMap(libproxy's own resolution, called viactypesbindings to its C API — no subprocess) is also available as a standaloneProxyMapyou can use directly on any platform. - Env var proxy config —
HTTP_PROXY/HTTPS_PROXY/NO_PROXY(and lowercase variants), with correct exact/subdomainNO_PROXYmatching, plus CIDR entries,<local>, and*(bypass everything; curl andrequestshonour it only as the whole value, proxylib anywhere in the list). - PAC support — the full Netscape PAC utility-function set (including
dateRange/timeRange) plus the common Microsoft*Exextensions, runnable either as real JavaScript through a pluggable engine (dukpyorquickjs, picked viaPROXYLIB_JS_ENGINEor preferringquickjsif installed) or by subclassing in Python. - WPAD discovery — DNS + HTTP
wpad.<domain>/wpad.datlookup. Used directly bysystem_proxy()on any platform/desktop whose own auto-detect setting is on, and as a generic last-resort fallback inauto_proxy()when nothing else is configured. Results (including "no WPAD server here") are cached for 5 minutes so repeated lookups don't re-probe the network, and DNS resolution fails fast on a hanging lookup. requestsintegration — aProxyMapAdapterTransport Adapter that resolves the proxy from the real request URL (not just scheme+host).urllib.requestintegration —ProxyMapHandler, the same idea asProxyMapAdapterbut for the standard library's opener/handler system.- Global patching —
proxylib.patch(proxymap)wires an activeProxyMapinto newly createdrequests.Sessions and a globalurllibopener, without mounting an adapter by hand;proxylib.unpatch()undoes it, and anyProxyMapalso works directly as a context manager (with proxymap:). - Decorators & composition —
ChainProxyMapfor sequential fallback across multipleProxyMaps, andConfigurableProxyMapfor opt-in caching, active reachability probing (with a circuit breaker for dead proxies), round-robin selection, browser-style HTTPS privacy stripping, and local-address bypass.
Installation
pip install proxylib
Optional extras:
| Extra | Adds | Needed for |
|---|---|---|
proxylib[jspac] |
dukpy |
Executing PAC files as JavaScript — "any working engine" meta-extra |
proxylib[dukpy] |
dukpy |
The dukpy JS engine specifically |
proxylib[quickjs] |
quickjs-ng (Python ≥3.10) / quickjs (Python 3.9) |
The quickjs JS engine specifically (no dependency on dukpy) |
proxylib[ifaddr] |
ifaddr |
Accurate local network interface/prefix enumeration (used by NO_PROXY <local>) |
Without a JS engine installed, PAC files are still fetched and validated, but evaluate
to DIRECT (with a warning) since there's no engine to run them. With more than one
installed, quickjs is preferred by default; set PROXYLIB_JS_ENGINE=dukpy (or a
comma-separated priority list) to change that.
Quick start
Auto-detect the system proxy and use it with requests
import requests
from proxylib import auto_proxy, ProxyMapAdapter
proxymap = auto_proxy() # OS settings -> env vars -> WPAD discovery -> DIRECT
session = requests.Session()
adapter = ProxyMapAdapter(proxymap)
session.mount("http://", adapter)
session.mount("https://", adapter)
response = session.get("https://example.com")
ProxyMapAdapter is the recommended integration: it hooks HTTPAdapter.send(), so it
sees the real request URL (scheme, host and path) — exactly what a PAC file's
FindProxyForURL is meant to receive.
When you don't need that fidelity (no PAC path rules), ProxyDict is the simpler
option — a drop-in for any plain proxies dict:
from proxylib import auto_proxy, ProxyDict
requests.get("https://example.com", proxies=ProxyDict(auto_proxy()))
...or with plain urllib.request
import urllib.request
from proxylib import auto_proxy, ProxyMapHandler
proxymap = auto_proxy()
opener = urllib.request.build_opener(ProxyMapHandler(proxymap))
opener.open("https://example.com")
# or, to affect every urllib.request.urlopen() call process-wide:
urllib.request.install_opener(opener)
ProxyMapHandler subclasses urllib.request.ProxyHandler but resolves per-request
from the ProxyMap instead of a static {scheme: proxy_url} dict, for the same reason
ProxyMapAdapter exists for requests.
...or patch every new requests.Session/urllib opener at once
import proxylib
with proxylib.auto_proxy(): # patch() on __enter__, unpatch() on __exit__
requests.get("https://example.com") # any new Session picks up the active proxy
Outside a with block, call proxylib.patch(proxymap)/proxylib.unpatch() directly,
or pass targets=["requests"]/["urllib"] to patch just one. Sessions created before
patch() are untouched.
From environment variables
from proxylib import EnvProxyConfig
proxymap = EnvProxyConfig.from_env() # HTTP_PROXY / HTTPS_PROXY / NO_PROXY
proxymap["https://example.com"] # -> [Proxy(...)] or [None] for DIRECT
A fixed proxy, or DIRECT
from proxylib import SimpleProxyMap
proxymap = SimpleProxyMap("http://user:pass@proxy.example.com:8080")
direct = SimpleProxyMap("DIRECT")
Loading a PAC file directly
from proxylib import load_pac
proxymap = load_pac("https://example.com/proxy.pac")
proxymap = load_pac("file:./proxy.pac")
proxymap["https://example.com/"]
ProxyMap(source) is a small factory that picks the right implementation for you: a
bare scheme://host:port string builds a SimpleProxyMap, while anything that looks
like a PAC file/URL is loaded with load_pac.
API overview
| Module | Purpose |
|---|---|
proxylib.proxy |
Core types: Proxy, ProxyMap (protocol + factory), SimpleProxyMap, ChainProxyMap, ConfigurableProxyMap, URI parsing |
proxylib.patching |
patch() / unpatch() / register_patcher() — global patching registry |
proxylib.env |
EnvProxyConfig — HTTP_PROXY/HTTPS_PROXY/NO_PROXY |
proxylib.os |
system_proxy() / auto_proxy() — platform dispatch + WPAD fallback |
proxylib.os.nt / .darwin |
Windows (WinHTTP API) / macOS (scutil) system proxy backends |
proxylib.os.posix |
Linux dispatch: .libproxy, .gnome, .mate, .kde, .networkmanager |
proxylib.pac |
PAC, JSProxyAutoConfig, load() — PAC utility functions + evaluation |
proxylib.pac.wpad |
discover() — DNS+HTTP WPAD auto-discovery |
proxylib.pac.javascript |
JSContext — runs a PAC script against a pluggable engine |
proxylib.pac.engines |
JSEngine interface, plus the dukpy/quickjs backends |
proxylib.integrations.requests |
ProxyMapAdapter — the recommended requests integration |
proxylib.integrations.urllib |
ProxyMapHandler — a per-request-aware urllib.request.ProxyHandler |
proxylib.integrations.dict |
ProxyDict — the plain proxies-dict integration |
proxylib.netutils |
get_ip, get_default_port, get_local_interfaces, first_working_proxy |
Every ProxyMap implementation (SimpleProxyMap, EnvProxyConfig, PAC, ...) answers
the same question: proxymap[url] returns the sequence of Proxy (or None for
DIRECT) to try, in order.
Supported Python versions
Python 3.9–3.14. The codebase uses from __future__ import annotations so modern
X | Y type syntax can be used everywhere while staying importable on 3.9.
Development
python -m venv .venv/<your-python-version>
.venv/<your-python-version>/Scripts/pip install -e ".[dev,jspac,ifaddr]"
.venv/<your-python-version>/Scripts/pytest -q
Tests that need dukpy/quickjs/ifaddr skip automatically if those extras aren't
installed (requests comes from dev, since it's a real test dependency regardless of
the requests integration's own optional-import guard).
Releasing
This project follows Semantic Versioning and keeps a
CHANGELOG.md. Pushing a tag matching v* (e.g. v1.0.0) triggers
.github/workflows/release.yaml, which runs the test
suite across platforms/Python versions as a gate, then in parallel: builds the
sdist/wheel, creates a GitHub Release (with auto-generated release notes and a diff link
against the previous tag) and publishes it to PyPI via Trusted
Publishing (no stored token), and
builds+publishes the documentation site to GitHub Pages.
Documentation site
API reference docs are built with MkDocs +
mkdocstrings from mkdocs.yml/docs/, and
published to GitHub Pages on every release (see above). To preview locally:
.venv/<your-python-version>/Scripts/pip install -e ".[docs]"
.venv/<your-python-version>/Scripts/mkdocs serve
Known limitations
- DHCP-based WPAD discovery is not implemented — only DNS+HTTP (
wpad.<domain>/wpad.dat). Reading DHCP option 252 needs raw OS lease access with no portable stdlib path. - Linux desktop proxy detection covers libproxy, GNOME/MATE (
gsettings), KDE (kioslaverc), and NetworkManager (nmcli, ordbus-sendifnmcliisn't installed), tried in that fixed order with no session detection to disambiguate — if a machine has stale config from more than one of these, the first one with anything set wins, even if it isn't the active desktop. Xfce, Cinnamon, and other desktop environments aren't covered directly (though libproxy, if installed, may cover them) — setHTTP_PROXY/HTTPS_PROXY/NO_PROXYorPROXY_PACexplicitly otherwise. - The NetworkManager
dbus-sendfallback path scrapesdbus-send's debug-oriented text output for the specific fields it needs; it hasn't been verified against a live NetworkManager D-Bus session, only a best-effort reconstruction of the expected format.nmcli, when available, is preferred and doesn't have this caveat. dateRange/timeRangeimplement the documented PAC overload shapes but haven't been exhaustively verified against every real-world PAC file's edge cases.- No automatic proxy failover in the adapters themselves —
ProxyMapAdapter/ProxyMapHandleruse the first proxy aProxyMapreturns; they don't retry the rest of aPROXY a; PROXY b; DIRECTchain on connection failure. Wrap your map withConfigurableProxyMap(proxymap, probe=True)(active reachability probing, with a circuit breaker for proxies that keep failing), or callfirst_working_proxy(proxymap[url])directly, to pre-select a reachable entry.
License
MIT — see LICENSE.
Metadata
Release files for proxylib 1.1.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-1.1.0.tar.gz | 92.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| proxylib-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 162.4 kB
Release files / proxylib-1.1.0.tar.gz
| Download URL | proxylib-1.1.0.tar.gz |
|---|---|
| Size | 92.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f62dec81ecf2c9c928596b38e99a108092d92a7dee65b8f91cf5cfef5ffda54
|
|
BLAKE2b-256 checksum How to use checksums |
54c94e8be4918764973d6d6c0775e8b828a42c88362ed4f0d13a20a0d7d67e9a
|
| 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 18, 2026.
Transparency logRelease files / proxylib-1.1.0-py3-none-any.whl
| Download URL | proxylib-1.1.0-py3-none-any.whl |
|---|---|
| Size | 69.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c60f5045d2d675d532232c1c29368a0aa8e77b0689dc581a9cc800f384234a83
|
|
BLAKE2b-256 checksum How to use checksums |
852c050af99660383b4cc7d2b0ebdadb66a40d5047f5b77e1f858cc8100ddd6d
|
| 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 18, 2026.
Transparency logSecurity for proxylib 1.1.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-1.1.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 18, 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.