🧅 Tor Session Manager
Una librería Python liviana para gestionar sesiones Tor y rotar circuitos programáticamente.
🎯 Casos de Uso
Esta librería está diseñada para propósitos legítimos:
- Web Scraping Ético: Rotar IPs para respetar rate limits mientras recolectás datos que tenés autorización para acceder
- Investigación de Seguridad: Testear cómo tus aplicaciones manejan requests desde diferentes ubicaciones geográficas
- Testing de Privacidad: QA para aplicaciones enfocadas en privacidad y sistemas de detección de VPN/proxy
- Investigación Académica: Estudiar comportamiento de redes, patrones de censura o características de la red Tor
- Penetration Testing: Evaluaciones de seguridad autorizadas que requieren rotación de IP
⚠️ Aviso de Responsabilidad: Siempre respetá
robots.txt, términos de servicio y rate limits. Esta herramienta es solo para uso legítimo. El autor no se responsabiliza por mal uso.
📦 Instalación
pip install tor-session-manager
Prerequisitos
Necesitás Tor corriendo localmente con el puerto de control habilitado:
macOS (Homebrew):
brew install tor
# Editá /opt/homebrew/etc/tor/torrc y agregá:
# ControlPort 9051
# CookieAuthentication 1
brew services start tor
Linux (Ubuntu/Debian):
sudo apt install tor
# Editá /etc/tor/torrc y descomentá/agregá:
# ControlPort 9051
# CookieAuthentication 1
sudo systemctl restart tor
Windows:
Descargá desde torproject.org y configurá torrc.
🚀 Inicio Rápido
Uso Básico
from tor_session_manager import TorClient
with TorClient() as client:
print(f"IP actual: {client.get_ip()}")
client.rotate() # Obtener nuevo circuito
print(f"Nueva IP: {client.get_ip()}")
One-liner
from tor_session_manager import rotate_and_get_ip
nueva_ip = rotate_and_get_ip()
print(f"Nueva IP: {nueva_ip}")
Con Requests
import requests
from tor_session_manager import TorClient
client = TorClient()
# Usá la propiedad proxies con cualquier llamada de requests
response = requests.get(
"https://httpbin.org/ip",
proxies=client.proxies,
timeout=30
)
print(response.json())
Scraping con Rotación
import time
import requests
from tor_session_manager import TorClient
def scrape_con_rotacion(urls: list[str], delay: float = 1.0):
"""Scrapear URLs con rotación automática de IP y rate limiting."""
resultados = []
with TorClient() as client:
for i, url in enumerate(urls):
# Rotar cada 10 requests
if i > 0 and i % 10 == 0:
client.rotate()
print(f"Rotado a nueva IP: {client.get_ip()}")
# Delay respetuoso entre requests
time.sleep(delay)
response = requests.get(url, proxies=client.proxies, timeout=30)
resultados.append(response.text)
return resultados
📖 Referencia de API
TorClient
TorClient(
control_port: int = 9051, # Puerto de control de Tor
socks_port: int = 9050, # Puerto proxy SOCKS de Tor
password: str = None, # Password del puerto de control (si no usás cookie auth)
rotate_delay: float = 2.0, # Segundos de espera después de rotar
)
Métodos:
| Método | Descripción |
|---|---|
is_ready() |
Verificar si Tor está corriendo y bootstrapped |
rotate() |
Solicitar nuevo circuito (nueva IP de salida) |
get_ip() |
Obtener IP pública actual a través de Tor (con fallback entre varios servicios) |
wait_for_new_ip(previous_ip=None, max_attempts=5) |
Rotar hasta que la IP de salida realmente cambie |
list_circuits() |
Listar todos los circuitos Tor activos (CircuitInfo) |
get_circuit_info(circuit_id=None) |
Detalle de un circuito, o del circuito activo (BUILT/GENERAL más reciente) |
get_exit_country(circuit_id=None) |
Código de país ISO del nodo de salida del circuito |
measure_latency(url=None, samples=3) |
Latencia (ms, mediana de N muestras) del circuito actual |
measure_throughput(url=None) |
Ancho de banda de descarga (KB/s) del circuito actual |
benchmark(...) |
Combina latencia + throughput en un CircuitHealth |
set_exit_country(country, strict=True) |
Forzar el país del nodo de salida |
set_exit_nodes(countries=None, fingerprints=None, strict=True) |
Restringir exits por país y/o fingerprint |
reset_exit_nodes() |
Limpiar cualquier restricción de exit |
new_circuit(exit_country=None, exit_fingerprint=None, ...) |
Construir circuito nuevo con el exit elegido |
pinned_exit(...) |
Context manager: fija el exit y lo limpia al salir |
circuit_pool(size=3) |
Crear un CircuitPool de circuitos aislados ligado a este cliente |
request(url, method="GET", ...) |
Hacer un request a través de Tor |
request_with_retry(url, max_rotations=3, ...) |
Request que rota a un exit nuevo si detecta bloqueo |
is_exit_blocklisted(url) |
Motivo de bloqueo del exit actual al pedir url (o None) |
proxies |
Propiedad que devuelve dict de proxy para requests |
💡 Rotación verificada:
rotate()a veces reutiliza el mismo nodo de salida, así que la IP puede no cambiar.wait_for_new_ip()rota repetidamente hasta observar una IP distinta (o agotarmax_attempts), útil cuando necesitás garantizar un exit nuevo entre requests.
with TorClient() as client:
ip_vieja = client.get_ip()
ip_nueva = client.wait_for_new_ip(previous_ip=ip_vieja)
print(f"{ip_vieja} -> {ip_nueva}")
🔁 IP checkers con fallback:
get_ip()prueba varios servicios en orden (ipify, ifconfig.me, icanhazip, httpbin), de modo que la caída de uno solo no rompe la resolución de IP. Si todos fallan levantaAllIPCheckersFailedError.
Inspección de circuitos
Podés ver qué relays componen tus circuitos y por qué país salís:
with TorClient() as client:
info = client.get_circuit_info() # circuito activo (BUILT/GENERAL más reciente)
print(f"Circuito {info.id} · estado {info.status}")
for hop in info.path:
print(f" {hop.nickname or hop.fingerprint} ({hop.country or '??'})")
exit_relay = info.exit_relay
print(f"Salida: {exit_relay.address} en {info.exit_country}")
# También podés listar todos los circuitos o consultar solo el país de salida
print(f"Circuitos activos: {len(client.list_circuits())}")
print(f"País de salida: {client.get_exit_country()}")
CircuitInfo expone id, status, purpose, path (lista de RelayInfo), build_flags,
created, y las propiedades exit_relay y exit_country. RelayInfo tiene fingerprint,
nickname, address y country. La resolución de IP/país del exit es best-effort: si el GeoIP
o la consulta al consenso fallan, esos campos quedan en None sin romper la llamada.
Medición de calidad del circuito
Podés medir qué tan bueno es el circuito actual (latencia y ancho de banda, a nivel aplicación a través de Tor) para decidir si conviene rotar:
with TorClient() as client:
health = client.benchmark()
print(f"Latencia: {health.latency_ms:.0f} ms")
print(f"Throughput: {health.throughput_kbps:.0f} KB/s")
# O medir por separado
print(f"Latencia: {client.measure_latency(samples=5):.0f} ms")
# Rotar si el circuito está lento
if health.latency_ms and health.latency_ms > 2000:
client.rotate()
benchmark() es best-effort: si una métrica falla (p. ej. cae el endpoint de throughput), ese campo
queda en None y la otra se reporta igual. CircuitHealth expone latency_ms, throughput_kbps,
samples, measured_at (epoch) y la propiedad ok (True si al menos una métrica se resolvió).
Elegir el nodo de salida (país / a demanda)
Podés forzar por qué país —o por qué relay— salís, útil para esquivar bloqueos del destino que prohíben ciertas IPs de salida:
with TorClient() as client:
# Salir por un país específico y confirmar que el circuito funciona
nueva_ip = client.new_circuit(exit_country="de")
print(f"Saliendo por Alemania: {nueva_ip} ({client.get_exit_country()})")
# Pinear el exit solo para un bloque; se limpia automáticamente al salir
with client.pinned_exit(countries=["nl"]):
requests.get(url, proxies=client.proxies) # sale por Países Bajos
# Limpiar cualquier restricción manualmente
client.reset_exit_nodes()
⚠️ StrictNodes y países sin exits: con
strict=True(por defecto) Tor usa solo exits que cumplan la restricción. Si el país no tiene exits usables, no puede armar circuito;new_circuit(verify=True)(default) lo detecta y levantaTorSessionErrorcon un mensaje claro en vez de colgarse. La restricción persiste a nivel del proceso Tor hasta que la limpiás conreset_exit_nodes()(o usáspinned_exit, que lo hace por vos).
Pool de circuitos y "best circuit"
Para elegir el circuito más rápido, CircuitPool mantiene varios circuitos en paralelo, los
benchmarkea y te deja usar el mejor:
with TorClient() as client:
with client.circuit_pool(size=4) as pool:
pool.build().benchmark() # levanta 4 circuitos y los mide
best = pool.pin_fastest()
print(f"Mejor circuito: {best.exit_ip} · {best.health.latency_ms:.0f} ms")
# Usar el circuito más rápido para tus requests
requests.get(url, proxies=pool.pinned_proxies)
# Ver el ranking, o descartar/pinear otro
for c in pool.ranked():
print(c.name, c.exit_ip, c.health.latency_ms)
pool.prune(keep=2) # quedarse solo con los 2 mejores
Cada "lane" del pool es un circuito independiente: se logra dándole a cada requests.Session
una credencial SOCKS distinta, y Tor (con IsolateSOCKSAuth, activo por defecto) rutea cada
credencial por un circuito separado. PooledCircuit expone session, proxies, health
(CircuitHealth) y exit_ip. drop(circuit) recicla un lane (nueva credencial = circuito nuevo),
útil para descartar uno lento o bloqueado.
Detección de bloqueo del destino y auto-rotación
Muchos sitios bloquean IPs de salida Tor (Cloudflare, CAPTCHA, 403/429). request_with_retry()
detecta esas respuestas y rota a un exit nuevo automáticamente hasta pasar:
with TorClient() as client:
# Rota de exit hasta obtener una respuesta no bloqueada (o levanta BlockedResponseError)
response = client.request_with_retry("https://sitio-que-bloquea-tor.com", max_rotations=5)
print(response.status_code)
# ¿El exit actual está bloqueado por este sitio?
motivo = client.is_exit_blocklisted("https://sitio-que-bloquea-tor.com")
if motivo:
print(f"Exit bloqueado: {motivo}")
La detección (detect_block) reconoce challenges de Cloudflare/CAPTCHA en el body y statuses de
bloqueo (403/429/503). Es heurística —un 403 legítimo puede parecer un bloqueo—, así que los
marcadores y statuses son ajustables:
response = client.request_with_retry(
url,
markers=("acceso denegado", "captcha"), # marcadores de challenge propios
statuses={403, 429, 503, 418}, # statuses considerados bloqueo
)
Cuando se agotan las rotaciones sin éxito, levanta BlockedResponseError (con .reason y
.response para inspeccionar el último intento).
Async / scraping concurrente
Para scraping paralelo hay un cliente async, TorClientAsync (sobre aiohttp). Instalá el extra:
pip install tor-session-manager[async]
import asyncio
from tor_session_manager import TorClientAsync
async def main():
async with TorClientAsync(max_concurrency=10) as client:
print(await client.get_ip())
# Descargar muchas URLs concurrentemente (acotado por max_concurrency)
urls = ["https://httpbin.org/get"] * 50
responses = await client.gather_requests(urls)
ok = [r for r in responses if r is not None]
print(f"{len(ok)}/{len(urls)} OK")
# También async: rotate(), request_with_retry(), benchmark(),
# set_exit_country(), new_circuit()
await client.rotate()
asyncio.run(main())
El control-plane (rotate, selección de exit) usa stem por debajo vía asyncio.to_thread; el
data-plane (requests, medición) va por aiohttp con aiohttp_socks (DNS ruteado por Tor). Cada URL
que falla en gather_requests queda como None en su posición, sin cortar el lote.
Bridges y pluggable transports (bajo censura)
Cuando el ISP o el país bloquean Tor mismo, los relays normales son inalcanzables. Con bridges (relays no listados) y pluggable transports (obfs4, snowflake, meek, webtunnel) el tráfico se ofusca para no parecer Tor. La librería lanza un Tor gestionado con esa config:
from tor_session_manager import launch_bridged_tor, TorClient
# Las bridge lines las obtenés de https://bridges.torproject.org o Telegram @GetBridgesBot
proc = launch_bridged_tor(
bridges=[
"obfs4 192.0.2.1:443 0123...CDEF cert=... iat-mode=0",
"obfs4 192.0.2.2:443 89AB...4567 cert=... iat-mode=0",
],
socks_port=9050,
control_port=9051,
)
# Ahora usás el TorClient normal contra esos puertos
with TorClient(control_port=9051, socks_port=9050) as client:
print(client.get_ip())
proc.terminate() # cerrar el Tor gestionado al terminar
⚠️ Binarios de los transports:
obfs4proxy,snowflake-client, etc. no vienen con la librería — tienen que estar instalados y en elPATH(o pasás la ruta contransport_paths={"obfs4": "/ruta/obfs4proxy"}). Si falta el binario, se levantaBridgeConfigErrorcon un mensaje claro.
UseBridges/Bridge/ClientTransportPlugin son opciones de arranque de Tor (no se pueden encender
en un Tor ya corriendo), por eso el flujo soportado es lanzar un Tor propio. También podés usar
parse_bridge_line() y build_bridge_config() por separado si querés armar tu propio torrc.
Anti-fingerprint: rotación de headers + pacing
Rotar la IP no alcanza si cada request lleva el mismo User-Agent: ese fingerprint te identifica igual.
TorClient puede rotar perfiles de headers coherentes (de navegadores reales) y pacear el ritmo por
host con backoff adaptativo ante 429:
client = TorClient(rotate_headers=True, rate_limit=1.0) # 1s mínimo entre requests por host
with client:
# request() inyecta un perfil de headers distinto por request y respeta el ritmo
r1 = client.request("https://ejemplo.com/a")
r2 = client.request("https://ejemplo.com/b") # espera ~1s; si el host devuelve 429, el ritmo se ensancha
También podés usar los componentes sueltos: HeaderRotator (round-robin sobre BROWSER_PROFILES,
o shuffle=True) y RateLimiter (pacing por host con acquire()/record()).
⚠️ Sobre TLS/JA3:
requestsusa elsslde la stdlib, así que el fingerprint JA3/TLS del handshake es fijo y no se puede cambiar desde esta librería. La rotación de headers es la palanca a nivel aplicación; para spoofear JA3 haría falta algo comocurl_cffi(fuera de alcance).
🔒 Política de enrutamiento
Las sesiones internas de TorClient y CircuitPool usan socks5h://127.0.0.1:<puerto>
(DNS a través del proxy) y trust_env=False. HTTP_PROXY, HTTPS_PROXY, ALL_PROXY,
NO_PROXY y sus variantes en minúscula no reemplazan ni evitan ese proxy, tampoco en
redirecciones. Si el SOCKS no está disponible, la solicitud falla; no se reintenta por
una conexión directa. Los checkers alternativos de IP conservan la misma ruta.
client.request(...) y request_with_retry(...) rechazan el argumento proxies, incluso
None o un diccionario vacío, con ValueError. TorClientAsync usa un conector SOCKS
con DNS remoto y trust_env=False, y rechaza proxy y proxy_auth por solicitud.
Elegí el puerto mediante socks_port al crear el cliente.
En Requests, desactivar trust_env también desactiva credenciales automáticas de .netrc
y REQUESTS_CA_BUNDLE/CURL_CA_BUNDLE. Si necesitás una CA personalizada, pasá
verify="/ruta/ca.pem" explícitamente a client.request; no desactives la verificación TLS.
Esta política cubre las sesiones administradas por la biblioteca. El diccionario
client.proxies no endurece una sesión externa ni impide que el llamador la modifique.
Para una sesión propia, configurá explícitamente session.trust_env = False y
session.proxies = client.proxies, sin overrides por solicitud. Cambiar sesiones,
adaptadores o conectores queda bajo responsabilidad del llamador. El proxy configurado
debe ser un Tor confiable: este enrutamiento no garantiza anonimato ni demuestra por
sí solo que el servidor SOCKS pertenece a Tor.
🖥️ CLI
Instalar el paquete deja disponible el comando tor-session:
tor-session ip # IP de salida actual
tor-session rotate # rota y muestra la nueva IP
tor-session circuit # relays del circuito activo y país de salida
tor-session country # país del nodo de salida
tor-session benchmark # latencia + throughput del circuito
tor-session status --json # snapshot completo en JSON (para monitoreo)
Flags comunes: --control-port, --socks-port, --password y --json (salida machine-readable).
Se aceptan antes o después del subcomando: tor-session --json status y
tor-session status --json son equivalentes. Los puertos deben estar entre 1 y 65535.
Preferí autenticación por cookie: un password pasado como argumento puede ser visible
en el historial del shell o la lista de procesos.
status conserva los campos version, ready, ip, exit_country, num_circuits y
health, y agrega checks y errors. Cada check usa ok, failed o not_checked:
controller: conexión al puerto de control.authentication: autenticación con Stem; falla de credenciales distinguida de conexión.bootstrap: progreso informado por Tor; debe alcanzar 100%.socks: el listener local responde al saludo SOCKS5 sin autenticación.connectivity: un checker público devuelve una IP válida a través del SOCKS configurado.
ready requiere los cinco checks exitosos. Si falla SOCKS o el bootstrap está incompleto,
la conectividad pública queda sin comprobar. La ruta SOCKS se diagnostica incluso si
el controlador no está disponible. El saludo SOCKS y una IP pública válida no certifican
que ese proxy sea Tor ni garantizan anonimato.
Exit codes: 0 si está listo, 1 si ready=false (o el comando falla), 2 para
argumentos inválidos. Los errores de campos auxiliares se informan en errors y no
cambian por sí solos la disponibilidad. En modo --json, los errores de ejecución se
emiten como JSON; los errores de sintaxis de argumentos siguen el formato de argparse.
Las sondas locales de diagnóstico usan un timeout de 3 segundos por operación; no es
un límite global de status: los checkers y benchmarks conservan sus timeouts habituales.
status hace consultas externas para conectividad y métricas a través del proxy.
Ejemplo de observabilidad (con pipefail para conservar el error de status):
set -o pipefail
tor-session status --json | jq '{ready, checks, ip, errors}'
🧪 Fixture de pytest
El paquete registra una fixture tor_client (vía entry point de pytest) que provee un TorClient
listo, o skipea el test si Tor no está corriendo:
def test_mi_scraper(tor_client):
assert tor_client.get_ip() # se saltea automáticamente si no hay Tor
Context Managers:
# Uso estándar - verifica que Tor esté listo
with TorClient() as client:
...
# Rotar antes de una operación específica
with client.rotated_session():
# Circuito fresco para este bloque
...
Excepciones
| Excepción | Descripción |
|---|---|
TorSessionError |
Excepción base |
TorConnectionError |
No se puede conectar al controlador de Tor |
TorNotReadyError |
Tor no está completamente bootstrapped |
IPFetchError |
No se puede determinar la IP pública |
AllIPCheckersFailedError |
Fallaron todos los servicios de checkeo de IP (subclase de IPFetchError) |
BlockedResponseError |
La respuesta siguió bloqueada tras agotar las rotaciones de exit |
BridgeConfigError |
Bridge line inválida o binario de pluggable transport faltante |
⚙️ Cómo Funciona
Esta librería actúa como un puente entre tu código Python y la red Tor:
Tu código → TorClient → Tor Proxy (SOCKS5) → Internet
↕
Controlador Tor
(rotación de circuitos)
Componentes
- Proxy SOCKS5 (puerto 9050): Tu tráfico HTTP/HTTPS pasa por acá para salir a través de Tor
- Puerto de Control (puerto 9051): Permite enviar comandos a Tor (como rotar circuitos)
- TorClient: Maneja la autenticación y envía señales al controlador
Flujo de Rotación
- Se envía señal
NEWNYMal puerto de control de Tor - Tor construye un nuevo circuito con diferentes nodos relay
- Se espera
rotate_delaysegundos (por defecto 2s) para que el circuito se establezca - Las próximas requests usarán el nuevo circuito → nueva IP de salida
Limitaciones
- Rate limits de Tor: No podés rotar circuitos instantáneamente. Tor tiene límites (~10 rotaciones/min)
- Velocidad: Tor es más lento que una conexión directa (3+ saltos en la red)
- Misma IP posible: Ocasionalmente podés obtener la misma IP después de rotar (aunque es poco probable)
🔧 Configuración
Puertos Personalizados
client = TorClient(
control_port=9151, # Puerto de control no estándar
socks_port=9150, # Puerto SOCKS no estándar
)
Autenticación por Password
Si usás password auth en lugar de cookie auth:
client = TorClient(password="tu_password_de_control")
Logging
Habilitá debug logging para ver qué está pasando:
import logging
logging.basicConfig(level=logging.DEBUG)
🧪 Testeando tu Setup
from tor_session_manager import TorClient
client = TorClient()
if client.is_ready():
print("✅ Tor está corriendo y listo")
with client:
print(f"📍 Tu IP de Tor: {client.get_ip()}")
else:
print("❌ Tor no está listo - verificá la instalación")
🔍 Troubleshooting
"TorConnectionError: Failed to connect to Tor controller"
Causas comunes:
- Tor no está corriendo →
brew services start tor(macOS) osudo systemctl start tor(Linux) - Puerto de control no habilitado → agregá
ControlPort 9051entorrc - Firewall bloqueando conexión local
Verificar:
# Verificar si Tor está corriendo
ps aux | grep tor
# En Linux, verificar status
sudo systemctl status tor
"TorNotReadyError: Tor is not fully bootstrapped"
Tor puede tardar unos segundos en conectarse a la red. Esperá ~10-15 segundos después de iniciar Tor antes de usar la librería.
Verificar status:
from tor_session_manager import TorClient
client = TorClient()
if client.is_ready():
print("✅ Listo")
else:
print("❌ Esperá un momento y volvé a intentar")
"IPFetchError: Failed to fetch IP address"
Causas:
- Tor no está ruteando el tráfico correctamente
- Problema de conectividad general
- Sitio de verificación de IP bloqueado
Solución:
- Verificá que Tor esté corriendo
- Probá manualmente:
curl --proxy socks5h://127.0.0.1:9050 https://api.ipify.org - Si falla, verificá la configuración de Tor
Puertos personalizados no funcionan
Si cambiaste los puertos en torrc, asegurate de reiniciar Tor:
# macOS
brew services restart tor
# Linux
sudo systemctl restart tor
La rotación no cambia la IP
Esto puede pasar ocasionalmente. Tor tiene un pool finito de nodos de salida y puede asignarte el mismo. Intentá rotar nuevamente o esperá unos segundos.
Logging para debugging
Habilitá logs detallados para ver qué está pasando:
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
🤝 Contribuciones
¡Las contribuciones son bienvenidas! Sentite libre de abrir issues y pull requests.
📄 Licencia
MIT License - ver LICENSE para detalles.
👤 Autor
Pablo Alaniz - @PabloAlaniz
Construido para la comunidad de investigación de seguridad e ingeniería de datos 🔐
Metadata
Release files for tor-session-manager 1.10.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 | |
|---|---|---|---|
| tor_session_manager-1.10.0.tar.gz | 67.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tor_session_manager-1.10.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 112.1 kB
Release files / tor_session_manager-1.10.0.tar.gz
| Download URL | tor_session_manager-1.10.0.tar.gz |
|---|---|
| Size | 67.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
801ebbda2be4c27f1fbefc6ca5b2678fa867219b5c03f1872140c4e323ba9cf5
|
|
BLAKE2b-256 checksum How to use checksums |
67f7fcbea7ea840a78ec550ef0494d67f96977cac27bb076006339c3bb467e11
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / tor_session_manager-1.10.0-py3-none-any.whl
| Download URL | tor_session_manager-1.10.0-py3-none-any.whl |
|---|---|
| Size | 45.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d72cc80a17070624535023fd8b35f43a7ae2e5cc36e580398465104b38728ce9
|
|
BLAKE2b-256 checksum How to use checksums |
9db298fd781db4beeb3eb963561530a17d4c5e4df18afedd7fad00c8dba6221e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log