Metadata-Version: 2.5
Name: shade-sdk
Version: 0.14.0
Summary: O runtime dos robôs da ShadeOne: ledger por chave de negócio, recuperação, relatório e o protocolo com o executor.
Author: ShadeOne
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: loguru>=0.7.0
Requires-Dist: openpyxl>=3.1.0
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: pydantic>=2.0.0
Description-Content-Type: text/markdown

# shade-sdk

O runtime e o framework dos robôs da ShadeOne. O robô gerado pela plataforma importa este
pacote, na versão fixada pelo `requirements.lock` da imagem do sandbox, e o robô escrito à mão
importa o mesmo pacote: um runtime só, para os dois. O import é `shade_sdk`.

```python
from decimal import Decimal

from pydantic import BaseModel, Field
from shade_sdk import Alvo, Item, Pagina, Retentativa, Robo


class Pedido(BaseModel):
    cliente: str = Field(min_length=1)
    valor: Decimal = Field(gt=0)


class TelaDePedido(Pagina):
    url = "/pedidos/novo"  # relativa ao [config] base_url
    pronta = Alvo(papel="heading", nome="Novo pedido")  # abrir() espera por ela
    cliente = Alvo(rotulo="Cliente")
    salvar = Alvo(papel="button", nome="Salvar")


class CadastroDePedido(Robo):
    chave = ("cliente", "pedido")  # a chave de negócio do item
    modelo = Pedido  # o item inválido fica retido antes de abrir o navegador
    retentativas = Retentativa(sistema=3)  # erro de sistema repete; negócio nunca

    def entrar(self, pagina) -> None:  # opcional: ao abrir a sessão e se ela cair
        ...

    def processar(self, item: Item[Pedido], pagina) -> None:
        tela = TelaDePedido(pagina)
        with item.etapa("preencher"):  # tempo, resultado e erro no relatório
            tela.abrir()
            tela.cliente.fill(item.dados.cliente)  # item.dados é o Pedido validado
        with item.efetivar("salvar pedido"):  # o ensaio para aqui; o real registra antes
            tela.salvar.click()
```

`main.py` na raiz do projeto: `CadastroDePedido.main()`. Sem flag, roda em modo real, como o
robô gerado; `--rehearsal` é o ensaio. A entrada é `--input <arquivo>`, ou `data/input.json`
(onde a plataforma a deixa), ou `[itens] exemplo` do `shade.toml`: JSON, CSV (o delimitador é
detectado) ou XLSX (`[itens] aba` escolhe a aba). Planilha chega como texto, e o `modelo`
converte.

Com `[itens] fonte = "fila"`, o run real pega cada item da fila hospedada pelo executor da
plataforma, um por vez, e o fecha com o desfecho (a chave do item é o id que o servidor
entregou). A queda no meio de um item o devolve pelo efeito: sem efeito, volta para a fila; com
o efeito em voo, fica retido para conferência. `--input` e o ensaio leem o arquivo (o ensaio
nunca pega item da fila do cliente), e o run real sem o executor e sem `--input` para com
`ErroDeContrato`.

- `Robo` (uma lista de itens) e `Tarefa` (uma execução única, `executar(item, pagina)`).
- `Item`: `item.dados` (a instância do `modelo`, ou a linha), `item["campo"]` (a linha como
  veio), `item.chave`, `with item.efetivar(nome)`, `with item.etapa(nome)`,
  `item.saida(**campos)`, `item.reter(motivo)`, `item.log(texto)`.
- `modelo = <pydantic.BaseModel>`: cada item é validado antes de abrir o navegador; o inválido
  fica retido com o campo e a regra (sem o valor), e sem item válido o navegador nem abre.
- `Pagina` (`url`, `pronta`, `abrir()`, `esperar_pronta()`) e `Alvo(papel=, nome=)`,
  `Alvo(rotulo=)`, `Alvo(test_id=)`, `Alvo(texto=)` e, por último, `Alvo(css=)`: o `Locator` do
  Playwright (`tela.salvar.click()`; `tela.salvar.locator` é o cru). Quando a ação falha,
  `ErroDoAlvo` diz o alvo, a URL (sem a query), se não achou nada, se achou mais de um (e
  quais) ou em que elemento caiu, e os candidatos parecidos. `diagnosticar(pagina, alvo)` dá o
  mesmo texto sem agir.
- `shade_sdk.entradas.ler_entrada(caminho, aba=None)`: os itens de um JSON, CSV ou XLSX.
- `Retentativa(sistema=N, espera=Exponencial(base, teto), sessao_perdida="reentrar" | "falhar")`:
  a política por classe de falha. Negócio nunca repete, e nada repete depois do `__enter__` de
  `efetivar()`.
- `ao_falhar = "seguir" | "parar" | "pausar" | "pausar_run"`: o que o lote faz com o item que
  falhou depois da `Retentativa`. `"seguir"` (o padrão) passa ao próximo; `"parar"` termina o
  lote e sai com 1; `"pausar"` espera `RECOVERY_PAUSE_S` segundos por uma pessoa e segue;
  `"pausar_run"` pausa o run para uma decisão humana na plataforma e sai com 0. Na fila, as duas
  que saem fecham o item com a falha antes.
