zsynctech-sdk 1.4.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zsynctech_sdk-1.4.9/PKG-INFO +208 -0
- zsynctech_sdk-1.4.9/README.md +195 -0
- zsynctech_sdk-1.4.9/pyproject.toml +61 -0
- zsynctech_sdk-1.4.9/pyproject.toml.orig +59 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/__init__.py +95 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/client.py +122 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/connection.py +286 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/credentials.py +120 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/exceptions.py +50 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/execution.py +118 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/loggers.py +42 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/models/__init__.py +39 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/models/base.py +24 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/models/config.py +45 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/models/credential.py +47 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/models/enums.py +98 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/models/execution.py +19 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/models/robot.py +38 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/models/task.py +38 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/protocol.py +39 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/py.typed +0 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/queue.py +118 -0
- zsynctech_sdk-1.4.9/src/zsynctech_studio_sdk/utils.py +47 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: zsynctech-sdk
|
|
3
|
+
Version: 1.4.9
|
|
4
|
+
Summary: SDK Python para conectar robôs (RPA) à plataforma zsynctech-studio via Socket.IO
|
|
5
|
+
Author: Rodrigo Zavan
|
|
6
|
+
Author-email: Rodrigo Zavan <rodrigo.zavan@zsynctech.com.br>
|
|
7
|
+
Requires-Dist: loguru>=0.7
|
|
8
|
+
Requires-Dist: pydantic>=2.9
|
|
9
|
+
Requires-Dist: python-socketio[client]>=5.11
|
|
10
|
+
Requires-Dist: requests>=2.31
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# zsynctech-sdk
|
|
15
|
+
|
|
16
|
+
SDK Python oficial para conectar robôs de RPA à plataforma **zsynctech-studio**: handshake,
|
|
17
|
+
heartbeat, ciclo de execução, consumo de fila e acesso ao cofre de credenciais, tudo via
|
|
18
|
+
Socket.IO (namespace `/robot`).
|
|
19
|
+
|
|
20
|
+
## Uso rápido
|
|
21
|
+
|
|
22
|
+
`client.on_automation_start` registra o que rodar quando a plataforma disparar o botão
|
|
23
|
+
"Iniciar" para esse robô. `client.listen()` mantém o processo vivo aguardando esse evento,
|
|
24
|
+
até a conexão encerrar ou você pressionar Ctrl+C.
|
|
25
|
+
|
|
26
|
+
Há duas formas de reportar trabalho, dependendo de onde a lista de tasks vem:
|
|
27
|
+
|
|
28
|
+
### Consumindo a fila da plataforma
|
|
29
|
+
|
|
30
|
+
Use quando as tasks vêm de uma planilha importada na plataforma - `client.queue.consume()`
|
|
31
|
+
entrega uma a uma até a fila esvaziar.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from zsynctech_studio_sdk import RobotClient, TaskStatus
|
|
35
|
+
|
|
36
|
+
client = RobotClient.from_api_key(
|
|
37
|
+
"<sua-api-key>",
|
|
38
|
+
backend_url="http://localhost:5000",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@client.on_automation_start
|
|
42
|
+
def handle_automation() -> None:
|
|
43
|
+
with client.execution.run(observation="Minha automação"):
|
|
44
|
+
for task in client.queue.consume():
|
|
45
|
+
with client.queue.processing(task) as outcome:
|
|
46
|
+
resultado = processar(task.payload)
|
|
47
|
+
outcome.success(result=resultado)
|
|
48
|
+
|
|
49
|
+
with client:
|
|
50
|
+
client.listen()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Sem fila: robô que processa a própria fonte de dados
|
|
54
|
+
|
|
55
|
+
Use quando o robô lê de uma fonte própria (planilha local, API interna, scraping, banco de
|
|
56
|
+
dados...) em vez de uma fila importada na plataforma - pule `client.queue` inteiramente e
|
|
57
|
+
reporte cada resultado direto com `client.execution.report_tasks(...)`.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from zsynctech_studio_sdk import RobotClient, TaskItem, TaskStatus
|
|
61
|
+
from zsynctech_studio_sdk.utils import utc_now
|
|
62
|
+
|
|
63
|
+
client = RobotClient.from_api_key(
|
|
64
|
+
"<sua-api-key>",
|
|
65
|
+
backend_url="http://localhost:5000",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@client.on_automation_start
|
|
69
|
+
def handle_automation() -> None:
|
|
70
|
+
itens = buscar_itens_a_processar() # a fonte de dados é sua
|
|
71
|
+
|
|
72
|
+
with client.execution.run(observation="Processamento de itens próprios"):
|
|
73
|
+
for item in itens:
|
|
74
|
+
started_at = utc_now()
|
|
75
|
+
resultado = processar(item)
|
|
76
|
+
|
|
77
|
+
client.execution.report_tasks([
|
|
78
|
+
TaskItem(
|
|
79
|
+
external_id=item.id,
|
|
80
|
+
status=TaskStatus.SUCCESS,
|
|
81
|
+
result=resultado,
|
|
82
|
+
started_at=started_at,
|
|
83
|
+
finished_at=utc_now(),
|
|
84
|
+
)
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
with client:
|
|
88
|
+
client.listen()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Exemplos completos em [`examples/`](examples/):
|
|
92
|
+
|
|
93
|
+
- [`simulate_robot.py`](examples/simulate_robot.py) - robô completo consumindo tasks de uma
|
|
94
|
+
fila da plataforma (`client.queue.consume()`); também demonstra `client.credentials.reveal()`
|
|
95
|
+
logo após conectar.
|
|
96
|
+
- [`own_tasks_robot.py`](examples/own_tasks_robot.py) - robô que processa a própria fonte de
|
|
97
|
+
dados (planilha, API interna, etc.) e reporta os resultados direto na execução
|
|
98
|
+
(`client.execution.report_tasks()`), sem usar fila.
|
|
99
|
+
|
|
100
|
+
### Observação da execução
|
|
101
|
+
|
|
102
|
+
`observation` descreve o que está acontecendo numa execução - visível na plataforma. Dá pra
|
|
103
|
+
atualizá-la quantas vezes quiser enquanto a execução está rodando, e sobrescrevê-la no
|
|
104
|
+
`finish()` com o resultado final. `execution.run()` (o atalho com `with`) sempre finaliza com a
|
|
105
|
+
`observation` original, então esse controle mais fino pede o par `start()`/`finish()` manual:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
client.execution.start(observation="Boletagem VCOM")
|
|
109
|
+
try:
|
|
110
|
+
tasks = list(client.queue.consume())
|
|
111
|
+
if not tasks:
|
|
112
|
+
client.execution.finish(observation="Fila vazia, nada a processar")
|
|
113
|
+
else:
|
|
114
|
+
for i, task in enumerate(tasks, start=1):
|
|
115
|
+
client.execution.update_observation(f"Processando {i}/{len(tasks)}")
|
|
116
|
+
...
|
|
117
|
+
client.execution.finish()
|
|
118
|
+
except Exception:
|
|
119
|
+
client.execution.finish(ExecutionFinishStatus.FAILED)
|
|
120
|
+
raise
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Credenciais
|
|
124
|
+
|
|
125
|
+
O cofre de credenciais é acessado via REST (`/robot/credentials`), sem precisar de uma conexão
|
|
126
|
+
`/robot` aberta - `client.credentials` funciona mesmo antes de `client.connect()`:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
credencial = client.credentials.reveal("<credential-id>")
|
|
130
|
+
print(credencial.value) # str (TEXT), dict[str, str] (KEY_VALUE) ou JSON (JSON)
|
|
131
|
+
|
|
132
|
+
client.credentials.rotate("<credential-id>", "novo-valor")
|
|
133
|
+
|
|
134
|
+
client.credentials.expire("<credential-id>", "Login rejeitado pelo site do fornecedor")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Veja `log_credential()` em [`examples/simulate_robot.py`](examples/simulate_robot.py) para um
|
|
138
|
+
exemplo em contexto (revela uma credencial e loga o valor assim que conecta).
|
|
139
|
+
|
|
140
|
+
### Buscando por `key` em vez de `id`
|
|
141
|
+
|
|
142
|
+
`id` é o identificador interno da credencial - regenerado se o banco da plataforma for
|
|
143
|
+
restaurado/migrado, quebrando qualquer robô que o tenha fixo no `.env`. `key` é um identificador
|
|
144
|
+
estável, definido pelo admin na plataforma (ex.: `"financeiro/senha-sap"`) e independente de
|
|
145
|
+
`id`/`name`/pasta - renomear a credencial ou movê-la de pasta nunca muda a `key`. Prefira `key`
|
|
146
|
+
para qualquer credencial referenciada de fora da plataforma:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
credencial = client.credentials.reveal_by_key("financeiro/senha-sap")
|
|
150
|
+
|
|
151
|
+
client.credentials.rotate_by_key("financeiro/senha-sap", "novo-valor")
|
|
152
|
+
|
|
153
|
+
client.credentials.expire_by_key("financeiro/senha-sap", "Login rejeitado pelo site do fornecedor")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Arquitetura
|
|
157
|
+
|
|
158
|
+
| Módulo | Responsabilidade |
|
|
159
|
+
| --- | --- |
|
|
160
|
+
| `client.py` | `RobotClient` - fachada única que compõe as peças abaixo |
|
|
161
|
+
| `connection.py` | `SocketConnection` - handshake, heartbeat automático, reconexão, `call`/`emit` |
|
|
162
|
+
| `execution.py` | `ExecutionManager` - ciclo `execution:start` / `execution:task` / `execution:finish` |
|
|
163
|
+
| `queue.py` | `QueueConsumer` - `queue:next` / `queue:task-result`, iteração da fila |
|
|
164
|
+
| `credentials.py` | `CredentialManager` - `reveal`/`rotate`/`expire`, por `id` ou `key`, via REST |
|
|
165
|
+
| `protocol.py` | Constantes do protocolo (namespace, nomes de eventos, rotas REST) |
|
|
166
|
+
| `exceptions.py` | Hierarquia de exceções do SDK (`RobotSDKError` e subclasses) |
|
|
167
|
+
| `utils.py` | Funções utilitárias reutilizáveis (detecção de plataforma, hostname, timestamps) |
|
|
168
|
+
| `loggers.py` | Configuração do `loguru` compartilhada pelo SDK |
|
|
169
|
+
| `models/` | Modelos de dados e enums (Pydantic), espelhando os DTOs/enums do backend |
|
|
170
|
+
|
|
171
|
+
Cada classe tem uma única responsabilidade (SRP): a conexão bruta não sabe o que é uma
|
|
172
|
+
execução, a execução não sabe como a fila funciona, e o `RobotClient` só orquestra as três.
|
|
173
|
+
Novos comportamentos (ex.: outro jeito de processar tasks) se registram via
|
|
174
|
+
`on_automation_start`/`queue.processing`, sem precisar alterar essas classes.
|
|
175
|
+
|
|
176
|
+
## Modelos e enums (`models/`)
|
|
177
|
+
|
|
178
|
+
Todos os payloads trocados com a plataforma são validados com **Pydantic** e convertidos
|
|
179
|
+
entre `snake_case` (Python) e `camelCase` (protocolo) automaticamente via `SdkBaseModel`.
|
|
180
|
+
Os enums (`TaskStatus`, `RobotInstanceStatus`, `Platform`, ...) espelham exatamente os
|
|
181
|
+
enums TypeScript usados por `RobotGateway` no backend.
|
|
182
|
+
|
|
183
|
+
## Logs
|
|
184
|
+
|
|
185
|
+
O SDK usa [`loguru`](https://loguru.readthedocs.io/) e já vem configurado com um formato
|
|
186
|
+
legível por padrão. Para customizar (nível, arquivo de destino, JSON estruturado):
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from zsynctech_studio_sdk.loggers import configure_logging
|
|
190
|
+
|
|
191
|
+
configure_logging(level="DEBUG", sink="robot.log")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Chame antes de criar o `RobotClient`.
|
|
195
|
+
|
|
196
|
+
## Desenvolvimento
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
uv run pytest # testes
|
|
200
|
+
uv run ruff check src examples tests # lint
|
|
201
|
+
uv run ruff format src examples tests # formatação
|
|
202
|
+
uv run mypy src examples tests # checagem de tipos (modo strict)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`ruff` e `mypy` só olham `src`/`examples`/`tests` (não o README) - a partir do ruff 0.12+ o
|
|
206
|
+
`ruff format` também reformata blocos de código Python dentro de Markdown, o que reescreveria
|
|
207
|
+
os exemplos deste arquivo com as convenções de um `.py` (2 linhas em branco antes de decorators
|
|
208
|
+
etc.) toda vez que rodasse. CI usa esse mesmo escopo (veja `.github/workflows/ci.yml`).
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# zsynctech-sdk
|
|
2
|
+
|
|
3
|
+
SDK Python oficial para conectar robôs de RPA à plataforma **zsynctech-studio**: handshake,
|
|
4
|
+
heartbeat, ciclo de execução, consumo de fila e acesso ao cofre de credenciais, tudo via
|
|
5
|
+
Socket.IO (namespace `/robot`).
|
|
6
|
+
|
|
7
|
+
## Uso rápido
|
|
8
|
+
|
|
9
|
+
`client.on_automation_start` registra o que rodar quando a plataforma disparar o botão
|
|
10
|
+
"Iniciar" para esse robô. `client.listen()` mantém o processo vivo aguardando esse evento,
|
|
11
|
+
até a conexão encerrar ou você pressionar Ctrl+C.
|
|
12
|
+
|
|
13
|
+
Há duas formas de reportar trabalho, dependendo de onde a lista de tasks vem:
|
|
14
|
+
|
|
15
|
+
### Consumindo a fila da plataforma
|
|
16
|
+
|
|
17
|
+
Use quando as tasks vêm de uma planilha importada na plataforma - `client.queue.consume()`
|
|
18
|
+
entrega uma a uma até a fila esvaziar.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from zsynctech_studio_sdk import RobotClient, TaskStatus
|
|
22
|
+
|
|
23
|
+
client = RobotClient.from_api_key(
|
|
24
|
+
"<sua-api-key>",
|
|
25
|
+
backend_url="http://localhost:5000",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
@client.on_automation_start
|
|
29
|
+
def handle_automation() -> None:
|
|
30
|
+
with client.execution.run(observation="Minha automação"):
|
|
31
|
+
for task in client.queue.consume():
|
|
32
|
+
with client.queue.processing(task) as outcome:
|
|
33
|
+
resultado = processar(task.payload)
|
|
34
|
+
outcome.success(result=resultado)
|
|
35
|
+
|
|
36
|
+
with client:
|
|
37
|
+
client.listen()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Sem fila: robô que processa a própria fonte de dados
|
|
41
|
+
|
|
42
|
+
Use quando o robô lê de uma fonte própria (planilha local, API interna, scraping, banco de
|
|
43
|
+
dados...) em vez de uma fila importada na plataforma - pule `client.queue` inteiramente e
|
|
44
|
+
reporte cada resultado direto com `client.execution.report_tasks(...)`.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from zsynctech_studio_sdk import RobotClient, TaskItem, TaskStatus
|
|
48
|
+
from zsynctech_studio_sdk.utils import utc_now
|
|
49
|
+
|
|
50
|
+
client = RobotClient.from_api_key(
|
|
51
|
+
"<sua-api-key>",
|
|
52
|
+
backend_url="http://localhost:5000",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@client.on_automation_start
|
|
56
|
+
def handle_automation() -> None:
|
|
57
|
+
itens = buscar_itens_a_processar() # a fonte de dados é sua
|
|
58
|
+
|
|
59
|
+
with client.execution.run(observation="Processamento de itens próprios"):
|
|
60
|
+
for item in itens:
|
|
61
|
+
started_at = utc_now()
|
|
62
|
+
resultado = processar(item)
|
|
63
|
+
|
|
64
|
+
client.execution.report_tasks([
|
|
65
|
+
TaskItem(
|
|
66
|
+
external_id=item.id,
|
|
67
|
+
status=TaskStatus.SUCCESS,
|
|
68
|
+
result=resultado,
|
|
69
|
+
started_at=started_at,
|
|
70
|
+
finished_at=utc_now(),
|
|
71
|
+
)
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
with client:
|
|
75
|
+
client.listen()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Exemplos completos em [`examples/`](examples/):
|
|
79
|
+
|
|
80
|
+
- [`simulate_robot.py`](examples/simulate_robot.py) - robô completo consumindo tasks de uma
|
|
81
|
+
fila da plataforma (`client.queue.consume()`); também demonstra `client.credentials.reveal()`
|
|
82
|
+
logo após conectar.
|
|
83
|
+
- [`own_tasks_robot.py`](examples/own_tasks_robot.py) - robô que processa a própria fonte de
|
|
84
|
+
dados (planilha, API interna, etc.) e reporta os resultados direto na execução
|
|
85
|
+
(`client.execution.report_tasks()`), sem usar fila.
|
|
86
|
+
|
|
87
|
+
### Observação da execução
|
|
88
|
+
|
|
89
|
+
`observation` descreve o que está acontecendo numa execução - visível na plataforma. Dá pra
|
|
90
|
+
atualizá-la quantas vezes quiser enquanto a execução está rodando, e sobrescrevê-la no
|
|
91
|
+
`finish()` com o resultado final. `execution.run()` (o atalho com `with`) sempre finaliza com a
|
|
92
|
+
`observation` original, então esse controle mais fino pede o par `start()`/`finish()` manual:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
client.execution.start(observation="Boletagem VCOM")
|
|
96
|
+
try:
|
|
97
|
+
tasks = list(client.queue.consume())
|
|
98
|
+
if not tasks:
|
|
99
|
+
client.execution.finish(observation="Fila vazia, nada a processar")
|
|
100
|
+
else:
|
|
101
|
+
for i, task in enumerate(tasks, start=1):
|
|
102
|
+
client.execution.update_observation(f"Processando {i}/{len(tasks)}")
|
|
103
|
+
...
|
|
104
|
+
client.execution.finish()
|
|
105
|
+
except Exception:
|
|
106
|
+
client.execution.finish(ExecutionFinishStatus.FAILED)
|
|
107
|
+
raise
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Credenciais
|
|
111
|
+
|
|
112
|
+
O cofre de credenciais é acessado via REST (`/robot/credentials`), sem precisar de uma conexão
|
|
113
|
+
`/robot` aberta - `client.credentials` funciona mesmo antes de `client.connect()`:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
credencial = client.credentials.reveal("<credential-id>")
|
|
117
|
+
print(credencial.value) # str (TEXT), dict[str, str] (KEY_VALUE) ou JSON (JSON)
|
|
118
|
+
|
|
119
|
+
client.credentials.rotate("<credential-id>", "novo-valor")
|
|
120
|
+
|
|
121
|
+
client.credentials.expire("<credential-id>", "Login rejeitado pelo site do fornecedor")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Veja `log_credential()` em [`examples/simulate_robot.py`](examples/simulate_robot.py) para um
|
|
125
|
+
exemplo em contexto (revela uma credencial e loga o valor assim que conecta).
|
|
126
|
+
|
|
127
|
+
### Buscando por `key` em vez de `id`
|
|
128
|
+
|
|
129
|
+
`id` é o identificador interno da credencial - regenerado se o banco da plataforma for
|
|
130
|
+
restaurado/migrado, quebrando qualquer robô que o tenha fixo no `.env`. `key` é um identificador
|
|
131
|
+
estável, definido pelo admin na plataforma (ex.: `"financeiro/senha-sap"`) e independente de
|
|
132
|
+
`id`/`name`/pasta - renomear a credencial ou movê-la de pasta nunca muda a `key`. Prefira `key`
|
|
133
|
+
para qualquer credencial referenciada de fora da plataforma:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
credencial = client.credentials.reveal_by_key("financeiro/senha-sap")
|
|
137
|
+
|
|
138
|
+
client.credentials.rotate_by_key("financeiro/senha-sap", "novo-valor")
|
|
139
|
+
|
|
140
|
+
client.credentials.expire_by_key("financeiro/senha-sap", "Login rejeitado pelo site do fornecedor")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Arquitetura
|
|
144
|
+
|
|
145
|
+
| Módulo | Responsabilidade |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `client.py` | `RobotClient` - fachada única que compõe as peças abaixo |
|
|
148
|
+
| `connection.py` | `SocketConnection` - handshake, heartbeat automático, reconexão, `call`/`emit` |
|
|
149
|
+
| `execution.py` | `ExecutionManager` - ciclo `execution:start` / `execution:task` / `execution:finish` |
|
|
150
|
+
| `queue.py` | `QueueConsumer` - `queue:next` / `queue:task-result`, iteração da fila |
|
|
151
|
+
| `credentials.py` | `CredentialManager` - `reveal`/`rotate`/`expire`, por `id` ou `key`, via REST |
|
|
152
|
+
| `protocol.py` | Constantes do protocolo (namespace, nomes de eventos, rotas REST) |
|
|
153
|
+
| `exceptions.py` | Hierarquia de exceções do SDK (`RobotSDKError` e subclasses) |
|
|
154
|
+
| `utils.py` | Funções utilitárias reutilizáveis (detecção de plataforma, hostname, timestamps) |
|
|
155
|
+
| `loggers.py` | Configuração do `loguru` compartilhada pelo SDK |
|
|
156
|
+
| `models/` | Modelos de dados e enums (Pydantic), espelhando os DTOs/enums do backend |
|
|
157
|
+
|
|
158
|
+
Cada classe tem uma única responsabilidade (SRP): a conexão bruta não sabe o que é uma
|
|
159
|
+
execução, a execução não sabe como a fila funciona, e o `RobotClient` só orquestra as três.
|
|
160
|
+
Novos comportamentos (ex.: outro jeito de processar tasks) se registram via
|
|
161
|
+
`on_automation_start`/`queue.processing`, sem precisar alterar essas classes.
|
|
162
|
+
|
|
163
|
+
## Modelos e enums (`models/`)
|
|
164
|
+
|
|
165
|
+
Todos os payloads trocados com a plataforma são validados com **Pydantic** e convertidos
|
|
166
|
+
entre `snake_case` (Python) e `camelCase` (protocolo) automaticamente via `SdkBaseModel`.
|
|
167
|
+
Os enums (`TaskStatus`, `RobotInstanceStatus`, `Platform`, ...) espelham exatamente os
|
|
168
|
+
enums TypeScript usados por `RobotGateway` no backend.
|
|
169
|
+
|
|
170
|
+
## Logs
|
|
171
|
+
|
|
172
|
+
O SDK usa [`loguru`](https://loguru.readthedocs.io/) e já vem configurado com um formato
|
|
173
|
+
legível por padrão. Para customizar (nível, arquivo de destino, JSON estruturado):
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from zsynctech_studio_sdk.loggers import configure_logging
|
|
177
|
+
|
|
178
|
+
configure_logging(level="DEBUG", sink="robot.log")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Chame antes de criar o `RobotClient`.
|
|
182
|
+
|
|
183
|
+
## Desenvolvimento
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
uv run pytest # testes
|
|
187
|
+
uv run ruff check src examples tests # lint
|
|
188
|
+
uv run ruff format src examples tests # formatação
|
|
189
|
+
uv run mypy src examples tests # checagem de tipos (modo strict)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`ruff` e `mypy` só olham `src`/`examples`/`tests` (não o README) - a partir do ruff 0.12+ o
|
|
193
|
+
`ruff format` também reformata blocos de código Python dentro de Markdown, o que reescreveria
|
|
194
|
+
os exemplos deste arquivo com as convenções de um `.py` (2 linhas em branco antes de decorators
|
|
195
|
+
etc.) toda vez que rodasse. CI usa esse mesmo escopo (veja `.github/workflows/ci.yml`).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zsynctech-sdk"
|
|
3
|
+
version = "1.4.9"
|
|
4
|
+
description = "SDK Python para conectar robôs (RPA) à plataforma zsynctech-studio via Socket.IO"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"loguru>=0.7",
|
|
9
|
+
"pydantic>=2.9",
|
|
10
|
+
"python-socketio[client]>=5.11",
|
|
11
|
+
"requests>=2.31",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[project.authors]]
|
|
15
|
+
name = "Rodrigo Zavan"
|
|
16
|
+
email = "rodrigo.zavan@zsynctech.com.br"
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
zsynctech-sdk = "zsynctech_studio_sdk:main"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["uv_build>=0.12.8,<0.13.0"]
|
|
23
|
+
build-backend = "uv_build"
|
|
24
|
+
|
|
25
|
+
[tool.uv.build-backend]
|
|
26
|
+
module-name = "zsynctech_studio_sdk"
|
|
27
|
+
|
|
28
|
+
[tool.ruff]
|
|
29
|
+
line-length = 120
|
|
30
|
+
target-version = "py313"
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
select = [
|
|
34
|
+
"E",
|
|
35
|
+
"F",
|
|
36
|
+
"I",
|
|
37
|
+
"UP",
|
|
38
|
+
"B",
|
|
39
|
+
"SIM",
|
|
40
|
+
"N",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.mypy]
|
|
44
|
+
python_version = "3.13"
|
|
45
|
+
strict = true
|
|
46
|
+
plugins = ["pydantic.mypy"]
|
|
47
|
+
|
|
48
|
+
[[tool.mypy.overrides]]
|
|
49
|
+
module = "socketio.*"
|
|
50
|
+
ignore_missing_imports = true
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|
|
54
|
+
|
|
55
|
+
[dependency-groups]
|
|
56
|
+
dev = [
|
|
57
|
+
"mypy>=1.13",
|
|
58
|
+
"pytest>=8.3",
|
|
59
|
+
"ruff>=0.7",
|
|
60
|
+
"types-requests>=2.31",
|
|
61
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zsynctech-sdk"
|
|
3
|
+
version = "1.4.9"
|
|
4
|
+
description = "SDK Python para conectar robôs (RPA) à plataforma zsynctech-studio via Socket.IO"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Rodrigo Zavan", email = "rodrigo.zavan@zsynctech.com.br" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"loguru>=0.7",
|
|
12
|
+
"pydantic>=2.9",
|
|
13
|
+
"python-socketio[client]>=5.11",
|
|
14
|
+
"requests>=2.31",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
zsynctech-sdk = "zsynctech_studio_sdk:main"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["uv_build>=0.12.8,<0.13.0"]
|
|
22
|
+
build-backend = "uv_build"
|
|
23
|
+
|
|
24
|
+
# The importable module stays zsynctech_studio_sdk (src/zsynctech_studio_sdk/) even though the
|
|
25
|
+
# distribution was renamed to zsynctech-sdk on PyPI (the old "zsynctech-studio-sdk" name is
|
|
26
|
+
# stuck on a PyPI account we lost access to) - uv_build otherwise infers the module name from
|
|
27
|
+
# the project name (hyphens to underscores), which would no longer match this package's actual
|
|
28
|
+
# directory. Keeping the import path the same means every robot that already does
|
|
29
|
+
# `from zsynctech_studio_sdk import ...` only needs its pyproject.toml dependency line updated,
|
|
30
|
+
# not a single import statement.
|
|
31
|
+
[tool.uv.build-backend]
|
|
32
|
+
module-name = "zsynctech_studio_sdk"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"mypy>=1.13",
|
|
37
|
+
"pytest>=8.3",
|
|
38
|
+
"ruff>=0.7",
|
|
39
|
+
"types-requests>=2.31",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 120
|
|
44
|
+
target-version = "py313"
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "N"]
|
|
48
|
+
|
|
49
|
+
[tool.mypy]
|
|
50
|
+
python_version = "3.13"
|
|
51
|
+
strict = true
|
|
52
|
+
plugins = ["pydantic.mypy"]
|
|
53
|
+
|
|
54
|
+
[[tool.mypy.overrides]]
|
|
55
|
+
module = "socketio.*"
|
|
56
|
+
ignore_missing_imports = true
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""SDK Python para conectar robôs (RPA) à plataforma zsynctech-studio via `/robot` (Socket.IO).
|
|
2
|
+
|
|
3
|
+
Uso rápido:
|
|
4
|
+
|
|
5
|
+
from zsynctech_studio_sdk import RobotClient, TaskStatus
|
|
6
|
+
|
|
7
|
+
client = RobotClient.from_api_key("<sua-api-key>", backend_url="http://localhost:5000")
|
|
8
|
+
|
|
9
|
+
@client.on_automation_start
|
|
10
|
+
def handle_automation() -> None:
|
|
11
|
+
with client.execution.run(observation="Minha automação"):
|
|
12
|
+
for task in client.queue.consume():
|
|
13
|
+
with client.queue.processing(task) as outcome:
|
|
14
|
+
outcome.success(result={"ok": True})
|
|
15
|
+
|
|
16
|
+
with client:
|
|
17
|
+
client.listen()
|
|
18
|
+
|
|
19
|
+
O cofre de credenciais é acessado à parte, via REST (não precisa de conexão aberta):
|
|
20
|
+
|
|
21
|
+
credencial = client.credentials.reveal("<credential-id>")
|
|
22
|
+
print(credencial.value)
|
|
23
|
+
|
|
24
|
+
Veja `examples/simulate_robot.py` para um exemplo completo.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from .client import RobotClient
|
|
28
|
+
from .credentials import CredentialManager
|
|
29
|
+
from .exceptions import (
|
|
30
|
+
AckTimeoutError,
|
|
31
|
+
CredentialRequestError,
|
|
32
|
+
NoActiveExecutionError,
|
|
33
|
+
NotConnectedError,
|
|
34
|
+
RobotConnectionError,
|
|
35
|
+
RobotSDKError,
|
|
36
|
+
ServerRejectedError,
|
|
37
|
+
)
|
|
38
|
+
from .execution import ExecutionManager
|
|
39
|
+
from .models import (
|
|
40
|
+
ClaimedTask,
|
|
41
|
+
CredentialInfo,
|
|
42
|
+
CredentialStatus,
|
|
43
|
+
CredentialType,
|
|
44
|
+
ExecutionFinishResult,
|
|
45
|
+
ExecutionFinishStatus,
|
|
46
|
+
ExecutionStartResult,
|
|
47
|
+
Platform,
|
|
48
|
+
RevealedCredential,
|
|
49
|
+
RobotAvailability,
|
|
50
|
+
RobotClientConfig,
|
|
51
|
+
RobotConnection,
|
|
52
|
+
RobotInfo,
|
|
53
|
+
RobotInstanceStatus,
|
|
54
|
+
RobotStatus,
|
|
55
|
+
TaskItem,
|
|
56
|
+
TaskStatus,
|
|
57
|
+
)
|
|
58
|
+
from .queue import QueueConsumer, TaskOutcome
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
"RobotClient",
|
|
62
|
+
"RobotClientConfig",
|
|
63
|
+
"ExecutionManager",
|
|
64
|
+
"QueueConsumer",
|
|
65
|
+
"TaskOutcome",
|
|
66
|
+
"CredentialManager",
|
|
67
|
+
"RobotSDKError",
|
|
68
|
+
"RobotConnectionError",
|
|
69
|
+
"NotConnectedError",
|
|
70
|
+
"AckTimeoutError",
|
|
71
|
+
"ServerRejectedError",
|
|
72
|
+
"NoActiveExecutionError",
|
|
73
|
+
"CredentialRequestError",
|
|
74
|
+
"ClaimedTask",
|
|
75
|
+
"TaskItem",
|
|
76
|
+
"RobotInfo",
|
|
77
|
+
"RobotConnection",
|
|
78
|
+
"ExecutionStartResult",
|
|
79
|
+
"ExecutionFinishResult",
|
|
80
|
+
"CredentialInfo",
|
|
81
|
+
"RevealedCredential",
|
|
82
|
+
"Platform",
|
|
83
|
+
"RobotStatus",
|
|
84
|
+
"RobotAvailability",
|
|
85
|
+
"RobotInstanceStatus",
|
|
86
|
+
"TaskStatus",
|
|
87
|
+
"ExecutionFinishStatus",
|
|
88
|
+
"CredentialStatus",
|
|
89
|
+
"CredentialType",
|
|
90
|
+
"main",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main() -> None:
|
|
95
|
+
print("zsynctech-studio-sdk - use RobotClient para conectar um robô à plataforma zsynctech-studio.")
|