beaver-db 2.0rc2__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.
- beaver/__init__.py +16 -0
- beaver/blobs.py +223 -0
- beaver/bridge.py +167 -0
- beaver/cache.py +274 -0
- beaver/channels.py +249 -0
- beaver/cli/__init__.py +133 -0
- beaver/cli/blobs.py +225 -0
- beaver/cli/channels.py +166 -0
- beaver/cli/collections.py +500 -0
- beaver/cli/dicts.py +171 -0
- beaver/cli/lists.py +244 -0
- beaver/cli/locks.py +202 -0
- beaver/cli/logs.py +248 -0
- beaver/cli/queues.py +215 -0
- beaver/client.py +392 -0
- beaver/core.py +646 -0
- beaver/dicts.py +314 -0
- beaver/docs.py +459 -0
- beaver/events.py +155 -0
- beaver/graphs.py +212 -0
- beaver/lists.py +337 -0
- beaver/locks.py +186 -0
- beaver/logs.py +187 -0
- beaver/manager.py +203 -0
- beaver/queries.py +66 -0
- beaver/queues.py +215 -0
- beaver/security.py +144 -0
- beaver/server.py +452 -0
- beaver/sketches.py +307 -0
- beaver/types.py +32 -0
- beaver/vectors.py +198 -0
- beaver_db-2.0rc2.dist-info/METADATA +149 -0
- beaver_db-2.0rc2.dist-info/RECORD +36 -0
- beaver_db-2.0rc2.dist-info/WHEEL +4 -0
- beaver_db-2.0rc2.dist-info/entry_points.txt +2 -0
- beaver_db-2.0rc2.dist-info/licenses/LICENSE +21 -0
beaver/cli/logs.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typer
|
|
3
|
+
import rich
|
|
4
|
+
import rich.table
|
|
5
|
+
import statistics
|
|
6
|
+
import time
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from typing_extensions import Annotated
|
|
10
|
+
from typing import Optional, List, Dict, Any
|
|
11
|
+
from rich.live import Live
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from beaver import BeaverDB
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(
|
|
17
|
+
name="log",
|
|
18
|
+
help="Interact with time-indexed logs. (e.g., beaver log errors write '{\"code\": 500}')",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# --- Helper Functions ---
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_db(ctx: typer.Context) -> BeaverDB:
|
|
25
|
+
"""Helper to get the DB instance from the main context."""
|
|
26
|
+
return ctx.find_object(dict)["db"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _parse_value(value: str):
|
|
30
|
+
"""
|
|
31
|
+
Intelligently parses the input string.
|
|
32
|
+
- Tries to parse as JSON if it starts with '{' or '['.
|
|
33
|
+
- Tries to parse as int, then float.
|
|
34
|
+
- Checks for 'true'/'false'/'null'.
|
|
35
|
+
- Defaults to a plain string.
|
|
36
|
+
"""
|
|
37
|
+
# 1. Try JSON object or array
|
|
38
|
+
if value.startswith("{") or value.startswith("["):
|
|
39
|
+
try:
|
|
40
|
+
return json.loads(value)
|
|
41
|
+
except json.JSONDecodeError:
|
|
42
|
+
# It's not valid JSON, so treat it as a string
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
# 2. Try boolean
|
|
46
|
+
if value.lower() == "true":
|
|
47
|
+
return True
|
|
48
|
+
if value.lower() == "false":
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
# 3. Try null
|
|
52
|
+
if value.lower() == "null":
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
# 4. Try int
|
|
56
|
+
try:
|
|
57
|
+
return int(value)
|
|
58
|
+
except ValueError:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
# 5. Try float
|
|
62
|
+
try:
|
|
63
|
+
return float(value)
|
|
64
|
+
except ValueError:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
# 6. Default to string (remove quotes if user added them)
|
|
68
|
+
if len(value) >= 2 and value.startswith('"') and value.endswith('"'):
|
|
69
|
+
return value[1:-1]
|
|
70
|
+
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _build_stats_aggregator(window: List[Dict[str, Any]]) -> dict:
|
|
75
|
+
"""
|
|
76
|
+
The custom aggregator function.
|
|
77
|
+
It processes a list of log entries and returns a stats summary.
|
|
78
|
+
"""
|
|
79
|
+
total_count = len(window)
|
|
80
|
+
# Use defaultdict to easily build nested stats
|
|
81
|
+
key_stats = defaultdict(lambda: {"count": 0, "numeric_values": [], "types": set()})
|
|
82
|
+
non_dict_count = 0
|
|
83
|
+
|
|
84
|
+
for entry in window:
|
|
85
|
+
if not isinstance(entry, dict):
|
|
86
|
+
non_dict_count += 1
|
|
87
|
+
continue # Only aggregate stats for dict logs
|
|
88
|
+
|
|
89
|
+
for key, value in entry.items():
|
|
90
|
+
stats = key_stats[key]
|
|
91
|
+
stats["count"] += 1
|
|
92
|
+
stats["types"].add(type(value).__name__)
|
|
93
|
+
if isinstance(value, (int, float)):
|
|
94
|
+
stats["numeric_values"].append(value)
|
|
95
|
+
|
|
96
|
+
# Finalize stats
|
|
97
|
+
summary = {"total_count": total_count, "non_dict_count": non_dict_count, "keys": {}}
|
|
98
|
+
for key, stats in sorted(key_stats.items()):
|
|
99
|
+
key_summary = {"count": stats["count"], "types": sorted(list(stats["types"]))}
|
|
100
|
+
if stats["numeric_values"]:
|
|
101
|
+
key_summary["min"] = min(stats["numeric_values"])
|
|
102
|
+
key_summary["max"] = max(stats["numeric_values"])
|
|
103
|
+
key_summary["mean"] = statistics.mean(stats["numeric_values"])
|
|
104
|
+
summary["keys"][key] = key_summary
|
|
105
|
+
|
|
106
|
+
return summary
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _generate_stats_table(summary: dict, name: str, window_s: int) -> Table:
|
|
110
|
+
"""Builds a rich.Table object from the stats summary."""
|
|
111
|
+
|
|
112
|
+
table = Table(title=f"Live Log Stats: [bold]{name}[/bold] ({window_s}s window)")
|
|
113
|
+
table.add_column("Key", style="cyan", no_wrap=True)
|
|
114
|
+
table.add_column("Count", style="magenta", justify="right")
|
|
115
|
+
table.add_column("Types", style="green")
|
|
116
|
+
table.add_column("Min", style="blue", justify="right")
|
|
117
|
+
table.add_column("Max", style="blue", justify="right")
|
|
118
|
+
table.add_column("Mean", style="blue", justify="right")
|
|
119
|
+
|
|
120
|
+
for key, stats in summary.get("keys", {}).items():
|
|
121
|
+
table.add_row(
|
|
122
|
+
key,
|
|
123
|
+
str(stats["count"]),
|
|
124
|
+
", ".join(stats["types"]),
|
|
125
|
+
f"{stats.get('min', 'N/A'):.2f}" if "min" in stats else "N/A",
|
|
126
|
+
f"{stats.get('max', 'N/A'):.2f}" if "max" in stats else "N/A",
|
|
127
|
+
f"{stats.get('mean', 'N/A'):.2f}" if "mean" in stats else "N/A",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
caption = f"Total Events: {summary['total_count']}"
|
|
131
|
+
if summary["non_dict_count"] > 0:
|
|
132
|
+
caption += f" ({summary['non_dict_count']} non-JSON-object events not shown)"
|
|
133
|
+
table.caption = caption
|
|
134
|
+
return table
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# --- CLI Commands ---
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.callback(invoke_without_command=True)
|
|
141
|
+
def log_main(
|
|
142
|
+
ctx: typer.Context,
|
|
143
|
+
name: Annotated[
|
|
144
|
+
Optional[str], typer.Argument(help="The name of the log to interact with.")
|
|
145
|
+
] = None,
|
|
146
|
+
):
|
|
147
|
+
"""
|
|
148
|
+
Manage time-indexed logs.
|
|
149
|
+
|
|
150
|
+
If no name is provided, lists all available logs.
|
|
151
|
+
"""
|
|
152
|
+
db = _get_db(ctx)
|
|
153
|
+
|
|
154
|
+
if name is None:
|
|
155
|
+
rich.print("[bold]Available Logs:[/bold]")
|
|
156
|
+
try:
|
|
157
|
+
log_names = db.logs
|
|
158
|
+
if not log_names:
|
|
159
|
+
rich.print(" (No logs found)")
|
|
160
|
+
else:
|
|
161
|
+
for log_name in log_names:
|
|
162
|
+
rich.print(f" • {log_name}")
|
|
163
|
+
rich.print("\n[bold]Usage:[/bold] beaver log [bold]<NAME>[/bold] [COMMAND]")
|
|
164
|
+
return
|
|
165
|
+
except Exception as e:
|
|
166
|
+
rich.print(f"[bold red]Error querying logs:[/] {e}")
|
|
167
|
+
raise typer.Exit(code=1)
|
|
168
|
+
|
|
169
|
+
ctx.obj = {"name": name, "db": db}
|
|
170
|
+
|
|
171
|
+
if ctx.invoked_subcommand is None:
|
|
172
|
+
rich.print(f"Log '[bold]{name}[/bold]'.")
|
|
173
|
+
rich.print("\n[bold]Commands:[/bold]")
|
|
174
|
+
rich.print(" write, watch")
|
|
175
|
+
rich.print(
|
|
176
|
+
f"\nRun [bold]beaver log {name} --help[/bold] for command-specific options."
|
|
177
|
+
)
|
|
178
|
+
raise typer.Exit()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@app.command()
|
|
182
|
+
def write(
|
|
183
|
+
ctx: typer.Context,
|
|
184
|
+
data: Annotated[
|
|
185
|
+
str,
|
|
186
|
+
typer.Argument(
|
|
187
|
+
help="The data to log (e.g., '{\"a\": 1}', '\"my string\"', '123.45', 'true')."
|
|
188
|
+
),
|
|
189
|
+
],
|
|
190
|
+
):
|
|
191
|
+
"""
|
|
192
|
+
Write a new data entry to the log.
|
|
193
|
+
|
|
194
|
+
The data will be parsed as JSON, a number, a boolean, or a string.
|
|
195
|
+
"""
|
|
196
|
+
db = ctx.obj["db"]
|
|
197
|
+
name = ctx.obj["name"]
|
|
198
|
+
try:
|
|
199
|
+
parsed_data = _parse_value(data)
|
|
200
|
+
db.log(name).log(parsed_data)
|
|
201
|
+
rich.print(f"[green]Success:[/] Log entry added to '{name}'.")
|
|
202
|
+
except Exception as e:
|
|
203
|
+
rich.print(f"[bold red]Error:[/] {e}")
|
|
204
|
+
raise typer.Exit(code=1)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@app.command()
|
|
208
|
+
def watch(
|
|
209
|
+
ctx: typer.Context,
|
|
210
|
+
window: Annotated[
|
|
211
|
+
int, typer.Option("--window", help="Time window in seconds to aggregate over.")
|
|
212
|
+
] = 60,
|
|
213
|
+
frequency: Annotated[
|
|
214
|
+
int, typer.Option("--frequency", help="Time in seconds between updates.")
|
|
215
|
+
] = 1,
|
|
216
|
+
):
|
|
217
|
+
"""
|
|
218
|
+
Watch a live, aggregated view of JSON log entries.
|
|
219
|
+
|
|
220
|
+
This command only processes logs that are JSON objects and provides
|
|
221
|
+
basic statistics for the keys found in those objects.
|
|
222
|
+
"""
|
|
223
|
+
db: BeaverDB = ctx.obj["db"]
|
|
224
|
+
name = ctx.obj["name"]
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
log_manager = db.log(name)
|
|
228
|
+
live_stream = log_manager.live(
|
|
229
|
+
window=timedelta(seconds=window),
|
|
230
|
+
period=timedelta(seconds=frequency),
|
|
231
|
+
aggregator=_build_stats_aggregator,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
rich.print(
|
|
235
|
+
f"[cyan]Watching log '[bold]{name}[/bold]' (Window: {window}s, Freq: {frequency}s)... Press Ctrl+C to stop.[/cyan]"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Use screen=True to create a new buffer and avoid flickering
|
|
239
|
+
with Live(screen=True, refresh_per_second=4, transient=True) as live:
|
|
240
|
+
for summary in live_stream:
|
|
241
|
+
live.update(_generate_stats_table(summary, name, window))
|
|
242
|
+
|
|
243
|
+
except KeyboardInterrupt:
|
|
244
|
+
rich.print("\n[cyan]Stopping watcher...[/cyan]")
|
|
245
|
+
raise typer.Exit()
|
|
246
|
+
except Exception as e:
|
|
247
|
+
rich.print(f"[bold red]Error:[/] {e}")
|
|
248
|
+
raise typer.Exit(code=1)
|
beaver/cli/queues.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typer
|
|
3
|
+
import rich
|
|
4
|
+
import rich.table
|
|
5
|
+
from typing_extensions import Annotated
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from beaver import BeaverDB
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="queue",
|
|
12
|
+
help="Interact with persistent priority queues. (e.g., beaver queue my-tasks put 1 'new task')",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_db(ctx: typer.Context) -> BeaverDB:
|
|
17
|
+
"""Helper to get the DB instance from the main context."""
|
|
18
|
+
return ctx.find_object(dict)["db"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _parse_value(value: str):
|
|
22
|
+
"""Parses the value string as JSON if appropriate."""
|
|
23
|
+
if value.startswith("{") or value.startswith("["):
|
|
24
|
+
try:
|
|
25
|
+
return json.loads(value)
|
|
26
|
+
except json.JSONDecodeError:
|
|
27
|
+
return value
|
|
28
|
+
return value
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.callback(invoke_without_command=True)
|
|
32
|
+
def queue_main(
|
|
33
|
+
ctx: typer.Context,
|
|
34
|
+
name: Annotated[
|
|
35
|
+
Optional[str], typer.Argument(help="The name of the queue to interact with.")
|
|
36
|
+
] = None,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Manage persistent priority queues.
|
|
40
|
+
|
|
41
|
+
If no name is provided, lists all available queues.
|
|
42
|
+
"""
|
|
43
|
+
db = _get_db(ctx)
|
|
44
|
+
|
|
45
|
+
if name is None:
|
|
46
|
+
# No name given, so list all queues
|
|
47
|
+
rich.print("[bold]Available Queues:[/bold]")
|
|
48
|
+
try:
|
|
49
|
+
queue_names = db.queues
|
|
50
|
+
if not queue_names:
|
|
51
|
+
rich.print(" (No queues found)")
|
|
52
|
+
else:
|
|
53
|
+
for queue_name in queue_names:
|
|
54
|
+
rich.print(f" • {queue_name}")
|
|
55
|
+
rich.print(
|
|
56
|
+
"\n[bold]Usage:[/bold] beaver queue [bold]<NAME>[/bold] [COMMAND]"
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
except Exception as e:
|
|
60
|
+
rich.print(f"[bold red]Error querying queues:[/] {e}")
|
|
61
|
+
raise typer.Exit(code=1)
|
|
62
|
+
|
|
63
|
+
# A name was provided, store it in the context for subcommands
|
|
64
|
+
ctx.obj = {"name": name, "db": db}
|
|
65
|
+
|
|
66
|
+
if ctx.invoked_subcommand is None:
|
|
67
|
+
# A name was given, but no command
|
|
68
|
+
try:
|
|
69
|
+
count = len(db.queue(name))
|
|
70
|
+
rich.print(f"Queue '[bold]{name}[/bold]' contains {count} items.")
|
|
71
|
+
rich.print("\n[bold]Commands:[/bold]")
|
|
72
|
+
rich.print(" put, get, peek, show, dump")
|
|
73
|
+
rich.print(
|
|
74
|
+
f"\nRun [bold]beaver queue {name} --help[/bold] for command-specific options."
|
|
75
|
+
)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
rich.print(f"[bold red]Error:[/] {e}")
|
|
78
|
+
raise typer.Exit(code=1)
|
|
79
|
+
raise typer.Exit()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.command()
|
|
83
|
+
def put(
|
|
84
|
+
ctx: typer.Context,
|
|
85
|
+
priority: Annotated[
|
|
86
|
+
float,
|
|
87
|
+
typer.Argument(help="The item priority (float). Lower is higher priority."),
|
|
88
|
+
],
|
|
89
|
+
value: Annotated[str, typer.Argument(help="The value to add (JSON or string).")],
|
|
90
|
+
):
|
|
91
|
+
"""
|
|
92
|
+
Add (put) an item into the queue with a specific priority.
|
|
93
|
+
"""
|
|
94
|
+
db = ctx.obj["db"]
|
|
95
|
+
name = ctx.obj["name"]
|
|
96
|
+
try:
|
|
97
|
+
parsed_value = _parse_value(value)
|
|
98
|
+
db.queue(name).put(parsed_value, priority=priority)
|
|
99
|
+
rich.print(
|
|
100
|
+
f"[green]Success:[/] Item added to queue '{name}' with priority {priority}."
|
|
101
|
+
)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
rich.print(f"[bold red]Error:[/] {e}")
|
|
104
|
+
raise typer.Exit(code=1)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.command()
|
|
108
|
+
def get(
|
|
109
|
+
ctx: typer.Context,
|
|
110
|
+
block: Annotated[
|
|
111
|
+
bool,
|
|
112
|
+
typer.Option("--block/--no-block", help="Block until an item is available."),
|
|
113
|
+
] = True,
|
|
114
|
+
timeout: Annotated[
|
|
115
|
+
Optional[float], typer.Option(help="Max seconds to block. Requires --block.")
|
|
116
|
+
] = 5.0,
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Get and remove the highest-priority item from the queue.
|
|
120
|
+
"""
|
|
121
|
+
db = ctx.obj["db"]
|
|
122
|
+
name = ctx.obj["name"]
|
|
123
|
+
try:
|
|
124
|
+
if block:
|
|
125
|
+
rich.print(f"Waiting for item from '{name}' (timeout={timeout}s)...")
|
|
126
|
+
|
|
127
|
+
item = db.queue(name).get(block=block, timeout=timeout)
|
|
128
|
+
|
|
129
|
+
rich.print(f"[green]Got item (Priority: {item.priority}):[/green]")
|
|
130
|
+
if isinstance(item.data, (dict, list)):
|
|
131
|
+
rich.print_json(data=item.data)
|
|
132
|
+
else:
|
|
133
|
+
rich.print(item.data)
|
|
134
|
+
|
|
135
|
+
except IndexError:
|
|
136
|
+
rich.print(f"Queue '{name}' is empty (non-blocking get).")
|
|
137
|
+
except TimeoutError:
|
|
138
|
+
rich.print(
|
|
139
|
+
f"[bold yellow]Timeout:[/] No item received from queue '{name}' after {timeout}s."
|
|
140
|
+
)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
rich.print(f"[bold red]Error:[/] {e}")
|
|
143
|
+
raise typer.Exit(code=1)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.command()
|
|
147
|
+
def peek(ctx: typer.Context):
|
|
148
|
+
"""
|
|
149
|
+
View the highest-priority item without removing it.
|
|
150
|
+
"""
|
|
151
|
+
db = ctx.obj["db"]
|
|
152
|
+
name = ctx.obj["name"]
|
|
153
|
+
try:
|
|
154
|
+
item = db.queue(name).peek()
|
|
155
|
+
if item is None:
|
|
156
|
+
rich.print(f"Queue '{name}' is empty.")
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
rich.print(f"[green]Next item (Priority: {item.priority}):[/green]")
|
|
160
|
+
if isinstance(item.data, (dict, list)):
|
|
161
|
+
rich.print_json(data=item.data)
|
|
162
|
+
else:
|
|
163
|
+
rich.print(item.data)
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
rich.print(f"[bold red]Error:[/] {e}")
|
|
167
|
+
raise typer.Exit(code=1)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@app.command()
|
|
171
|
+
def show(ctx: typer.Context):
|
|
172
|
+
"""
|
|
173
|
+
Print all items in the queue, in priority order.
|
|
174
|
+
"""
|
|
175
|
+
db = ctx.obj["db"]
|
|
176
|
+
name = ctx.obj["name"]
|
|
177
|
+
try:
|
|
178
|
+
# The __iter__ for QueueManager yields all items in order
|
|
179
|
+
all_items = list(db.queue(name))
|
|
180
|
+
if not all_items:
|
|
181
|
+
rich.print(f"Queue '{name}' is empty.")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
table = rich.table.Table(title=f"Items in Queue: [bold]{name}[/bold]")
|
|
185
|
+
table.add_column("Priority", style="cyan", justify="right")
|
|
186
|
+
table.add_column("Timestamp", style="magenta")
|
|
187
|
+
table.add_column("Data")
|
|
188
|
+
|
|
189
|
+
for item in all_items:
|
|
190
|
+
data_str = (
|
|
191
|
+
json.dumps(item.data)
|
|
192
|
+
if isinstance(item.data, (dict, list))
|
|
193
|
+
else str(item.data)
|
|
194
|
+
)
|
|
195
|
+
table.add_row(str(item.priority), str(item.timestamp), data_str)
|
|
196
|
+
rich.print(table)
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
rich.print(f"[bold red]Error:[/] {e}")
|
|
200
|
+
raise typer.Exit(code=1)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@app.command()
|
|
204
|
+
def dump(ctx: typer.Context):
|
|
205
|
+
"""
|
|
206
|
+
Dump the entire queue as JSON.
|
|
207
|
+
"""
|
|
208
|
+
db = ctx.obj["db"]
|
|
209
|
+
name = ctx.obj["name"]
|
|
210
|
+
try:
|
|
211
|
+
dump_data = db.queue(name).dump()
|
|
212
|
+
rich.print_json(data=dump_data)
|
|
213
|
+
except Exception as e:
|
|
214
|
+
rich.print(f"[bold red]Error:[/] {e}")
|
|
215
|
+
raise typer.Exit(code=1)
|