onerom-core 0.1.0__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.
- onerom_core-0.1.0/.gitignore +2 -0
- onerom_core-0.1.0/PKG-INFO +156 -0
- onerom_core-0.1.0/README.md +133 -0
- onerom_core-0.1.0/onerom_core/__init__.py +15 -0
- onerom_core-0.1.0/onerom_core/client.py +79 -0
- onerom_core-0.1.0/onerom_core/execution.py +232 -0
- onerom_core-0.1.0/onerom_core/logging_handler.py +54 -0
- onerom_core-0.1.0/onerom_core/params.py +208 -0
- onerom_core-0.1.0/pyproject.toml +36 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: onerom-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SDK oficial para automacoes ONEROM
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: automation,onerom,rpa,sdk
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Requires-Dist: pydantic>=2.0
|
|
18
|
+
Requires-Dist: requests>=2.28
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# onerom-core
|
|
25
|
+
|
|
26
|
+
SDK oficial Python para automações ONEROM.
|
|
27
|
+
|
|
28
|
+
Usado dentro de bots executados pelo ONEROM Runner.
|
|
29
|
+
|
|
30
|
+
## Instalação
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install onerom-core
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Ou via `pyproject.toml` do bot:
|
|
37
|
+
|
|
38
|
+
```toml
|
|
39
|
+
[project]
|
|
40
|
+
dependencies = ["onerom-core>=0.1.0"]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Uso básico
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from onerom_core import OneromExecution
|
|
47
|
+
import logging
|
|
48
|
+
|
|
49
|
+
# Inicializa contexto da execução
|
|
50
|
+
# - Carrega parâmetros da execução automaticamente
|
|
51
|
+
# - Anexa handler de log estruturado ao logger raiz
|
|
52
|
+
exec = OneromExecution()
|
|
53
|
+
|
|
54
|
+
# Acesso a parâmetros tipados
|
|
55
|
+
nome = exec.params.nome
|
|
56
|
+
count = exec.params.count
|
|
57
|
+
|
|
58
|
+
# Logging — capturado pelo runner e enviado ao backend
|
|
59
|
+
logging.info(f"Processando {nome}")
|
|
60
|
+
|
|
61
|
+
# Contagem de itens
|
|
62
|
+
for item in get_items():
|
|
63
|
+
try:
|
|
64
|
+
process(item)
|
|
65
|
+
exec.add_success_item()
|
|
66
|
+
except Exception:
|
|
67
|
+
exec.add_failed_item()
|
|
68
|
+
|
|
69
|
+
# Finaliza com sucesso
|
|
70
|
+
exec.finish_success()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Uso com wrapper automático
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from onerom_core import OneromExecution
|
|
77
|
+
|
|
78
|
+
exec = OneromExecution()
|
|
79
|
+
|
|
80
|
+
def main():
|
|
81
|
+
logging.info("Bot iniciado")
|
|
82
|
+
# ... lógica do bot ...
|
|
83
|
+
|
|
84
|
+
# Gerencia mark_running → main() → finish_success/failed automaticamente
|
|
85
|
+
exec.run(main)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Ciclo de vida
|
|
89
|
+
|
|
90
|
+
| Método | Descrição |
|
|
91
|
+
|--------|-----------|
|
|
92
|
+
| `mark_running()` | Notifica backend que a execução começou |
|
|
93
|
+
| `add_success_item()` | Incrementa contador de itens processados |
|
|
94
|
+
| `add_failed_item()` | Incrementa contador de itens que falharam |
|
|
95
|
+
| `report_progress(processed, total)` | Reporta progresso parcial |
|
|
96
|
+
| `finish_success()` | Finaliza como bem-sucedida |
|
|
97
|
+
| `finish_failed(error_message)` | Finaliza como falha |
|
|
98
|
+
| `run(fn)` | Wrapper automático completo |
|
|
99
|
+
|
|
100
|
+
## Parâmetros
|
|
101
|
+
|
|
102
|
+
Os parâmetros são injetados pelo runner via variável de ambiente `ONEROM_PARAMETERS`.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
exec = OneromExecution()
|
|
106
|
+
|
|
107
|
+
# Acesso por atributo
|
|
108
|
+
valor = exec.params.meu_parametro
|
|
109
|
+
|
|
110
|
+
# Acesso como dict (sem schema)
|
|
111
|
+
dados = exec.params.as_dict()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Schema tipado (opcional)
|
|
115
|
+
|
|
116
|
+
Se `ONEROM_PARAM_SCHEMA` estiver definido, os parâmetros são validados com Pydantic:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# Backend retorna schema:
|
|
120
|
+
# [{"name": "cnpj", "type": "string", "required": true}]
|
|
121
|
+
# Validação automática e acesso tipado:
|
|
122
|
+
cnpj: str = exec.params.cnpj
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Tipos suportados: `string`, `int`, `double`, `bool`, `json`, `list`
|
|
126
|
+
|
|
127
|
+
## Logging
|
|
128
|
+
|
|
129
|
+
O `OneromLogHandler` é anexado automaticamente ao logger raiz. Cada chamada de log
|
|
130
|
+
gera uma linha JSON no stdout, que o runner captura e envia ao backend.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
import logging
|
|
134
|
+
|
|
135
|
+
logging.info("Processando item") # → backend via runner
|
|
136
|
+
logging.warning("Item ignorado")
|
|
137
|
+
logging.error("Falha ao processar")
|
|
138
|
+
|
|
139
|
+
# Logger customizado
|
|
140
|
+
exec.attach_logger(logging.getLogger("meu_modulo"))
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Variáveis de ambiente
|
|
144
|
+
|
|
145
|
+
Injetadas automaticamente pelo runner:
|
|
146
|
+
|
|
147
|
+
| Variável | Descrição |
|
|
148
|
+
|----------|-----------|
|
|
149
|
+
| `ONEROM_EXECUTION_ID` | ID da execução atual |
|
|
150
|
+
| `ONEROM_BOT_NAME` | Nome do bot |
|
|
151
|
+
| `ONEROM_RUNNER_ID` | ID do runner |
|
|
152
|
+
| `ONEROM_PARAMETERS` | JSON com parâmetros da execução |
|
|
153
|
+
| `ONEROM_CONTEXT` | JSON com contexto completo |
|
|
154
|
+
| `ONEROM_METRICS_FILE` | Caminho do arquivo de métricas |
|
|
155
|
+
| `ONEROM_API_URL` | URL base da API runner-agent |
|
|
156
|
+
| `ONEROM_RUNNER_KEY` | Chave de autenticação do runner |
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# onerom-core
|
|
2
|
+
|
|
3
|
+
SDK oficial Python para automações ONEROM.
|
|
4
|
+
|
|
5
|
+
Usado dentro de bots executados pelo ONEROM Runner.
|
|
6
|
+
|
|
7
|
+
## Instalação
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install onerom-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Ou via `pyproject.toml` do bot:
|
|
14
|
+
|
|
15
|
+
```toml
|
|
16
|
+
[project]
|
|
17
|
+
dependencies = ["onerom-core>=0.1.0"]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Uso básico
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from onerom_core import OneromExecution
|
|
24
|
+
import logging
|
|
25
|
+
|
|
26
|
+
# Inicializa contexto da execução
|
|
27
|
+
# - Carrega parâmetros da execução automaticamente
|
|
28
|
+
# - Anexa handler de log estruturado ao logger raiz
|
|
29
|
+
exec = OneromExecution()
|
|
30
|
+
|
|
31
|
+
# Acesso a parâmetros tipados
|
|
32
|
+
nome = exec.params.nome
|
|
33
|
+
count = exec.params.count
|
|
34
|
+
|
|
35
|
+
# Logging — capturado pelo runner e enviado ao backend
|
|
36
|
+
logging.info(f"Processando {nome}")
|
|
37
|
+
|
|
38
|
+
# Contagem de itens
|
|
39
|
+
for item in get_items():
|
|
40
|
+
try:
|
|
41
|
+
process(item)
|
|
42
|
+
exec.add_success_item()
|
|
43
|
+
except Exception:
|
|
44
|
+
exec.add_failed_item()
|
|
45
|
+
|
|
46
|
+
# Finaliza com sucesso
|
|
47
|
+
exec.finish_success()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Uso com wrapper automático
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from onerom_core import OneromExecution
|
|
54
|
+
|
|
55
|
+
exec = OneromExecution()
|
|
56
|
+
|
|
57
|
+
def main():
|
|
58
|
+
logging.info("Bot iniciado")
|
|
59
|
+
# ... lógica do bot ...
|
|
60
|
+
|
|
61
|
+
# Gerencia mark_running → main() → finish_success/failed automaticamente
|
|
62
|
+
exec.run(main)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Ciclo de vida
|
|
66
|
+
|
|
67
|
+
| Método | Descrição |
|
|
68
|
+
|--------|-----------|
|
|
69
|
+
| `mark_running()` | Notifica backend que a execução começou |
|
|
70
|
+
| `add_success_item()` | Incrementa contador de itens processados |
|
|
71
|
+
| `add_failed_item()` | Incrementa contador de itens que falharam |
|
|
72
|
+
| `report_progress(processed, total)` | Reporta progresso parcial |
|
|
73
|
+
| `finish_success()` | Finaliza como bem-sucedida |
|
|
74
|
+
| `finish_failed(error_message)` | Finaliza como falha |
|
|
75
|
+
| `run(fn)` | Wrapper automático completo |
|
|
76
|
+
|
|
77
|
+
## Parâmetros
|
|
78
|
+
|
|
79
|
+
Os parâmetros são injetados pelo runner via variável de ambiente `ONEROM_PARAMETERS`.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
exec = OneromExecution()
|
|
83
|
+
|
|
84
|
+
# Acesso por atributo
|
|
85
|
+
valor = exec.params.meu_parametro
|
|
86
|
+
|
|
87
|
+
# Acesso como dict (sem schema)
|
|
88
|
+
dados = exec.params.as_dict()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Schema tipado (opcional)
|
|
92
|
+
|
|
93
|
+
Se `ONEROM_PARAM_SCHEMA` estiver definido, os parâmetros são validados com Pydantic:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# Backend retorna schema:
|
|
97
|
+
# [{"name": "cnpj", "type": "string", "required": true}]
|
|
98
|
+
# Validação automática e acesso tipado:
|
|
99
|
+
cnpj: str = exec.params.cnpj
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Tipos suportados: `string`, `int`, `double`, `bool`, `json`, `list`
|
|
103
|
+
|
|
104
|
+
## Logging
|
|
105
|
+
|
|
106
|
+
O `OneromLogHandler` é anexado automaticamente ao logger raiz. Cada chamada de log
|
|
107
|
+
gera uma linha JSON no stdout, que o runner captura e envia ao backend.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import logging
|
|
111
|
+
|
|
112
|
+
logging.info("Processando item") # → backend via runner
|
|
113
|
+
logging.warning("Item ignorado")
|
|
114
|
+
logging.error("Falha ao processar")
|
|
115
|
+
|
|
116
|
+
# Logger customizado
|
|
117
|
+
exec.attach_logger(logging.getLogger("meu_modulo"))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Variáveis de ambiente
|
|
121
|
+
|
|
122
|
+
Injetadas automaticamente pelo runner:
|
|
123
|
+
|
|
124
|
+
| Variável | Descrição |
|
|
125
|
+
|----------|-----------|
|
|
126
|
+
| `ONEROM_EXECUTION_ID` | ID da execução atual |
|
|
127
|
+
| `ONEROM_BOT_NAME` | Nome do bot |
|
|
128
|
+
| `ONEROM_RUNNER_ID` | ID do runner |
|
|
129
|
+
| `ONEROM_PARAMETERS` | JSON com parâmetros da execução |
|
|
130
|
+
| `ONEROM_CONTEXT` | JSON com contexto completo |
|
|
131
|
+
| `ONEROM_METRICS_FILE` | Caminho do arquivo de métricas |
|
|
132
|
+
| `ONEROM_API_URL` | URL base da API runner-agent |
|
|
133
|
+
| `ONEROM_RUNNER_KEY` | Chave de autenticação do runner |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
onerom-core — SDK oficial para bots ONEROM.
|
|
3
|
+
|
|
4
|
+
Uso:
|
|
5
|
+
from onerom_core import OneromExecution
|
|
6
|
+
|
|
7
|
+
exec = OneromExecution()
|
|
8
|
+
print(exec.params.meu_parametro)
|
|
9
|
+
exec.run(lambda: minha_funcao())
|
|
10
|
+
"""
|
|
11
|
+
from .execution import OneromExecution
|
|
12
|
+
from .logging_handler import OneromLogHandler
|
|
13
|
+
|
|
14
|
+
__all__ = ["OneromExecution", "OneromLogHandler"]
|
|
15
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cliente HTTP interno do SDK ONEROM.
|
|
3
|
+
|
|
4
|
+
Encapsula todas as chamadas ao runner-agent API.
|
|
5
|
+
Autenticacao via X-Runner-Key (injetado pelo runner como ONEROM_RUNNER_KEY).
|
|
6
|
+
|
|
7
|
+
Se ONEROM_API_URL ou ONEROM_RUNNER_KEY nao estiverem disponiveis,
|
|
8
|
+
o cliente opera em modo passivo (sem chamadas HTTP) — o runner
|
|
9
|
+
ja gerencia o ciclo de vida da execucao via metrics file e stdout.
|
|
10
|
+
"""
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OneromClient:
|
|
19
|
+
"""
|
|
20
|
+
Cliente HTTP para a API runner-agent do ONEROM.
|
|
21
|
+
|
|
22
|
+
Todas as chamadas falham silenciosamente — nunca propagam excecoes.
|
|
23
|
+
O runner e responsavel pelo status final via metrics file.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self.api_url = os.environ.get("ONEROM_API_URL", "").rstrip("/")
|
|
28
|
+
self.runner_key = os.environ.get("ONEROM_RUNNER_KEY", "")
|
|
29
|
+
self.available = bool(self.api_url and self.runner_key)
|
|
30
|
+
|
|
31
|
+
raw_id = os.environ.get("ONEROM_EXECUTION_ID")
|
|
32
|
+
if raw_id is None:
|
|
33
|
+
raise RuntimeError(
|
|
34
|
+
"ONEROM_EXECUTION_ID nao definido. "
|
|
35
|
+
"Este SDK deve ser executado dentro do ONEROM Runner."
|
|
36
|
+
)
|
|
37
|
+
self.execution_id = int(raw_id)
|
|
38
|
+
|
|
39
|
+
if not self.available:
|
|
40
|
+
logger.debug(
|
|
41
|
+
"ONEROM_API_URL ou ONEROM_RUNNER_KEY ausentes — "
|
|
42
|
+
"cliente HTTP desabilitado (modo passivo)"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def put_execution(self, status: str, **kwargs: Any) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Atualiza status da execucao no backend.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
status: RUNNING | SUCCESS | FAILED | TIMEOUT
|
|
51
|
+
**kwargs: Campos opcionais (items_processed, items_failed,
|
|
52
|
+
error_message, error_traceback, log_output, started_at)
|
|
53
|
+
"""
|
|
54
|
+
if not self.available:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
import requests
|
|
59
|
+
|
|
60
|
+
payload: dict[str, Any] = {"status": status}
|
|
61
|
+
for key, value in kwargs.items():
|
|
62
|
+
if value is not None:
|
|
63
|
+
payload[key] = value
|
|
64
|
+
|
|
65
|
+
resp = requests.put(
|
|
66
|
+
f"{self.api_url}/api/v1/runner-agent/executions/{self.execution_id}",
|
|
67
|
+
json=payload,
|
|
68
|
+
headers={"X-Runner-Key": self.runner_key},
|
|
69
|
+
timeout=10,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if resp.status_code not in (200, 204):
|
|
73
|
+
logger.debug(
|
|
74
|
+
f"put_execution retornou {resp.status_code}: {resp.text[:200]}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
# Nunca propaga — o runner cobre o status via metrics file
|
|
79
|
+
logger.debug(f"put_execution falhou silenciosamente: {exc}")
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OneromExecution — ponto de entrada principal do SDK ONEROM.
|
|
3
|
+
|
|
4
|
+
Uso basico:
|
|
5
|
+
from onerom_core import OneromExecution
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
exec = OneromExecution()
|
|
9
|
+
logging.info("Iniciando bot") # capturado pelo runner via stdout JSON
|
|
10
|
+
|
|
11
|
+
nome = exec.params.nome # parametro tipado da execucao
|
|
12
|
+
|
|
13
|
+
exec.add_success_item()
|
|
14
|
+
exec.finish_success()
|
|
15
|
+
|
|
16
|
+
Uso com wrapper automatico:
|
|
17
|
+
exec.run(lambda: my_main_function())
|
|
18
|
+
|
|
19
|
+
Uso com logger customizado:
|
|
20
|
+
exec.attach_logger(logging.getLogger("meu_modulo"))
|
|
21
|
+
"""
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
import traceback as tb
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Callable, Optional
|
|
28
|
+
|
|
29
|
+
from .client import OneromClient
|
|
30
|
+
from .logging_handler import OneromLogHandler
|
|
31
|
+
from .params import build_params
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OneromExecution:
|
|
37
|
+
"""
|
|
38
|
+
Contexto de execucao ONEROM.
|
|
39
|
+
|
|
40
|
+
Gerencia:
|
|
41
|
+
- Parametros tipados e validados
|
|
42
|
+
- Logging estruturado via stdout (capturado pelo runner)
|
|
43
|
+
- Metricas de itens processados via arquivo (lido pelo runner)
|
|
44
|
+
- Ciclo de vida da execucao via HTTP (quando disponivel)
|
|
45
|
+
|
|
46
|
+
Design:
|
|
47
|
+
- Nunca lanca excecao em metodos de ciclo de vida (mark_running, finish_*)
|
|
48
|
+
- Erros de parametro sao imediatos e claros
|
|
49
|
+
- Compativel com Python 3.9+
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Inicializa o contexto de execucao.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
RuntimeError: Se ONEROM_EXECUTION_ID nao estiver definido.
|
|
58
|
+
ValueError: Se parametros obrigatorios estiverem ausentes ou invalidos.
|
|
59
|
+
"""
|
|
60
|
+
# Valida ambiente ONEROM antes de qualquer coisa
|
|
61
|
+
if "ONEROM_EXECUTION_ID" not in os.environ:
|
|
62
|
+
raise RuntimeError(
|
|
63
|
+
"ONEROM_EXECUTION_ID nao definido.\n"
|
|
64
|
+
"Este SDK deve ser executado dentro do ONEROM Runner.\n"
|
|
65
|
+
"Variaveis necessarias: ONEROM_EXECUTION_ID, ONEROM_BOT_NAME, "
|
|
66
|
+
"ONEROM_RUNNER_ID, ONEROM_PARAMETERS"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Cliente HTTP (modo passivo se API_URL/RUNNER_KEY ausentes)
|
|
70
|
+
self._client = OneromClient()
|
|
71
|
+
|
|
72
|
+
# Arquivo de metricas (runner le apos fim da execucao)
|
|
73
|
+
metrics_path = os.environ.get("ONEROM_METRICS_FILE", "")
|
|
74
|
+
self._metrics_file: Optional[Path] = Path(metrics_path) if metrics_path else None
|
|
75
|
+
|
|
76
|
+
# Contadores locais de itens
|
|
77
|
+
self._items_processed: int = 0
|
|
78
|
+
self._items_failed: int = 0
|
|
79
|
+
|
|
80
|
+
# Parametros da execucao (validados em tempo de instanciacao)
|
|
81
|
+
self._params: Any = build_params()
|
|
82
|
+
|
|
83
|
+
# Anexa handler de log estruturado ao logger raiz automaticamente
|
|
84
|
+
root_logger = logging.getLogger()
|
|
85
|
+
# Evita duplicar o handler se OneromExecution for instanciado mais de uma vez
|
|
86
|
+
if not any(isinstance(h, OneromLogHandler) for h in root_logger.handlers):
|
|
87
|
+
root_logger.addHandler(OneromLogHandler())
|
|
88
|
+
|
|
89
|
+
logger.debug(
|
|
90
|
+
f"OneromExecution inicializado "
|
|
91
|
+
f"(execution_id={self._client.execution_id}, "
|
|
92
|
+
f"http_available={self._client.available})"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# ── Parametros ──────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def params(self) -> Any:
|
|
99
|
+
"""
|
|
100
|
+
Parametros da execucao como objeto com acesso por atributo.
|
|
101
|
+
|
|
102
|
+
Se ONEROM_PARAM_SCHEMA estiver definido: Pydantic model validado.
|
|
103
|
+
Caso contrario: SimpleNamespace com valores brutos.
|
|
104
|
+
|
|
105
|
+
Exemplo:
|
|
106
|
+
nome = exec.params.nome
|
|
107
|
+
count = exec.params.count
|
|
108
|
+
"""
|
|
109
|
+
return self._params
|
|
110
|
+
|
|
111
|
+
# ── Logging ─────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
def attach_logger(self, log: logging.Logger) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Anexa o handler ONEROM a um logger especifico.
|
|
116
|
+
|
|
117
|
+
O logger raiz ja recebe o handler automaticamente no __init__.
|
|
118
|
+
Use este metodo para loggers de modulos especificos.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
log: Logger ao qual o handler sera adicionado.
|
|
122
|
+
"""
|
|
123
|
+
if not any(isinstance(h, OneromLogHandler) for h in log.handlers):
|
|
124
|
+
log.addHandler(OneromLogHandler())
|
|
125
|
+
|
|
126
|
+
# ── Ciclo de vida ────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
def mark_running(self) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Notifica o backend que a execucao iniciou.
|
|
131
|
+
O runner ja reporta RUNNING antes de spawnar o subprocess —
|
|
132
|
+
use este metodo para reconfirmar ou apos delay no startup.
|
|
133
|
+
"""
|
|
134
|
+
self._client.put_execution("RUNNING")
|
|
135
|
+
|
|
136
|
+
def report_progress(self, processed: int, total: int) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Reporta progresso parcial da execucao.
|
|
139
|
+
|
|
140
|
+
Atualiza o arquivo de metricas (lido pelo runner ao fim).
|
|
141
|
+
Nao faz chamada HTTP — o runner le as metricas ao encerrar.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
processed: Numero de itens processados ate agora.
|
|
145
|
+
total: Total de itens esperados (para calculo de %).
|
|
146
|
+
"""
|
|
147
|
+
self._items_processed = processed
|
|
148
|
+
self._write_metrics()
|
|
149
|
+
|
|
150
|
+
def add_success_item(self) -> None:
|
|
151
|
+
"""Incrementa contador de itens processados com sucesso."""
|
|
152
|
+
self._items_processed += 1
|
|
153
|
+
|
|
154
|
+
def add_failed_item(self) -> None:
|
|
155
|
+
"""Incrementa contador de itens que falharam."""
|
|
156
|
+
self._items_failed += 1
|
|
157
|
+
|
|
158
|
+
def finish_success(self) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Finaliza a execucao como bem-sucedida.
|
|
161
|
+
|
|
162
|
+
1. Persiste metricas no arquivo (runner le ao encerrar)
|
|
163
|
+
2. Envia status SUCCESS via HTTP (se disponivel)
|
|
164
|
+
"""
|
|
165
|
+
self._write_metrics()
|
|
166
|
+
self._client.put_execution(
|
|
167
|
+
"SUCCESS",
|
|
168
|
+
items_processed=self._items_processed,
|
|
169
|
+
items_failed=self._items_failed,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def finish_failed(self, error_message: str) -> None:
|
|
173
|
+
"""
|
|
174
|
+
Finaliza a execucao como falha.
|
|
175
|
+
|
|
176
|
+
1. Persiste metricas no arquivo
|
|
177
|
+
2. Envia status FAILED com mensagem e traceback via HTTP (se disponivel)
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
error_message: Descricao do erro que causou a falha.
|
|
181
|
+
"""
|
|
182
|
+
self._write_metrics()
|
|
183
|
+
self._client.put_execution(
|
|
184
|
+
"FAILED",
|
|
185
|
+
error_message=error_message,
|
|
186
|
+
error_traceback=tb.format_exc(),
|
|
187
|
+
items_processed=self._items_processed,
|
|
188
|
+
items_failed=self._items_failed,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# ── Wrapper automatico ───────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
def run(self, fn: Callable[[], None]) -> None:
|
|
194
|
+
"""
|
|
195
|
+
Executa fn() gerenciando o ciclo de vida automaticamente.
|
|
196
|
+
|
|
197
|
+
Sequencia:
|
|
198
|
+
mark_running() → fn() → finish_success()
|
|
199
|
+
Em caso de excecao:
|
|
200
|
+
finish_failed(str(e)) → re-raise
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
fn: Funcao principal do bot (sem argumentos).
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
Exception: Propaga qualquer excecao lancada por fn().
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
self.mark_running()
|
|
210
|
+
fn()
|
|
211
|
+
self.finish_success()
|
|
212
|
+
except Exception as exc:
|
|
213
|
+
self.finish_failed(str(exc))
|
|
214
|
+
raise
|
|
215
|
+
|
|
216
|
+
# ── Internos ─────────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
def _write_metrics(self) -> None:
|
|
219
|
+
"""Escreve metricas no arquivo lido pelo runner apos o subprocess encerrar."""
|
|
220
|
+
if not self._metrics_file:
|
|
221
|
+
return
|
|
222
|
+
try:
|
|
223
|
+
self._metrics_file.parent.mkdir(parents=True, exist_ok=True)
|
|
224
|
+
self._metrics_file.write_text(
|
|
225
|
+
json.dumps({
|
|
226
|
+
"items_processed": self._items_processed,
|
|
227
|
+
"items_failed": self._items_failed,
|
|
228
|
+
}),
|
|
229
|
+
encoding="utf-8",
|
|
230
|
+
)
|
|
231
|
+
except Exception as exc:
|
|
232
|
+
logger.debug(f"Falha ao escrever metrics file: {exc}")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OneromLogHandler — envia logs estruturados via stdout.
|
|
3
|
+
|
|
4
|
+
O runner captura stdout linha a linha e envia ao backend.
|
|
5
|
+
Este handler nunca faz chamadas HTTP diretamente — apenas escreve JSON no stdout.
|
|
6
|
+
|
|
7
|
+
Formato esperado pelo runner:
|
|
8
|
+
{
|
|
9
|
+
"ts": ISO8601,
|
|
10
|
+
"execution_id": int,
|
|
11
|
+
"runner_id": int,
|
|
12
|
+
"bot": str,
|
|
13
|
+
"source": "bot",
|
|
14
|
+
"level": str,
|
|
15
|
+
"message": str,
|
|
16
|
+
"extra": {...}
|
|
17
|
+
}
|
|
18
|
+
"""
|
|
19
|
+
import json
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OneromLogHandler(logging.Handler):
|
|
27
|
+
"""
|
|
28
|
+
Handler que escreve logs estruturados como JSON no stdout.
|
|
29
|
+
|
|
30
|
+
O runner ONEROM captura cada linha do stdout e, se for JSON valido
|
|
31
|
+
com os campos obrigatorios, reenvia ao backend como log estruturado.
|
|
32
|
+
|
|
33
|
+
Nunca lanca excecoes — falha silenciosa para nao impactar o bot.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
37
|
+
try:
|
|
38
|
+
entry = {
|
|
39
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
40
|
+
"execution_id": int(os.environ.get("ONEROM_EXECUTION_ID", 0)),
|
|
41
|
+
"runner_id": int(os.environ.get("ONEROM_RUNNER_ID", 0) or 0),
|
|
42
|
+
"bot": os.environ.get("ONEROM_BOT_NAME", ""),
|
|
43
|
+
"source": "bot",
|
|
44
|
+
"level": record.levelname,
|
|
45
|
+
"message": self.format(record),
|
|
46
|
+
"extra": {
|
|
47
|
+
"logger": record.name,
|
|
48
|
+
"module": record.module,
|
|
49
|
+
"line": record.lineno,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
print(json.dumps(entry, ensure_ascii=False), flush=True)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass # nunca propaga excecao do handler
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geracao dinamica de modelo de parametros em runtime.
|
|
3
|
+
|
|
4
|
+
Fontes de dados (prioridade):
|
|
5
|
+
1. Env var ONEROM_PARAMETERS (JSON dict injetado pelo runner)
|
|
6
|
+
2. Env var ONEROM_CONTEXT -> parameters (fallback)
|
|
7
|
+
3. Dict vazio
|
|
8
|
+
|
|
9
|
+
Validacao com schema (opcional, via ONEROM_PARAM_SCHEMA):
|
|
10
|
+
- Se presente: usa pydantic create_model para validacao tipada
|
|
11
|
+
- Se ausente: expoe como SimpleNamespace (acesso por atributo)
|
|
12
|
+
|
|
13
|
+
Tipos suportados (valores do backend ParameterType enum):
|
|
14
|
+
string -> str
|
|
15
|
+
int -> int
|
|
16
|
+
double -> float
|
|
17
|
+
bool -> bool
|
|
18
|
+
|
|
19
|
+
Aliases adicionais (compatibilidade com o prompt):
|
|
20
|
+
integer -> int
|
|
21
|
+
float -> float
|
|
22
|
+
json -> dict
|
|
23
|
+
list -> list
|
|
24
|
+
"""
|
|
25
|
+
import json
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
import types
|
|
29
|
+
import warnings
|
|
30
|
+
from typing import Any, Optional
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
TYPE_MAP: dict[str, type] = {
|
|
35
|
+
"string": str,
|
|
36
|
+
"int": int,
|
|
37
|
+
"double": float,
|
|
38
|
+
"bool": bool,
|
|
39
|
+
# aliases
|
|
40
|
+
"integer": int,
|
|
41
|
+
"float": float,
|
|
42
|
+
"json": dict,
|
|
43
|
+
"list": list,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _load_raw_parameters() -> dict:
|
|
48
|
+
"""Carrega parametros brutos das env vars injetadas pelo runner."""
|
|
49
|
+
# Fonte 1: ONEROM_PARAMETERS (JSON string direto)
|
|
50
|
+
raw = os.environ.get("ONEROM_PARAMETERS", "")
|
|
51
|
+
if raw:
|
|
52
|
+
try:
|
|
53
|
+
data = json.loads(raw)
|
|
54
|
+
if isinstance(data, dict):
|
|
55
|
+
return data
|
|
56
|
+
except (json.JSONDecodeError, TypeError):
|
|
57
|
+
logger.warning("ONEROM_PARAMETERS nao e JSON valido, ignorando")
|
|
58
|
+
|
|
59
|
+
# Fonte 2: ONEROM_CONTEXT -> parameters
|
|
60
|
+
ctx_raw = os.environ.get("ONEROM_CONTEXT", "")
|
|
61
|
+
if ctx_raw:
|
|
62
|
+
try:
|
|
63
|
+
ctx = json.loads(ctx_raw)
|
|
64
|
+
params = ctx.get("parameters")
|
|
65
|
+
if isinstance(params, dict):
|
|
66
|
+
return params
|
|
67
|
+
except (json.JSONDecodeError, TypeError):
|
|
68
|
+
logger.warning("ONEROM_CONTEXT nao e JSON valido, ignorando")
|
|
69
|
+
|
|
70
|
+
return {}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _load_schema() -> list[dict] | None:
|
|
74
|
+
"""Carrega schema de parametros de ONEROM_PARAM_SCHEMA (opcional)."""
|
|
75
|
+
raw = os.environ.get("ONEROM_PARAM_SCHEMA", "")
|
|
76
|
+
if not raw:
|
|
77
|
+
return None
|
|
78
|
+
try:
|
|
79
|
+
schema = json.loads(raw)
|
|
80
|
+
if isinstance(schema, list):
|
|
81
|
+
return schema
|
|
82
|
+
except (json.JSONDecodeError, TypeError):
|
|
83
|
+
logger.warning("ONEROM_PARAM_SCHEMA nao e JSON valido, ignorando schema")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _coerce_value(value: Any, python_type: type) -> Any:
|
|
88
|
+
"""Converte value para python_type se possivel."""
|
|
89
|
+
if value is None:
|
|
90
|
+
return None
|
|
91
|
+
if isinstance(value, python_type):
|
|
92
|
+
return value
|
|
93
|
+
# Conversoes especiais
|
|
94
|
+
if python_type is bool:
|
|
95
|
+
if isinstance(value, str):
|
|
96
|
+
return value.lower() in ("true", "1", "yes")
|
|
97
|
+
return bool(value)
|
|
98
|
+
if python_type in (int, float):
|
|
99
|
+
return python_type(value)
|
|
100
|
+
if python_type is str:
|
|
101
|
+
return str(value)
|
|
102
|
+
# Para dict e list: tenta JSON parse se vier como string
|
|
103
|
+
if python_type in (dict, list) and isinstance(value, str):
|
|
104
|
+
try:
|
|
105
|
+
parsed = json.loads(value)
|
|
106
|
+
if isinstance(parsed, python_type):
|
|
107
|
+
return parsed
|
|
108
|
+
except (json.JSONDecodeError, TypeError):
|
|
109
|
+
pass
|
|
110
|
+
return value
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def build_params() -> Any:
|
|
114
|
+
"""
|
|
115
|
+
Constroi objeto de parametros validado.
|
|
116
|
+
|
|
117
|
+
Com schema (ONEROM_PARAM_SCHEMA presente):
|
|
118
|
+
Retorna instancia de Pydantic model dinamico com campos tipados.
|
|
119
|
+
Levanta ValidationError se validacao falhar.
|
|
120
|
+
|
|
121
|
+
Sem schema:
|
|
122
|
+
Retorna SimpleNamespace com valores brutos (acesso por atributo).
|
|
123
|
+
Tambem expoe .as_dict() para acesso como dict.
|
|
124
|
+
"""
|
|
125
|
+
raw_params = _load_raw_parameters()
|
|
126
|
+
schema = _load_schema()
|
|
127
|
+
|
|
128
|
+
if schema:
|
|
129
|
+
return _build_with_schema(raw_params, schema)
|
|
130
|
+
else:
|
|
131
|
+
return _build_simple(raw_params)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _build_with_schema(raw_params: dict, schema: list[dict]) -> Any:
|
|
135
|
+
"""Gera Pydantic model dinamico a partir do schema."""
|
|
136
|
+
try:
|
|
137
|
+
from pydantic import create_model
|
|
138
|
+
from pydantic import ValidationError
|
|
139
|
+
except ImportError:
|
|
140
|
+
warnings.warn(
|
|
141
|
+
"pydantic nao instalado — parametros sem validacao de schema",
|
|
142
|
+
RuntimeWarning,
|
|
143
|
+
stacklevel=3,
|
|
144
|
+
)
|
|
145
|
+
return _build_simple(raw_params)
|
|
146
|
+
|
|
147
|
+
fields: dict[str, Any] = {}
|
|
148
|
+
coerced_values: dict[str, Any] = {}
|
|
149
|
+
|
|
150
|
+
for item in schema:
|
|
151
|
+
name = item.get("name")
|
|
152
|
+
type_str = item.get("type", "string")
|
|
153
|
+
required = item.get("required", False)
|
|
154
|
+
default_value = item.get("default_value") # string or None
|
|
155
|
+
|
|
156
|
+
if not name:
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
# Resolve tipo Python
|
|
160
|
+
if type_str not in TYPE_MAP:
|
|
161
|
+
logger.warning(
|
|
162
|
+
f"Tipo desconhecido '{type_str}' para parametro '{name}', usando dict"
|
|
163
|
+
)
|
|
164
|
+
python_type = dict
|
|
165
|
+
else:
|
|
166
|
+
python_type = TYPE_MAP[type_str]
|
|
167
|
+
|
|
168
|
+
# Prepara valor
|
|
169
|
+
raw_val = raw_params.get(name)
|
|
170
|
+
if raw_val is None and default_value is not None:
|
|
171
|
+
raw_val = default_value
|
|
172
|
+
|
|
173
|
+
coerced = _coerce_value(raw_val, python_type) if raw_val is not None else None
|
|
174
|
+
coerced_values[name] = coerced
|
|
175
|
+
|
|
176
|
+
# Define campo Pydantic
|
|
177
|
+
if required:
|
|
178
|
+
fields[name] = (python_type, ...)
|
|
179
|
+
else:
|
|
180
|
+
fields[name] = (Optional[python_type], None)
|
|
181
|
+
|
|
182
|
+
# Campos extras em raw_params que nao estao no schema
|
|
183
|
+
for key, val in raw_params.items():
|
|
184
|
+
if key not in fields:
|
|
185
|
+
fields[key] = (Any, None)
|
|
186
|
+
coerced_values[key] = val
|
|
187
|
+
|
|
188
|
+
if not fields:
|
|
189
|
+
return _build_simple(raw_params)
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
DynamicParams = create_model("ExecutionParams", **fields)
|
|
193
|
+
# Filtra None para campos obrigatorios que tem valor
|
|
194
|
+
return DynamicParams(**{k: v for k, v in coerced_values.items() if v is not None or k in coerced_values})
|
|
195
|
+
except Exception as e:
|
|
196
|
+
# Levanta erro claro — nunca silencia erros de validacao
|
|
197
|
+
raise ValueError(
|
|
198
|
+
f"Falha ao validar parametros da execucao: {e}\n"
|
|
199
|
+
f"Parametros recebidos: {raw_params}"
|
|
200
|
+
) from e
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _build_simple(raw_params: dict) -> Any:
|
|
204
|
+
"""Retorna SimpleNamespace para acesso por atributo sem validacao de schema."""
|
|
205
|
+
ns = types.SimpleNamespace(**raw_params)
|
|
206
|
+
# Adiciona acesso como dict
|
|
207
|
+
ns.as_dict = lambda: dict(raw_params)
|
|
208
|
+
return ns
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "onerom-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "SDK oficial para automacoes ONEROM"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
keywords = ["onerom", "rpa", "automation", "sdk"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.9",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Topic :: Software Development :: Libraries",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"requests>=2.28",
|
|
22
|
+
"pydantic>=2.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=7.0",
|
|
28
|
+
"pytest-cov",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["hatchling"]
|
|
33
|
+
build-backend = "hatchling.build"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["onerom_core"]
|