Skip to main content

hobots

SDK Python da Hobots para robôs (RPAs) e integrações. Tudo é uma task: você registra o que o robô faz e a plataforma passa a receber logs, itens processados, progresso, prints, erros e o "estou vivo" do robô.

  • hobots.register(task, handler) — sob demanda: o robô fica online aguardando solicitações criadas no app (formulários e agendamentos) e executa o handler a cada uma. Módulo Solicitações.
  • hobots.start(task, handler) — sempre ligado: o robô abre a própria execução ao iniciar e roda o handler (o seu loop) dentro dela; a plataforma só recebe logs, itens e progresso. Sem formulário, sem solicitante.

Cliente HTTP puro, sem dependências de runtime (só a stdlib). Requer Python 3.9+. A API é síncrona: handlers são funções normais (def) e o SDK administra as threads de fundo — combina com Selenium, Playwright síncrono, pyautogui e código legado.

Todas as durações são em milissegundos, como no SDK Node: retry_delay=5000 são 5 segundos, hobots.sleep(2000) dorme 2 segundos. Os nomes não levam sufixo _ms.


Instalação

pip install hobots
import hobots  # o próprio módulo é o objeto global

Em 30 segundos

export HOBOTS_CLIENT_SECRET=hb_<64 hex>   # Configurações → Agentes
export HOBOTS_INSTANCE_ID=vm-cliente-01   # opcional — default: hostname da máquina
export HOBOTS_ENVIRONMENT=staging         # opcional — default: production
import hobots


# sob demanda: uma execução por solicitação do app (o task_slug casa com o formulário)
def processar_nfe(params, ctx):
    hobots.log.info(f"lote {params['lote']}")
    hobots.progress.total(len(params["notas"]))
    for nota in params["notas"]:
        baixar(nota)
        hobots.items.succeeded({"numero_nota": nota}, id=nota)
        hobots.progress.advance()


# sempre ligado: o handler é o loop do robô; cada iteração reporta o que fez
def monitor_pasta(params, ctx):
    while True:
        hobots.sleep(60_000)  # cancelável
        for arquivo in novos_arquivos():
            hobots.items.succeeded({"arquivo": arquivo})
        hobots.log.info("ciclo ok")


hobots.register("processar-nfe", processar_nfe)
hobots.start("monitor-pasta", monitor_pasta)
# as duas chamadas voltam na hora; o processo fica vivo (heartbeat) até hobots.close()
# ou Ctrl-C — encerramento gracioso em "Encerramento", abaixo

O módulo hobots é o objeto global: hobots.log, hobots.items, hobots.progress, hobots.transaction… resolvem a execução atual de qualquer módulo do programa — sem passar contexto de mão em mão, mesmo com várias execuções em paralelo.


Credenciais e identidade

