Skip to main content

proxylib

PyPI version Python versions License: MIT Docs

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 parsingPROXY host:port; DIRECT-style PAC strings, scheme://host:port URLs, and env-var-style values, all normalized into a common Proxy type.
  • 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, mode auto with a blank Configuration URL on GNOME/MATE, ProxyType=3 in KDE's kioslaverc, or NetworkManager's proxy.method=auto with 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 via nmcli if installed, else dbus-send directly. LibProxyMap (libproxy's own resolution, called via ctypes bindings to its C API — no subprocess) is also available as a standalone ProxyMap you can use directly on any platform.
  • Env var proxy configHTTP_PROXY/HTTPS_PROXY/NO_PROXY (and lowercase variants), with correct exact/subdomain NO_PROXY matching, plus CIDR entries, <local>, and * (bypass everything) as curl and requests read them.
  • PAC support — the full Netscape PAC utility-function set (including dateRange/timeRange) plus the common Microsoft *Ex extensions, runnable either as real JavaScript through a pluggable engine (dukpy or quickjs, picked via PROXYLIB_JS_ENGINE or preferring quickjs if installed) or by subclassing in Python.
  • WPAD discovery — DNS + HTTP wpad.<domain>/wpad.dat lookup. Used directly by system_proxy() on any platform/desktop whose own auto-detect setting is on, and as a generic last-resort fallback in auto_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.
  • requests integration — a ProxyMapAdapter Transport Adapter that resolves the proxy from the real request URL (not just scheme+host).
  • urllib.request integrationProxyMapHandler, the same idea as ProxyMapAdapter but for the standard library's opener/handler system.
  • Global patchingproxylib.patch(proxymap) wires an active ProxyMap into newly created requests.Sessions and a global urllib opener, without mounting an adapter by hand; proxylib.unpatch() undoes it, and any ProxyMap also works directly as a context manager (with proxymap:).
  • Decorators & compositionChainProxyMap for sequential fallback across multiple ProxyMaps, and ConfigurableProxyMap for 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 EnvProxyConfigHTTP_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, or dbus-send if nmcli isn'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) — set HTTP_PROXY/HTTPS_PROXY/NO_PROXY or PROXY_PAC explicitly otherwise.
  • The NetworkManager dbus-send fallback path scrapes dbus-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/timeRange implement 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 themselvesProxyMapAdapter/ ProxyMapHandler use the first proxy a ProxyMap returns; they don't retry the rest of a PROXY a; PROXY b; DIRECT chain on connection failure. Wrap your map with ConfigurableProxyMap(proxymap, probe=True) (active reachability probing, with a circuit breaker for proxies that keep failing), or call first_working_proxy(proxymap[url]) directly, to pre-select a reachable entry.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

proxylib-1.0.2.tar.gz (81.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

proxylib-1.0.2-py3-none-any.whl (64.9 kB view details)

Uploaded Python 3

File details

Details for the file proxylib-1.0.2.tar.gz.

File metadata

  • Download URL: proxylib-1.0.2.tar.gz
  • Upload date:
  • Size: 81.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for proxylib-1.0.2.tar.gz
Algorithm Hash digest
SHA256 b83478785a5a77fc97f45c16105944540eea0759c18ea4e83e420e2532f7885c
MD5 8bf6d8f6a45ad908e5f645d5a7e79143
BLAKE2b-256 a5ac8151cf5434bfee5e3c9182789f7aed82a5d253bd817ae2a1933cf45f832e

See more details on using hashes here.

Provenance

The following attestation bundles were made for proxylib-1.0.2.tar.gz:

Publisher: release.yaml on jose-pr/proxylib

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file proxylib-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: proxylib-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 64.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for proxylib-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4e0116450d4d8db2e4f2515af5937008223e89b87adc3e3f7b07fd8139fce2d5
MD5 65d14ad42fada0e205cae8339204fbbb
BLAKE2b-256 ad3b9de41512b59f1471f4dee50c355599d349362bf01bf5ad8d4825ba51a651

See more details on using hashes here.

Provenance

The following attestation bundles were made for proxylib-1.0.2-py3-none-any.whl:

Publisher: release.yaml on jose-pr/proxylib

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 files

1.0.1

2 files

1.0.0

2 files

0.5.9

2 files

0.5.5

2 files

0.5.0

2 files

0.1.1

2 files

0.1.0

2 files

0.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page