Repository navigation
Roadmap de 10 sprints: saltar bloqueos + calidad de conexión (v1.10.0) - #6
Merged
Merged
Conversation
…de 10 sprints Sprint 1 del roadmap enfocado en saltar bloqueos y calidad de conexión: - ip_checkers.py: resuelve la IP de salida probando varios servicios en orden (ipify, ifconfig.me, icanhazip, httpbin); get_ip() ya no depende de un solo endpoint. Nueva excepción AllIPCheckersFailedError. - TorClient.wait_for_new_ip(): rota hasta que la IP de salida cambie de verdad (rotate() puede reutilizar el mismo exit). - _retry_with_backoff(): helper de reintentos con backoff exponencial reutilizable. - Tests unitarios mockeados (sin Tor real) para checkers, retry y wait_for_new_ip. - docs/SPRINTS.md: roadmap de 10 sprints como fuente única de verdad; se marcan como supersedidos los ROADMAP/FODA duplicados y contradictorios. - Bump a v1.1.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
Da visibilidad de los circuitos Tor activos, base para la selección de exit (Sprint 4) y el best-circuit (Sprints 3/5): - circuits.py: dataclasses RelayInfo y CircuitInfo (con exit_relay/exit_country) y parse_circuit(), que mapea un circuito de stem y resuelve dirección/país del exit vía get_network_status + ip-to-country de forma best-effort (si un lookup falla, deja None sin romper). - TorClient.list_circuits(): todos los circuitos activos. - TorClient.get_circuit_info(circuit_id=None): circuito indicado, o el activo (BUILT/GENERAL más reciente); TorSessionError si no hay ninguno. - TorClient.get_exit_country(circuit_id=None): país del exit del circuito. - Tests mockeados en tests/test_circuits.py (sin Tor real). 23 tests, 90% cov. - README + docs/SPRINTS.md actualizados. Bump a v1.2.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
…/benchmark) Permite medir qué tan bueno es el circuito actual, insumo para la selección de exit (Sprint 4) y el best-circuit (Sprint 5): - quality.py: dataclass CircuitHealth (latency_ms, throughput_kbps, samples, measured_at, .ok) y funciones puras measure_latency() (mediana de N muestras cronometradas, robusta a outliers) y measure_throughput() (KB/s sobre descarga de tamaño conocido, con guarda de división por cero). Testeable sin Tor real. - TorClient.measure_latency() / measure_throughput() / benchmark(): benchmark corre ambas de forma tolerante (si una falla, ese campo queda None) y sella measured_at. Endpoints por defecto en httpbin, configurables. - Tests mockeados en tests/test_quality.py. 32 tests en total, 88% cov global (quality.py 100%). - README + docs/SPRINTS.md actualizados. Bump a v1.3.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
Permite elegir por dónde sale el tráfico, para esquivar bloqueos del destino
(forzar país permitido o exit no bloqueado) o apuntar a un exit concreto:
- exits.py: helpers puros normalize_country() y format_exit_nodes() que arman el
valor de ExitNodes combinando tokens {cc} y $FINGERPRINT.
- TorClient.set_exit_country() / set_exit_nodes() / reset_exit_nodes(): configuran
ExitNodes + StrictNodes en runtime vía el control port.
- TorClient.new_circuit(exit_country=..., verify=True): aplica la restricción,
rota y confirma un circuito funcional; si el país no tiene exits usables levanta
TorSessionError claro en vez de colgarse.
- TorClient.pinned_exit(): context manager que fija el exit y siempre lo limpia al
salir (idiomático como rotated_session).
- Tests mockeados en tests/test_exits.py. 50 tests en total, 92% cov global.
- README + docs/SPRINTS.md actualizados. Bump a v1.4.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
Une la medición (Sprint 3) con la selección (Sprint 4): mantiene varios circuitos en paralelo, los benchmarkea y usa el más rápido. - pool.py: PooledCircuit (session/health/exit_ip/proxies) y CircuitPool. Cada lane es un circuito independiente vía credencial SOCKS distinta (socks5h://tsmN:tsmN@...), aprovechando IsolateSOCKSAuth de Tor — sin attachment de streams por control port. - CircuitPool.build()/benchmark()/ranked()/fastest()/pin_fastest() (+ pinned_proxies), drop(circuit) y prune(keep) para descartar lentos/bloqueados, close() + context manager. Reutiliza measure_latency/measure_throughput/fetch_ip_with_fallback. - TorClient.circuit_pool(size=3) como factory. - Tests mockeados en tests/test_pool.py. 63 tests en total, 93% cov global (pool.py 97%). - README + docs/SPRINTS.md actualizados. Bump a v1.5.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
El núcleo de "saltar bloqueos": muchos sitios bloquean IPs de exit Tor (Cloudflare, CAPTCHA, 403/429). Detecta esas respuestas y rota a un exit nuevo automáticamente hasta pasar. - blocking.py: detect_block(response) reconoce challenges de Cloudflare/CAPTCHA en el body y statuses de bloqueo (403/429/503). Markers/statuses ajustables para minimizar falsos positivos. Lógica pura, testeable sin red. - BlockedResponseError (con .reason/.response) en exceptions.py. - TorClient.request(): request a través de Tor. request_with_retry(): rota ante bloqueo hasta max_rotations; levanta BlockedResponseError si se agotan. is_exit_blocklisted(url): motivo de bloqueo del exit actual (o None). - Tests mockeados en tests/test_blocking.py. 78 tests en total, 93% cov global (blocking.py 100%). - README + docs/SPRINTS.md actualizados. Bump a v1.6.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
…ente La palanca de concurrencia: un cliente async sobre aiohttp para scraping paralelo a través de Tor. - aio.py: TorClientAsync. Data-plane (get_ip, request, request_with_retry, measure_latency/throughput, benchmark) sobre aiohttp + aiohttp_socks (rdns para DNS por Tor). Control-plane (rotate, is_ready, set_exit_*, new_circuit) delegado al TorClient síncrono vía asyncio.to_thread. AsyncResponse reutiliza tal cual el detect_block del Sprint 6. - gather_requests(): fetch concurrente acotado por asyncio.Semaphore (max_concurrency); fallo por-URL -> None, sin cortar el lote. - Dependencia opcional [async] (aiohttp, aiohttp_socks); __init__ con import guardado y placeholder que da ImportError claro si falta el extra. - CI instala [dev,async] para correr los tests async (que si no se skipean). - Tests mockeados en tests/test_aio.py con asyncio.run + importorskip. 96 tests en total, 92% cov global. - README + docs/SPRINTS.md actualizados. Bump a v1.7.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
La otra mitad de "saltar bloqueos": llegar a Tor cuando el ISP/país bloquea Tor mismo, vía bridges y pluggable transports (obfs4, snowflake, meek, webtunnel). - bridges.py: Bridge dataclass + parse_bridge_line() (vanilla y con transport), transport_binary() (resuelve el PT del PATH o path explícito), build_bridge_config() (arma UseBridges/Bridge/ClientTransportPlugin, dedup de transports) y launch_bridged_tor() (lanza un Tor gestionado vía stem.process.launch_tor_with_config). - BridgeConfigError (bridge line inválida / binario PT faltante). - Nota: UseBridges/ClientTransportPlugin son launch-time; el flujo es lanzar un Tor propio. Los binarios PT no se bundlean, se resuelven del PATH con error claro si faltan. - Tests mockeados en tests/test_bridges.py (sin Tor ni binarios PT). 113 tests en total, 93% cov global (bridges.py 100%). - README + docs/SPRINTS.md actualizados. Bump a v1.8.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
…ativo) Rotar la IP no alcanza si el fingerprint del request te delata. Rota perfiles de headers coherentes y pacea el ritmo por host. - fingerprint.py: HeaderProfile + BROWSER_PROFILES (Chrome/Firefox/Safari con headers coherentes, sin mezclar UA de uno con headers de otro) y HeaderRotator (round-robin determinístico, shuffle opcional; next() devuelve copia). - pacing.py: RateLimiter con pacing por host (acquire/record), backoff multiplicativo ante 429/503 (cap max_interval) y decay en respuestas OK. - TorClient(rotate_headers=..., rate_limit=...) integrado de forma aditiva/opt-in en request(); request_with_retry() lo hereda. - Nota honesta: JA3/TLS es fijo con requests (stdlib ssl); documentado como límite conocido, sin agregar curl_cffi. - Tests puros en tests/test_fingerprint.py y tests/test_pacing.py + integración en tests/test_blocking.py. 129 tests en total, 94% cov global. - README + docs/SPRINTS.md actualizados. Bump a v1.9.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
…ap completo) Cierre del roadmap de 10 sprints: consolidación y paquete listo para publicar. - cli.py + entry point `tor-session`: subcomandos ip/rotate/circuit/country/ benchmark/status, con --json para salida machine-readable. main(argv) testeable. - Observabilidad: CircuitHealth.as_dict() y CircuitInfo.as_dict() serializables (reusan dataclasses.asdict); `status --json` arma el snapshot completo. - pytest_plugin.py: fixture `tor_client` (skipea si Tor no está listo) vía entry point pytest11. Se desactiva su autoload en la suite propia (deflactaba coverage) y se cubre con test directo. - Packaging: py.typed (PEP 561) + package-data, [project.scripts] y [pytest11] en pyproject, .github/workflows/publish.yml (PyPI trusted publishing en release). - README: badges (CI/PyPI/Python/License) + secciones de CLI y fixture de pytest. - Nota honesta: middleware de Scrapy queda como futuro (Scrapy no soporta SOCKS nativo); el fixture de pytest sí se incluye porque funciona. - Verificado: `tor-session` resuelve, `python -m build` genera sdist+wheel con py.typed y entry points. 144 tests, 94% cov global. Bump a v1.10.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsmHxv6jNwyMzbFRi5CveS
Disable environment proxy overrides, reject request-level proxy changes, and verify fail-closed routing with offline regressions across sync, pool and async. Distinguish controller, authentication, bootstrap, SOCKS and connectivity status; return nonzero for unavailable Tor and accept CLI options before or after commands. Fix existing type annotations and Black formatting. Align declared Python minimum with the existing Python 3.9+ README, CI and asyncio.to_thread usage; retain a compatible mypy major version. Validation: 238 tests pass, 95% coverage; Black, isort, mypy and compileall pass. No live Tor or external endpoint traffic used in tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resumen
Implementa un roadmap de 10 sprints sobre
tor-session-manager, enfocado en dos ejes: 🛡️ saltar cualquier bloqueo (llegar a Tor bajo censura + esquivar bloqueos del destino) y ⚡ mejor calidad de conexión (medir/elegir circuitos rápidos, robustez, concurrencia).Partimos de una librería de ~230 líneas con un solo
TorClient(rotarNEWNYM, SOCKS5, IP contra un único endpoint) y quedó en 13 módulos, 144 tests, 94% coverage. Cada sprint es un commit autocontenido con tests. Cada bump de versión es aditivo/retrocompatible (semver minor).Sprints
wait_for_new_ip(),_retry_with_backoff(),AllIPCheckersFailedErrorCircuitInfo/RelayInfo,list_circuits(),get_circuit_info(),get_exit_country()CircuitHealth,measure_latency()/measure_throughput(),benchmark()set_exit_country(),set_exit_nodes(),new_circuit(),pinned_exit()CircuitPool(aislamiento por credencial SOCKS),pin_fastest(),drop()/prune()detect_block(),request_with_retry()(auto-rotación),is_exit_blocklisted(),BlockedResponseErrorTorClientAsync(aiohttp/aiohttp_socks),gather_requests()concurrente; extra[async]launch_bridged_tor(),BridgeConfigErrorHeaderRotator(perfiles coherentes) +RateLimiter(pacing por host)tor-session,as_dict()serializable, fixture pytest,py.typed, workflow de PyPI, badgesCobertura de los dos ejes
Además reconcilia los archivos de roadmap/FODA duplicados y contradictorios previos (
/ROADMAP.mdvs/docs/ROADMAP.md,/FODA.mdvs/docs/FODA.md), marcándolos como supersedidos por el nuevodocs/SPRINTS.md.Notas de diseño (honestas)
.[dev,async]para correr también los tests async.requestsusa elsslde la stdlib, así que el fingerprint del handshake es fijo; la rotación de headers es la palanca a nivel aplicación. Spoofear JA3 (curl_cffi) queda fuera de alcance y está documentado..github/workflows/publish.ymlpublica a PyPI vía trusted publishing solo en un GitHub release (no se dispara en este PR).Verificación
pytest --cov=tor_session_manager→ 144 tests en verde, 94% coverage.tor-session --version/--helpresuelven;python -m buildgenera sdist+wheel conpy.typedy los entry points.black/isortlimpios sobre el código nuevo.🤖 Generated with Claude Code
Generated by Claude Code