Commit Graph
22 Commits
Author SHA1 Message Date
dsql 385425b180 release: 1.0.0
first stable release. pre-1.0.0 verification complete: all surviving MED regressions and
gaps resolved and independently re-fired, tree audited clean across the suite.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 21:21:00 -04:00
dsql 0b6b6b440b fix: cooldown handout never truncates a longer active burn deadline
A forced soonest-recovering handout unconditionally set timeout = now + cooldown,
silently collapsing an explicit burn(proxy, 3600) to the cooldown window. Cooldown now
only EXTENDS availability delay: it leaves a still-running longer timed burn untouched
and never resurrects a permanent _DEAD (-1) burn into a future timestamp, while a fine
proxy and a burn shorter than cooldown are cooled as before.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 19:26:31 -04:00
dsql 1adc8139b9 docs: fix README health-check snippet's invalid aiohttp kwarg; widen add()/replace() type hints to accept dict shapes
the flagship health-check snippet called session.get(url, proxies=proxy) on a
plain aiohttp session; aiohttp's ClientSession.get/_request has no `proxies=`
kwarg (only singular `proxy:`), so the snippet TypeErrors immediately if
followed verbatim. pm.get()'s dict output is documented (aiohttp()'s own
docstring) as being for aioweb's ExtendedSession(proxies=...), so the snippet
now names that session type instead of a bare aiohttp one.

add()/replace() type hints omitted Dict[str, str] even though both already
call to_proxy() at runtime and accept dict-shaped proxy specs, same as the
burn()/restore()/is_burned()/remove() family. purely annotation, no behavior
change.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 00:15:29 -04:00
dsql a4a0227d5d fix: URL-aware parse() at static/template/net.current_ip call sites; self-clear stale burn flag; guard replace([])
static=/template=/net.current_ip() fed URL-shaped input straight into parse(),
which only understands host:port(:user:pass) specs and mis-splits a url's
colons into a garbage Proxy (e.g. embedded-auth urls) instead of raising or
normalizing. all three now route through to_proxy(), the url-aware normalizer
add()/replace() already use.