Env Opção O que é
HOBOTS_CLIENT_SECRET client_secret O Client Secret do agente (hb_<64 hex>), copiado em Configurações → Agentes. Viaja só no header Authorization: Bearer, nunca na URL. Ausente ou inválido → register()/start() levanta ValueError na hora, citando a env (sem ecoar o valor).
HOBOTS_INSTANCE_ID instance_id Identidade estável desta instância do robô (vm-cliente-01). Sem ela, o hostname. Heartbeat e tasks convergem nela: um agente só no painel.
HOBOTS_ENVIRONMENT environment Ambiente lógico do robô (development | testing | staging | production). Sem ela, production.
HOBOTS_API_URL — Só em desenvolvimento: aponta o SDK para outro host (http://localhost:3001). Em produção é sempre https://api2.hobots.app.

A opção passada em register()/start() tem precedência sobre a env — útil para copiar e colar o snippet do app. O secret nunca aparece em repr() do SDK.


Os dois modos

register(task, handler, **opções) start(task, handler, **opções)
Quem dispara Uma solicitação no app (formulário com esse task_slug, ou agendamento) O próprio robô, ao iniciar
params do handler O dict validado do formulário {}
Quantas execuções Uma por solicitação; concurrency em paralelo (1..5) Uma só — o handler é o seu loop; quando ele retorna, a execução fecha
O processo Fica vivo fazendo polling Fica vivo pelo heartbeat, mesmo depois do handler retornar
Reexecutar pelo app Sim Não — só reiniciando o robô

As duas chamadas voltam na hora: o polling e o handler do modo start rodam em threads de fundo. Os dois modos podem coexistir no mesmo processo: um monitor sempre ligado + tasks sob demanda, um heartbeat, uma instância. Cada slug só pode ser registrado uma vez.

O handler é sempre handler(params, ctx) — dois argumentos posicionais, também no modo start (onde params é {}). O retorno é ignorado.

Modo start e o limite de logs: o servidor guarda no máximo 1000 linhas de log por execução. Num loop que roda por dias, logue o que importa (ciclos com novidade, avisos, erros) e registre o trabalho em hobots.items — itens não têm esse teto tão baixo (10.000 por execução).


O módulo hobots

Membro O que faz
hobots.log.debug/info/warn/error/success(msg, *rest) Log da execução, enviado incrementalmente ao app (e ecoado no terminal). Aceita vários argumentos como print() — hobots.log.info("nota", numero, "ok") — cada um vira texto, unidos por espaço. Nunca levanta — pode ir em finally.
hobots.items.succeeded(payload=None, *, id=None) · .failed(msg, payload=None, *, id=None) · .occurrence(msg, …) · .add(status, …) A entrega do robô: cada item processado, com status e atributos (viram colunas no app). Bufferizado, em lotes.
hobots.progress.total(n) · .advance(k=1) · .set(atual, total=None) · .current · .snapshot Barra, percentual e tempo restante na solicitação. Sem total() nada é enviado.
hobots.transaction.start_child(op, descrição) Uma step no waterfall de performance da execução (também como with). A transaction da execução já existe e é finalizada pelo SDK.
hobots.attach(imagem, *, caption=None, level="info") Pendura um print (PNG/JPEG/WEBP, até 5 MB, 20 por execução) na linha de log. Nunca levanta nem bloqueia — devolve um Future[AttachResult].
hobots.capture_exception(erro, hint=None) Abre uma issue (módulo Issues). hint["attachment"] anexa um print à linha de log correspondente.
hobots.retry(fn, *, retries=3, delay=1000, should_retry=None) Retry ciente do cancelamento da execução. fn recebe o número da tentativa; delay aceita callable(tentativa) -> ms.
hobots.sleep(ms) Espera cancelável: levanta RunCanceled se a execução for cancelada. Use no lugar de time.sleep().
hobots.signal · hobots.raise_if_canceled() Cancelamento cooperativo manual (signal é um threading.Event), para controle fino.
hobots.run_id · hobots.task · hobots.params Identidade da execução atual.
hobots.register(...) · hobots.start(...) · hobots.close(timeout=30_000) Ciclo de vida do processo.

O handler também recebe o mesmo contexto como 2º argumento (ctx.log, ctx.items, ctx.progress, ctx.transaction, ctx.signal, ctx.params, ctx.sleep, ctx.attach, ctx.retry, ctx.raise_if_canceled), para quem prefere explícito. Fora de qualquer execução, os membros de contexto caem em fallbacks seguros e avisam uma vez — hobots.log continua imprimindo no terminal.

Evite from hobots import log (ou items, progress…) no topo de um módulo: o valor capturado no import é o de fora de execução. Importe o módulo e chame hobots.log.info(...) — cada acesso resolve a execução atual.


Cancelamento automático

Uma execução cancelada no app não mata o handler — o SDK dispara hobots.signal e o próximo ponto de parada levanta RunCanceled:

  • hobots.items.*, hobots.progress.total/advance/set, hobots.transaction.start_child() (e step.start_child()), hobots.sleep() e cada tentativa de hobots.retry().
  • Nunca hobots.log.* — um log dentro de um finally de limpeza não pode explodir.

Num loop de itens, isso costuma bastar: registrar o item processado já é o ponto de parada. Para controle fino, hobots.raise_if_canceled() e hobots.signal.is_set() continuam disponíveis. Quando o cancelamento veio do app, o RunCanceled é suprimido — a execução já está cancelada no servidor e nada é reportado por cima. Limpeza (fechar navegador, desfazer estado) vai em try/finally.

Atenção: não use if hobots.signal.is_set(): return. Retornar normalmente reporta a execução como succeeded. É preciso levantar — e é o que os pontos de parada fazem. Se capturar o RunCanceled para limpar, re-levante.


Retry

Quando o handler levanta, o SDK tenta de novo na mesma execução: aviso no log (tentativa 1/2 falhou: … — nova tentativa em 5000ms), espera cancelável, re-invocação com os mesmos params. Default: 1 tentativa extra com 5 s. Itens e progresso já enviados permanecem — escreva handlers idempotentes (use o id de negócio nos itens: repetido, o servidor ignora).

hobots.register("processar-nfe", handler, retries=3, retry_delay=lambda tentativa: tentativa * 10_000)

Nunca há nova tentativa em cancelamento, timeout (TaskTimeout é terminal) ou execução já cancelada no app. Para uma chamada instável dentro do handler, use hobots.retry().


Opções

hobots.register(task, handler, *, client_secret=None, ..., concurrency=None, poll_interval=None)
hobots.start(task, handler, *, client_secret=None, ...)

Todas as opções são keyword-only.

Opção Default Descrição
client_secret env HOBOTS_CLIENT_SECRET Client Secret do agente
instance_id env HOBOTS_INSTANCE_ID → hostname Identidade da instância
environment env HOBOTS_ENVIRONMENT → "production" development | testing | staging | production — decide quem é alertado
release — Versão do programa, ex. robo-nfe@1.4.2
tags — Tags de todos os eventos
debug False Loga a atividade do SDK
machine_metrics True CPU/memória/disco no heartbeat e em cada evento
capture_unhandled True sys.excepthook/threading.excepthook viram issues
retries 1 Tentativas extras do handler (0 desliga)
retry_delay 5000 ms fixo ou callable(tentativa) -> ms
timeout — (infinito) Timeout por tentativa, em ms; terminal (TaskTimeout). O handler não é morto: o signal dispara e o slot é liberado
concurrency (register) 1 Execuções simultâneas desta task (1..5)
poll_interval (register) 2000 Intervalo entre polls sem trabalho (mín. 1000)
avançadas: install_exit_handlers, log_flush_interval, cancel_check_interval, cancel_grace True, 2000, 5000, 10000 Ver Referência de API

As opções de processo (client_secret, instance_id, environment, release, tags, debug, machine_metrics, capture_unhandled, install_exit_handlers, e poll_interval entre os register()) valem as da primeira chamada; chamadas seguintes com valor diferente avisam e são ignoradas.


Encerramento

O heartbeat mantém o processo vivo por design — é a única thread não daemon do SDK, o que faz o script continuar rodando depois da última linha. hobots.close(timeout=30_000) para o polling, aguarda as execuções em voo, para o heartbeat (libera o processo) e faz o flush final; devolve False se algo não terminou a tempo.

Para um encerramento gracioso no Ctrl-C/SIGTERM (esperar a execução em andamento terminar), instale o seu handler de sinal e segure a main thread até ele disparar:

import signal
import threading

import hobots

parar = threading.Event()
signal.signal(signal.SIGINT, lambda signum, frame: parar.set())
signal.signal(signal.SIGTERM, lambda signum, frame: parar.set())

hobots.register("processar-nfe", processar_nfe)

while not parar.wait(1):  # o timeout mantém o Ctrl-C responsivo também no Windows
    pass
hobots.close(15_000)  # espera a execução em voo, para o heartbeat e faz o flush

Com handler próprio de sinal, o SDK não interfere. Sem ele, um SIGINT/SIGTERM com execuções ativas reporta cada uma como failed/AgentTerminated, faz o flush da telemetria e o processo sai com 130/143 — o app mostra a falha na hora. Uma exceção não tratada na main thread também encerra o processo (exit 1), mesmo com o heartbeat vivo.

Não chame sys.exit() de dentro do handler de sinal quando o script principal já terminou: o SystemExit cai dentro do threading._shutdown do interpretador ("Exception ignored in…"). O padrão acima — sinal só seta um Event, a main thread chama close() — evita isso.

Dados sensíveis: logs, itens, mensagens de erro e prints são enviados ao servidor e ficam visíveis no app. O SDK redige padrões óbvios de segredo (hb_…, Bearer, sk-…, credenciais em URL), mas não borra prints nem filtra dados pessoais. Capture só o necessário.


Migrando da 2.x

A 3.0 remove o init() e a telemetria "solta": tudo passa a acontecer dentro de uma task.

2.x 3.0
hobots.init(client_secret=…, instance_id=…, tasks=…, heartbeat=…) Nada — env HOBOTS_CLIENT_SECRET/HOBOTS_INSTANCE_ID (ou as opções de register()/start()); heartbeat sempre ligado
@hobots.task("slug") + hobots.run_forever() hobots.register("slug", handler) (volta na hora; o processo fica vivo sozinho)
Robô que roda sozinho em loop + start_transaction hobots.start("slug", handler) — uma execução própria, com logs/itens/progresso
hobots.stop() / hobots.flush() hobots.close(timeout)
ctx.tx hobots.transaction / ctx.transaction
ctx.cancel_event · ctx.is_cancelled() · ctx.raise_if_cancelled() hobots.signal · hobots.signal.is_set() · hobots.raise_if_canceled()
ctx.attach(...) (bloqueia) · ctx.attach_nowait(...) hobots.attach(...) — não bloqueia; .result() no Future para esperar
tasks={"task_timeout": …, "concurrency": …, "poll_interval": …} register(..., timeout=…, concurrency=…, poll_interval=…) — concurrency agora é por task
capture_message, add_breadcrumb, set_tag(s)/set_user/set_context/set_extra, with_scope Removidos — narração vai em hobots.log, dados de negócio em hobots.items; tags fixas em tags=
get_client, Client, Scope, InitOptions, TasksOptions, HeartbeatOptions, sample_rate, traces_sample_rate, before_send, max_breadcrumbs Removidos
Handler sem retry retries=1 por padrão — o handler é re-invocado na mesma execução

Antes (2.x):

import hobots

hobots.init(client_secret="hb_…", instance_id="vm-01", heartbeat=True, tasks={"concurrency": 2})


@hobots.task("processar-nfe")
def processar(params, ctx):
    for nota in params["notas"]:
        ctx.raise_if_cancelled()
        with ctx.tx.start_child("http", f"GET /notas/{nota}"):
            baixar(nota)
        ctx.items.succeeded({"numero_nota": nota}, id=nota)


hobots.run_forever()

Depois (3.0):

import hobots  # HOBOTS_CLIENT_SECRET e HOBOTS_INSTANCE_ID no ambiente


def processar(params, ctx):
    for nota in params["notas"]:
        with hobots.transaction.start_child("http", f"GET /notas/{nota}"):  # ponto de parada
            baixar(nota)
        hobots.items.succeeded({"numero_nota": nota}, id=nota)  # ponto de parada


hobots.register("processar-nfe", processar, concurrency=2)

Diferenças em relação ao SDK Node

Mesmo protocolo, mesmos defaults, nomes em snake_case. O que muda por causa da linguagem:

  • Boot síncrono: o primeiro register()/start() configura o processo na hora (no Node, num microtask). Tasks registradas depois entram no próximo poll, antecipado na hora.
  • Sem globalThis: import hobots já é o objeto global — os membros de contexto são resolvidos por __getattr__ do módulo, com contextvars no lugar do AsyncLocalStorage.
  • attach devolve concurrent.futures.Future em vez de Promise; hobots.signal é um threading.Event em vez de AbortSignal.
  • Threads próprias do handler não herdam o contexto da execução. Com uma execução só ativa, hobots.* ainda a encontra; com concurrency > 1, passe o ctx ou rode a thread com contextvars.copy_context().run(...).
  • Requisições em voo não são interrompíveis (urllib não tem AbortSignal): um close() durante um poll espera a resposta e devolve à plataforma, como AgentTerminated, qualquer run que ele ainda entregue.

Changelog resumido

  • 3.1.0 — hobots.log.* aceita vários argumentos (como print); eco de transaction/steps no terminal só com debug=True; None dentro de extra/payload/params/metadata viaja como null; heartbeat com jitter de ±10% e interval_ms clampado à faixa do servidor; fila da telemetria nunca descarta heartbeat; prazos do reporter (flush, progresso, heartbeat de cancelamento) valem mesmo com o handler num loop apertado; logs emitidos depois do fim da execução seguem como log tardio; close() nunca deixa uma run reivindicada sem dono; PATCH terminal nunca passa de 100 logs; progress.set(inf)/advance concorrente corrigidos; tipos Environment/EnvironmentInput e todos os tipos do protocolo exportados.

Documentação

  • Getting started — instalação, envs, primeira task nos dois modos, encerramento.
  • Tasks — handler, hobots.*, itens, progresso, anexos, cancelamento, retry, concorrência, idempotência.
  • Conceitos — o vocabulário: task, execução, evento, transaction, step, heartbeat, instância, claim…
  • Referência de API — assinaturas, tabelas de opções e tempos.
  • Protocolo & transporte — endpoints, envelope, retries, claim.
  • Exemplos — scripts executáveis, um por funcionalidade.

Desenvolvimento

pip install -e ".[dev]"
pytest            # sem rede (servidor HTTP local nas fixtures)
ruff check . && mypy src
coverage run -m pytest && coverage report   # reprova abaixo de 97% (linhas + branches)

Publicação

  1. Rode a suíte — não publique com ela vermelha:
    pytest && ruff check . && mypy src && coverage run -m pytest && coverage report
    
  2. Ajuste a versão em src/hobots/__about__.py (semver).
  3. Construa o pacote:
    python3 -m pip install build twine
    python3 -m build
    
  4. Publique no PyPI (requer credenciais em ~/.pypirc):
    python3 -m twine upload dist/*
    
  5. Confirme a publicação em pypi.org/project/hobots.
  6. Crie um commit com a nova versão.

Os artifacts em dist/ podem ser descartados após a publicação.

Licença

MIT © Hobots

Metadata

Release files for hobots 3.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hobots 3.0.1
File Size Uploaded
hobots-3.0.1.tar.gz 186.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hobots 3.0.1
File Interpreter ABI Platform
hobots-3.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 285.0 kB

Release files / hobots-3.0.1.tar.gz

Download URL hobots-3.0.1.tar.gz
Size 186.0 kB
Tags Source
SHA-256 checksum
How to use checksums
1956b17c15df0e21a8b1d55d5fa7292f3c03912456e05e2582ffc1b60efdd1fa
BLAKE2b-256 checksum
How to use checksums
03ef0e18d4a432de65761a5a6fb72828a2216263fd36ff9ba73aa17a9d8dd714
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / hobots-3.0.1-py3-none-any.whl

Download URL hobots-3.0.1-py3-none-any.whl
Size 99.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
514859890e0a64ac1d3ae760be7be334143040f0218bb4d2c03d4a2e30fc6653
BLAKE2b-256 checksum
How to use checksums
a772292a8c6202d0b70d6f2e6e8fb1a92ce006257643c32f6727bc7e48d933f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release history Release notifications | RSS feed

3.3.1

2 release files

3.3.0

2 release files

3.2.0

2 release files

3.1.1

2 release files

3.1.0

2 release files

This release

3.0.1 This release

2 release files

3.0.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.0.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page