- Os ganchos do lote: `ao_terminar_item(item, desfecho)` roda uma vez por item, depois que o
  desfecho está gravado (`desfecho.estado`, `status`, `efetivou`), e é onde ficam a soma entre
  itens, a marca na planilha de origem e o aviso de falha (a exceção dele só vai ao log);
  `encerrar(pagina)` roda uma vez depois do último item, com o navegador aberto, e não roda
  quando o lote parou. Em `entrar` e `encerrar`, `with self.efetivar(nome)` é o ponto sem volta
  do lote: no ensaio o bloco não roda e o gancho termina ali; no real roda sem registro.
- `sessao_salva = "sessao.json"`: o navegador nasce do estado salvo (cookies e
  `localStorage`), `self.sessao_reaproveitada` diz ao `entrar` que pode conferir a sessão e
  pular o login, e cada `entrar` que termina bem regrava o arquivo (permissão 0600). O arquivo
  é segredo: ponha-o no `.gitignore`; ele nunca vai em anexo.
- O item que falha deixa a tela e o trecho do trace do Playwright em
  `evidencias/<sha256 da chave>/`, com teto por item e por run e os dados do item mascarados
  (`SHADE_MASCARAR_PII=0` desliga); o que passa não guarda nada.
- `shade_sdk.http`: `http.get` a qualquer momento; `http.post`, `put`, `patch` e `delete` só
  dentro de `efetivar()` (fora, `ErroDeContrato`; no ensaio, nada sai), com `Idempotency-Key`
  derivada da chave do item, do nome da efetivação e da ordem da escrita no bloco (num `Tarefa`,
  com `idempotencia=`). Host fora de `[hosts] permitidos` é recusado antes de qualquer byte,
  timeout sempre finito, e só repete quando a rede recusou antes do envio. `http.NadaSaiu`
  (certamente sem efeito: `desfazer_se=(http.NadaSaiu,)` devolve o item à fila) e
  `http.ResultadoIncerto` (pode ter gravado: o item fica retido). É fidelidade, não segurança:
  na plataforma, quem barra o host é o proxy da allowlist.
- `self.segredo(nome)`: só a credencial declarada em `[credenciais] nomes`; `self.config`: a
  tabela `[config]`.
- Erros: `ErroDeNegocio` (não repete), `SessaoPerdida` (entra de novo e refaz),
  `ErroDeContrato`, e os sinais `ParadaDoEnsaio` e `ItemRetido` (`BaseException`: um
  `except Exception` não os engole).
- `shade_sdk.plugins`: o contrato dos pacotes `shade-plugins-<nome>`. `Plugin(nome, versao,
  shade_sdk=..., dono=..., hosts=[...], credenciais=[...])` declara o plugin, que se registra no
  entry point `shade_sdk.plugins`; `@plugin.operacao` (sem efeito) e `@plugin.efeito` (só dentro
  de `efetivar()`, fora dele `ErroDeContrato`; no ensaio nem roda). A operação recebe a
  `Chamada` primeiro: `chamada.timeout` (toda chamada aceita `timeout=`, sempre finito),
  `chamada.idempotencia` (derivada da chave do item, do nome da efetivação, do plugin, da
  operação e da ordem no bloco: a mesma em toda execução do item), `chamada.segredo(nome)` (só
  credencial declarada pelo plugin e pelo projeto) e `chamada.destino(host)` (para o protocolo
  que não é o `shade_sdk.http`). Dentro da operação, o `shade_sdk.http` recusa o host que o
  plugin não declara. Cada chamada vira um evento no log do run: plugin, operação, hosts,
  duração e status, sem argumento, corpo nem mensagem de erro.
- `shade_sdk.testing.plugins.ContratoDePlugin`: a suíte de contrato que todo plugin passa
  (declaração, efeito fora de `efetivar()`, egresso só para os hosts declarados, timeout,
  credencial só por `segredo()`, idempotência, um evento por chamada sem corpo nem credencial e,
  com o robô em subprocesso, o ensaio sem efeito e o kill no meio do efeito sem duplicata).
  `dentro_de_efetivar(chave)` roda uma operação com efeito num teste de unidade.
- `shade_sdk.testing`: `ExecutorLocal` (o lado do executor do protocolo, com o robô em
  subprocesso, e `ex.fila`, a fila hospedada do lado do servidor) e `pagina_local` (um site local que conta os efeitos, do formulário e da API
  JSON, com a `Idempotency-Key` de cada chamada).

O runtime:

- `shade_sdk.runtime.laco`: o laço por item (ledger, retomada, política de retentativa,
  `efetivar`, etapas, resumo).
- `shade_sdk.runtime.evidencia`: o trace por item, a tela, a máscara e os tetos.
- `shade_sdk.runtime.ledger`: identidade do item por chave de negócio, o `incerto` gravado
  antes de efetivar e o canal do registro com o executor.
- `shade_sdk.runtime.fila`: o `pegar` e o `fechar` da fila hospedada, pelo mesmo canal.
- `shade_sdk.runtime.recovery`: classificação da falha, sessão perdida e modal inesperado.
- `shade_sdk.runtime.report`: relatório HTML da execução e o histórico.
- `shade_sdk.runtime.config`: `Settings` e a carga do `.env`.
- `shade_sdk.runtime.output`: a planilha de saída, com ou sem mascaramento de PII.
- `shade_sdk.runtime.protocolo`: o número e o formato do contrato robô ↔ executor, inclusive a
  entrada (`main.py`, argv e ambiente).

Testes do pacote: `cd sdk && uv run pytest` (Chromium do Playwright instalado).

Licença: Apache-2.0.
