modal 0.62.16__py3-none-any.whl → 0.72.11__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.
- modal/__init__.py +17 -13
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +420 -937
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -59
- modal/_resources.py +51 -0
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1036 -0
- modal/_runtime/execution_context.py +89 -0
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +134 -9
- modal/_traceback.py +47 -187
- modal/_tunnel.py +52 -16
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +479 -100
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +460 -171
- modal/_utils/grpc_testing.py +47 -31
- modal/_utils/grpc_utils.py +62 -109
- modal/_utils/hash_utils.py +61 -19
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +5 -7
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +14 -12
- modal/app.py +1003 -314
- modal/app.pyi +540 -264
- modal/call_graph.py +7 -6
- modal/cli/_download.py +63 -53
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +205 -45
- modal/cli/config.py +12 -5
- modal/cli/container.py +62 -14
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +64 -58
- modal/cli/launch.py +32 -18
- modal/cli/network_file_system.py +64 -83
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +35 -10
- modal/cli/programs/vscode.py +60 -10
- modal/cli/queues.py +131 -0
- modal/cli/run.py +234 -131
- modal/cli/secret.py +8 -7
- modal/cli/token.py +7 -2
- modal/cli/utils.py +79 -10
- modal/cli/volume.py +110 -109
- modal/client.py +250 -144
- modal/client.pyi +157 -118
- modal/cloud_bucket_mount.py +108 -34
- modal/cloud_bucket_mount.pyi +32 -38
- modal/cls.py +535 -148
- modal/cls.pyi +190 -146
- modal/config.py +41 -19
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +111 -65
- modal/dict.pyi +136 -131
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +34 -43
- modal/experimental.py +61 -2
- modal/extensions/ipython.py +5 -5
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +197 -0
- modal/functions.py +906 -911
- modal/functions.pyi +466 -430
- modal/gpu.py +57 -44
- modal/image.py +1089 -479
- modal/image.pyi +584 -228
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +314 -101
- modal/mount.pyi +241 -235
- modal/network_file_system.py +92 -92
- modal/network_file_system.pyi +152 -110
- modal/object.py +67 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +434 -0
- modal/parallel_map.pyi +75 -0
- modal/partial_function.py +282 -117
- modal/partial_function.pyi +222 -129
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +182 -65
- modal/queue.pyi +218 -118
- modal/requirements/2024.04.txt +29 -0
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +48 -7
- modal/runner.py +459 -156
- modal/runner.pyi +135 -71
- modal/running_app.py +38 -0
- modal/sandbox.py +514 -236
- modal/sandbox.pyi +397 -169
- modal/schedule.py +4 -4
- modal/scheduler_placement.py +20 -3
- modal/secret.py +56 -31
- modal/secret.pyi +62 -42
- modal/serving.py +51 -56
- modal/serving.pyi +44 -36
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +285 -157
- modal/volume.pyi +249 -184
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/METADATA +7 -7
- modal-0.72.11.dist-info/RECORD +174 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +5 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1288 -533
- modal_proto/api_grpc.py +856 -456
- modal_proto/api_pb2.py +2165 -1157
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1674 -855
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_entrypoint.pyi +0 -378
- modal/_container_exec.py +0 -128
- modal/_sandbox_shell.py +0 -49
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal/stub.py +0 -783
- modal/stub.pyi +0 -332
- modal-0.62.16.dist-info/RECORD +0 -198
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -262
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -659
- test/client_test.py +0 -194
- test/cls_test.py +0 -630
- test/config_test.py +0 -137
- test/conftest.py +0 -1420
- test/container_app_test.py +0 -32
- test/container_test.py +0 -1389
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -33
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -653
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -141
- test/helpers.py +0 -42
- test/image_test.py +0 -669
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -329
- test/network_file_system_test.py +0 -181
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -97
- test/resolver_test.py +0 -58
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -29
- test/secret_test.py +0 -78
- test/serialization_test.py +0 -42
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -360
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -341
- test/watcher_test.py +0 -30
- test/webhook_test.py +0 -146
- /modal/{requirements.312.txt → requirements/2023.12.312.txt} +0 -0
- /modal/{requirements.txt → requirements/2023.12.txt} +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/LICENSE +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/WHEEL +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/entry_points.txt +0 -0
@@ -8,16 +8,32 @@ import subprocess
|
|
8
8
|
import threading
|
9
9
|
import time
|
10
10
|
import webbrowser
|
11
|
-
from typing import Any
|
11
|
+
from typing import Any
|
12
12
|
|
13
|
-
from modal import Image, Queue,
|
13
|
+
from modal import App, Image, Queue, Secret, Volume, forward
|
14
14
|
|
15
|
-
# Passed by `modal launch` locally via CLI,
|
16
|
-
args:
|
15
|
+
# Passed by `modal launch` locally via CLI, plumbed to remote runner through secrets.
|
16
|
+
args: dict[str, Any] = json.loads(os.environ.get("MODAL_LAUNCH_ARGS", "{}"))
|
17
17
|
|
18
|
+
app = App()
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
image = Image.from_registry(args.get("image"), add_python=args.get("add_python")).pip_install("jupyterlab")
|
21
|
+
|
22
|
+
if args.get("mount"):
|
23
|
+
image = image.add_local_dir(
|
24
|
+
args.get("mount"),
|
25
|
+
remote_path="/root/lab/mount",
|
26
|
+
)
|
27
|
+
|
28
|
+
volume = (
|
29
|
+
Volume.from_name(
|
30
|
+
args.get("volume"),
|
31
|
+
create_if_missing=True,
|
32
|
+
)
|
33
|
+
if args.get("volume")
|
34
|
+
else None
|
35
|
+
)
|
36
|
+
volumes = {"/root/lab/volume": volume} if volume else {}
|
21
37
|
|
22
38
|
|
23
39
|
def wait_for_port(url: str, q: Queue):
|
@@ -33,9 +49,18 @@ def wait_for_port(url: str, q: Queue):
|
|
33
49
|
q.put(url)
|
34
50
|
|
35
51
|
|
36
|
-
@
|
52
|
+
@app.function(
|
53
|
+
image=image,
|
54
|
+
cpu=args.get("cpu"),
|
55
|
+
memory=args.get("memory"),
|
56
|
+
gpu=args.get("gpu"),
|
57
|
+
timeout=args.get("timeout"),
|
58
|
+
secrets=[Secret.from_dict({"MODAL_LAUNCH_ARGS": json.dumps(args)})],
|
59
|
+
volumes=volumes,
|
60
|
+
concurrency_limit=1 if volume else None,
|
61
|
+
)
|
37
62
|
def run_jupyter(q: Queue):
|
38
|
-
os.
|
63
|
+
os.makedirs("/root/lab", exist_ok=True)
|
39
64
|
token = secrets.token_urlsafe(13)
|
40
65
|
with forward(8888) as tunnel:
|
41
66
|
url = tunnel.url + "/?token=" + token
|
@@ -48,7 +73,7 @@ def run_jupyter(q: Queue):
|
|
48
73
|
"--allow-root",
|
49
74
|
"--ip=0.0.0.0",
|
50
75
|
"--port=8888",
|
51
|
-
"--notebook-dir=/lab",
|
76
|
+
"--notebook-dir=/root/lab",
|
52
77
|
"--LabApp.allow_origin='*'",
|
53
78
|
"--LabApp.allow_remote_access=1",
|
54
79
|
],
|
@@ -58,7 +83,7 @@ def run_jupyter(q: Queue):
|
|
58
83
|
q.put("done")
|
59
84
|
|
60
85
|
|
61
|
-
@
|
86
|
+
@app.local_entrypoint()
|
62
87
|
def main():
|
63
88
|
with Queue.ephemeral() as q:
|
64
89
|
run_jupyter.spawn(q)
|
modal/cli/programs/vscode.py
CHANGED
@@ -8,19 +8,60 @@ import subprocess
|
|
8
8
|
import threading
|
9
9
|
import time
|
10
10
|
import webbrowser
|
11
|
-
from typing import Any
|
11
|
+
from typing import Any
|
12
12
|
|
13
|
-
from modal import Image, Queue,
|
13
|
+
from modal import App, Image, Queue, Secret, Volume, forward
|
14
14
|
|
15
|
-
# Passed by `modal launch` locally via CLI,
|
16
|
-
args:
|
15
|
+
# Passed by `modal launch` locally via CLI, plumbed to remote runner through secrets.
|
16
|
+
args: dict[str, Any] = json.loads(os.environ.get("MODAL_LAUNCH_ARGS", "{}"))
|
17
17
|
|
18
|
+
CODE_SERVER_INSTALLER = "https://code-server.dev/install.sh"
|
19
|
+
CODE_SERVER_ENTRYPOINT = (
|
20
|
+
"https://raw.githubusercontent.com/coder/code-server/refs/tags/v4.96.1/ci/release-image/entrypoint.sh"
|
21
|
+
)
|
22
|
+
FIXUD_INSTALLER = "https://github.com/boxboat/fixuid/releases/download/v0.6.0/fixuid-0.6.0-linux-$ARCH.tar.gz"
|
18
23
|
|
19
|
-
stub = Stub()
|
20
|
-
stub.image = Image.from_registry("codercom/code-server", add_python="3.11").dockerfile_commands("ENTRYPOINT []")
|
21
24
|
|
25
|
+
app = App()
|
26
|
+
image = (
|
27
|
+
Image.from_registry(args.get("image"), add_python="3.11")
|
28
|
+
.apt_install("curl", "dumb-init", "git", "git-lfs")
|
29
|
+
.run_commands(
|
30
|
+
f"curl -fsSL {CODE_SERVER_INSTALLER} | sh",
|
31
|
+
f"curl -fsSL {CODE_SERVER_ENTRYPOINT} > /code-server.sh",
|
32
|
+
"chmod u+x /code-server.sh",
|
33
|
+
)
|
34
|
+
.run_commands(
|
35
|
+
'ARCH="$(dpkg --print-architecture)"'
|
36
|
+
f' && curl -fsSL "{FIXUD_INSTALLER}" | tar -C /usr/local/bin -xzf - '
|
37
|
+
" && chown root:root /usr/local/bin/fixuid"
|
38
|
+
" && chmod 4755 /usr/local/bin/fixuid"
|
39
|
+
" && mkdir -p /etc/fixuid"
|
40
|
+
' && echo "user: root" >> /etc/fixuid/config.yml'
|
41
|
+
' && echo "group: root" >> /etc/fixuid/config.yml'
|
42
|
+
)
|
43
|
+
.run_commands("mkdir /home/coder")
|
44
|
+
.env({"ENTRYPOINTD": ""})
|
45
|
+
)
|
22
46
|
|
23
|
-
|
47
|
+
if args.get("mount"):
|
48
|
+
image = image.add_local_dir(
|
49
|
+
args.get("mount"),
|
50
|
+
remote_path="/home/coder/mount",
|
51
|
+
)
|
52
|
+
|
53
|
+
volume = (
|
54
|
+
Volume.from_name(
|
55
|
+
args.get("volume"),
|
56
|
+
create_if_missing=True,
|
57
|
+
)
|
58
|
+
if args.get("volume")
|
59
|
+
else None
|
60
|
+
)
|
61
|
+
volumes = {"/home/coder/volume": volume} if volume else {}
|
62
|
+
|
63
|
+
|
64
|
+
def wait_for_port(data: tuple[str, str], q: Queue):
|
24
65
|
start_time = time.monotonic()
|
25
66
|
while True:
|
26
67
|
try:
|
@@ -33,7 +74,16 @@ def wait_for_port(data: Tuple[str, str], q: Queue):
|
|
33
74
|
q.put(data)
|
34
75
|
|
35
76
|
|
36
|
-
@
|
77
|
+
@app.function(
|
78
|
+
image=image,
|
79
|
+
cpu=args.get("cpu"),
|
80
|
+
memory=args.get("memory"),
|
81
|
+
gpu=args.get("gpu"),
|
82
|
+
timeout=args.get("timeout"),
|
83
|
+
secrets=[Secret.from_dict({"MODAL_LAUNCH_ARGS": json.dumps(args)})],
|
84
|
+
volumes=volumes,
|
85
|
+
concurrency_limit=1 if volume else None,
|
86
|
+
)
|
37
87
|
def run_vscode(q: Queue):
|
38
88
|
os.chdir("/home/coder")
|
39
89
|
token = secrets.token_urlsafe(13)
|
@@ -41,13 +91,13 @@ def run_vscode(q: Queue):
|
|
41
91
|
url = tunnel.url
|
42
92
|
threading.Thread(target=wait_for_port, args=((url, token), q)).start()
|
43
93
|
subprocess.run(
|
44
|
-
["/
|
94
|
+
["/code-server.sh", "--bind-addr", "0.0.0.0:8080", "."],
|
45
95
|
env={**os.environ, "SHELL": "/bin/bash", "PASSWORD": token},
|
46
96
|
)
|
47
97
|
q.put("done")
|
48
98
|
|
49
99
|
|
50
|
-
@
|
100
|
+
@app.local_entrypoint()
|
51
101
|
def main():
|
52
102
|
with Queue.ephemeral() as q:
|
53
103
|
run_vscode.spawn(q)
|
modal/cli/queues.py
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
import typer
|
5
|
+
from rich.console import Console
|
6
|
+
from typer import Argument, Option, Typer
|
7
|
+
|
8
|
+
from modal._resolver import Resolver
|
9
|
+
from modal._utils.async_utils import synchronizer
|
10
|
+
from modal._utils.grpc_utils import retry_transient_errors
|
11
|
+
from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table, timestamp_to_local
|
12
|
+
from modal.client import _Client
|
13
|
+
from modal.environments import ensure_env
|
14
|
+
from modal.queue import _Queue
|
15
|
+
from modal_proto import api_pb2
|
16
|
+
|
17
|
+
queue_cli = Typer(
|
18
|
+
name="queue",
|
19
|
+
no_args_is_help=True,
|
20
|
+
help="Manage `modal.Queue` objects and inspect their contents.",
|
21
|
+
)
|
22
|
+
|
23
|
+
PARTITION_OPTION = Option(
|
24
|
+
None,
|
25
|
+
"-p",
|
26
|
+
"--partition",
|
27
|
+
help="Name of the partition to use, otherwise use the default (anonymous) partition.",
|
28
|
+
)
|
29
|
+
|
30
|
+
|
31
|
+
@queue_cli.command(name="create", rich_help_panel="Management")
|
32
|
+
@synchronizer.create_blocking
|
33
|
+
async def create(name: str, *, env: Optional[str] = ENV_OPTION):
|
34
|
+
"""Create a named Queue.
|
35
|
+
|
36
|
+
Note: This is a no-op when the Queue already exists.
|
37
|
+
"""
|
38
|
+
q = _Queue.from_name(name, environment_name=env, create_if_missing=True)
|
39
|
+
client = await _Client.from_env()
|
40
|
+
resolver = Resolver(client=client)
|
41
|
+
await resolver.load(q)
|
42
|
+
|
43
|
+
|
44
|
+
@queue_cli.command(name="delete", rich_help_panel="Management")
|
45
|
+
@synchronizer.create_blocking
|
46
|
+
async def delete(name: str, *, yes: bool = YES_OPTION, env: Optional[str] = ENV_OPTION):
|
47
|
+
"""Delete a named Queue and all of its data."""
|
48
|
+
# Lookup first to validate the name, even though delete is a staticmethod
|
49
|
+
await _Queue.lookup(name, environment_name=env)
|
50
|
+
if not yes:
|
51
|
+
typer.confirm(
|
52
|
+
f"Are you sure you want to irrevocably delete the modal.Queue '{name}'?",
|
53
|
+
default=False,
|
54
|
+
abort=True,
|
55
|
+
)
|
56
|
+
await _Queue.delete(name, environment_name=env)
|
57
|
+
|
58
|
+
|
59
|
+
@queue_cli.command(name="list", rich_help_panel="Management")
|
60
|
+
@synchronizer.create_blocking
|
61
|
+
async def list_(*, json: bool = False, env: Optional[str] = ENV_OPTION):
|
62
|
+
"""List all named Queues."""
|
63
|
+
env = ensure_env(env)
|
64
|
+
|
65
|
+
max_total_size = 100_000
|
66
|
+
client = await _Client.from_env()
|
67
|
+
request = api_pb2.QueueListRequest(environment_name=env, total_size_limit=max_total_size + 1)
|
68
|
+
response = await retry_transient_errors(client.stub.QueueList, request)
|
69
|
+
|
70
|
+
rows = [
|
71
|
+
(
|
72
|
+
q.name,
|
73
|
+
timestamp_to_local(q.created_at, json),
|
74
|
+
str(q.num_partitions),
|
75
|
+
str(q.total_size) if q.total_size <= max_total_size else f">{max_total_size}",
|
76
|
+
)
|
77
|
+
for q in response.queues
|
78
|
+
]
|
79
|
+
display_table(["Name", "Created at", "Partitions", "Total size"], rows, json)
|
80
|
+
|
81
|
+
|
82
|
+
@queue_cli.command(name="clear", rich_help_panel="Management")
|
83
|
+
@synchronizer.create_blocking
|
84
|
+
async def clear(
|
85
|
+
name: str,
|
86
|
+
partition: Optional[str] = PARTITION_OPTION,
|
87
|
+
all: bool = Option(False, "-a", "--all", help="Clear the contents of all partitions."),
|
88
|
+
yes: bool = YES_OPTION,
|
89
|
+
*,
|
90
|
+
env: Optional[str] = ENV_OPTION,
|
91
|
+
):
|
92
|
+
"""Clear the contents of a queue by removing all of its data."""
|
93
|
+
q = await _Queue.lookup(name, environment_name=env)
|
94
|
+
if not yes:
|
95
|
+
typer.confirm(
|
96
|
+
f"Are you sure you want to irrevocably delete the contents of modal.Queue '{name}'?",
|
97
|
+
default=False,
|
98
|
+
abort=True,
|
99
|
+
)
|
100
|
+
await q.clear(partition=partition, all=all)
|
101
|
+
|
102
|
+
|
103
|
+
@queue_cli.command(name="peek", rich_help_panel="Inspection")
|
104
|
+
@synchronizer.create_blocking
|
105
|
+
async def peek(
|
106
|
+
name: str, n: int = Argument(1), partition: Optional[str] = PARTITION_OPTION, *, env: Optional[str] = ENV_OPTION
|
107
|
+
):
|
108
|
+
"""Print the next N items in the queue or queue partition (without removal)."""
|
109
|
+
q = await _Queue.lookup(name, environment_name=env)
|
110
|
+
console = Console()
|
111
|
+
i = 0
|
112
|
+
async for item in q.iterate(partition=partition):
|
113
|
+
console.print(item)
|
114
|
+
i += 1
|
115
|
+
if i >= n:
|
116
|
+
break
|
117
|
+
|
118
|
+
|
119
|
+
@queue_cli.command(name="len", rich_help_panel="Inspection")
|
120
|
+
@synchronizer.create_blocking
|
121
|
+
async def len(
|
122
|
+
name: str,
|
123
|
+
partition: Optional[str] = PARTITION_OPTION,
|
124
|
+
total: bool = Option(False, "-t", "--total", help="Compute the sum of the queue lengths across all partitions"),
|
125
|
+
*,
|
126
|
+
env: Optional[str] = ENV_OPTION,
|
127
|
+
):
|
128
|
+
"""Print the length of a queue partition or the total length of all partitions."""
|
129
|
+
q = await _Queue.lookup(name, environment_name=env)
|
130
|
+
console = Console()
|
131
|
+
console.print(await q.len(partition=partition, total=total))
|