roberty-code 0.1.0__py3-none-any.whl
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.
- roberty_code/__init__.py +52 -0
- roberty_code/_args_file.py +24 -0
- roberty_code/_env.py +8 -0
- roberty_code/context.py +19 -0
- roberty_code/errors.py +15 -0
- roberty_code/exception.py +22 -0
- roberty_code/inputs.py +22 -0
- roberty_code/output.py +47 -0
- roberty_code/py.typed +0 -0
- roberty_code/run.py +36 -0
- roberty_code-0.1.0.dist-info/METADATA +96 -0
- roberty_code-0.1.0.dist-info/RECORD +14 -0
- roberty_code-0.1.0.dist-info/WHEEL +4 -0
- roberty_code-0.1.0.dist-info/licenses/LICENSE +21 -0
roberty_code/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SDK oficial para Code Projects da Roberty.
|
|
3
|
+
|
|
4
|
+
import roberty_code as roberty
|
|
5
|
+
|
|
6
|
+
cpf = roberty.input("cpf")
|
|
7
|
+
roberty.output({"status": "ok"})
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
11
|
+
|
|
12
|
+
from . import context as _context
|
|
13
|
+
from ._env import ENV_VARS
|
|
14
|
+
from .errors import RobertyArgsError, RobertyOutputError
|
|
15
|
+
from .exception import ExceptionDetails, exception
|
|
16
|
+
from .inputs import input, inputs # noqa: A004
|
|
17
|
+
from .output import output
|
|
18
|
+
from .run import run
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ENV_VARS",
|
|
22
|
+
"ExceptionDetails",
|
|
23
|
+
"RobertyArgsError",
|
|
24
|
+
"RobertyOutputError",
|
|
25
|
+
"environment",
|
|
26
|
+
"exception",
|
|
27
|
+
"input",
|
|
28
|
+
"inputs",
|
|
29
|
+
"is_roberty",
|
|
30
|
+
"output",
|
|
31
|
+
"run",
|
|
32
|
+
"trigger",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
trigger: Optional[str]
|
|
37
|
+
environment: Optional[str]
|
|
38
|
+
is_roberty: bool
|
|
39
|
+
|
|
40
|
+
_DYNAMIC = {
|
|
41
|
+
"trigger": _context.trigger,
|
|
42
|
+
"environment": _context.environment,
|
|
43
|
+
"is_roberty": _context.is_roberty,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def __getattr__(name: str) -> Any:
|
|
48
|
+
"""`trigger`, `environment` e `is_roberty` são lidos da env a cada acesso."""
|
|
49
|
+
getter = _DYNAMIC.get(name)
|
|
50
|
+
if getter is None:
|
|
51
|
+
raise AttributeError(f"module 'roberty_code' has no attribute '{name}'")
|
|
52
|
+
return getter()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ._env import ENV_VARS
|
|
6
|
+
from .errors import RobertyArgsError
|
|
7
|
+
|
|
8
|
+
_cache: dict[str, Any] = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def read_args_file() -> Any:
|
|
12
|
+
"""Lê (com cache) o JSON apontado por `roberty-args-file-path`; `None` sem a variável."""
|
|
13
|
+
file_path = os.environ.get(ENV_VARS["args_file_path"])
|
|
14
|
+
if not file_path:
|
|
15
|
+
return None
|
|
16
|
+
if file_path in _cache:
|
|
17
|
+
return _cache[file_path]
|
|
18
|
+
try:
|
|
19
|
+
with open(file_path, encoding="utf-8") as file:
|
|
20
|
+
parsed = json.load(file)
|
|
21
|
+
except (OSError, ValueError) as cause:
|
|
22
|
+
raise RobertyArgsError(file_path, cause) from cause
|
|
23
|
+
_cache[file_path] = parsed
|
|
24
|
+
return parsed
|
roberty_code/_env.py
ADDED
roberty_code/context.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from ._env import ENV_VARS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def trigger() -> Optional[str]:
|
|
8
|
+
"""`manual`, `schedule`, `webhook` ou `evaluation`. `None` fora da Roberty."""
|
|
9
|
+
return os.environ.get(ENV_VARS["trigger"])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def environment() -> Optional[str]:
|
|
13
|
+
"""`production` ou `preview`. `None` fora da Roberty."""
|
|
14
|
+
return os.environ.get(ENV_VARS["environment"])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_roberty() -> bool:
|
|
18
|
+
"""`True` quando o processo roda na Roberty."""
|
|
19
|
+
return bool(os.environ.get(ENV_VARS["trigger"]))
|
roberty_code/errors.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class RobertyArgsError(Exception):
|
|
2
|
+
"""O arquivo de argumentos existe mas não pôde ser lido como JSON."""
|
|
3
|
+
|
|
4
|
+
def __init__(self, file_path: str, cause: BaseException) -> None:
|
|
5
|
+
super().__init__(
|
|
6
|
+
f'Roberty: não foi possível ler os argumentos em "{file_path}": {cause}'
|
|
7
|
+
)
|
|
8
|
+
self.file_path = file_path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RobertyOutputError(Exception):
|
|
12
|
+
"""O resultado não pôde ser serializado como JSON."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, cause: BaseException) -> None:
|
|
15
|
+
super().__init__(f"Roberty: o resultado não pôde ser serializado como JSON: {cause}")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Optional, TypedDict
|
|
2
|
+
|
|
3
|
+
from ._args_file import read_args_file
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExceptionDetails(TypedDict):
|
|
7
|
+
message: str
|
|
8
|
+
stack: Optional[str]
|
|
9
|
+
exitCode: int
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def exception() -> Optional[ExceptionDetails]:
|
|
13
|
+
"""Detalhes do erro dentro do `exceptionHandler`. `None` fora dele."""
|
|
14
|
+
details = read_args_file()
|
|
15
|
+
if not isinstance(details, dict):
|
|
16
|
+
return None
|
|
17
|
+
stack = details.get("stack")
|
|
18
|
+
return ExceptionDetails(
|
|
19
|
+
message=str(details.get("message", "")),
|
|
20
|
+
stack=stack if isinstance(stack, str) else None,
|
|
21
|
+
exitCode=int(details.get("exitCode", 1)),
|
|
22
|
+
)
|
roberty_code/inputs.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any, Optional, TypeVar, Union, overload
|
|
2
|
+
|
|
3
|
+
from ._args_file import read_args_file
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def inputs() -> Optional[Any]:
|
|
9
|
+
"""Todos os argumentos da execução (`webhook`/`evaluation`). `None` em `manual`/`schedule`."""
|
|
10
|
+
return read_args_file()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@overload
|
|
14
|
+
def input(name: str) -> Optional[Any]: ... # noqa: A001
|
|
15
|
+
@overload
|
|
16
|
+
def input(name: str, default: T) -> Union[Any, T]: ... # noqa: A001
|
|
17
|
+
def input(name: str, default: Any = None) -> Any: # noqa: A001
|
|
18
|
+
"""Um argumento específico. `default` quando não existe ou não há argumentos."""
|
|
19
|
+
args = read_args_file()
|
|
20
|
+
if not isinstance(args, dict):
|
|
21
|
+
return default
|
|
22
|
+
return args.get(name, default)
|
roberty_code/output.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ._env import ENV_VARS
|
|
7
|
+
from .errors import RobertyOutputError
|
|
8
|
+
|
|
9
|
+
OUTPUT_LIMIT_BYTES = 256 * 1024
|
|
10
|
+
|
|
11
|
+
_state = {"warned_missing_path": False}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def output(value: Any) -> bool:
|
|
15
|
+
"""Grava `value` como JSON no arquivo de resultado; `False` fora da Roberty (aviso único)."""
|
|
16
|
+
serialized = _serialize(value)
|
|
17
|
+
file_path = os.environ.get(ENV_VARS["output_file_path"])
|
|
18
|
+
if not file_path:
|
|
19
|
+
_warn_missing_path_once()
|
|
20
|
+
return False
|
|
21
|
+
if len(serialized.encode("utf-8")) > OUTPUT_LIMIT_BYTES:
|
|
22
|
+
print(
|
|
23
|
+
"Roberty: resultado acima de 256 KB; "
|
|
24
|
+
"em execuções webhook a plataforma corta o excedente.",
|
|
25
|
+
file=sys.stderr,
|
|
26
|
+
)
|
|
27
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
|
28
|
+
file.write(serialized)
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _serialize(value: Any) -> str:
|
|
33
|
+
try:
|
|
34
|
+
return json.dumps(value, ensure_ascii=False)
|
|
35
|
+
except (TypeError, ValueError) as cause:
|
|
36
|
+
raise RobertyOutputError(cause) from cause
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _warn_missing_path_once() -> None:
|
|
40
|
+
if _state["warned_missing_path"]:
|
|
41
|
+
return
|
|
42
|
+
_state["warned_missing_path"] = True
|
|
43
|
+
print(
|
|
44
|
+
f'Roberty: "{ENV_VARS["output_file_path"]}" não definida; '
|
|
45
|
+
"o resultado não foi gravado (execução fora da Roberty).",
|
|
46
|
+
file=sys.stderr,
|
|
47
|
+
)
|
roberty_code/py.typed
ADDED
|
File without changes
|
roberty_code/run.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from .context import is_roberty
|
|
9
|
+
from .output import output
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run(handler: Callable[[Any], Any], exit_on_complete: bool = True) -> Any:
|
|
13
|
+
"""
|
|
14
|
+
Atalho opcional: executa `handler(roberty)`, grava o retorno como resultado e encerra
|
|
15
|
+
com exit code 0/1. Fora da Roberty imprime o resultado no stdout. Aceita `async def`.
|
|
16
|
+
"""
|
|
17
|
+
import roberty_code
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
result = handler(roberty_code)
|
|
21
|
+
if inspect.isawaitable(result):
|
|
22
|
+
result = asyncio.run(result)
|
|
23
|
+
if result is not None:
|
|
24
|
+
output(result)
|
|
25
|
+
if not is_roberty():
|
|
26
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
27
|
+
if exit_on_complete:
|
|
28
|
+
sys.exit(0)
|
|
29
|
+
return result
|
|
30
|
+
except SystemExit:
|
|
31
|
+
raise
|
|
32
|
+
except BaseException:
|
|
33
|
+
traceback.print_exc()
|
|
34
|
+
if exit_on_complete:
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
raise
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: roberty-code
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SDK oficial para Code Projects da Roberty: leia argumentos, escreva o resultado e descubra o gatilho da execução.
|
|
5
|
+
Project-URL: Homepage, https://github.com/robertyautomation/code-sdk/tree/main/packages/python#readme
|
|
6
|
+
Project-URL: Repository, https://github.com/robertyautomation/code-sdk
|
|
7
|
+
Project-URL: Issues, https://github.com/robertyautomation/code-sdk/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/robertyautomation/code/blob/main/roberty-code-project-guide.md
|
|
9
|
+
Author: Roberty Automation
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: automation,code-project,roberty,rpa,sdk
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# roberty-code
|
|
29
|
+
|
|
30
|
+
SDK oficial para **Code Projects** da [Roberty](https://roberty.app) em Python. Leia argumentos, escreva o resultado e descubra o gatilho da execução sem lidar com variáveis de ambiente.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install roberty-code
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Python ≥ 3.9. Sem dependências. Tipado.
|
|
37
|
+
|
|
38
|
+
## Uso
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from datetime import datetime, timezone
|
|
42
|
+
import roberty_code as roberty
|
|
43
|
+
|
|
44
|
+
print(f"Iniciando ({roberty.environment}, gatilho: {roberty.trigger})")
|
|
45
|
+
|
|
46
|
+
cpf = roberty.input("cpf") # um argumento
|
|
47
|
+
args = roberty.inputs() # todos os argumentos (ou None)
|
|
48
|
+
|
|
49
|
+
# ... lógica do robô ...
|
|
50
|
+
|
|
51
|
+
roberty.output({"status": "ok", "processedAt": datetime.now(timezone.utc).isoformat()})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Fora da Roberty o mesmo código roda: `inputs()` devolve `None`, `output()` avisa uma vez e devolve `False`.
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
| Membro | Retorno | Descrição |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `roberty.trigger` | `str \| None` | `manual`, `schedule`, `webhook` ou `evaluation` |
|
|
61
|
+
| `roberty.environment` | `str \| None` | `production` ou `preview` |
|
|
62
|
+
| `roberty.is_roberty` | `bool` | `True` quando roda na Roberty |
|
|
63
|
+
| `roberty.inputs()` | `Any \| None` | Todos os argumentos (`webhook`/`evaluation`) |
|
|
64
|
+
| `roberty.input(name, default=None)` | `Any` | Um argumento específico |
|
|
65
|
+
| `roberty.output(value)` | `bool` | Grava o resultado (JSON) lido pela plataforma |
|
|
66
|
+
| `roberty.exception()` | `ExceptionDetails \| None` | `{"message", "stack", "exitCode"}` dentro do `exceptionHandler` |
|
|
67
|
+
| `roberty.run(handler, exit_on_complete=True)` | `Any` | **Opcional**: executa `handler(roberty)`, grava o retorno e encerra com exit code `0`/`1`. Aceita `async def` |
|
|
68
|
+
| `roberty.ENV_VARS` | `dict` | Nomes das variáveis `roberty-*` |
|
|
69
|
+
|
|
70
|
+
Erros: `RobertyArgsError` (arquivo de argumentos com JSON inválido) e `RobertyOutputError` (valor não serializável; converta `datetime` com `.isoformat()`).
|
|
71
|
+
|
|
72
|
+
### `exceptionHandler`
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
# on_error.py — declarado em roberty.json como "exceptionHandler": "on_error.py"
|
|
76
|
+
import roberty_code as roberty
|
|
77
|
+
|
|
78
|
+
error = roberty.exception()
|
|
79
|
+
print(f"Robô falhou (exit code {error['exitCode']}): {error['message']}")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Atalho `run`
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
roberty.run(lambda r: {"status": "ok", "cpf": r.input("cpf")})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Encerra o processo ao terminar; passe `exit_on_complete=False` para embutir em testes.
|
|
89
|
+
|
|
90
|
+
## Documentação
|
|
91
|
+
|
|
92
|
+
[Guia de Code Projects](https://github.com/robertyautomation/code/blob/main/roberty-code-project-guide.md): contrato de execução, `roberty.json`, gatilhos e webhook.
|
|
93
|
+
|
|
94
|
+
## Licença
|
|
95
|
+
|
|
96
|
+
MIT. Copyright © Roberty Automation. Todos os direitos reservados.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
roberty_code/__init__.py,sha256=UR05kr3qyLnU4pU1EkSM4QrF8gioQAmVYfIJ_Wj9tXQ,1200
|
|
2
|
+
roberty_code/_args_file.py,sha256=e7Eh3IlYyJyFLccVS_4AsflJJcCsxXz9PycTXsMLESo,682
|
|
3
|
+
roberty_code/_env.py,sha256=-GmFtPw3G4A_DrSV0FbkslJM1QGC8iACnBb2XJEN1n8,255
|
|
4
|
+
roberty_code/context.py,sha256=xDyIcCYp9YWn09Gf1Fmhz7qIBnpDLUZX6asXm-HNF1o,513
|
|
5
|
+
roberty_code/errors.py,sha256=rM8uu29fu16WuaqrWi0WLA3JWJLnD2bVYLDeFh2IYl4,590
|
|
6
|
+
roberty_code/exception.py,sha256=LZHF-CYSq4B28STRfphWoCX5r8Dj2xxZT5p1hClg7yM,613
|
|
7
|
+
roberty_code/inputs.py,sha256=5IUWLHAx7v5kiTdOujqOysnNq2CQnAUpHBAPzMsseFc,701
|
|
8
|
+
roberty_code/output.py,sha256=c4Hd6c-fjcHSfwnN-zqLE5rSLivn0mqGabaOcWlCxQ8,1354
|
|
9
|
+
roberty_code/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
roberty_code/run.py,sha256=U4TQdXdahwq7RXlBcFb6muSunKWrxMon8T02JSu_vJQ,991
|
|
11
|
+
roberty_code-0.1.0.dist-info/METADATA,sha256=LefAiPYdwoadDjuvpK1HJVDP11SJz3b74UR1581jA44,3852
|
|
12
|
+
roberty_code-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
+
roberty_code-0.1.0.dist-info/licenses/LICENSE,sha256=ut1mZ0GFLRXhMBelw08WVhY3Ww6bZLVV5QmJZRNZdNA,1075
|
|
14
|
+
roberty_code-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roberty Automation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|