modal 1.5.2.dev13__tar.gz → 1.5.2.dev14__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.5.2.dev13 → modal-1.5.2.dev14}/PKG-INFO +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/grpc_utils.py +49 -9
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/task_command_router_client.py +17 -5
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_workspace.py +80 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/container.py +16 -5
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/workspace.py +46 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/client.pyi +2 -2
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/config.py +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/functions.pyi +6 -6
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/sandbox.py +11 -10
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/sandbox.pyi +24 -3
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/workspace.py +5 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/workspace.pyi +129 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_version/__init__.py +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/LICENSE +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/README.md +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/__main__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_billing.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_clustered_functions.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_environments.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_function_variants.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_functions.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_grpc_client.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_image.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_ipython.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_load_context.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_location.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_logs.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_object.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/pty.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/rich.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/status.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_partial_function.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_resolver.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_resources.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_serialization.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_server.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_traceback.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_tunnel.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_tunnel.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_type_manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/logger.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/version.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_watcher.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/app.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/app.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/billing.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/billing.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/README.md +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/base-images.json +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/call_graph.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/_download.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/_help.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/_traceback.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/app.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/billing.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/changelog.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/cluster.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/config.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/curl.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/dashboard.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/dict.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/endpoint.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/entry_point.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/environment.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/image.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/import_refs.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/launch.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/logo.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/profile.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/queues.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/run.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/secret.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/selector.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/shell.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/skills.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/token.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/volume.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/client.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cls.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cls.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/container_process.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/container_process.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/dict.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/dict.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/environments.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/environments.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/exception.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/experimental/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/experimental/flash.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/experimental/ipython.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/file_io.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/file_io.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/functions.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/image.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/image.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/io_streams.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/io_streams.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/mount.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/mount.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/network_file_system.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/network_file_system.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/object.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/object.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/output.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/parallel_map.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/parallel_map.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/partial_function.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/partial_function.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/proxy.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/proxy.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/py.typed +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/queue.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/queue.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/retries.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/runner.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/runner.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/running_app.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/sandbox_fs.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/schedule.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/scheduler_placement.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/secret.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/secret.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/server.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/server.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/serving.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/serving.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/snapshot.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/snapshot.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/stream_type.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/token_flow.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/token_flow.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/volume.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/volume.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_pb2.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/py.typed +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_version/__main__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/pyproject.toml +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev14}/setup.cfg +0 -0
|
@@ -31,7 +31,7 @@ from modal_version import __version__
|
|
|
31
31
|
|
|
32
32
|
from .._traceback import suppress_tb_frame
|
|
33
33
|
from ..config import config
|
|
34
|
-
from .async_utils import retry
|
|
34
|
+
from .async_utils import TaskContext, retry
|
|
35
35
|
from .logger import logger
|
|
36
36
|
|
|
37
37
|
RequestType = TypeVar("RequestType", bound=Message)
|
|
@@ -190,14 +190,7 @@ class ConnectionManager:
|
|
|
190
190
|
|
|
191
191
|
async def get_or_create_channel(self, server_url: str) -> grpclib.client.Channel:
|
|
192
192
|
if server_url not in self._channels:
|
|
193
|
-
self._channels[server_url] =
|
|
194
|
-
server_url,
|
|
195
|
-
self._metadata,
|
|
196
|
-
)
|
|
197
|
-
try:
|
|
198
|
-
await connect_channel(self._channels[server_url])
|
|
199
|
-
except (OSError, asyncio.TimeoutError) as exc:
|
|
200
|
-
raise ConnectionError("Could not connect to the Modal server.") from exc
|
|
193
|
+
self._channels[server_url] = await create_channel_with_fallbacks(server_url, self._metadata)
|
|
201
194
|
return self._channels[server_url]
|
|
202
195
|
|
|
203
196
|
def close(self):
|
|
@@ -296,6 +289,12 @@ def create_channel(
|
|
|
296
289
|
for k, v in metadata.items():
|
|
297
290
|
event.metadata[k] = v
|
|
298
291
|
|
|
292
|
+
if o.hostname is not None:
|
|
293
|
+
# GRPC actually injects a :authority pseudo-header containing the hostname.
|
|
294
|
+
# But this gets stripped out by the C/C++ extension server-side and is hard to fix.
|
|
295
|
+
# So let's just add a separate header we can parse easily.
|
|
296
|
+
event.metadata["x-modal-host"] = o.hostname
|
|
297
|
+
|
|
299
298
|
idempotency_key = typing.cast(Optional[str], event.metadata.get("x-idempotency-key"))
|
|
300
299
|
if idempotency_key is None:
|
|
301
300
|
logger.debug(f"Sending request to {event.method_name}")
|
|
@@ -314,6 +313,47 @@ async def connect_channel(channel: grpclib.client.Channel):
|
|
|
314
313
|
await channel.__connect__()
|
|
315
314
|
|
|
316
315
|
|
|
316
|
+
async def create_channel_with_fallbacks(
|
|
317
|
+
server_url: str,
|
|
318
|
+
metadata: dict[str, str] = {},
|
|
319
|
+
stagger_delay: float = 1.0,
|
|
320
|
+
) -> grpclib.client.Channel:
|
|
321
|
+
"""Create a connected channel, supporting comma-separated fallback URLs.
|
|
322
|
+
|
|
323
|
+
`server_url` may be a single URL or a comma-separated list of URLs. The connection attempts
|
|
324
|
+
are raced against each other and the first one to connect wins, which lets the client fail
|
|
325
|
+
over to a secondary endpoint when the primary is unreachable (e.g. a DNS outage on the
|
|
326
|
+
primary's domain). Attempts are staggered: the candidate at index `i` waits `i * stagger_delay`
|
|
327
|
+
seconds before starting, so earlier URLs (e.g. the primary) get a head start and are preferred
|
|
328
|
+
when reachable. Losing attempts are cancelled and their channels closed.
|
|
329
|
+
"""
|
|
330
|
+
urls = [url.strip() for url in server_url.split(",")]
|
|
331
|
+
|
|
332
|
+
async def connect(index: int, url: str) -> grpclib.client.Channel:
|
|
333
|
+
# Stagger the start so earlier URLs are preferred; once started, all attempts race.
|
|
334
|
+
await asyncio.sleep(index * stagger_delay)
|
|
335
|
+
channel = create_channel(url, metadata)
|
|
336
|
+
try:
|
|
337
|
+
await connect_channel(channel)
|
|
338
|
+
except BaseException:
|
|
339
|
+
# Close the channel of a losing/cancelled attempt so its socket doesn't leak.
|
|
340
|
+
channel.close()
|
|
341
|
+
raise
|
|
342
|
+
return channel
|
|
343
|
+
|
|
344
|
+
first_exc: BaseException | None = None
|
|
345
|
+
async with TaskContext() as tc:
|
|
346
|
+
tasks = [tc.create_task(connect(i, url)) for i, url in enumerate(urls)]
|
|
347
|
+
for attempt in asyncio.as_completed(tasks):
|
|
348
|
+
try:
|
|
349
|
+
return await attempt
|
|
350
|
+
except Exception as exc:
|
|
351
|
+
if first_exc is None:
|
|
352
|
+
first_exc = exc
|
|
353
|
+
|
|
354
|
+
raise ConnectionError("Could not connect to the Modal server.") from first_exc
|
|
355
|
+
|
|
356
|
+
|
|
317
357
|
if typing.TYPE_CHECKING:
|
|
318
358
|
import modal.client
|
|
319
359
|
|
|
@@ -811,13 +811,25 @@ class TaskCommandRouterClient:
|
|
|
811
811
|
lambda: self._call_with_auth_retry(self._stub.TaskSetNetworkAccess, request)
|
|
812
812
|
)
|
|
813
813
|
|
|
814
|
-
async def reload_volumes(self, task_id: str) -> sr_pb2.TaskReloadVolumesResponse:
|
|
815
|
-
"""Reload all Volumes mounted in the task to reflect their latest committed state.
|
|
814
|
+
async def reload_volumes(self, task_id: str, timeout: float) -> sr_pb2.TaskReloadVolumesResponse:
|
|
815
|
+
"""Reload all Volumes mounted in the task to reflect their latest committed state.
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
task_id: The task whose mounted Volumes should be reloaded.
|
|
819
|
+
timeout: Client-side deadline in seconds. If the reload does not complete within this
|
|
820
|
+
window, the call is cancelled and a `modal.exception.TimeoutError` is raised.
|
|
821
|
+
"""
|
|
816
822
|
request = sr_pb2.TaskReloadVolumesRequest(task_id=task_id)
|
|
817
823
|
with grpc_error_converter():
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
824
|
+
try:
|
|
825
|
+
return await asyncio.wait_for(
|
|
826
|
+
call_with_retries_on_transient_errors(
|
|
827
|
+
lambda: self._call_with_auth_retry(self._stub.TaskReloadVolumes, request, timeout=timeout),
|
|
828
|
+
),
|
|
829
|
+
timeout=timeout,
|
|
830
|
+
)
|
|
831
|
+
except asyncio.TimeoutError:
|
|
832
|
+
raise ModalTimeoutError("Timeout expired")
|
|
821
833
|
|
|
822
834
|
async def _snapshot_with_deadline(self, rpc, request, *, timeout: float, **kwargs):
|
|
823
835
|
# helper method for snapshot_directory and snapshot_filesystem to handle grpc
|
|
@@ -125,6 +125,10 @@ class _Workspace(_Object, type_prefix="ac"):
|
|
|
125
125
|
def proxy_tokens(self) -> "_WorkspaceProxyTokenManager":
|
|
126
126
|
return _WorkspaceProxyTokenManager(self)
|
|
127
127
|
|
|
128
|
+
@property
|
|
129
|
+
def settings(self) -> "_WorkspaceSettingsManager":
|
|
130
|
+
return _WorkspaceSettingsManager(self)
|
|
131
|
+
|
|
128
132
|
|
|
129
133
|
@dataclass(frozen=True)
|
|
130
134
|
class TokenData:
|
|
@@ -340,3 +344,79 @@ class _WorkspaceBillingManager:
|
|
|
340
344
|
BillingReportItem._from_proto(pb_item)
|
|
341
345
|
async for pb_item in self._workspace.client.stub.WorkspaceBillingReport.unary_stream(request)
|
|
342
346
|
]
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@dataclass(frozen=True)
|
|
350
|
+
class WorkspaceSettings:
|
|
351
|
+
"""Current settings for the workspace."""
|
|
352
|
+
|
|
353
|
+
default_environment: str
|
|
354
|
+
image_builder_version: str
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class _WorkspaceSettingsManager:
|
|
358
|
+
"""mdmd:namespace
|
|
359
|
+
Namespace for Workspace settings APIs.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
@classmethod
|
|
363
|
+
def valid_settings(cls):
|
|
364
|
+
return ("default-environment", "image-builder-version")
|
|
365
|
+
|
|
366
|
+
def __init__(self, workspace: _Workspace):
|
|
367
|
+
"""mdmd:hidden"""
|
|
368
|
+
self._workspace = workspace
|
|
369
|
+
|
|
370
|
+
async def list(self):
|
|
371
|
+
"""Return a the current workspace settings.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
A `WorkspaceSettings` dataclass.
|
|
375
|
+
"""
|
|
376
|
+
if not self._workspace.is_hydrated:
|
|
377
|
+
await self._workspace.hydrate()
|
|
378
|
+
resp = await self._workspace.client.stub.WorkspaceSettings(Empty())
|
|
379
|
+
return WorkspaceSettings(
|
|
380
|
+
default_environment=resp.default_environment_name, image_builder_version=resp.image_builder_version
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
async def _set_image_builder_version(self, version: str) -> None:
|
|
384
|
+
"""mdmd:hidden
|
|
385
|
+
Set the image builder version for the Workspace.
|
|
386
|
+
"""
|
|
387
|
+
if not self._workspace.is_hydrated:
|
|
388
|
+
await self._workspace.hydrate()
|
|
389
|
+
req = api_pb2.WorkspaceSetImageBuilderVersionRequest(new_image_builder_version=version)
|
|
390
|
+
await self._workspace.client.stub.WorkspaceSetImageBuilderVersion(req)
|
|
391
|
+
|
|
392
|
+
async def _set_default_environment(self, name: str) -> None:
|
|
393
|
+
"""Set the default environment for the Workspace."""
|
|
394
|
+
if not self._workspace.is_hydrated:
|
|
395
|
+
await self._workspace.hydrate()
|
|
396
|
+
req = api_pb2.WorkspaceSetDefaultEnvironmentRequest(environment_name=name)
|
|
397
|
+
await self._workspace.client.stub.WorkspaceSetDefaultEnvironment(req)
|
|
398
|
+
|
|
399
|
+
async def set(self, name: str, value: str) -> None:
|
|
400
|
+
"""Set a workspace setting to a new value. Must be workspace manager or owner.
|
|
401
|
+
|
|
402
|
+
The following settings can be updated:
|
|
403
|
+
|
|
404
|
+
- image-builder-version: The image builder version determines the software included in our base images.
|
|
405
|
+
- default-environment: The default environment when the environment is omitted from SDK or CLI methods.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
name: The name of the setting.
|
|
409
|
+
value: The new value of the setting.
|
|
410
|
+
|
|
411
|
+
Examples:
|
|
412
|
+
```python notest
|
|
413
|
+
modal.Workspace.from_context().settings.set("default-environment", "dev")
|
|
414
|
+
```
|
|
415
|
+
"""
|
|
416
|
+
match name:
|
|
417
|
+
case "image-builder-version":
|
|
418
|
+
await self._set_image_builder_version(value)
|
|
419
|
+
case "default-environment":
|
|
420
|
+
await self._set_default_environment(value)
|
|
421
|
+
case _:
|
|
422
|
+
raise ValueError(f"Unknown setting {name!r}. Valid settings: {', '.join(self.valid_settings())}")
|
|
@@ -332,14 +332,25 @@ def exec(
|
|
|
332
332
|
|
|
333
333
|
@container_cli.command("stop")
|
|
334
334
|
@click.argument("container_id")
|
|
335
|
+
@click.option(
|
|
336
|
+
"--graceful",
|
|
337
|
+
is_flag=True,
|
|
338
|
+
default=False,
|
|
339
|
+
help="Let the container finish its current inputs before exiting, instead of cancelling them.",
|
|
340
|
+
)
|
|
335
341
|
@yes_option
|
|
336
342
|
@synchronizer.create_blocking
|
|
337
|
-
async def stop(container_id: str = "", *, yes: bool = False):
|
|
343
|
+
async def stop(container_id: str = "", *, graceful: bool = False, yes: bool = False):
|
|
338
344
|
"""Terminate a running container.
|
|
339
345
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
on other containers.
|
|
346
|
+
By default, this will send the container a SIGINT signal that Modal will handle.
|
|
347
|
+
For Functions, any inputs that are currently running on the container will be cancelled
|
|
348
|
+
and rescheduled on other containers.
|
|
349
|
+
|
|
350
|
+
With `--graceful`, the container will be allowed to finish the inputs it is currently
|
|
351
|
+
running, exiting once they complete. Graceful stops are only supported for containers
|
|
352
|
+
running a Modal Function.
|
|
353
|
+
|
|
343
354
|
"""
|
|
344
355
|
client = await _Client.from_env()
|
|
345
356
|
resp = await client.stub.TaskGetInfo(api_pb2.TaskGetInfoRequest(task_id=container_id))
|
|
@@ -347,5 +358,5 @@ async def stop(container_id: str = "", *, yes: bool = False):
|
|
|
347
358
|
raise SystemExit(f"Container '{container_id}' is already stopped.")
|
|
348
359
|
if not yes:
|
|
349
360
|
confirm_or_suggest_yes(f"Are you sure you want to stop container '{container_id}'?")
|
|
350
|
-
request = api_pb2.ContainerStopRequest(task_id=container_id)
|
|
361
|
+
request = api_pb2.ContainerStopRequest(task_id=container_id, graceful=graceful)
|
|
351
362
|
await client.stub.ContainerStop(request)
|
|
@@ -127,3 +127,49 @@ def proxy_tokens_delete(token_id: str, yes: bool = False):
|
|
|
127
127
|
confirm_or_suggest_yes(message)
|
|
128
128
|
Workspace.from_context().proxy_tokens.delete(token_id)
|
|
129
129
|
rich.print(f"[green]✓[/green] Deleted proxy token {token_id!r}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
settings_cli = ModalGroup(name="settings", help="Manage workspace settings. Must be workspace manager or owner.")
|
|
133
|
+
workspace_cli.add_command(settings_cli)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@settings_cli.command("list")
|
|
137
|
+
@click.option("--json", is_flag=True, default=False)
|
|
138
|
+
@click.pass_context
|
|
139
|
+
def settings_group(
|
|
140
|
+
ctx: click.Context,
|
|
141
|
+
json: bool,
|
|
142
|
+
):
|
|
143
|
+
"""View the current settings for the workspace."""
|
|
144
|
+
if ctx.invoked_subcommand is not None:
|
|
145
|
+
return
|
|
146
|
+
s = Workspace.from_context().settings.list()
|
|
147
|
+
rows = [
|
|
148
|
+
["default-environment", s.default_environment],
|
|
149
|
+
["image-builder-version", s.image_builder_version],
|
|
150
|
+
]
|
|
151
|
+
display_table(["Setting", "Value"], rows, json=json)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@settings_cli.command("set", no_args_is_help=True)
|
|
155
|
+
@click.argument("setting")
|
|
156
|
+
@click.argument("value")
|
|
157
|
+
def settings_set(setting: str, value: str):
|
|
158
|
+
"""Update a workspace setting. Must be workspace manager or owner.
|
|
159
|
+
|
|
160
|
+
The following settings can be updated:
|
|
161
|
+
- `image-builder-version`: The image builder version determines the software included in our base images.
|
|
162
|
+
- `default-environment`: The default environment to use when the environment is omitted from SDK or CLI methods.
|
|
163
|
+
|
|
164
|
+
Usage:
|
|
165
|
+
- `modal workspace settings set image-builder-version 2025.06`
|
|
166
|
+
- `modal workspace settings set default-environment main`
|
|
167
|
+
"""
|
|
168
|
+
ws = Workspace.from_context().hydrate()
|
|
169
|
+
s = ws.settings.list()
|
|
170
|
+
try:
|
|
171
|
+
ws.settings.set(setting, value)
|
|
172
|
+
rich.print(f"[green]✓[/green] {setting}: updated from {getattr(s, setting.replace('-', '_'))} to {value}")
|
|
173
|
+
except Exception as e:
|
|
174
|
+
rich.print(f"[red]✗[/red] {setting}: {e}")
|
|
175
|
+
raise SystemExit(1)
|
|
@@ -35,7 +35,7 @@ class _Client:
|
|
|
35
35
|
server_url: str,
|
|
36
36
|
client_type: int,
|
|
37
37
|
credentials: typing.Optional[tuple[str, str]],
|
|
38
|
-
version: str = "1.5.2.
|
|
38
|
+
version: str = "1.5.2.dev14",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -205,7 +205,7 @@ class Client:
|
|
|
205
205
|
server_url: str,
|
|
206
206
|
client_type: int,
|
|
207
207
|
credentials: typing.Optional[tuple[str, str]],
|
|
208
|
-
version: str = "1.5.2.
|
|
208
|
+
version: str = "1.5.2.dev14",
|
|
209
209
|
):
|
|
210
210
|
"""mdmd:hidden
|
|
211
211
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -106,7 +106,7 @@ from modal_proto import api_pb2
|
|
|
106
106
|
from ._utils.logger import configure_logger
|
|
107
107
|
from .exception import InvalidError, NotFoundError
|
|
108
108
|
|
|
109
|
-
DEFAULT_SERVER_URL = "https://api.modal.com"
|
|
109
|
+
DEFAULT_SERVER_URL = "https://api.modal.com" # TODO(erikbern): add modal2.com
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
# Locate config file and read it
|
|
@@ -491,7 +491,7 @@ class Function(
|
|
|
491
491
|
|
|
492
492
|
_call_generator: ___call_generator_spec
|
|
493
493
|
|
|
494
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
494
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
495
495
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
496
496
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
497
497
|
|
|
@@ -516,7 +516,7 @@ class Function(
|
|
|
516
516
|
"""
|
|
517
517
|
...
|
|
518
518
|
|
|
519
|
-
remote: __remote_spec[modal._functions.
|
|
519
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
|
|
520
520
|
|
|
521
521
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
522
522
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -566,7 +566,7 @@ class Function(
|
|
|
566
566
|
"""
|
|
567
567
|
...
|
|
568
568
|
|
|
569
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
569
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
570
570
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
571
571
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
572
572
|
|
|
@@ -599,7 +599,7 @@ class Function(
|
|
|
599
599
|
"""
|
|
600
600
|
...
|
|
601
601
|
|
|
602
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
602
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
603
603
|
|
|
604
604
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
605
605
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -607,7 +607,7 @@ class Function(
|
|
|
607
607
|
|
|
608
608
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
609
609
|
|
|
610
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
610
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
611
611
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
612
612
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
613
613
|
|
|
@@ -640,7 +640,7 @@ class Function(
|
|
|
640
640
|
"""
|
|
641
641
|
...
|
|
642
642
|
|
|
643
|
-
spawn: __spawn_spec[modal._functions.
|
|
643
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
644
644
|
|
|
645
645
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
646
646
|
"""Return the inner Python object wrapped by this Modal Function.
|
|
@@ -1637,22 +1637,23 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1637
1637
|
resp = await self._client.stub.SandboxCreateConnectToken(req)
|
|
1638
1638
|
return SandboxConnectCredentials(resp.url, resp.token)
|
|
1639
1639
|
|
|
1640
|
-
async def reload_volumes(self) -> None:
|
|
1640
|
+
async def reload_volumes(self, *, timeout: int = 55) -> None:
|
|
1641
1641
|
"""Reload all Volumes mounted in the Sandbox.
|
|
1642
1642
|
|
|
1643
1643
|
Added in v1.1.0.
|
|
1644
1644
|
|
|
1645
|
+
Blocks until the Volumes have been reloaded, bounded by `timeout` (55 seconds by default). If the reload
|
|
1646
|
+
does not complete within that window, `modal.exception.TimeoutError` is raised; note that the reload may
|
|
1647
|
+
still complete in the background.
|
|
1648
|
+
|
|
1649
|
+
Args:
|
|
1650
|
+
timeout: Maximum time in seconds to wait for the reload. Must be positive.
|
|
1645
1651
|
"""
|
|
1652
|
+
if timeout <= 0:
|
|
1653
|
+
raise InvalidError("The `timeout` argument to `Sandbox.reload_volumes` must be positive.")
|
|
1646
1654
|
task_id = await self._get_task_id()
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
await command_router_client.reload_volumes(task_id)
|
|
1650
|
-
else:
|
|
1651
|
-
await self._client.stub.ContainerReloadVolumes(
|
|
1652
|
-
api_pb2.ContainerReloadVolumesRequest(
|
|
1653
|
-
task_id=task_id,
|
|
1654
|
-
),
|
|
1655
|
-
)
|
|
1655
|
+
command_router_client = await self._get_command_router_client(task_id)
|
|
1656
|
+
await command_router_client.reload_volumes(task_id, timeout=float(timeout))
|
|
1656
1657
|
|
|
1657
1658
|
@overload
|
|
1658
1659
|
async def terminate(
|
|
@@ -711,10 +711,17 @@ class _Sandbox(modal._object._Object):
|
|
|
711
711
|
"""
|
|
712
712
|
...
|
|
713
713
|
|
|
714
|
-
async def reload_volumes(self) -> None:
|
|
714
|
+
async def reload_volumes(self, *, timeout: int = 55) -> None:
|
|
715
715
|
"""Reload all Volumes mounted in the Sandbox.
|
|
716
716
|
|
|
717
717
|
Added in v1.1.0.
|
|
718
|
+
|
|
719
|
+
Blocks until the Volumes have been reloaded, bounded by `timeout` (55 seconds by default). If the reload
|
|
720
|
+
does not complete within that window, `modal.exception.TimeoutError` is raised; note that the reload may
|
|
721
|
+
still complete in the background.
|
|
722
|
+
|
|
723
|
+
Args:
|
|
724
|
+
timeout: Maximum time in seconds to wait for the reload. Must be positive.
|
|
718
725
|
"""
|
|
719
726
|
...
|
|
720
727
|
|
|
@@ -2389,17 +2396,31 @@ class Sandbox(modal.object.Object):
|
|
|
2389
2396
|
create_connect_token: __create_connect_token_spec
|
|
2390
2397
|
|
|
2391
2398
|
class __reload_volumes_spec(typing_extensions.Protocol):
|
|
2392
|
-
def __call__(self,
|
|
2399
|
+
def __call__(self, /, *, timeout: int = 55) -> None:
|
|
2393
2400
|
"""Reload all Volumes mounted in the Sandbox.
|
|
2394
2401
|
|
|
2395
2402
|
Added in v1.1.0.
|
|
2403
|
+
|
|
2404
|
+
Blocks until the Volumes have been reloaded, bounded by `timeout` (55 seconds by default). If the reload
|
|
2405
|
+
does not complete within that window, `modal.exception.TimeoutError` is raised; note that the reload may
|
|
2406
|
+
still complete in the background.
|
|
2407
|
+
|
|
2408
|
+
Args:
|
|
2409
|
+
timeout: Maximum time in seconds to wait for the reload. Must be positive.
|
|
2396
2410
|
"""
|
|
2397
2411
|
...
|
|
2398
2412
|
|
|
2399
|
-
async def aio(self,
|
|
2413
|
+
async def aio(self, /, *, timeout: int = 55) -> None:
|
|
2400
2414
|
"""Reload all Volumes mounted in the Sandbox.
|
|
2401
2415
|
|
|
2402
2416
|
Added in v1.1.0.
|
|
2417
|
+
|
|
2418
|
+
Blocks until the Volumes have been reloaded, bounded by `timeout` (55 seconds by default). If the reload
|
|
2419
|
+
does not complete within that window, `modal.exception.TimeoutError` is raised; note that the reload may
|
|
2420
|
+
still complete in the background.
|
|
2421
|
+
|
|
2422
|
+
Args:
|
|
2423
|
+
timeout: Maximum time in seconds to wait for the reload. Must be positive.
|
|
2403
2424
|
"""
|
|
2404
2425
|
...
|
|
2405
2426
|
|
|
@@ -4,16 +4,19 @@ from ._workspace import (
|
|
|
4
4
|
ProxyTokenInfo,
|
|
5
5
|
TokenData,
|
|
6
6
|
WorkspaceMemberInfo,
|
|
7
|
+
WorkspaceSettings,
|
|
7
8
|
_Workspace,
|
|
8
9
|
_WorkspaceBillingManager,
|
|
9
10
|
_WorkspaceMembersManager,
|
|
10
11
|
_WorkspaceProxyTokenManager,
|
|
12
|
+
_WorkspaceSettingsManager,
|
|
11
13
|
)
|
|
12
14
|
|
|
13
15
|
Workspace = synchronize_api(_Workspace, target_module=__name__)
|
|
14
16
|
WorkspaceMembersManager = synchronize_api(_WorkspaceMembersManager, target_module=__name__)
|
|
15
17
|
WorkspaceBillingManager = synchronize_api(_WorkspaceBillingManager, target_module=__name__)
|
|
16
18
|
WorkspaceProxyTokenManager = synchronize_api(_WorkspaceProxyTokenManager, target_module=__name__)
|
|
19
|
+
WorkspaceSettingsManager = synchronize_api(_WorkspaceSettingsManager, target_module=__name__)
|
|
17
20
|
|
|
18
21
|
__all__ = [
|
|
19
22
|
"ProxyTokenInfo",
|
|
@@ -22,4 +25,6 @@ __all__ = [
|
|
|
22
25
|
"WorkspaceMemberInfo",
|
|
23
26
|
"WorkspaceMembersManager",
|
|
24
27
|
"WorkspaceProxyTokenManager",
|
|
28
|
+
"WorkspaceSettingsManager",
|
|
29
|
+
"WorkspaceSettings",
|
|
25
30
|
]
|
|
@@ -109,6 +109,36 @@ class WorkspaceMemberInfo:
|
|
|
109
109
|
"""Return hash(self)."""
|
|
110
110
|
...
|
|
111
111
|
|
|
112
|
+
class WorkspaceSettings:
|
|
113
|
+
"""Current settings for the workspace."""
|
|
114
|
+
|
|
115
|
+
default_environment: str
|
|
116
|
+
image_builder_version: str
|
|
117
|
+
|
|
118
|
+
def __init__(self, default_environment: str, image_builder_version: str) -> None:
|
|
119
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
120
|
+
...
|
|
121
|
+
|
|
122
|
+
def __repr__(self):
|
|
123
|
+
"""Return repr(self)."""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
def __eq__(self, other):
|
|
127
|
+
"""Return self==value."""
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
def __setattr__(self, name, value):
|
|
131
|
+
"""Implement setattr(self, name, value)."""
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
def __delattr__(self, name):
|
|
135
|
+
"""Implement delattr(self, name)."""
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
def __hash__(self):
|
|
139
|
+
"""Return hash(self)."""
|
|
140
|
+
...
|
|
141
|
+
|
|
112
142
|
class Workspace(modal.object.Object):
|
|
113
143
|
_name: typing.Optional[str]
|
|
114
144
|
|
|
@@ -135,6 +165,8 @@ class Workspace(modal.object.Object):
|
|
|
135
165
|
def billing(self) -> WorkspaceBillingManager: ...
|
|
136
166
|
@property
|
|
137
167
|
def proxy_tokens(self) -> WorkspaceProxyTokenManager: ...
|
|
168
|
+
@property
|
|
169
|
+
def settings(self) -> WorkspaceSettingsManager: ...
|
|
138
170
|
|
|
139
171
|
class WorkspaceMembersManager:
|
|
140
172
|
"""mdmd:namespace
|
|
@@ -448,3 +480,100 @@ class WorkspaceProxyTokenManager:
|
|
|
448
480
|
async def aio(self, /, environment_name: str) -> str: ...
|
|
449
481
|
|
|
450
482
|
_environment_id: ___environment_id_spec
|
|
483
|
+
|
|
484
|
+
class WorkspaceSettingsManager:
|
|
485
|
+
"""mdmd:namespace
|
|
486
|
+
Namespace for Workspace settings APIs.
|
|
487
|
+
"""
|
|
488
|
+
def __init__(self, workspace: Workspace):
|
|
489
|
+
"""mdmd:hidden"""
|
|
490
|
+
...
|
|
491
|
+
|
|
492
|
+
@classmethod
|
|
493
|
+
def valid_settings(cls): ...
|
|
494
|
+
|
|
495
|
+
class __list_spec(typing_extensions.Protocol):
|
|
496
|
+
def __call__(self, /):
|
|
497
|
+
"""Return a the current workspace settings.
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
A `WorkspaceSettings` dataclass.
|
|
501
|
+
"""
|
|
502
|
+
...
|
|
503
|
+
|
|
504
|
+
async def aio(self, /):
|
|
505
|
+
"""Return a the current workspace settings.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
A `WorkspaceSettings` dataclass.
|
|
509
|
+
"""
|
|
510
|
+
...
|
|
511
|
+
|
|
512
|
+
list: __list_spec
|
|
513
|
+
|
|
514
|
+
class ___set_image_builder_version_spec(typing_extensions.Protocol):
|
|
515
|
+
def __call__(self, /, version: str) -> None:
|
|
516
|
+
"""mdmd:hidden
|
|
517
|
+
Set the image builder version for the Workspace.
|
|
518
|
+
"""
|
|
519
|
+
...
|
|
520
|
+
|
|
521
|
+
async def aio(self, /, version: str) -> None:
|
|
522
|
+
"""mdmd:hidden
|
|
523
|
+
Set the image builder version for the Workspace.
|
|
524
|
+
"""
|
|
525
|
+
...
|
|
526
|
+
|
|
527
|
+
_set_image_builder_version: ___set_image_builder_version_spec
|
|
528
|
+
|
|
529
|
+
class ___set_default_environment_spec(typing_extensions.Protocol):
|
|
530
|
+
def __call__(self, /, name: str) -> None:
|
|
531
|
+
"""Set the default environment for the Workspace."""
|
|
532
|
+
...
|
|
533
|
+
|
|
534
|
+
async def aio(self, /, name: str) -> None:
|
|
535
|
+
"""Set the default environment for the Workspace."""
|
|
536
|
+
...
|
|
537
|
+
|
|
538
|
+
_set_default_environment: ___set_default_environment_spec
|
|
539
|
+
|
|
540
|
+
class __set_spec(typing_extensions.Protocol):
|
|
541
|
+
def __call__(self, /, name: str, value: str) -> None:
|
|
542
|
+
"""Set a workspace setting to a new value. Must be workspace manager or owner.
|
|
543
|
+
|
|
544
|
+
The following settings can be updated:
|
|
545
|
+
|
|
546
|
+
- image-builder-version: The image builder version determines the software included in our base images.
|
|
547
|
+
- default-environment: The default environment when the environment is omitted from SDK or CLI methods.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
name: The name of the setting.
|
|
551
|
+
value: The new value of the setting.
|
|
552
|
+
|
|
553
|
+
Examples:
|
|
554
|
+
```python notest
|
|
555
|
+
modal.Workspace.from_context().settings.set("default-environment", "dev")
|
|
556
|
+
```
|
|
557
|
+
"""
|
|
558
|
+
...
|
|
559
|
+
|
|
560
|
+
async def aio(self, /, name: str, value: str) -> None:
|
|
561
|
+
"""Set a workspace setting to a new value. Must be workspace manager or owner.
|
|
562
|
+
|
|
563
|
+
The following settings can be updated:
|
|
564
|
+
|
|
565
|
+
- image-builder-version: The image builder version determines the software included in our base images.
|
|
566
|
+
- default-environment: The default environment when the environment is omitted from SDK or CLI methods.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
name: The name of the setting.
|
|
570
|
+
value: The new value of the setting.
|
|
571
|
+
|
|
572
|
+
Examples:
|
|
573
|
+
```python notest
|
|
574
|
+
modal.Workspace.from_context().settings.set("default-environment", "dev")
|
|
575
|
+
```
|
|
576
|
+
"""
|
|
577
|
+
...
|
|
578
|
+
|
|
579
|
+
set: __set_spec
|
|
File without changes
|
|
File without changes
|
|
File without changes
|