fix: preserve URL-embedded proxy auth; clearer empty-list + malformed-template errors (v0.2.2)

- _proxy_from_dict server branch falls back to auth embedded in the server URL when no
  explicit username/password keys are given, so it isn't dropped and the proxy keys
  with its credentials instead of colliding auth-less (L5)
- AioProxies(proxies=[]) now raises a clear 'empty' error, not the misleading
  'provide exactly one of' (nit)
- a malformed template re-raises with a naming message instead of a bare str.format
  ValueError (nit).

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
disqualifier 2026-06-29 17:57:37 -04:00
parent 260b92b66a
commit fc27d77000
5 changed files with 15 additions and 6 deletions

View File

@ -9,9 +9,9 @@ edits. **Credentials are always injected — never hardcoded.**
## Install ## Install
``` ```
aioproxies @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioproxies.git@v0.2.1 aioproxies @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioproxies.git@v0.2.2
# network helpers (current_ip / reset) need the extra: # network helpers (current_ip / reset) need the extra:
aioproxies[net] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioproxies.git@v0.2.1 aioproxies[net] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioproxies.git@v0.2.2
``` ```
The core has no dependencies. The `net` extra adds `aiohttp` for `current_ip` / The core has no dependencies. The `net` extra adds `aiohttp` for `current_ip` /

View File

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "aioproxies" name = "aioproxies"
version = "0.2.1" version = "0.2.2"
description = "proxy parsing, formatting, health, and pool management for aiohttp/aioweb, camoufox, and socks5" description = "proxy parsing, formatting, health, and pool management for aiohttp/aioweb, camoufox, and socks5"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [] dependencies = []

View File

@ -18,4 +18,4 @@ __all__ = [
"to_proxy", "to_proxy",
] ]
__version__ = "0.2.1" __version__ = "0.2.2"

View File

@ -58,6 +58,8 @@ class AioProxies:
shuffle: bool = True, shuffle: bool = True,
cooldown: int = 0, cooldown: int = 0,
): ):
if proxies is not None and len(proxies) == 0:
raise ValueError("proxies list is empty; provide at least one proxy")
sources = [s for s in (template, proxies, static) if s] sources = [s for s in (template, proxies, static) if s]
if len(sources) != 1: if len(sources) != 1:
raise ValueError("provide exactly one of: template, proxies, static") raise ValueError("provide exactly one of: template, proxies, static")
@ -129,6 +131,10 @@ class AioProxies:
raise ValueError( raise ValueError(
f"template placeholder {exc} not provided; pass it to next(**fields)" f"template placeholder {exc} not provided; pass it to next(**fields)"
) from exc ) from exc
except ValueError as exc:
# a malformed template (e.g. an unmatched '{') makes str.format raise a
# bare ValueError; re-raise naming the cause so it isn't cryptic
raise ValueError(f"malformed proxy template {self.template!r}: {exc}") from exc
return parse(filled) return parse(filled)
return self._next_from_list() return self._next_from_list()

View File

@ -133,8 +133,11 @@ def _proxy_from_dict(spec: Dict[str, str]) -> Proxy:
"""normalize an aiohttp / camoufox / socks5 dict into a Proxy""" """normalize an aiohttp / camoufox / socks5 dict into a Proxy"""
if "server" in spec: if "server" in spec:
proxy = _proxy_from_url(spec["server"]) proxy = _proxy_from_url(spec["server"])
user = spec.get("username") # prefer explicit dict auth; otherwise fall back to auth embedded in the server
password = spec.get("password") # URL (http://user:pass@host:port) so it isn't silently dropped — which would
# key the proxy auth-less and collide with a genuinely auth-less one
user = spec.get("username") or proxy.user
password = spec.get("password") or proxy.password
if user and password: if user and password:
return Proxy(proxy.host, proxy.port, user, password) return Proxy(proxy.host, proxy.port, user, password)
return Proxy(proxy.host, proxy.port) return Proxy(proxy.host, proxy.port)