modAL 1.3.1.dev34__tar.gz → 1.3.2__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.
- {modal-1.3.1.dev34 → modal-1.3.2}/PKG-INFO +1 -1
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_object.py +40 -35
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_output.py +2 -1
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_server.py +1 -1
- modal-1.3.2/modal/_utils/browser_utils.py +28 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/task_command_router_client.py +3 -3
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/app.py +34 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/container.py +9 -1
- modal-1.3.2/modal/cli/dashboard.py +30 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/entry_point.py +2 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/token.py +2 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/utils.py +4 -2
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/volume.py +28 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/client.py +6 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/client.pyi +12 -10
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cls.py +57 -44
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cls.pyi +1 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/config.py +0 -2
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/experimental/flash.py +84 -36
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/experimental/flash.pyi +14 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/functions.pyi +6 -6
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/object.pyi +11 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/queue.py +11 -1
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/queue.pyi +33 -3
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/runner.py +1 -5
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/sandbox.py +3 -6
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/token_flow.py +2 -17
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/token_flow.pyi +0 -3
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/volume.pyi +2 -2
- {modal-1.3.1.dev34 → modal-1.3.2}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.1.dev34 → modal-1.3.2}/modal.egg-info/SOURCES.txt +2 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/api.proto +12 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/api_grpc.py +16 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/api_pb2.py +178 -158
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/api_pb2.pyi +32 -1
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_version/__init__.py +1 -1
- {modal-1.3.1.dev34 → modal-1.3.2}/LICENSE +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/README.md +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/__main__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_billing.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_clustered_functions.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_functions.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_grpc_client.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_ipython.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_load_context.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_location.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_partial_function.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_pty.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_resolver.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_resources.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_serialization.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_traceback.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_tunnel.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_tunnel.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_type_manager.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/async_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/function_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/logger.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/_watcher.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/app.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/app.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/billing.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/builder/README.md +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/builder/base-images.json +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/call_graph.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/_download.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/_traceback.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/cluster.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/config.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/dict.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/environment.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/import_refs.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/launch.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/profile.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/queues.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/run.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/secret.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cli/shell.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/container_process.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/container_process.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/dict.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/dict.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/environments.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/environments.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/exception.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/experimental/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/experimental/ipython.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/file_io.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/file_io.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/functions.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/gpu.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/image.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/image.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/io_streams.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/io_streams.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/mount.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/mount.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/network_file_system.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/network_file_system.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/object.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/output.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/parallel_map.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/parallel_map.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/partial_function.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/partial_function.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/proxy.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/proxy.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/py.typed +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/retries.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/runner.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/running_app.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/sandbox.pyi +3 -3
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/schedule.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/scheduler_placement.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/secret.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/secret.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/server.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/server.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/serving.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/serving.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/snapshot.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/snapshot.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/stream_type.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal/volume.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_docs/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/__init__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/py.typed +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/task_command_router.proto +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/modal_version/__main__.py +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/pyproject.toml +0 -0
- {modal-1.3.1.dev34 → modal-1.3.2}/setup.cfg +0 -0
|
@@ -39,6 +39,41 @@ def _get_environment_name(
|
|
|
39
39
|
return config.get("environment")
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
def live_method(method):
|
|
43
|
+
@wraps(method)
|
|
44
|
+
async def wrapped(self, *args, **kwargs):
|
|
45
|
+
await self.hydrate()
|
|
46
|
+
return await method(self, *args, **kwargs)
|
|
47
|
+
|
|
48
|
+
return wrapped
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def live_method_gen(method):
|
|
52
|
+
@wraps(method)
|
|
53
|
+
async def wrapped(self, *args, **kwargs):
|
|
54
|
+
await self.hydrate()
|
|
55
|
+
async with aclosing(method(self, *args, **kwargs)) as stream:
|
|
56
|
+
async for item in stream:
|
|
57
|
+
yield item
|
|
58
|
+
|
|
59
|
+
return wrapped
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def live_method_contextmanager(method):
|
|
63
|
+
# make sure a wrapped function returning an async context manager
|
|
64
|
+
# will not require both an `await func.aio()` and `async with`
|
|
65
|
+
# which would have been the case if it was wrapped in live_method
|
|
66
|
+
|
|
67
|
+
@wraps(method)
|
|
68
|
+
@contextlib.asynccontextmanager
|
|
69
|
+
async def wrapped(self, *args, **kwargs):
|
|
70
|
+
await self.hydrate()
|
|
71
|
+
async with method(self, *args, **kwargs) as ctx:
|
|
72
|
+
yield ctx
|
|
73
|
+
|
|
74
|
+
return wrapped
|
|
75
|
+
|
|
76
|
+
|
|
42
77
|
class _Object:
|
|
43
78
|
_type_prefix: ClassVar[Optional[str]] = None
|
|
44
79
|
_prefix_to_type: ClassVar[dict[str, type]] = {}
|
|
@@ -275,6 +310,11 @@ class _Object:
|
|
|
275
310
|
raise AttributeError(f"Attempting to get object_id of unhydrated {self}")
|
|
276
311
|
return self._object_id
|
|
277
312
|
|
|
313
|
+
@live_method
|
|
314
|
+
async def get_dashboard_url(self) -> str:
|
|
315
|
+
"""mdmd:hidden"""
|
|
316
|
+
return f"https://modal.com/id/{self.object_id}"
|
|
317
|
+
|
|
278
318
|
@property
|
|
279
319
|
def client(self) -> _Client:
|
|
280
320
|
"""mdmd:hidden"""
|
|
@@ -336,38 +376,3 @@ class _Object:
|
|
|
336
376
|
with suppress_tb_frame(): # skip this frame by default
|
|
337
377
|
await resolver.load(self, root_load_context)
|
|
338
378
|
return self
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
def live_method(method):
|
|
342
|
-
@wraps(method)
|
|
343
|
-
async def wrapped(self, *args, **kwargs):
|
|
344
|
-
await self.hydrate()
|
|
345
|
-
return await method(self, *args, **kwargs)
|
|
346
|
-
|
|
347
|
-
return wrapped
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
def live_method_gen(method):
|
|
351
|
-
@wraps(method)
|
|
352
|
-
async def wrapped(self, *args, **kwargs):
|
|
353
|
-
await self.hydrate()
|
|
354
|
-
async with aclosing(method(self, *args, **kwargs)) as stream:
|
|
355
|
-
async for item in stream:
|
|
356
|
-
yield item
|
|
357
|
-
|
|
358
|
-
return wrapped
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
def live_method_contextmanager(method):
|
|
362
|
-
# make sure a wrapped function returning an async context manager
|
|
363
|
-
# will not require both an `await func.aio()` and `async with`
|
|
364
|
-
# which would have been the case if it was wrapped in live_method
|
|
365
|
-
|
|
366
|
-
@wraps(method)
|
|
367
|
-
@contextlib.asynccontextmanager
|
|
368
|
-
async def wrapped(self, *args, **kwargs):
|
|
369
|
-
await self.hydrate()
|
|
370
|
-
async with method(self, *args, **kwargs) as ctx:
|
|
371
|
-
yield ctx
|
|
372
|
-
|
|
373
|
-
return wrapped
|
|
@@ -545,7 +545,7 @@ async def get_app_logs_loop(
|
|
|
545
545
|
output_mgr: OutputManager,
|
|
546
546
|
app_id: str | None = None,
|
|
547
547
|
task_id: str | None = None,
|
|
548
|
-
|
|
548
|
+
sandbox_id: str | None = None,
|
|
549
549
|
):
|
|
550
550
|
last_log_batch_entry_id = ""
|
|
551
551
|
|
|
@@ -600,6 +600,7 @@ async def get_app_logs_loop(
|
|
|
600
600
|
request = api_pb2.AppGetLogsRequest(
|
|
601
601
|
app_id=app_id or "",
|
|
602
602
|
task_id=task_id or "",
|
|
603
|
+
sandbox_id=sandbox_id or "",
|
|
603
604
|
timeout=55,
|
|
604
605
|
last_entry_id=last_log_batch_entry_id,
|
|
605
606
|
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
import webbrowser
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def open_url(url: str) -> bool:
|
|
8
|
+
"""Opens url in web browser, making sure we use a modern one (not Lynx etc)"""
|
|
9
|
+
try:
|
|
10
|
+
browser = webbrowser.get()
|
|
11
|
+
# zpresto defines `BROWSER=open` by default on macOS, which causes `webbrowser` to return `GenericBrowser`.
|
|
12
|
+
if isinstance(browser, webbrowser.GenericBrowser) and browser.name != "open":
|
|
13
|
+
return False
|
|
14
|
+
else:
|
|
15
|
+
return browser.open_new_tab(url)
|
|
16
|
+
except webbrowser.Error:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def open_url_and_display(url: str, target: str, console: Console) -> None:
|
|
21
|
+
"""Open a URL in the web browser and display it to the user."""
|
|
22
|
+
if open_url(url):
|
|
23
|
+
console.print(f"Opening {target} in your web browser...")
|
|
24
|
+
console.print(f"[link={url}]{url}[/link]")
|
|
25
|
+
else:
|
|
26
|
+
console.print("[yellow]Could not open web browser automatically.[/yellow]")
|
|
27
|
+
console.print("Please open this URL in your browser:")
|
|
28
|
+
console.print(f"[link={url}]{url}[/link]")
|
|
@@ -15,7 +15,7 @@ import grpclib.events
|
|
|
15
15
|
from grpclib import GRPCError, Status
|
|
16
16
|
from grpclib.exceptions import StreamTerminatedError
|
|
17
17
|
|
|
18
|
-
from modal.config import
|
|
18
|
+
from modal.config import logger
|
|
19
19
|
from modal.exception import ConflictError, ExecTimeoutError
|
|
20
20
|
from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
|
|
21
21
|
from modal_proto.task_command_router_grpc import TaskCommandRouterStub
|
|
@@ -152,8 +152,8 @@ class TaskCommandRouterClient:
|
|
|
152
152
|
ssl_context = ssl.create_default_context()
|
|
153
153
|
|
|
154
154
|
# Allow insecure TLS when explicitly enabled via config.
|
|
155
|
-
if
|
|
156
|
-
logger.warning("Using insecure TLS for task command router
|
|
155
|
+
if server_client._is_localhost:
|
|
156
|
+
logger.warning("Using insecure TLS for task command router because server client points to localhost")
|
|
157
157
|
ssl_context.check_hostname = False
|
|
158
158
|
ssl_context.verify_mode = ssl.CERT_NONE
|
|
159
159
|
|
|
@@ -10,7 +10,9 @@ from rich.text import Text
|
|
|
10
10
|
from typer import Argument
|
|
11
11
|
|
|
12
12
|
from modal._object import _get_environment_name
|
|
13
|
+
from modal._output import make_console
|
|
13
14
|
from modal._utils.async_utils import synchronizer
|
|
15
|
+
from modal._utils.browser_utils import open_url_and_display
|
|
14
16
|
from modal.client import _Client
|
|
15
17
|
from modal.environments import ensure_env
|
|
16
18
|
from modal_proto import api_pb2
|
|
@@ -248,3 +250,35 @@ async def history(
|
|
|
248
250
|
|
|
249
251
|
if deployments_with_dirty_commit and not json:
|
|
250
252
|
rich.print("* - repo had uncommitted changes")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@app_cli.command("dashboard", no_args_is_help=True)
|
|
256
|
+
@synchronizer.create_blocking
|
|
257
|
+
async def dashboard(
|
|
258
|
+
app_identifier: str = APP_IDENTIFIER,
|
|
259
|
+
*,
|
|
260
|
+
env: Optional[str] = ENV_OPTION,
|
|
261
|
+
):
|
|
262
|
+
"""Open an App's dashboard page in your web browser.
|
|
263
|
+
|
|
264
|
+
**Examples:**
|
|
265
|
+
|
|
266
|
+
Open dashboard for an app by name:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
modal app dashboard my-app
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Use a specified environment:
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
modal app dashboard my-app --env dev
|
|
276
|
+
```
|
|
277
|
+
"""
|
|
278
|
+
client = await _Client.from_env()
|
|
279
|
+
app_id = await get_app_id.aio(app_identifier, env, client)
|
|
280
|
+
|
|
281
|
+
console = make_console()
|
|
282
|
+
url = f"https://modal.com/id/{app_id}"
|
|
283
|
+
|
|
284
|
+
open_url_and_display(url, "App dashboard", console)
|
|
@@ -13,6 +13,7 @@ from modal.client import _Client
|
|
|
13
13
|
from modal.config import config
|
|
14
14
|
from modal.container_process import _ContainerProcess
|
|
15
15
|
from modal.environments import ensure_env
|
|
16
|
+
from modal.exception import InvalidError
|
|
16
17
|
from modal.stream_type import StreamType
|
|
17
18
|
from modal_proto import api_pb2
|
|
18
19
|
|
|
@@ -52,7 +53,14 @@ def logs(
|
|
|
52
53
|
timestamps: bool = typer.Option(False, "--timestamps", help="Show timestamps for each log line"),
|
|
53
54
|
):
|
|
54
55
|
"""Show logs for a specific container, streaming while active."""
|
|
55
|
-
|
|
56
|
+
task_id, sandbox_id = None, None
|
|
57
|
+
if container_id.startswith("sb-"):
|
|
58
|
+
sandbox_id = container_id
|
|
59
|
+
elif container_id.startswith("ta-"):
|
|
60
|
+
task_id = container_id
|
|
61
|
+
else:
|
|
62
|
+
raise InvalidError(f"Invalid container ID: {container_id}")
|
|
63
|
+
stream_app_logs(task_id=task_id, sandbox_id=sandbox_id, show_timestamps=timestamps)
|
|
56
64
|
|
|
57
65
|
|
|
58
66
|
@container_cli.command("exec")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from modal._output import make_console
|
|
7
|
+
from modal._utils.async_utils import synchronizer
|
|
8
|
+
from modal._utils.browser_utils import open_url_and_display
|
|
9
|
+
from modal.client import _Client
|
|
10
|
+
from modal.config import config
|
|
11
|
+
from modal_proto import api_pb2
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@synchronizer.create_blocking
|
|
15
|
+
async def dashboard(
|
|
16
|
+
object_id: Optional[str] = typer.Argument(None, help="Open a view for a specific object."),
|
|
17
|
+
):
|
|
18
|
+
"""Open the Modal Dashboard in a web browser."""
|
|
19
|
+
console = make_console()
|
|
20
|
+
|
|
21
|
+
if object_id:
|
|
22
|
+
url = f"https://modal.com/id/{object_id}"
|
|
23
|
+
else:
|
|
24
|
+
env = config.get("environment")
|
|
25
|
+
request = api_pb2.WorkspaceDashboardUrlRequest(environment_name=env)
|
|
26
|
+
client = await _Client.from_env()
|
|
27
|
+
response = await client.stub.WorkspaceDashboardUrlGet(request)
|
|
28
|
+
url = response.url
|
|
29
|
+
|
|
30
|
+
open_url_and_display(url, "dashboard", console)
|
|
@@ -13,6 +13,7 @@ from .app import app_cli
|
|
|
13
13
|
from .cluster import cluster_cli
|
|
14
14
|
from .config import config_cli
|
|
15
15
|
from .container import container_cli
|
|
16
|
+
from .dashboard import dashboard
|
|
16
17
|
from .dict import dict_cli
|
|
17
18
|
from .environment import environment_cli
|
|
18
19
|
from .launch import launch_cli
|
|
@@ -118,6 +119,7 @@ async def setup(profile: Optional[str] = None):
|
|
|
118
119
|
entrypoint_cli_typer.command("deploy", no_args_is_help=True)(run.deploy)
|
|
119
120
|
entrypoint_cli_typer.command("serve", no_args_is_help=True)(run.serve)
|
|
120
121
|
entrypoint_cli_typer.command("shell")(shell_module.shell)
|
|
122
|
+
entrypoint_cli_typer.command("dashboard")(dashboard)
|
|
121
123
|
entrypoint_cli_typer.add_typer(launch_cli)
|
|
122
124
|
|
|
123
125
|
# Deployments
|
|
@@ -87,6 +87,8 @@ async def info():
|
|
|
87
87
|
console.print(f"[dim](Using {env_vars_str} environment variable{plural})[/dim]")
|
|
88
88
|
|
|
89
89
|
console.print(f"[bold]Token:[/bold] {resp.token_id}")
|
|
90
|
+
if resp.token_name:
|
|
91
|
+
console.print(f"[bold]Name:[/bold] {resp.token_name}")
|
|
90
92
|
console.print(f"[bold]Workspace:[/bold] {resp.workspace_name} [dim]({resp.workspace_id})[/dim]")
|
|
91
93
|
|
|
92
94
|
if resp.HasField("user_identity"):
|
|
@@ -21,14 +21,16 @@ from ..exception import NotFoundError
|
|
|
21
21
|
async def stream_app_logs(
|
|
22
22
|
app_id: Optional[str] = None,
|
|
23
23
|
task_id: Optional[str] = None,
|
|
24
|
+
sandbox_id: Optional[str] = None,
|
|
24
25
|
app_logs_url: Optional[str] = None,
|
|
25
26
|
show_timestamps: bool = False,
|
|
26
27
|
):
|
|
27
28
|
client = await _Client.from_env()
|
|
28
|
-
|
|
29
|
+
display_id = app_id or task_id or sandbox_id
|
|
30
|
+
output_mgr = OutputManager(status_spinner_text=f"Tailing logs for {display_id}", show_timestamps=show_timestamps)
|
|
29
31
|
try:
|
|
30
32
|
with output_mgr.show_status_spinner():
|
|
31
|
-
await get_app_logs_loop(client, output_mgr, app_id=app_id, task_id=task_id,
|
|
33
|
+
await get_app_logs_loop(client, output_mgr, app_id=app_id, task_id=task_id, sandbox_id=sandbox_id)
|
|
32
34
|
except asyncio.CancelledError:
|
|
33
35
|
pass
|
|
34
36
|
except KeyboardInterrupt:
|
|
@@ -12,6 +12,7 @@ from typer import Argument, Option, Typer
|
|
|
12
12
|
import modal
|
|
13
13
|
from modal._output import OutputManager, ProgressHandler, make_console
|
|
14
14
|
from modal._utils.async_utils import synchronizer
|
|
15
|
+
from modal._utils.browser_utils import open_url_and_display
|
|
15
16
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
16
17
|
from modal.cli._download import _volume_download
|
|
17
18
|
from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
|
|
@@ -310,3 +311,30 @@ async def rename(
|
|
|
310
311
|
)
|
|
311
312
|
|
|
312
313
|
await _Volume.rename(old_name, new_name, environment_name=env)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@volume_cli.command(
|
|
317
|
+
name="dashboard",
|
|
318
|
+
help="Open the Volume's dashboard page in your web browser.",
|
|
319
|
+
rich_help_panel="Management",
|
|
320
|
+
)
|
|
321
|
+
@synchronizer.create_blocking
|
|
322
|
+
async def dashboard(
|
|
323
|
+
volume_name: str = Argument(help="Name of the Volume to view in the dashboard."),
|
|
324
|
+
env: Optional[str] = ENV_OPTION,
|
|
325
|
+
):
|
|
326
|
+
"""Open a Volume's dashboard page in your web browser.
|
|
327
|
+
|
|
328
|
+
**Example:**
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
modal volume dashboard my-volume
|
|
332
|
+
```
|
|
333
|
+
"""
|
|
334
|
+
env = ensure_env(env)
|
|
335
|
+
volume = await _Volume.from_name(volume_name, environment_name=env).hydrate()
|
|
336
|
+
|
|
337
|
+
console = make_console()
|
|
338
|
+
url = f"https://modal.com/id/{volume.object_id}"
|
|
339
|
+
|
|
340
|
+
open_url_and_display(url, "Volume dashboard", console)
|
|
@@ -100,6 +100,12 @@ class _Client:
|
|
|
100
100
|
def is_closed(self) -> bool:
|
|
101
101
|
return self._closed
|
|
102
102
|
|
|
103
|
+
@property
|
|
104
|
+
def _is_localhost(self) -> bool:
|
|
105
|
+
"""Returns True if the server URL points to localhost."""
|
|
106
|
+
hostname = urllib.parse.urlparse(self.server_url).hostname
|
|
107
|
+
return hostname in {"localhost", "127.0.0.1", "::1", "172.21.0.1"}
|
|
108
|
+
|
|
103
109
|
@property
|
|
104
110
|
def stub(self) -> modal_api_grpc.ModalClientModal:
|
|
105
111
|
"""mdmd:hidden
|
|
@@ -31,11 +31,7 @@ class _Client:
|
|
|
31
31
|
client_type: int
|
|
32
32
|
|
|
33
33
|
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
server_url: str,
|
|
36
|
-
client_type: int,
|
|
37
|
-
credentials: typing.Optional[tuple[str, str]],
|
|
38
|
-
version: str = "1.3.1.dev34",
|
|
34
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.3.2"
|
|
39
35
|
):
|
|
40
36
|
"""mdmd:hidden
|
|
41
37
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -43,6 +39,11 @@ class _Client:
|
|
|
43
39
|
...
|
|
44
40
|
|
|
45
41
|
def is_closed(self) -> bool: ...
|
|
42
|
+
@property
|
|
43
|
+
def _is_localhost(self) -> bool:
|
|
44
|
+
"""Returns True if the server URL points to localhost."""
|
|
45
|
+
...
|
|
46
|
+
|
|
46
47
|
@property
|
|
47
48
|
def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal:
|
|
48
49
|
"""mdmd:hidden
|
|
@@ -162,11 +163,7 @@ class Client:
|
|
|
162
163
|
client_type: int
|
|
163
164
|
|
|
164
165
|
def __init__(
|
|
165
|
-
self,
|
|
166
|
-
server_url: str,
|
|
167
|
-
client_type: int,
|
|
168
|
-
credentials: typing.Optional[tuple[str, str]],
|
|
169
|
-
version: str = "1.3.1.dev34",
|
|
166
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.3.2"
|
|
170
167
|
):
|
|
171
168
|
"""mdmd:hidden
|
|
172
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -174,6 +171,11 @@ class Client:
|
|
|
174
171
|
...
|
|
175
172
|
|
|
176
173
|
def is_closed(self) -> bool: ...
|
|
174
|
+
@property
|
|
175
|
+
def _is_localhost(self) -> bool:
|
|
176
|
+
"""Returns True if the server URL points to localhost."""
|
|
177
|
+
...
|
|
178
|
+
|
|
177
179
|
@property
|
|
178
180
|
def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal:
|
|
179
181
|
"""mdmd:hidden
|
|
@@ -99,21 +99,27 @@ class _ServiceOptions:
|
|
|
99
99
|
"""Implement protobuf-like MergeFrom semantics for this dataclass.
|
|
100
100
|
|
|
101
101
|
This mostly exists to support "stacking" of `.with_options()` calls.
|
|
102
|
+
Returns a new _ServiceOptions instance without modifying self.
|
|
102
103
|
"""
|
|
104
|
+
# Create a shallow copy of self to start with
|
|
105
|
+
merged = dataclasses.replace(self)
|
|
106
|
+
|
|
103
107
|
# Don't use dataclasses.asdict() because it does a deepcopy(), which chokes on a hydrated object
|
|
104
108
|
new_options_dict = {k.name: getattr(new_options, k.name) for k in dataclasses.fields(new_options)}
|
|
105
109
|
|
|
106
110
|
# Resources needs special merge handling because individual fields are parameters in the public API
|
|
107
111
|
merged_resources = api_pb2.Resources()
|
|
108
|
-
if
|
|
109
|
-
merged_resources.MergeFrom(
|
|
112
|
+
if merged.resources:
|
|
113
|
+
merged_resources.MergeFrom(merged.resources)
|
|
110
114
|
if new_resources := new_options_dict.pop("resources"):
|
|
111
115
|
merged_resources.MergeFrom(new_resources)
|
|
112
|
-
|
|
116
|
+
merged.resources = merged_resources
|
|
113
117
|
|
|
114
118
|
for key, value in new_options_dict.items():
|
|
115
119
|
if value: # Only overwrite data when the value was set in the new options
|
|
116
|
-
setattr(
|
|
120
|
+
setattr(merged, key, value)
|
|
121
|
+
|
|
122
|
+
return merged
|
|
117
123
|
|
|
118
124
|
|
|
119
125
|
def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name: str):
|
|
@@ -743,32 +749,6 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
743
749
|
" please use the `.with_concurrency` method instead.",
|
|
744
750
|
)
|
|
745
751
|
|
|
746
|
-
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
747
|
-
# this is a bit confusing, the cls will always have the same metadata
|
|
748
|
-
# since it has the same *class* service function (i.e. "template")
|
|
749
|
-
# But the (instance) service function for each Obj will be different
|
|
750
|
-
# since it will rebind to whatever `_options` have been assigned on
|
|
751
|
-
# the particular Cls parent
|
|
752
|
-
if not self.is_hydrated:
|
|
753
|
-
# this should only happen for Cls.from_name instances
|
|
754
|
-
# other classes should already be hydrated!
|
|
755
|
-
await resolver.load(self, load_context)
|
|
756
|
-
|
|
757
|
-
new_cls._initialize_from_other(self)
|
|
758
|
-
|
|
759
|
-
def _deps():
|
|
760
|
-
return []
|
|
761
|
-
|
|
762
|
-
cls = _Cls._from_loader(
|
|
763
|
-
_load_from_base,
|
|
764
|
-
rep=f"{self._name}.with_options(...)",
|
|
765
|
-
is_another_app=True,
|
|
766
|
-
deps=_deps,
|
|
767
|
-
load_context_overrides=self._load_context_overrides,
|
|
768
|
-
hydrate_lazily=True,
|
|
769
|
-
)
|
|
770
|
-
cls._initialize_from_other(self)
|
|
771
|
-
|
|
772
752
|
# Validate volumes
|
|
773
753
|
validated_volumes = validate_volumes(volumes)
|
|
774
754
|
cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
|
|
@@ -801,8 +781,37 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
801
781
|
target_concurrent_inputs=allow_concurrent_inputs,
|
|
802
782
|
)
|
|
803
783
|
|
|
804
|
-
|
|
805
|
-
|
|
784
|
+
combined_options = self._options.merge_options(new_options)
|
|
785
|
+
|
|
786
|
+
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
787
|
+
# this is a bit confusing, the cls will always have the same metadata
|
|
788
|
+
# since it has the same *class* service function (i.e. "template")
|
|
789
|
+
# But the (instance) service function for each Obj will be different
|
|
790
|
+
# since it will rebind to whatever `_options` have been assigned on
|
|
791
|
+
# the particular Cls parent
|
|
792
|
+
if not self.is_hydrated:
|
|
793
|
+
# this should only happen for Cls.from_name instances
|
|
794
|
+
# other classes should already be hydrated!
|
|
795
|
+
await resolver.load(self, load_context)
|
|
796
|
+
|
|
797
|
+
new_cls._initialize_from_other(self)
|
|
798
|
+
# Restore the merged options after _initialize_from_other overwrites them
|
|
799
|
+
new_cls._options = combined_options
|
|
800
|
+
|
|
801
|
+
def _deps():
|
|
802
|
+
return []
|
|
803
|
+
|
|
804
|
+
new_cls = _Cls._from_loader(
|
|
805
|
+
_load_from_base,
|
|
806
|
+
rep=f"{self._name}.with_options(...)",
|
|
807
|
+
is_another_app=True,
|
|
808
|
+
deps=_deps,
|
|
809
|
+
load_context_overrides=self._load_context_overrides,
|
|
810
|
+
hydrate_lazily=True,
|
|
811
|
+
)
|
|
812
|
+
new_cls._initialize_from_other(self)
|
|
813
|
+
new_cls._options = combined_options
|
|
814
|
+
return new_cls
|
|
806
815
|
|
|
807
816
|
def with_concurrency(self: "_Cls", *, max_inputs: int, target_inputs: Optional[int] = None) -> "_Cls":
|
|
808
817
|
"""Create an instance of the Cls with input concurrency enabled or overridden with new values.
|
|
@@ -815,16 +824,20 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
815
824
|
ModelUsingGPU().generate.remote(42) # will run on an A100 GPU with input concurrency enabled
|
|
816
825
|
```
|
|
817
826
|
"""
|
|
827
|
+
concurrency_options = _ServiceOptions(max_concurrent_inputs=max_inputs, target_concurrent_inputs=target_inputs)
|
|
828
|
+
combined_options = self._options.merge_options(concurrency_options)
|
|
818
829
|
|
|
819
830
|
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
820
831
|
if not self.is_hydrated:
|
|
821
832
|
await resolver.load(self, load_context)
|
|
822
833
|
new_cls._initialize_from_other(self)
|
|
834
|
+
# Restore the merged options after _initialize_from_other overwrites them
|
|
835
|
+
new_cls._options = combined_options
|
|
823
836
|
|
|
824
837
|
def _deps():
|
|
825
838
|
return []
|
|
826
839
|
|
|
827
|
-
|
|
840
|
+
new_cls = _Cls._from_loader(
|
|
828
841
|
_load_from_base,
|
|
829
842
|
rep=f"{self._name}.with_concurrency(...)",
|
|
830
843
|
is_another_app=True,
|
|
@@ -832,11 +845,9 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
832
845
|
load_context_overrides=self._load_context_overrides,
|
|
833
846
|
hydrate_lazily=True,
|
|
834
847
|
)
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
cls._options.merge_options(concurrency_options)
|
|
839
|
-
return cls
|
|
848
|
+
new_cls._initialize_from_other(self)
|
|
849
|
+
new_cls._options = combined_options
|
|
850
|
+
return new_cls
|
|
840
851
|
|
|
841
852
|
def with_batching(self: "_Cls", *, max_batch_size: int, wait_ms: int) -> "_Cls":
|
|
842
853
|
"""Create an instance of the Cls with dynamic batching enabled or overridden with new values.
|
|
@@ -849,16 +860,20 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
849
860
|
ModelUsingGPU().generate.remote(42) # will run on an A100 GPU with input concurrency enabled
|
|
850
861
|
```
|
|
851
862
|
"""
|
|
863
|
+
batching_options = _ServiceOptions(batch_max_size=max_batch_size, batch_wait_ms=wait_ms)
|
|
864
|
+
combined_options = self._options.merge_options(batching_options)
|
|
852
865
|
|
|
853
866
|
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
854
867
|
if not self.is_hydrated:
|
|
855
868
|
await resolver.load(self, load_context)
|
|
856
869
|
new_cls._initialize_from_other(self)
|
|
870
|
+
# Restore the merged options after _initialize_from_other overwrites them
|
|
871
|
+
new_cls._options = combined_options
|
|
857
872
|
|
|
858
873
|
def _deps():
|
|
859
874
|
return []
|
|
860
875
|
|
|
861
|
-
|
|
876
|
+
new_cls = _Cls._from_loader(
|
|
862
877
|
_load_from_base,
|
|
863
878
|
rep=f"{self._name}.with_batching(...)",
|
|
864
879
|
is_another_app=True,
|
|
@@ -866,11 +881,9 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
866
881
|
load_context_overrides=self._load_context_overrides,
|
|
867
882
|
hydrate_lazily=True,
|
|
868
883
|
)
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
cls._options.merge_options(batching_options)
|
|
873
|
-
return cls
|
|
884
|
+
new_cls._initialize_from_other(self)
|
|
885
|
+
new_cls._options = combined_options
|
|
886
|
+
return new_cls
|
|
874
887
|
|
|
875
888
|
@synchronizer.no_input_translation
|
|
876
889
|
def __call__(self, *args, **kwargs) -> _Obj:
|
|
@@ -251,8 +251,6 @@ _SETTINGS = {
|
|
|
251
251
|
"traceback": _Setting(False, transform=_to_boolean),
|
|
252
252
|
"image_builder_version": _Setting(),
|
|
253
253
|
"strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
|
|
254
|
-
# Allow insecure TLS for the task command router when running locally (testing/dev only)
|
|
255
|
-
"task_command_router_insecure": _Setting(False, transform=_to_boolean),
|
|
256
254
|
"snapshot_debug": _Setting(False, transform=_to_boolean),
|
|
257
255
|
"cuda_checkpoint_path": _Setting("/__modal/.bin/cuda-checkpoint"), # Used for snapshotting GPU memory.
|
|
258
256
|
"build_validation": _Setting("error", transform=_check_value(["error", "warn", "ignore"])),
|