lumera 0.9.9__py3-none-any.whl → 0.10.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.
lumera/automations.py
CHANGED
|
@@ -57,18 +57,76 @@ __all__ = [
|
|
|
57
57
|
"create",
|
|
58
58
|
"update",
|
|
59
59
|
"upsert",
|
|
60
|
-
# Log
|
|
60
|
+
# Log functions
|
|
61
61
|
"stream_logs",
|
|
62
|
+
"get_logs",
|
|
62
63
|
"get_log_download_url",
|
|
63
64
|
# Classes
|
|
64
65
|
"Run",
|
|
65
66
|
"Automation",
|
|
67
|
+
"LogsResponse",
|
|
66
68
|
]
|
|
67
69
|
|
|
68
70
|
from ._utils import LumeraAPIError, _api_request
|
|
69
71
|
from .sdk import get_automation_run as _get_automation_run
|
|
70
72
|
from .sdk import run_automation as _run_automation
|
|
71
73
|
|
|
74
|
+
# ============================================================================
|
|
75
|
+
# LogsResponse Class
|
|
76
|
+
# ============================================================================
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class LogsResponse:
|
|
80
|
+
"""Response from fetching automation run logs.
|
|
81
|
+
|
|
82
|
+
Attributes:
|
|
83
|
+
data: Raw log content as a string (NDJSON format).
|
|
84
|
+
offset: Byte offset where this chunk starts.
|
|
85
|
+
size: Number of bytes in this chunk.
|
|
86
|
+
total_size: Total size of the log file.
|
|
87
|
+
has_more: True if there are more logs after this chunk.
|
|
88
|
+
source: Where logs came from ("live" or "archived").
|
|
89
|
+
truncated: True if logs were truncated at storage time (>50MB).
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, data: dict[str, Any]) -> None:
|
|
93
|
+
self._data = data
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def data(self) -> str:
|
|
97
|
+
return self._data.get("data", "")
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def offset(self) -> int:
|
|
101
|
+
return self._data.get("offset", 0)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def size(self) -> int:
|
|
105
|
+
return self._data.get("size", 0)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def total_size(self) -> int:
|
|
109
|
+
return self._data.get("total_size", 0)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def has_more(self) -> bool:
|
|
113
|
+
return self._data.get("has_more", False)
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def source(self) -> str:
|
|
117
|
+
return self._data.get("source", "")
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def truncated(self) -> bool:
|
|
121
|
+
return self._data.get("truncated", False)
|
|
122
|
+
|
|
123
|
+
def __repr__(self) -> str:
|
|
124
|
+
return (
|
|
125
|
+
f"LogsResponse(offset={self.offset}, size={self.size}, "
|
|
126
|
+
f"total_size={self.total_size}, has_more={self.has_more})"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
72
130
|
# ============================================================================
|
|
73
131
|
# Run Class
|
|
74
132
|
# ============================================================================
|
|
@@ -246,6 +304,66 @@ class Run:
|
|
|
246
304
|
raise ValueError("Cannot get log URL without run id")
|
|
247
305
|
return get_log_download_url(self.id)
|
|
248
306
|
|
|
307
|
+
def logs(
|
|
308
|
+
self,
|
|
309
|
+
*,
|
|
310
|
+
offset: int = 0,
|
|
311
|
+
limit: int = 1024 * 1024,
|
|
312
|
+
all: bool = False,
|
|
313
|
+
) -> LogsResponse:
|
|
314
|
+
"""Fetch logs for this run.
|
|
315
|
+
|
|
316
|
+
Works for both live (running) and archived (completed) runs.
|
|
317
|
+
Returns raw log data as a string (NDJSON format).
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
offset: Byte offset to start from. Negative values read from end
|
|
321
|
+
(e.g., -1048576 = last 1MB).
|
|
322
|
+
limit: Maximum bytes to return (default 1MB).
|
|
323
|
+
all: If True, fetch all logs at once. Returns 400 if logs > 10MB.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
A LogsResponse object with data, offset, size, total_size, has_more,
|
|
327
|
+
source ("live" or "archived"), and truncated flag.
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
ValueError: If the run has no ID.
|
|
331
|
+
LumeraAPIError: If logs are not available or request fails.
|
|
332
|
+
|
|
333
|
+
Example:
|
|
334
|
+
>>> run = automations.get_run("run_id")
|
|
335
|
+
>>> resp = run.logs()
|
|
336
|
+
>>> print(resp.data) # Raw NDJSON log content
|
|
337
|
+
>>> while resp.has_more:
|
|
338
|
+
... resp = run.logs(offset=resp.offset + resp.size)
|
|
339
|
+
... print(resp.data)
|
|
340
|
+
"""
|
|
341
|
+
if not self.id:
|
|
342
|
+
raise ValueError("Cannot fetch logs without run id")
|
|
343
|
+
return get_logs(self.id, offset=offset, limit=limit, all=all)
|
|
344
|
+
|
|
345
|
+
def stream_logs(self, *, timeout: float = 30) -> Iterator[str]:
|
|
346
|
+
"""Stream logs from this run.
|
|
347
|
+
|
|
348
|
+
Works for both live (running) and archived (completed) runs.
|
|
349
|
+
For live runs, streams in real-time as logs are produced.
|
|
350
|
+
For archived runs, streams the entire log from S3.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
timeout: HTTP connection timeout in seconds.
|
|
354
|
+
|
|
355
|
+
Yields:
|
|
356
|
+
Log lines as strings (raw NDJSON lines).
|
|
357
|
+
|
|
358
|
+
Example:
|
|
359
|
+
>>> run = automations.run("automation_id", inputs={})
|
|
360
|
+
>>> for line in run.stream_logs():
|
|
361
|
+
... print(line)
|
|
362
|
+
"""
|
|
363
|
+
if not self.id:
|
|
364
|
+
raise ValueError("Cannot stream logs without run id")
|
|
365
|
+
return stream_logs(self.id, timeout=timeout)
|
|
366
|
+
|
|
249
367
|
def to_dict(self) -> dict[str, Any]:
|
|
250
368
|
"""Return the underlying data dict."""
|
|
251
369
|
return self._data.copy()
|
|
@@ -795,17 +913,19 @@ def delete(automation_id: str) -> None:
|
|
|
795
913
|
|
|
796
914
|
|
|
797
915
|
def stream_logs(run_id: str, *, timeout: float = 30) -> Iterator[str]:
|
|
798
|
-
"""Stream
|
|
916
|
+
"""Stream logs from an automation run.
|
|
799
917
|
|
|
918
|
+
Works for both live (running) and archived (completed) runs.
|
|
800
919
|
Connects to the server-sent events endpoint and yields log lines
|
|
801
|
-
as they arrive.
|
|
920
|
+
as they arrive. For live runs, streams in real-time. For archived
|
|
921
|
+
runs, streams the entire log from storage.
|
|
802
922
|
|
|
803
923
|
Args:
|
|
804
924
|
run_id: The run ID to stream logs from.
|
|
805
925
|
timeout: HTTP connection timeout in seconds.
|
|
806
926
|
|
|
807
927
|
Yields:
|
|
808
|
-
Log lines as strings.
|
|
928
|
+
Log lines as strings (raw NDJSON lines).
|
|
809
929
|
|
|
810
930
|
Example:
|
|
811
931
|
>>> for line in automations.stream_logs("run_id"):
|
|
@@ -825,7 +945,7 @@ def stream_logs(run_id: str, *, timeout: float = 30) -> Iterator[str]:
|
|
|
825
945
|
if not token:
|
|
826
946
|
raise ValueError("LUMERA_TOKEN environment variable is required")
|
|
827
947
|
|
|
828
|
-
url = f"{base_url}/automation-runs/{run_id}/logs
|
|
948
|
+
url = f"{base_url}/automation-runs/{run_id}/logs?stream=true"
|
|
829
949
|
headers = {
|
|
830
950
|
"Authorization": f"token {token}",
|
|
831
951
|
"Accept": "text/event-stream",
|
|
@@ -902,3 +1022,51 @@ def get_log_download_url(run_id: str) -> str:
|
|
|
902
1022
|
if isinstance(result, dict) and "url" in result:
|
|
903
1023
|
return result["url"]
|
|
904
1024
|
raise RuntimeError("Unexpected response: no download URL returned")
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
def get_logs(
|
|
1028
|
+
run_id: str,
|
|
1029
|
+
*,
|
|
1030
|
+
offset: int = 0,
|
|
1031
|
+
limit: int = 1024 * 1024,
|
|
1032
|
+
all: bool = False,
|
|
1033
|
+
) -> LogsResponse:
|
|
1034
|
+
"""Fetch logs for an automation run.
|
|
1035
|
+
|
|
1036
|
+
Works for both live (running) and archived (completed) runs.
|
|
1037
|
+
Returns raw log data as a string (NDJSON format).
|
|
1038
|
+
|
|
1039
|
+
Args:
|
|
1040
|
+
run_id: The run ID to get logs for.
|
|
1041
|
+
offset: Byte offset to start from. Negative values read from end
|
|
1042
|
+
(e.g., -1048576 = last 1MB).
|
|
1043
|
+
limit: Maximum bytes to return (default 1MB).
|
|
1044
|
+
all: If True, fetch all logs at once. Returns 400 if logs > 10MB.
|
|
1045
|
+
|
|
1046
|
+
Returns:
|
|
1047
|
+
A LogsResponse object with data, offset, size, total_size, has_more,
|
|
1048
|
+
source ("live" or "archived"), and truncated flag.
|
|
1049
|
+
|
|
1050
|
+
Raises:
|
|
1051
|
+
ValueError: If run_id is empty.
|
|
1052
|
+
LumeraAPIError: If logs are not available or request fails.
|
|
1053
|
+
|
|
1054
|
+
Example:
|
|
1055
|
+
>>> resp = automations.get_logs("run_id")
|
|
1056
|
+
>>> print(resp.data) # Raw NDJSON log content
|
|
1057
|
+
>>> while resp.has_more:
|
|
1058
|
+
... resp = automations.get_logs("run_id", offset=resp.offset + resp.size)
|
|
1059
|
+
... print(resp.data)
|
|
1060
|
+
"""
|
|
1061
|
+
run_id = run_id.strip()
|
|
1062
|
+
if not run_id:
|
|
1063
|
+
raise ValueError("run_id is required")
|
|
1064
|
+
|
|
1065
|
+
params: dict[str, Any] = {"offset": offset, "limit": limit}
|
|
1066
|
+
if all:
|
|
1067
|
+
params["all"] = "true"
|
|
1068
|
+
|
|
1069
|
+
result = _api_request("GET", f"automation-runs/{run_id}/logs", params=params)
|
|
1070
|
+
if isinstance(result, dict):
|
|
1071
|
+
return LogsResponse(result)
|
|
1072
|
+
raise RuntimeError("Unexpected response from logs endpoint")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
lumera/__init__.py,sha256=5FlY5dSJ1WNM4ko7wgmcajO8G2voBGn4S19E91_WdqE,2687
|
|
2
2
|
lumera/_utils.py,sha256=b-l3Ebh4n2pC-9T5mR6h4hPf_Wl48VDlHES0pLo1zKE,25766
|
|
3
|
-
lumera/automations.py,sha256=
|
|
3
|
+
lumera/automations.py,sha256=KPP_rD7WKmBs865jiKoonZJjdTno-FSAU7hajPFyqs0,32851
|
|
4
4
|
lumera/email.py,sha256=lk8KUsRw1ZvxgM0FPQXH-jVKUQA5f0zLv88jlc3IWlA,5056
|
|
5
5
|
lumera/exceptions.py,sha256=bNsx4iYaroAAGsYxErfELC2B5ZJ3w5lVa1kKdIx5s9g,2173
|
|
6
6
|
lumera/files.py,sha256=xMJmLTSaQQDttM3AMmpOWc6soh4lvCCKBreV0fXWHQw,3159
|
|
@@ -13,7 +13,7 @@ lumera/storage.py,sha256=fWkscTvKDzQ-5tsfA1lREO2qgtjJ4Yvxj3hvYNLKiW0,10527
|
|
|
13
13
|
lumera/webhooks.py,sha256=L_Q5YHBJKQNpv7G9Nq0QqlGMRch6x9ptlwu1xD2qwUc,8661
|
|
14
14
|
lumera/integrations/__init__.py,sha256=LnJmAnFB_p3YMKyeGVdDP4LYlJ85XFNQFAxGo6zF7CI,937
|
|
15
15
|
lumera/integrations/google.py,sha256=QkbBbbDh3I_OToPDFqcivU6sWy2UieHBxZ_TPv5rqK0,11862
|
|
16
|
-
lumera-0.
|
|
17
|
-
lumera-0.
|
|
18
|
-
lumera-0.
|
|
19
|
-
lumera-0.
|
|
16
|
+
lumera-0.10.0.dist-info/METADATA,sha256=uWvSDuD868zVICFyVUppHMIrWe6A-JKeyxFRurjxieU,1612
|
|
17
|
+
lumera-0.10.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
18
|
+
lumera-0.10.0.dist-info/top_level.txt,sha256=HgfK4XQkpMTnM2E5iWM4kB711FnYqUY9dglzib3pWlE,7
|
|
19
|
+
lumera-0.10.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|