proxy-converter
Parse any common proxy-string format once, then hand it to the major HTTP/SOCKS
libraries — the socks5h/socks4a incompatibility between requests/httpx and
aiohttp_socks/python-socks is handled for you.
Install
pip install proxy-converter
# or
uv add proxy-converter
Zero runtime dependencies. The PyPI name is proxy-converter; the import name is
proxy_converter.
Quick start
from proxy_converter import parse_proxy
# a messy provider format in -> clean, usable output out
proxy = parse_proxy("1.2.3.4:1080:user:pass", default_scheme="socks5")
proxy.to_url() # "socks5://user:pass@1.2.3.4:1080" -> httpx / requests
proxy.to_aiohttp_socks() # ready aiohttp_socks.ProxyConnector
proxy.host, proxy.port # ("1.2.3.4", 1080)
It also accepts schemeless and "weird" shapes — reversed credentials, the
proxy-list host:port:user:pass, a | separator, IPv6, and more. Schemeless
input needs an explicit default_scheme (the protocol can't be guessed from a
bare host:port):
parse_proxy("1.2.3.4:1080:user:pass", default_scheme="socks5") # proxy-list
parse_proxy("user:pass@1.2.3.4:1080", default_scheme="http") # standard creds
parse_proxy("1.2.3.4:1080@user:pass", default_scheme="socks5") # reversed creds
parse_proxy("user:pass|1.2.3.4:1080", default_scheme="socks5") # pipe separator
Why
Proxy providers hand out the same proxy in a dozen shapes, and the schemes are
not portable across clients: requests/httpx understand socks5h://, but
aiohttp_socks/python-socks reject it and instead want a base socks5 type
plus a separate rdns=True flag. parse_proxy normalizes every shape into one
ProxyInfo, and the to_* adapters emit exactly what each library expects — so
you never copy a socks5h:// URL into a connector that silently breaks.
Supported formats
The credential separator is @ or | (never both). The scheme is
case-insensitive; when absent, you must pass default_scheme — the protocol
can't be guessed from a bare host:port, so there is no built-in default.
| # | Example | Notes |
|---|---|---|
| 1 | socks5://user:pass@1.2.3.4:1080 |
URL, standard |
| 2 | socks5://1.2.3.4:1080@user:pass |
URL, reversed credentials |
| 3 | http://1.2.3.4:8080 |
URL, no credentials |
| 4 | http://1.2.3.4:1080:user:pass |
URL, proxy-list style |
| 5 | user:pass@1.2.3.4:1080 |
no scheme, with credentials |
| 6 | 1.2.3.4:1080@user:pass |
no scheme, reversed |
| 7 | 1.2.3.4:1080 |
no scheme, no credentials |
| 8 | 1.2.3.4:1080:user:pass |
proxy-list |
| is a drop-in alias for @: any @-form above (1, 2, 5, 6) also works with
|. Schemes: http, https, socks4, socks5, socks4a, socks5h — where
socks5h/socks4a mean "resolve DNS on the proxy" (stored as rdns=True).
Adapters
Each adapter returns plain data; the SOCKS/aiohttp ones import their target
library lazily, so the core stays dependency-free. Install the extras only for
what you use: pip install proxy-converter[aiohttp] /
proxy-converter[python-socks].
to_url(auth=True) — httpx, and a generic URL
import httpx
proxy = parse_proxy("socks5h://user:pass@1.2.3.4:1080")
client = httpx.Client(proxy=proxy.to_url())
proxy.to_url(auth=False) # "socks5h://1.2.3.4:1080" — credentials omitted
to_requests() — requests
import requests
requests.get("https://example.com", proxies=proxy.to_requests())
# {"http": "socks5h://...", "https": "socks5h://..."}
to_native_aiohttp() — aiohttp's built-in proxy (HTTP/HTTPS only)
proxy = parse_proxy("http://user:pass@1.2.3.4:8080")
async with session.get(url, **proxy.to_native_aiohttp()) as resp:
...
# {"proxy": "http://1.2.3.4:8080", "proxy_auth": BasicAuth(...)}
# Raises ProxyParseError on a SOCKS proxy — use to_aiohttp_socks() instead.
to_aiohttp_socks() — aiohttp_socks (SOCKS and HTTP)
import aiohttp
proxy = parse_proxy("socks5h://user:pass@1.2.3.4:1080")
async with aiohttp.ClientSession(connector=proxy.to_aiohttp_socks()) as session:
...
to_python_socks_kwargs() — python-socks (any backend)
from python_socks.async_.asyncio import Proxy # or .trio / .sync
p = Proxy(**proxy.to_python_socks_kwargs())
# {"proxy_type": ProxyType.SOCKS5, "host": ..., "port": ..., "rdns": True, ...}
The ProxyInfo object
parse_proxy returns a frozen ProxyInfo. Stored fields plus two read-only
properties:
proxy = parse_proxy("socks5h://user:pass@1.2.3.4:1080")
proxy.proxy_type # "socks5" (base type; never socks5h/socks4a)
proxy.host # "1.2.3.4"
proxy.port # 1080
proxy.username # "user"
proxy.password # "pass"
proxy.rdns # True (the "h"/"a" — resolve DNS on the proxy)
proxy.scheme # "socks5h" (computed: proxy_type + rdns)
proxy.auth # ("user", "pass") or None
It is immutable (hashable → usable in set() / as a dict key). To change a
field, build a new one — which also re-validates:
import dataclasses
other = dataclasses.replace(proxy, port=1234)
Gotchas
-
Don't feed a
socks5h://URL straight into aiohttp_socks/python-socks. They reject it. Useto_aiohttp_socks()/to_python_socks_kwargs(), which emit the base type +rdnsflag. (to_url()keepssocks5hfor httpx/requests.) -
Reversed-format detection is heuristic. For
creds@host:portvshost:port@creds, the side whose last:-segment is a valid port wins; a literal-IP host breaks ties. It can misreaduser:9050@admin:8080-style inputs where both sides look likehost:portand neither is an IP. Prefer the standarduser:pass@host:portor an explicit scheme when in doubt. -
Credentials are stored and emitted verbatim — never percent-decoded or encoded.
pa%40ssstayspa%40ss. (Proxy passwords aren't URL-encoded in practice, so this is what you want.) The only touch is trimming surrounding whitespace from the username/password; passstrip_credentials=Falseto keep even that byte-for-byte:parse_proxy("socks5://user: pass @1.2.3.4:1080").password # "pass" parse_proxy("socks5://user: pass @1.2.3.4:1080", strip_credentials=False).password # " pass "
Two more consequences of verbatim credentials: a proxy-list password can't contain
:,@, or|(they collide with the format's separators) — use an@/|form instead, picking the separator your password doesn't contain. And a password containing@/:makesto_url()ambiguous to a strict URL parser — for those, use the field-based adapters (to_python_socks_kwargs,to_aiohttp_socks). -
IPv6 needs brackets when a port is present:
[2001:db8::1]:1080. -
Passwords are masked.
str(),repr(), and error messages never reveal the password; only an explicitto_url()does.
Batch parsing
parse_many parses an iterable of lines (for proxy-list files) with an error
policy:
from proxy_converter import parse_many
# raise on first bad line
proxies = parse_many(lines, default_scheme="socks5")
# drop bad lines
proxies = parse_many(lines, default_scheme="socks5", on_error="skip")
# (parsed, [(index, line, exc), ...])
ok, errors = parse_many(lines, default_scheme="socks5", on_error="collect")
It is fast — ~650k lines/sec (~1.5 µs per line) on an Apple M4, CPython 3.12.
License
Metadata
Release files for proxy-converter 0.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 | Actions |
|---|---|---|---|---|
| proxy_converter-0.1.0.tar.gz | 91.4 kB | Without provenance | Details for proxy_converter-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| proxy_converter-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for proxy_converter-0.1.0-py3-none-any.whl |
Total release size: 107.1 kB
Release files / proxy_converter-0.1.0.tar.gz
| Download URL | proxy_converter-0.1.0.tar.gz |
|---|---|
| Size | 91.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d3be1c128975e64c951ed9c5b08c71458e45b29bfb0e6dd92bb23879ea6fc241
|
|
BLAKE2b-256 checksum How to use checksums |
02e42bb8b7f8cf6cb726b5c6dd2625bccd73a4b7eb98f5cbeee80f07dfe77f2d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / proxy_converter-0.1.0-py3-none-any.whl
| Download URL | proxy_converter-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0d710821d743bfb004fefe5ca9830a62ef435880cd17c462b975baf0e9639e0d
|
|
BLAKE2b-256 checksum How to use checksums |
851de0a4d28d789787dd68299df82a1e66c8cc50fe01935cd7e9985762b03f85
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for proxy-converter 0.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.