blobhub-cli 0.7.0__tar.gz → 0.8.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.
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/PKG-INFO +1 -1
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/__init__.py +1 -1
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/events.py +17 -5
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/definition/listener.py +26 -3
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/.gitignore +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/LICENSE +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/README.md +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/pyproject.toml +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/__main__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/api/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/api/client.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/api/commands.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/api/errors.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/blob/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/blob/reference.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/cli/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/cli/app.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/codes.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/auth.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/blob.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/completion.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/doctor.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/eject.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/execute.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/scheduler.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/commands/workflow.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/compiler/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/compiler/allowlist.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/compiler/emit.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/compiler/graph.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/compiler/sandbox.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/config/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/config/credentials.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/config/settings.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/config/sources.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/console.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/definition/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/definition/drift.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/definition/eject.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/definition/hashing.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/definition/model.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/exit.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/formats/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/formats/io.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/ids.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/limits.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/manifest/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/manifest/base.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/manifest/resolver.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/manifest/scheduler.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/manifest/workflow.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/scheduler/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/scheduler/references.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/scheduler/schedules.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/statestore.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/workflow/__init__.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/workflow/definitions.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/workflow/event_filter.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/workflow/events.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/workflow/executions.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/workflow/follow.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/workflow/reference.py +0 -0
- {blobhub_cli-0.7.0 → blobhub_cli-0.8.0}/src/blobhub_cli/workflow/session_reference.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: blobhub-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: BlobHub command line interface — work with blobs, revisions, and their contents from the terminal
|
|
5
5
|
Project-URL: Homepage, https://blobhub.io/
|
|
6
6
|
Project-URL: Documentation, https://docs.blobhub.io/
|
|
@@ -18,6 +18,7 @@ from blobhub_cli.api.commands import Api
|
|
|
18
18
|
from blobhub_cli.api.errors import ApiAuthError, ApiCommandError
|
|
19
19
|
from blobhub_cli.config import settings
|
|
20
20
|
from blobhub_cli.config.settings import Settings
|
|
21
|
+
from blobhub_cli.definition.listener import DEFAULT_RETRY_DELAY_SECONDS
|
|
21
22
|
from blobhub_cli.exit import CliExit
|
|
22
23
|
from blobhub_cli.workflow import event_filter, follow, session_reference
|
|
23
24
|
from blobhub_cli.workflow import events as event_lines
|
|
@@ -166,7 +167,7 @@ def events(
|
|
|
166
167
|
return payload
|
|
167
168
|
|
|
168
169
|
|
|
169
|
-
_ROW = "{:<{width}} {:<13} {:<9} {:<6} {:<8} {:<40} {}"
|
|
170
|
+
_ROW = "{:<{width}} {:<13} {:<9} {:<7} {:<6} {:<8} {:<40} {}"
|
|
170
171
|
_MORE_RUNS = "no runs in the newest 1,000 executions; more exist"
|
|
171
172
|
|
|
172
173
|
|
|
@@ -194,15 +195,25 @@ def _mode(entry: dict) -> str:
|
|
|
194
195
|
return entry["definition"].get("concurrency", {}).get("mode", "serial")
|
|
195
196
|
|
|
196
197
|
|
|
198
|
+
def _retry(entry: dict) -> str:
|
|
199
|
+
retry = entry["definition"].get("retry")
|
|
200
|
+
if not isinstance(retry, dict) or "attempts" not in retry:
|
|
201
|
+
return "-"
|
|
202
|
+
return f"{retry['attempts']}x{retry.get('delay_seconds', DEFAULT_RETRY_DELAY_SECONDS)}s"
|
|
203
|
+
|
|
204
|
+
|
|
197
205
|
def _render_entry_line(entry: dict, width: int) -> str:
|
|
198
206
|
pending = entry.get("pending", {}).get("count", 0)
|
|
199
|
-
cells = (
|
|
207
|
+
cells = (
|
|
208
|
+
entry["alias"], _enabled(entry), _mode(entry), _retry(entry), _slots(entry), pending, _last(entry),
|
|
209
|
+
_counts(entry),
|
|
210
|
+
)
|
|
200
211
|
return _ROW.format(*cells, width=width)
|
|
201
212
|
|
|
202
213
|
|
|
203
214
|
def _render_listeners(payload: dict) -> None:
|
|
204
215
|
width = max([len("ALIAS"), *(len(entry["alias"]) for entry in payload["listeners"])])
|
|
205
|
-
print(_ROW.format("ALIAS", "ENABLED", "MODE", "SLOTS", "PENDING", "LAST", "COUNTS", width=width))
|
|
216
|
+
print(_ROW.format("ALIAS", "ENABLED", "MODE", "RETRY", "SLOTS", "PENDING", "LAST", "COUNTS", width=width))
|
|
206
217
|
for entry in payload["listeners"]:
|
|
207
218
|
print(_render_entry_line(entry, width))
|
|
208
219
|
if payload["connections"]:
|
|
@@ -220,12 +231,13 @@ def _render_listener_detail(payload: dict) -> None:
|
|
|
220
231
|
print(f"{entry['alias']} {_enabled(entry)} slots {_slots(entry)} {_counts(entry)}")
|
|
221
232
|
if entry.get("description"):
|
|
222
233
|
print(f"description: {entry['description']}")
|
|
223
|
-
for field in ("filter", "batch", "concurrency", "invocation_target_type", "invocation_target"):
|
|
234
|
+
for field in ("filter", "batch", "concurrency", "retry", "invocation_target_type", "invocation_target"):
|
|
224
235
|
if field in body:
|
|
225
236
|
print(f"{field}: {json.dumps(body[field])}")
|
|
226
237
|
print("\nOutcomes")
|
|
227
238
|
for outcome in entry["outcomes"] or []:
|
|
228
|
-
|
|
239
|
+
retry = f" retry={outcome['retry']}" if "retry" in outcome else ""
|
|
240
|
+
print(f" {outcome['at']} {outcome['status']} count={outcome['count']} batch={outcome['batch_id']}{retry}")
|
|
229
241
|
if not entry["outcomes"]:
|
|
230
242
|
print(" none")
|
|
231
243
|
print("\nRuns")
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Offline validation, write-target shapes and comparison of a listener definition body.
|
|
2
2
|
|
|
3
|
-
Contract: `error` returns the first violation of docs/specs/2026-11-10-workflow_listeners.md section 4.4
|
|
4
|
-
platform's wording (blobhub-infra `listeners/definition.py`), or
|
|
3
|
+
Contract: `error` returns the first violation of docs/specs/2026-11-10-workflow_listeners.md section 4.4 and of
|
|
4
|
+
docs/specs/2026-11-14-listener-retry.md, in the platform's wording (blobhub-infra `listeners/definition.py`), or
|
|
5
|
+
None; `comparable` fills the documented defaults.
|
|
5
6
|
Invariant: templates are only checked for being strings -- their syntax and names are the platform's -- and nothing
|
|
6
7
|
here reads the catalog except through the `groups` a caller hands in.
|
|
7
8
|
"""
|
|
@@ -15,7 +16,7 @@ TYPE = "listener_definition"
|
|
|
15
16
|
VERSION = "1.0.0"
|
|
16
17
|
BODY_KEYS = (
|
|
17
18
|
"type", "version", "description", "enabled", "filter", "batch", "concurrency", "invocation_target_type",
|
|
18
|
-
"invocation_target", "receipt",
|
|
19
|
+
"invocation_target", "receipt", "retry",
|
|
19
20
|
)
|
|
20
21
|
CONTEXTS = ("batch", "event", "listener", "item", "object")
|
|
21
22
|
MARKERS = ("{{", "{%", "{#")
|
|
@@ -25,6 +26,7 @@ BATCH_KEYS = ("window_seconds", "max_events", "distinct_by")
|
|
|
25
26
|
CONCURRENCY_KEYS = ("mode", "key", "key_segments", "max_parallel")
|
|
26
27
|
CONCURRENCY_MODES = ("serial", "keyed", "parallel")
|
|
27
28
|
RECEIPT_KEYS = ("alias", "type", "parent")
|
|
29
|
+
RETRY_KEYS = ("attempts", "delay_seconds")
|
|
28
30
|
WORKFLOW_TARGET_KEYS = ("workflow", "input_data")
|
|
29
31
|
THREAD_ITEM_KEYS = ("alias", "content", "metadata", "parent_id")
|
|
30
32
|
OBJECT_KEYS = ("alias", "value")
|
|
@@ -33,6 +35,9 @@ REFERENCE_KEYS = ("org", "blob", "revision", "definition")
|
|
|
33
35
|
REFERENCE_DEPENDENCIES = {"org": "blob", "revision": "blob", "blob": "revision"}
|
|
34
36
|
|
|
35
37
|
DEFAULT_MAX_EVENTS = 100
|
|
38
|
+
MAX_RETRY_ATTEMPTS = 3
|
|
39
|
+
MAX_RETRY_DELAY_SECONDS = 900
|
|
40
|
+
DEFAULT_RETRY_DELAY_SECONDS = 30
|
|
36
41
|
MAX_DISTINCT_BY = 8
|
|
37
42
|
MAX_PATH_SEGMENTS = 8
|
|
38
43
|
MAX_KEY_SEGMENTS = 16
|
|
@@ -60,6 +65,7 @@ def error(body: object, *, groups: list[dict] | None = None) -> str | None:
|
|
|
60
65
|
or _concurrency(body.get("concurrency", {}), groups)
|
|
61
66
|
or _target(body)
|
|
62
67
|
or (_receipt(body["receipt"]) if "receipt" in body else None)
|
|
68
|
+
or (_retry(body["retry"], body["invocation_target_type"]) if "retry" in body else None)
|
|
63
69
|
)
|
|
64
70
|
|
|
65
71
|
|
|
@@ -111,6 +117,9 @@ def comparable(local: dict, remote: dict) -> tuple[dict, dict]:
|
|
|
111
117
|
if isinstance(batch, dict):
|
|
112
118
|
batch.setdefault("window_seconds", 0)
|
|
113
119
|
batch.setdefault("max_events", DEFAULT_MAX_EVENTS)
|
|
120
|
+
retry = body.get("retry")
|
|
121
|
+
if isinstance(retry, dict):
|
|
122
|
+
retry.setdefault("delay_seconds", DEFAULT_RETRY_DELAY_SECONDS)
|
|
114
123
|
omitted = isinstance(local["concurrency"], dict) and "max_parallel" not in local["concurrency"]
|
|
115
124
|
if omitted and isinstance(remote["concurrency"], dict):
|
|
116
125
|
remote["concurrency"].pop("max_parallel", None)
|
|
@@ -323,3 +332,17 @@ def _receipt(receipt: object) -> str | None:
|
|
|
323
332
|
if "parent" in receipt and receipt["parent"] != "item":
|
|
324
333
|
return 'receipt.parent: must be "item"'
|
|
325
334
|
return None
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _retry(retry: object, target_type: str) -> str | None:
|
|
338
|
+
if message := _keys("retry", retry, RETRY_KEYS):
|
|
339
|
+
return message
|
|
340
|
+
attempts = retry.get("attempts")
|
|
341
|
+
if not _is_int(attempts) or not 1 <= attempts <= MAX_RETRY_ATTEMPTS:
|
|
342
|
+
return f"retry.attempts: must be an integer from 1 to {MAX_RETRY_ATTEMPTS}"
|
|
343
|
+
delay = retry.get("delay_seconds", DEFAULT_RETRY_DELAY_SECONDS)
|
|
344
|
+
if not _is_int(delay) or not 0 <= delay <= MAX_RETRY_DELAY_SECONDS:
|
|
345
|
+
return f"retry.delay_seconds: must be an integer from 0 to {MAX_RETRY_DELAY_SECONDS}"
|
|
346
|
+
if target_type != "workflow":
|
|
347
|
+
return "retry: only a workflow action has a run to retry"
|
|
348
|
+
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|