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.
@@ -0,0 +1,2 @@
1
+ # Local Netlify folder
2
+ .netlify
@@ -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"]