the per-state `burned` flag (drives WARNING vs DEBUG on the cooldown
fallthrough log) was only ever cleared by restore(); a timed burn() that
lazily expires (is_burned() correctly reads False again) left `burned` stuck
True, permanently upgrading routine cooldown noise to WARNING. `burned` now
self-clears wherever the existing lazy-expiry check already finds the proxy
available (_next_from_list's fine-tier scan, is_burned()).

replace([]) silently wiped the entire pool with no error, even though the
constructor already rejects an empty proxies=[] list. replace() now raises
the same ValueError for an empty list on list sources.

parse()'s docstring corrected: it is spec-string only and does not understand
url shapes; points callers at to_proxy() for url/dict/spec input.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 00:15:06 -04:00
dsql 9649eb77c7 refactor: derive __version__ from package metadata (single source)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:59:41 -04:00
dsql 1ca3144245 fix: sync __version__ to 0.3.2 (drifted behind pyproject)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:25:17 -04:00
dsql e6663926c0 docs: pin install line to v0.3.2 (pyproject already bumped)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:23:52 -04:00
dsql bac459c5b5 fix: normalize_port raises a clear error on missing/empty port
canonical_key/key()/burn on a portless proxy url used to raise a bare
ValueError from int('') instead of naming the problem, breaking burn()'s
documented not-in-pool contract. normalize_port now raises a legible
'missing port' ValueError; to_proxy() on a portless url still succeeds.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:15:06 -04:00
dsql 84127a93ff docs: compress prose/module docstrings, em-dash->hyphen (de-bloat wave 1)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 00:12:54 -04:00
dsql 147341d38a fix: canonical port key, remove()/restore() drift, escape-aware templates
Port keys no longer drift on zero-padding (host:080 vs host:80 collapsed
one canonical key), so a get() -> burn() pairing on a padded port no
longer raises a false "not in pool". Constructor/from_file now dedupe by
canonical key like add(). remove() decrements the rotation cursor when
the removed slot precedes it, fixing a skip/double-serve. restore() on
an unknown key now warns instead of silently no-op, matching remove()'s
contract. A genuine timed burn() under cooldown>0 is now tracked
separately from routine cooldown resting, so it logs WARNING instead of
being buried as DEBUG. Source validation uses is-not-None instead of
truthiness, so a falsy-but-provided source (e.g. template="") is
accepted. Bare "{}" template normalization is now escape-aware, so an
intentional "{{}}" literal survives instead of being corrupted.

Stored-shape change: burn()/cooldown deadlines now write int(time.time())
instead of float; comparisons are unaffected.

Docstrings compressed (module + next()); no behavior change.

Signed-off-by: disqualifier <dev@disqualifier.me>
v0.3.0
2026-07-02 23:25:52 -04:00
dsql eef4b25f07 chore: ignore .claude/ dir (CLAUDE.md now lives under .claude/)
Signed-off-by: disqualifier <dev@disqualifier.me>
v0.2.2
2026-06-29 21:55:13 -04:00
dsql a5e36544d4 docs: note url()/aiohttp() percent-encode proxy credentials
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 21:41:36 -04:00
dsql 72c5342a6b fix: AP-1 percent-encode url() creds, AP-2 reject next(session=) collision
AP-1: url()/aiohttp() percent-encode user/password so reserved chars (/ # ? @) produce a
valid url, mirroring the parse-side unquote. AP-2: next(session=...) raises a clear
ValueError instead of an opaque str.format TypeError. net.current_ip uses
json(content_type=None) + dict guard.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 21:34:36 -04:00
dsql 932fb71c95 docs: pin install line to release, note unpinned-latest option
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 18:13:33 -04:00
dsql 0abc071f14 docs: show unpinned install line; note tag-pinning for reproducibility
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 18:07:17 -04:00
dsql fc27d77000 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>
2026-06-29 17:57:37 -04:00
dsql 260b92b66a docs: add v0.2.1 changelog entry (legible missing-template-field error)
the README changelog jumped from v0.2.0 to nothing; added the v0.2.1 entry documenting the legible ValueError for a template placeholder not supplied to next(**fields).

Signed-off-by: disqualifier <dev@disqualifier.me>
v0.2.1
2026-06-29 01:39:21 -04:00
dsql f618b6a6a1 style: drop inline comments in the template-field error path
the rationale they carried is already in the ValueError message; the lib convention is docstrings only.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-28 17:18:28 -04:00
dsql aa661bd6de fix: legible error on missing template placeholder (v0.2.1)
next() on a template source did template.format(...) directly, so a placeholder not
supplied in next(**fields) leaked a bare KeyError. catch KeyError/IndexError and
raise a clear ValueError naming the missing placeholder.

verified: next() without a required field -> ValueError naming it; happy path intact.
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-28 15:50:33 -04:00
dsql ca708191c8 feat: proxy health & pool management (v0.2.0)
burn/timeout (dead -1 vs timed, lazy expiry), usage counters, reuse cooldown,
and live pool management (replace/add/remove) for the rotating list source.
template/static sources treat these as no-ops that log a warning.

- canonical key (host:port:user:pass, or host:port auth-less) identifies a proxy
  across every input shape (spec/Proxy/url/aiohttp/camoufox/socks5 dict); host is
  lowercased (DNS-caseless), password included, 4-part split on first 3 colons so
  colon passwords survive
- ProxiesExhaustedError when the whole pool is permanently dead
- cooldown soft (falls through to soonest-recovering, never raises); default 0 = off
- soonest-recovering fallthrough logs warning on a genuine burn, debug on cooldown
- {session} now 8-char alphanumeric (was 10-digit numeric); session_len default 8
- backward-compatible: a v0.1.0 manager (no burns, cooldown=0) is byte-for-byte
  identical — sequential round-robin, next()->Proxy, get()->aiohttp dict, never raises

Signed-off-by: disqualifier <dev@disqualifier.me>
v0.2.0
2026-06-25 14:23:20 -04:00
dsql 993dd12fc2 add package: pyproject + src
AioProxies: proxy parsing/formatting (aiohttp/camoufox/socks5/url) and
source management (session template with {session} + caller fields,
rotating list, static, from_file). config-free, no module globals,
per-instance rotation, never sys.exits on a missing file. also exported
as ProxyManager / aioproxies. optional [net] extra adds aiohttp
current_ip/reset. src/ multi-module, hatchling, zero core deps.

Signed-off-by: disqualifier <dev@disqualifier.me>
v0.1.0
2026-06-24 21:36:43 -04:00
dsql 2ba7383f81 init: proxy parsing, formatting & source management
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-24 21:16:46 -04:00