Skip to main content

Parse any common proxy-string format and hand it to the major HTTP/SOCKS libraries.

Project description

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.

PyPI Python License: MIT

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. Use to_aiohttp_socks() / to_python_socks_kwargs(), which emit the base type + rdns flag. (to_url() keeps socks5h for httpx/requests.)

  • Reversed-format detection is heuristic. For creds@host:port vs host:port@creds, the side whose last :-segment is a valid port wins; a literal-IP host breaks ties. It can misread user:9050@admin:8080-style inputs where both sides look like host:port and neither is an IP. Prefer the standard user:pass@host:port or an explicit scheme when in doubt.

  • Credentials are stored and emitted verbatim — never percent-decoded or encoded. pa%40ss stays pa%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; pass strip_credentials=False to 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 @/: makes to_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 explicit to_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

MIT

Project details


Download files

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

Source Distribution

proxy_converter-0.1.0.tar.gz (91.4 kB view details)

Uploaded Source

Built Distribution

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

proxy_converter-0.1.0-py3-none-any.whl (15.7 kB view details)

Uploaded Python 3

File details

Details for the file proxy_converter-0.1.0.tar.gz.

File metadata

  • Download URL: proxy_converter-0.1.0.tar.gz
  • Upload date:
  • Size: 91.4 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for proxy_converter-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d3be1c128975e64c951ed9c5b08c71458e45b29bfb0e6dd92bb23879ea6fc241
MD5 8e981b82b7752507000d008946db9562
BLAKE2b-256 02e42bb8b7f8cf6cb726b5c6dd2625bccd73a4b7eb98f5cbeee80f07dfe77f2d

See more details on using hashes here.

File details

Details for the file proxy_converter-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: proxy_converter-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.7 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for proxy_converter-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0d710821d743bfb004fefe5ca9830a62ef435880cd17c462b975baf0e9639e0d
MD5 be925ce90c1228a8cbe22c07def687a2
BLAKE2b-256 851de0a4d28d789787dd68299df82a1e66c8cc50fe01935cd7e9985762b03f85

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page