Platform-agnostic proxy configuration: detect system/env proxy settings, evaluate PAC files (with WPAD discovery), and wire the result into requests or urllib
Project description
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. - 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.13. 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file proxylib-1.0.1.tar.gz.
File metadata
- Download URL: proxylib-1.0.1.tar.gz
- Upload date:
- Size: 73.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2d54ec8b44e2f6a3c1d84da69b86e41a395a752766b5243d52f85ff1d65de89
|
|
| MD5 |
a8c27c2680ee81eb6e98d07f5eea8eab
|
|
| BLAKE2b-256 |
25a409d66f8503ff8903bbbbe63a495683e80c0e6b5da96d237652ecc18aaac0
|
Provenance
The following attestation bundles were made for proxylib-1.0.1.tar.gz:
Publisher:
release.yaml on jose-pr/proxylib
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proxylib-1.0.1.tar.gz -
Subject digest:
c2d54ec8b44e2f6a3c1d84da69b86e41a395a752766b5243d52f85ff1d65de89 - Sigstore transparency entry: 2141468810
- Sigstore integration time:
-
Permalink:
jose-pr/proxylib@3686fdcaca32b6b1d190de2bbe32486af6e611e2 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/jose-pr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@3686fdcaca32b6b1d190de2bbe32486af6e611e2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file proxylib-1.0.1-py3-none-any.whl.
File metadata
- Download URL: proxylib-1.0.1-py3-none-any.whl
- Upload date:
- Size: 57.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2a196e3fe653b4275531b5e9cda5f588c4cfc5f2a7d7c164204b2082b9303f6
|
|
| MD5 |
6d0a8a0a1d3bf71830035932c8b2b4b2
|
|
| BLAKE2b-256 |
36d33f09eb247e8879ffc0e0c8fc97ec8a9608f18b1bea57f825dca74554e941
|
Provenance
The following attestation bundles were made for proxylib-1.0.1-py3-none-any.whl:
Publisher:
release.yaml on jose-pr/proxylib
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proxylib-1.0.1-py3-none-any.whl -
Subject digest:
b2a196e3fe653b4275531b5e9cda5f588c4cfc5f2a7d7c164204b2082b9303f6 - Sigstore transparency entry: 2141468829
- Sigstore integration time:
-
Permalink:
jose-pr/proxylib@3686fdcaca32b6b1d190de2bbe32486af6e611e2 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/jose-pr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@3686fdcaca32b6b1d190de2bbe32486af6e611e2 -
Trigger Event:
push
-
Statement type: