Skip to main content

proxylib

Version Python versions License: MIT Docs CI

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 (as urllib.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 a ProxyConfig from 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=True on 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, aiohttp and urllib resolve the proxy from the real request URL; a PROXY a; PROXY b; DIRECT answer 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 Proxy can carry headers sent to that proxy only (a Bearer token, or a credential under another header name), and prefix+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 raises UnsupportedProxyError when none is left, instead of leaking a header or going elsewhere.
  • Every client understands proxylib's proxy formats — requests, urllib, httpx, aiohttp and pip get proxylib's integration as they are imported. Each still chooses its proxy as it always did, and proxylib reads what it cannot: HTTP_PROXY="PROXY a:3128; PROXY b:3128; DIRECT" fails over, and HTTPS_PROXY=prefix+https://gw/fetch/ uses a gateway. PROXYLIB_AUTOPROXY=1 (or client names) makes them follow auto_proxy() — the OS settings, PAC and WPAD — and patch() a map of your own (nestable); pip --proxy still wins.
  • Bypass rules in each OS's syntax — NO_PROXY (curl), Windows ProxyOverride, macOS ExceptionsList, GNOME ignore-hosts, KDE NoProxyFor: suffixes, globs, IPv4 and IPv6 CIDR, ports, <local>, <subnet>.
  • Composable — any mapping is a proxy map; ChainProxyMap, Bypass, Cached, RoundRobin and Probed compose 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_AUTOPROXY=1 (or client names: requests:httpx:pip) makes those clients use auto_proxy() in every program that imports them, through importpatch. PROXYLIB_IMPORTPATCH chooses which clients get the integration at all (unset: every one) and PROXYLIB_IMPORTPATCH=0 turns it 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, or dbus-send); the first with a proxy configured wins. Other desktops are covered only through libproxy, if installed. The dbus-send path reads its debug output and has not been checked against a live NetworkManager.
  • urllib does not fail over — ProxyMapHandler uses the healthiest entry but cannot re-send a request through the next one; use Probed(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-Authorization proxy 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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for proxylib 2.0.1
File Size Uploaded Provenance
proxylib-2.0.1.tar.gz 144.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for proxylib 2.0.1
File Interpreter ABI Platform
proxylib-2.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 246.4 kB

Release files / proxylib-2.0.1.tar.gz

Download URL proxylib-2.0.1.tar.gz
Size 144.7 kB
Tags Source
SHA-256 checksum
How to use checksums
fc87b7c210b8f596da4b23b9137d73df584ffe7adeabb0bbf9a20eac9de4d7ca
BLAKE2b-256 checksum
How to use checksums
eb097bedcbb96a9889e1b68aaeb08fa75131e958aad4b411824bbf9b1e866cf8
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 log

Release files / proxylib-2.0.1-py3-none-any.whl

Download URL proxylib-2.0.1-py3-none-any.whl
Size 101.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7b9a7b74c6eb5c339051b4817164a3e6f17b5f25551a4fdc1e9371ac4ab80df5
BLAKE2b-256 checksum
How to use checksums
e6ca0b69aa85e739acf4b4bf670468b29b1205a6552207ffc9c06447c53fa84a
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 log

Security for proxylib 2.0.1

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.

Select from 2 files

Attestation for proxylib-2.0.1.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 log

A 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.

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