modal 1.4.2.dev14__tar.gz → 1.4.3.dev0__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.4.2.dev14 → modal-1.4.3.dev0}/PKG-INFO +1 -1
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/sandbox_fs_utils.py +37 -0
- modal-1.4.3.dev0/modal/cli/bootstrap.py +136 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/entry_point.py +8 -24
- modal-1.4.3.dev0/modal/cli/logo.py +70 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/selector.py +12 -4
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/client.pyi +2 -2
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/exception.py +6 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/file_io.py +2 -2
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/functions.pyi +6 -6
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/sandbox.py +20 -2
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/sandbox.pyi +30 -6
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/sandbox_fs.py +38 -1
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/sandbox_fs.pyi +84 -3
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/SOURCES.txt +2 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_version/__init__.py +1 -1
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/LICENSE +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/README.md +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/__main__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_billing.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_functions.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_grpc_client.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_ipython.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_load_context.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_location.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_logs.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_object.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/manager.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/pty.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/rich.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/status.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_partial_function.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_resolver.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_resources.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_serialization.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_server.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_traceback.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_tunnel.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_type_manager.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/version.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_watcher.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/app.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/app.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/billing.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/README.md +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/call_graph.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/_download.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/app.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/billing.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/changelog.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/config.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/container.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/dashboard.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/dict.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/environment.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/launch.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/profile.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/queues.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/run.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/secret.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/shell.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/token.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/utils.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/volume.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/client.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cls.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cls.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/config.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/container_process.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/container_process.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/dict.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/dict.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/environments.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/environments.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/experimental/flash.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/experimental/flash.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/file_io.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/functions.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/image.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/image.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/io_streams.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/mount.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/mount.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/network_file_system.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/object.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/object.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/output.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/parallel_map.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/partial_function.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/proxy.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/proxy.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/py.typed +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/queue.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/queue.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/retries.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/runner.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/runner.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/running_app.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/schedule.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/secret.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/secret.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/server.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/server.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/serving.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/serving.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/snapshot.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/stream_type.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/token_flow.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/volume.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/volume.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_grpc.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_pb2.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/py.typed +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_version/__main__.py +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/pyproject.toml +0 -0
- {modal-1.4.2.dev14 → modal-1.4.3.dev0}/setup.cfg +0 -0
|
@@ -18,6 +18,7 @@ from ..exception import (
|
|
|
18
18
|
SandboxFilesystemIsADirectoryError,
|
|
19
19
|
SandboxFilesystemNotADirectoryError,
|
|
20
20
|
SandboxFilesystemNotFoundError,
|
|
21
|
+
SandboxFilesystemPathAlreadyExistsError,
|
|
21
22
|
SandboxFilesystemPermissionError,
|
|
22
23
|
ServiceError,
|
|
23
24
|
)
|
|
@@ -188,6 +189,42 @@ def raise_remove_error(returncode: int, stderr: Union[str, bytes], remote_path:
|
|
|
188
189
|
raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
|
|
189
190
|
|
|
190
191
|
|
|
192
|
+
def make_make_directory_command(remote_path: str, create_parents: bool) -> str:
|
|
193
|
+
"""Build the JSON command string for a MakeDirectory operation.
|
|
194
|
+
|
|
195
|
+
The returned JSON must match the `Command` enum in the modal-sandbox-fs-tools
|
|
196
|
+
Rust crate (crates/modal-sandbox-fs-tools/src/lib.rs). Treat changes to
|
|
197
|
+
this schema like protobuf changes: fields must not be removed or renamed,
|
|
198
|
+
only added with backwards-compatible defaults.
|
|
199
|
+
"""
|
|
200
|
+
return json.dumps({"MakeDirectory": {"path": remote_path, "parents": create_parents}})
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def raise_make_directory_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
|
|
204
|
+
if payload := try_parse_error_payload(stderr):
|
|
205
|
+
logger.debug(
|
|
206
|
+
f"sandbox-fs-tools make_directory error: path={remote_path}, "
|
|
207
|
+
f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
|
|
208
|
+
)
|
|
209
|
+
if payload.error_kind == "NotFound":
|
|
210
|
+
raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
|
|
211
|
+
if payload.error_kind == "PathAlreadyExists":
|
|
212
|
+
raise SandboxFilesystemPathAlreadyExistsError(f"{payload.message}: {remote_path}")
|
|
213
|
+
if payload.error_kind == "NotDirectory":
|
|
214
|
+
raise SandboxFilesystemNotADirectoryError(f"{payload.message}: {remote_path}")
|
|
215
|
+
if payload.error_kind == "PermissionDenied":
|
|
216
|
+
raise SandboxFilesystemPermissionError(f"{payload.message}: {remote_path}")
|
|
217
|
+
if payload.error_kind == "NotSupported":
|
|
218
|
+
raise InvalidError(
|
|
219
|
+
f"{payload.message}: {remote_path} - this operation is not supported for CloudBucketMounts"
|
|
220
|
+
)
|
|
221
|
+
raise SandboxFilesystemError(payload.message)
|
|
222
|
+
|
|
223
|
+
if stderr_text := _stderr_to_text(stderr):
|
|
224
|
+
logger.debug(f"Unstructured modal-sandbox-fs-tools stderr: {stderr_text}")
|
|
225
|
+
raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
|
|
226
|
+
|
|
227
|
+
|
|
191
228
|
def validate_absolute_remote_path(remote_path: str, operation: str) -> None:
|
|
192
229
|
if not PurePosixPath(remote_path).is_absolute():
|
|
193
230
|
raise InvalidError(f"Sandbox.filesystem.{operation}() currently only supports absolute remote_path values")
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
import io
|
|
3
|
+
import shutil
|
|
4
|
+
import zipfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from modal._utils.async_utils import synchronizer
|
|
12
|
+
from modal._utils.http_utils import _http_client_with_tls
|
|
13
|
+
from modal.client import _Client
|
|
14
|
+
from modal.config import logger
|
|
15
|
+
from modal.output import OutputManager
|
|
16
|
+
from modal_proto import api_pb2
|
|
17
|
+
|
|
18
|
+
from .logo import GREEN, print_logo
|
|
19
|
+
from .selector import Selector
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@synchronizer.create_blocking
|
|
23
|
+
async def bootstrap(
|
|
24
|
+
name: Optional[str] = typer.Argument(None, help="The name of the template to load."),
|
|
25
|
+
output: str = typer.Option(".", "-o", "--output", help="Location for storing the template."),
|
|
26
|
+
force: bool = typer.Option(False, "--force", help="Overwrite the output directory if it already exists."),
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Initialize a sample Modal App."""
|
|
29
|
+
client = await _Client.from_env()
|
|
30
|
+
resp = await client.stub.TemplateList(api_pb2.TemplateListRequest())
|
|
31
|
+
names = sorted([item.name for item in resp.items])
|
|
32
|
+
|
|
33
|
+
output_manager = OutputManager.get()
|
|
34
|
+
print_logo()
|
|
35
|
+
|
|
36
|
+
if name is None:
|
|
37
|
+
if not names:
|
|
38
|
+
output_manager.print("No templates available. Try updating modal.")
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
try:
|
|
41
|
+
selector = Selector(names, title="Select a template", highlight_style=f"bold {GREEN}")
|
|
42
|
+
name = selector.run()
|
|
43
|
+
except Exception:
|
|
44
|
+
output_manager.print("Available templates:")
|
|
45
|
+
for n in names:
|
|
46
|
+
output_manager.print(f" - {n}")
|
|
47
|
+
output_manager.print("\nRun `modal bootstrap <name>` to select a template.")
|
|
48
|
+
raise typer.Exit(0)
|
|
49
|
+
|
|
50
|
+
item = next((item for item in resp.items if item.name == name), None)
|
|
51
|
+
if item is None:
|
|
52
|
+
output_manager.print(f"Unknown template: {name}")
|
|
53
|
+
output_manager.print(f"Available templates: {', '.join(names)}")
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
|
|
56
|
+
template_root = Path(output)
|
|
57
|
+
template_root.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
template_written_to_cwd = template_root.resolve() == Path.cwd().resolve()
|
|
59
|
+
|
|
60
|
+
dest = template_root / name
|
|
61
|
+
if dest.exists() and not force:
|
|
62
|
+
raise click.UsageError(f"Output path '{dest}' already exists. Use --force to overwrite.")
|
|
63
|
+
|
|
64
|
+
# Download the repo archive
|
|
65
|
+
ref = item.ref or "main"
|
|
66
|
+
archive_url = item.repo.rstrip("/") + f"/archive/refs/heads/{ref}.zip"
|
|
67
|
+
|
|
68
|
+
with output_manager.status(f"Downloading template '{name}'..."):
|
|
69
|
+
try:
|
|
70
|
+
session = _http_client_with_tls(timeout=30)
|
|
71
|
+
try:
|
|
72
|
+
async with session.get(archive_url) as resp_http:
|
|
73
|
+
resp_http.raise_for_status()
|
|
74
|
+
data = await resp_http.read()
|
|
75
|
+
finally:
|
|
76
|
+
await session.close()
|
|
77
|
+
except Exception as e:
|
|
78
|
+
output_manager.print(f"Failed to download template from {archive_url}: {e}")
|
|
79
|
+
raise typer.Exit(1)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
zf = zipfile.ZipFile(io.BytesIO(data))
|
|
83
|
+
all_names = zf.namelist()
|
|
84
|
+
root_prefix = all_names[0].split("/")[0] + "/"
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.debug(f"Failed to parse template archive: {e}")
|
|
87
|
+
output_manager.print(f"Unable to fetch template '{name}'.")
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
|
|
90
|
+
with zf:
|
|
91
|
+
template_prefix = root_prefix + name + "/"
|
|
92
|
+
logger.debug(f"Zip root: {root_prefix!r}, template: {template_prefix!r}, entries: {len(all_names)}")
|
|
93
|
+
for entry in all_names[:20]:
|
|
94
|
+
logger.debug(f" {entry}")
|
|
95
|
+
|
|
96
|
+
members = [m for m in all_names if m.startswith(template_prefix) and m != template_prefix]
|
|
97
|
+
logger.debug(f"Matched {len(members)} members under {template_prefix!r}")
|
|
98
|
+
|
|
99
|
+
if not members:
|
|
100
|
+
output_manager.print(f"Template '{name}' not found in repository.")
|
|
101
|
+
raise typer.Exit(1)
|
|
102
|
+
|
|
103
|
+
if dest.exists() and force:
|
|
104
|
+
shutil.rmtree(dest)
|
|
105
|
+
|
|
106
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
|
|
108
|
+
resolved_dest = dest.resolve()
|
|
109
|
+
for member in members:
|
|
110
|
+
rel_path = member[len(template_prefix) :]
|
|
111
|
+
out_path = (dest / rel_path).resolve()
|
|
112
|
+
if not out_path.is_relative_to(resolved_dest):
|
|
113
|
+
logger.debug(f"Zip entry escapes destination: {member!r}")
|
|
114
|
+
output_manager.print(f"Unable to fetch template '{name}'.")
|
|
115
|
+
raise typer.Exit(1)
|
|
116
|
+
if member.endswith("/"):
|
|
117
|
+
out_path.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
else:
|
|
119
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
out_path.write_bytes(zf.read(member))
|
|
121
|
+
|
|
122
|
+
output_manager.print(f"[{GREEN}]✓[/{GREEN}] Template '{name}' written to {dest}", highlight=False)
|
|
123
|
+
output_manager.print(f"[{GREEN}]→[/{GREEN}] To see it in action, run the following command:")
|
|
124
|
+
amp = " [dim white]&&[/dim white] "
|
|
125
|
+
if template_written_to_cwd:
|
|
126
|
+
steps = []
|
|
127
|
+
else:
|
|
128
|
+
steps = [f"[#ff8de6]cd {template_root}[/#ff8de6]"]
|
|
129
|
+
steps.extend(
|
|
130
|
+
[
|
|
131
|
+
f"[{GREEN}]modal deploy -m {name}.app[/{GREEN}]",
|
|
132
|
+
f"[#91c8ef]python -m {name}.try[/#91c8ef]",
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
command = amp.join(steps)
|
|
136
|
+
output_manager.print(command)
|
|
@@ -11,6 +11,7 @@ from modal.output import OutputManager
|
|
|
11
11
|
from . import run, shell as shell_module
|
|
12
12
|
from .app import app_cli
|
|
13
13
|
from .billing import billing_cli
|
|
14
|
+
from .bootstrap import bootstrap
|
|
14
15
|
from .changelog import changelog
|
|
15
16
|
from .cluster import cluster_cli
|
|
16
17
|
from .config import config_cli
|
|
@@ -19,6 +20,7 @@ from .dashboard import dashboard
|
|
|
19
20
|
from .dict import dict_cli
|
|
20
21
|
from .environment import environment_cli
|
|
21
22
|
from .launch import launch_cli
|
|
23
|
+
from .logo import print_logo
|
|
22
24
|
from .network_file_system import nfs_cli
|
|
23
25
|
from .profile import profile_cli
|
|
24
26
|
from .queues import queue_cli
|
|
@@ -88,28 +90,7 @@ def check_path():
|
|
|
88
90
|
@synchronizer.create_blocking
|
|
89
91
|
async def setup(profile: Optional[str] = None):
|
|
90
92
|
check_path()
|
|
91
|
-
|
|
92
|
-
art = """
|
|
93
|
-
############# #############
|
|
94
|
-
#### ## #### ##
|
|
95
|
-
## ## ## ## ## ##
|
|
96
|
-
## ## ## ## ## ##
|
|
97
|
-
## ## #### ## ##
|
|
98
|
-
## ############# ## ##
|
|
99
|
-
## ## #### ## ##
|
|
100
|
-
## ## ## ## ## ##
|
|
101
|
-
## ## ## ## ## ##
|
|
102
|
-
## ## ## ## ## ##
|
|
103
|
-
## ## ## ## ## ##
|
|
104
|
-
## ## ## ## #############
|
|
105
|
-
## ## ## ## ## ##
|
|
106
|
-
## ## ## ## ## ##
|
|
107
|
-
## ## ## ## ## ##
|
|
108
|
-
#### ## #### ##
|
|
109
|
-
############# #############
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
OutputManager.get().print(art, highlight=False, style="green")
|
|
93
|
+
print_logo()
|
|
113
94
|
|
|
114
95
|
# Fetch a new token (same as `modal token new` but redirect to /home once finishes)
|
|
115
96
|
await _new_token(profile=profile, next_url="/home")
|
|
@@ -145,8 +126,11 @@ entrypoint_cli_typer.add_typer(billing_cli, rich_help_panel="Observability")
|
|
|
145
126
|
entrypoint_cli_typer.command("changelog", rich_help_panel="Observability")(changelog)
|
|
146
127
|
entrypoint_cli_typer.command("dashboard", rich_help_panel="Observability")(dashboard)
|
|
147
128
|
|
|
148
|
-
#
|
|
149
|
-
entrypoint_cli_typer.command("setup", help="
|
|
129
|
+
# Onboarding
|
|
130
|
+
entrypoint_cli_typer.command("setup", help="Get started using Modal.", rich_help_panel="Onboarding")(setup)
|
|
131
|
+
entrypoint_cli_typer.command("bootstrap", help="Initialize a sample Modal App.", rich_help_panel="Onboarding")(
|
|
132
|
+
bootstrap
|
|
133
|
+
)
|
|
150
134
|
|
|
151
135
|
# Special handling for modal run, which is more complicated
|
|
152
136
|
entrypoint_cli = typer.main.get_command(entrypoint_cli_typer)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from modal.output import OutputManager
|
|
5
|
+
|
|
6
|
+
GREEN = "#7FEE64"
|
|
7
|
+
|
|
8
|
+
ASCII_LOGO = """
|
|
9
|
+
############# #############
|
|
10
|
+
#### ## #### ##
|
|
11
|
+
## ## ## ## ## ##
|
|
12
|
+
## ## ## ## ## ##
|
|
13
|
+
## ## #### ## ##
|
|
14
|
+
## ############# ## ##
|
|
15
|
+
## ## #### ## ##
|
|
16
|
+
## ## ## ## ## ##
|
|
17
|
+
## ## ## ## ## ##
|
|
18
|
+
## ## ## ## ## ##
|
|
19
|
+
## ## ## ## ## ##
|
|
20
|
+
## ## ## ## #############
|
|
21
|
+
## ## ## ## ## ##
|
|
22
|
+
## ## ## ## ## ##
|
|
23
|
+
## ## ## ## ## ##
|
|
24
|
+
#### ## #### ##
|
|
25
|
+
############# #############
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
UNICODE_LOGO_LARGE = """
|
|
29
|
+
█████████████ █████████████
|
|
30
|
+
████ ██ ████ ██
|
|
31
|
+
██ ██ ██ ██ ██ ██
|
|
32
|
+
██ ██ ██ ██ ██ ██
|
|
33
|
+
██ ██ ████ ██ ██
|
|
34
|
+
██ █████████████ ██ ██
|
|
35
|
+
██ ██ ████ ██ ██
|
|
36
|
+
██ ██ ██ ██ ██ ██
|
|
37
|
+
██ ██ ██ ██ ██ ██
|
|
38
|
+
██ ██ ██ ██ ██ ██
|
|
39
|
+
██ ██ ██ ██ ██ ██
|
|
40
|
+
██ ██ ██ ██ █████████████
|
|
41
|
+
██ ██ ██ ██ ██ ██
|
|
42
|
+
██ ██ ██ ██ ██ ██
|
|
43
|
+
██ ██ ██ ██ ██ ██
|
|
44
|
+
████ ██ ████ ██
|
|
45
|
+
█████████████ █████████████
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
UNICODE_LOGO_SMALL = """
|
|
49
|
+
▟█▀▀▀▀▜▖ ▗█▛▀▀▀▀▙
|
|
50
|
+
▟▘▝▙ ▜▖▗▛ ▜▖ ▝▙
|
|
51
|
+
▟▘ ▝▙▄▄▄▄█▛ ▜▖ ▝▙
|
|
52
|
+
▟▘ ▟▘ ▗▛▜▖ ▜▖ ▝▙
|
|
53
|
+
▟▘ ▟▘ ▗▛ ▜▖ ▜▖ ▝▙
|
|
54
|
+
▟▘ ▟▘ ▗▛ ▜▖ ▜▄▄▄▄▟▙
|
|
55
|
+
▝▙ ▟▘ ▗▛ ▜▖ ▗▛ ▟▘
|
|
56
|
+
▝▙▟▘ ▗▛ ▜▄▛ ▟▘
|
|
57
|
+
▝▀▀▀▀▀▀ ▀▀▀▀▀▀▘
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_logo():
|
|
62
|
+
if sys.stdout.encoding and sys.stdout.encoding.lower().startswith("utf"):
|
|
63
|
+
return UNICODE_LOGO_SMALL
|
|
64
|
+
else:
|
|
65
|
+
return ASCII_LOGO
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def print_logo():
|
|
69
|
+
if sys.stdout.isatty():
|
|
70
|
+
OutputManager.get().print(get_logo(), highlight=False, style=GREEN)
|
|
@@ -19,6 +19,13 @@ def _cbreak_terminal():
|
|
|
19
19
|
old_settings = termios.tcgetattr(fd)
|
|
20
20
|
try:
|
|
21
21
|
tty.setcbreak(fd, termios.TCSADRAIN)
|
|
22
|
+
# Disable ISIG so Ctrl-C is delivered as \x03 to os.read() rather than
|
|
23
|
+
# generating an asynchronous SIGINT. This lets _input_loop raise
|
|
24
|
+
# KeyboardInterrupt synchronously, ensuring context-manager cleanup
|
|
25
|
+
# (cursor visibility, terminal echo) completes without interruption.
|
|
26
|
+
attrs = termios.tcgetattr(fd)
|
|
27
|
+
attrs[3] &= ~termios.ISIG
|
|
28
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, attrs)
|
|
22
29
|
yield
|
|
23
30
|
finally:
|
|
24
31
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
@@ -44,10 +51,10 @@ class Selector:
|
|
|
44
51
|
exiting.
|
|
45
52
|
"""
|
|
46
53
|
|
|
47
|
-
def __init__(self, options: list[str], title: str = "Select an option"):
|
|
54
|
+
def __init__(self, options: list[str], title: str = "Select an option", highlight_style: str = "bold green"):
|
|
48
55
|
if not options:
|
|
49
56
|
raise ValueError("options must not be empty")
|
|
50
|
-
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
57
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty() or Console().is_dumb_terminal:
|
|
51
58
|
raise RuntimeError("Interactive selection requires a TTY")
|
|
52
59
|
# Fail fast on platforms without termios (e.g. Windows).
|
|
53
60
|
import termios # noqa: F401
|
|
@@ -55,6 +62,7 @@ class Selector:
|
|
|
55
62
|
self.options = options
|
|
56
63
|
self.title = title
|
|
57
64
|
self.selected = 0
|
|
65
|
+
self.highlight_style = highlight_style
|
|
58
66
|
|
|
59
67
|
# -- state manipulation ---------------------------------------------------
|
|
60
68
|
|
|
@@ -75,8 +83,8 @@ class Selector:
|
|
|
75
83
|
text.append(f"{self.title}\n\n", style="bold")
|
|
76
84
|
for i, opt in enumerate(self.options):
|
|
77
85
|
if i == self.selected:
|
|
78
|
-
text.append(" > ", style=
|
|
79
|
-
text.append(f"{opt}\n", style=
|
|
86
|
+
text.append(" > ", style=self.highlight_style)
|
|
87
|
+
text.append(f"{opt}\n", style=self.highlight_style)
|
|
80
88
|
else:
|
|
81
89
|
text.append(f" {opt}\n")
|
|
82
90
|
return text
|
|
@@ -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.4.
|
|
38
|
+
version: str = "1.4.3.dev0",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -175,7 +175,7 @@ class Client:
|
|
|
175
175
|
server_url: str,
|
|
176
176
|
client_type: int,
|
|
177
177
|
credentials: typing.Optional[tuple[str, str]],
|
|
178
|
-
version: str = "1.4.
|
|
178
|
+
version: str = "1.4.3.dev0",
|
|
179
179
|
):
|
|
180
180
|
"""mdmd:hidden
|
|
181
181
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -379,3 +379,9 @@ class SandboxFilesystemFileTooLargeError(SandboxFilesystemError):
|
|
|
379
379
|
"""Raised when a file exceeds the maximum allowed size for a read operation in the sandbox."""
|
|
380
380
|
|
|
381
381
|
pass
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class SandboxFilesystemPathAlreadyExistsError(SandboxFilesystemError):
|
|
385
|
+
"""Raised when a path already exists and the operation requires it to be absent."""
|
|
386
|
+
|
|
387
|
+
pass
|
|
@@ -406,7 +406,7 @@ class _FileIO(Generic[T]):
|
|
|
406
406
|
"""Create a new directory."""
|
|
407
407
|
deprecation_warning(
|
|
408
408
|
(2026, 3, 9),
|
|
409
|
-
"`FileIO.mkdir()` is deprecated. Use `Sandbox.
|
|
409
|
+
"`FileIO.mkdir()` is deprecated. Use `Sandbox.filesystem.make_directory()` instead.",
|
|
410
410
|
pending=True,
|
|
411
411
|
)
|
|
412
412
|
await mkdir(path, client, task_id, parents)
|
|
@@ -416,7 +416,7 @@ class _FileIO(Generic[T]):
|
|
|
416
416
|
"""Remove a file or directory in the Sandbox."""
|
|
417
417
|
deprecation_warning(
|
|
418
418
|
(2026, 3, 9),
|
|
419
|
-
"`FileIO.rm()` is deprecated. Use `Sandbox.
|
|
419
|
+
"`FileIO.rm()` is deprecated. Use `Sandbox.filesystem.remove()` instead.",
|
|
420
420
|
pending=True,
|
|
421
421
|
)
|
|
422
422
|
await rm(path, client, task_id, recursive)
|
|
@@ -347,7 +347,7 @@ class Function(
|
|
|
347
347
|
|
|
348
348
|
_call_generator: ___call_generator_spec
|
|
349
349
|
|
|
350
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
350
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
351
351
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
352
352
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
353
353
|
...
|
|
@@ -356,7 +356,7 @@ class Function(
|
|
|
356
356
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
357
357
|
...
|
|
358
358
|
|
|
359
|
-
remote: __remote_spec[modal._functions.
|
|
359
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
360
360
|
|
|
361
361
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
362
362
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -383,7 +383,7 @@ class Function(
|
|
|
383
383
|
"""
|
|
384
384
|
...
|
|
385
385
|
|
|
386
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
386
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
387
387
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
388
388
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
389
389
|
|
|
@@ -406,7 +406,7 @@ class Function(
|
|
|
406
406
|
"""
|
|
407
407
|
...
|
|
408
408
|
|
|
409
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
409
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
410
410
|
|
|
411
411
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
412
412
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -414,7 +414,7 @@ class Function(
|
|
|
414
414
|
|
|
415
415
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
416
416
|
|
|
417
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
417
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
418
418
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
419
419
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
420
420
|
|
|
@@ -435,7 +435,7 @@ class Function(
|
|
|
435
435
|
"""
|
|
436
436
|
...
|
|
437
437
|
|
|
438
|
-
spawn: __spawn_spec[modal._functions.
|
|
438
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
439
439
|
|
|
440
440
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
441
441
|
"""Return the inner Python object wrapped by this Modal Function."""
|
|
@@ -1647,14 +1647,32 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1647
1647
|
return await ls(path, self._client, task_id)
|
|
1648
1648
|
|
|
1649
1649
|
async def mkdir(self, path: str, parents: bool = False) -> None:
|
|
1650
|
-
"""[Alpha] Create a new directory in the Sandbox.
|
|
1650
|
+
"""[Alpha] Create a new directory in the Sandbox.
|
|
1651
|
+
|
|
1652
|
+
.. deprecated:: 2026-04-15
|
|
1653
|
+
Use `Sandbox.filesystem.make_directory()` instead.
|
|
1654
|
+
"""
|
|
1651
1655
|
self._ensure_v1("mkdir")
|
|
1656
|
+
deprecation_warning(
|
|
1657
|
+
(2026, 4, 15),
|
|
1658
|
+
"`Sandbox.mkdir()` is deprecated. Use `Sandbox.filesystem.make_directory()` instead.",
|
|
1659
|
+
pending=True,
|
|
1660
|
+
)
|
|
1652
1661
|
task_id = await self._get_task_id()
|
|
1653
1662
|
return await mkdir(path, self._client, task_id, parents)
|
|
1654
1663
|
|
|
1655
1664
|
async def rm(self, path: str, recursive: bool = False) -> None:
|
|
1656
|
-
"""[Alpha] Remove a file or directory in the Sandbox.
|
|
1665
|
+
"""[Alpha] Remove a file or directory in the Sandbox.
|
|
1666
|
+
|
|
1667
|
+
.. deprecated:: 2026-04-15
|
|
1668
|
+
Use `Sandbox.filesystem.remove()` instead.
|
|
1669
|
+
"""
|
|
1657
1670
|
self._ensure_v1("rm")
|
|
1671
|
+
deprecation_warning(
|
|
1672
|
+
(2026, 4, 15),
|
|
1673
|
+
"`Sandbox.rm()` is deprecated. Use `Sandbox.filesystem.remove()` instead.",
|
|
1674
|
+
pending=True,
|
|
1675
|
+
)
|
|
1658
1676
|
task_id = await self._get_task_id()
|
|
1659
1677
|
return await rm(path, self._client, task_id, recursive)
|
|
1660
1678
|
|
|
@@ -617,11 +617,19 @@ class _Sandbox(modal._object._Object):
|
|
|
617
617
|
...
|
|
618
618
|
|
|
619
619
|
async def mkdir(self, path: str, parents: bool = False) -> None:
|
|
620
|
-
"""[Alpha] Create a new directory in the Sandbox.
|
|
620
|
+
"""[Alpha] Create a new directory in the Sandbox.
|
|
621
|
+
|
|
622
|
+
.. deprecated:: 2026-04-15
|
|
623
|
+
Use `Sandbox.filesystem.make_directory()` instead.
|
|
624
|
+
"""
|
|
621
625
|
...
|
|
622
626
|
|
|
623
627
|
async def rm(self, path: str, recursive: bool = False) -> None:
|
|
624
|
-
"""[Alpha] Remove a file or directory in the Sandbox.
|
|
628
|
+
"""[Alpha] Remove a file or directory in the Sandbox.
|
|
629
|
+
|
|
630
|
+
.. deprecated:: 2026-04-15
|
|
631
|
+
Use `Sandbox.filesystem.remove()` instead.
|
|
632
|
+
"""
|
|
625
633
|
...
|
|
626
634
|
|
|
627
635
|
def watch(
|
|
@@ -1911,22 +1919,38 @@ class Sandbox(modal.object.Object):
|
|
|
1911
1919
|
|
|
1912
1920
|
class __mkdir_spec(typing_extensions.Protocol):
|
|
1913
1921
|
def __call__(self, /, path: str, parents: bool = False) -> None:
|
|
1914
|
-
"""[Alpha] Create a new directory in the Sandbox.
|
|
1922
|
+
"""[Alpha] Create a new directory in the Sandbox.
|
|
1923
|
+
|
|
1924
|
+
.. deprecated:: 2026-04-15
|
|
1925
|
+
Use `Sandbox.filesystem.make_directory()` instead.
|
|
1926
|
+
"""
|
|
1915
1927
|
...
|
|
1916
1928
|
|
|
1917
1929
|
async def aio(self, /, path: str, parents: bool = False) -> None:
|
|
1918
|
-
"""[Alpha] Create a new directory in the Sandbox.
|
|
1930
|
+
"""[Alpha] Create a new directory in the Sandbox.
|
|
1931
|
+
|
|
1932
|
+
.. deprecated:: 2026-04-15
|
|
1933
|
+
Use `Sandbox.filesystem.make_directory()` instead.
|
|
1934
|
+
"""
|
|
1919
1935
|
...
|
|
1920
1936
|
|
|
1921
1937
|
mkdir: __mkdir_spec
|
|
1922
1938
|
|
|
1923
1939
|
class __rm_spec(typing_extensions.Protocol):
|
|
1924
1940
|
def __call__(self, /, path: str, recursive: bool = False) -> None:
|
|
1925
|
-
"""[Alpha] Remove a file or directory in the Sandbox.
|
|
1941
|
+
"""[Alpha] Remove a file or directory in the Sandbox.
|
|
1942
|
+
|
|
1943
|
+
.. deprecated:: 2026-04-15
|
|
1944
|
+
Use `Sandbox.filesystem.remove()` instead.
|
|
1945
|
+
"""
|
|
1926
1946
|
...
|
|
1927
1947
|
|
|
1928
1948
|
async def aio(self, /, path: str, recursive: bool = False) -> None:
|
|
1929
|
-
"""[Alpha] Remove a file or directory in the Sandbox.
|
|
1949
|
+
"""[Alpha] Remove a file or directory in the Sandbox.
|
|
1950
|
+
|
|
1951
|
+
.. deprecated:: 2026-04-15
|
|
1952
|
+
Use `Sandbox.filesystem.remove()` instead.
|
|
1953
|
+
"""
|
|
1930
1954
|
...
|
|
1931
1955
|
|
|
1932
1956
|
rm: __rm_spec
|
|
@@ -11,9 +11,11 @@ from typing import Union, cast
|
|
|
11
11
|
from ._utils.async_utils import synchronize_api
|
|
12
12
|
from ._utils.logger import logger
|
|
13
13
|
from ._utils.sandbox_fs_utils import (
|
|
14
|
+
make_make_directory_command,
|
|
14
15
|
make_read_file_command,
|
|
15
16
|
make_remove_command,
|
|
16
17
|
make_write_file_command,
|
|
18
|
+
raise_make_directory_error,
|
|
17
19
|
raise_read_file_error,
|
|
18
20
|
raise_remove_error,
|
|
19
21
|
raise_write_file_error,
|
|
@@ -338,7 +340,7 @@ class _SandboxFilesystem:
|
|
|
338
340
|
To remove a directory and all its contents:
|
|
339
341
|
|
|
340
342
|
```python fixture:sandbox
|
|
341
|
-
sandbox.
|
|
343
|
+
sandbox.filesystem.make_directory("/tmp/mydir/subdir")
|
|
342
344
|
sandbox.filesystem.remove("/tmp/mydir", recursive=True)
|
|
343
345
|
```
|
|
344
346
|
"""
|
|
@@ -351,5 +353,40 @@ class _SandboxFilesystem:
|
|
|
351
353
|
if returncode != 0:
|
|
352
354
|
raise_remove_error(returncode, stderr, remote_path)
|
|
353
355
|
|
|
356
|
+
async def make_directory(self, remote_path: str, *, create_parents: bool = True) -> None:
|
|
357
|
+
"""Create a new directory in the Sandbox.
|
|
358
|
+
|
|
359
|
+
`remote_path` must be an absolute path in the Sandbox.
|
|
360
|
+
|
|
361
|
+
When `create_parents` is `True` (the default), any missing parent directories are created and the call is
|
|
362
|
+
idempotent (succeeds silently if the directory already exists). When `create_parents` is `False`, the
|
|
363
|
+
immediate parent directory must already exist and the path must not already exist.
|
|
364
|
+
|
|
365
|
+
**Raises**
|
|
366
|
+
|
|
367
|
+
- `SandboxFilesystemNotFoundError`: the parent directory does not exist and `create_parents` is `False`.
|
|
368
|
+
- `SandboxFilesystemPathAlreadyExistsError`: the path already exists.
|
|
369
|
+
- `SandboxFilesystemNotADirectoryError`: a path component is not a directory.
|
|
370
|
+
- `SandboxFilesystemPermissionError`: creation is not permitted.
|
|
371
|
+
- `InvalidError`: the operation is not supported by the mount.
|
|
372
|
+
- `SandboxFilesystemError`: the command fails for any other reason.
|
|
373
|
+
|
|
374
|
+
**Usage**
|
|
375
|
+
|
|
376
|
+
```python fixture:sandbox
|
|
377
|
+
sandbox.filesystem.make_directory("/tmp/a/b/c")
|
|
378
|
+
```
|
|
379
|
+
"""
|
|
380
|
+
validate_absolute_remote_path(remote_path, "make_directory")
|
|
381
|
+
|
|
382
|
+
with translate_exec_errors("make_directory", remote_path):
|
|
383
|
+
process = await self._sandbox.exec(
|
|
384
|
+
_SANDBOX_FS_TOOLS_PATH, make_make_directory_command(remote_path, create_parents)
|
|
385
|
+
)
|
|
386
|
+
stderr, returncode = await asyncio.gather(process.stderr.read(), process.wait())
|
|
387
|
+
|
|
388
|
+
if returncode != 0:
|
|
389
|
+
raise_make_directory_error(returncode, stderr, remote_path)
|
|
390
|
+
|
|
354
391
|
|
|
355
392
|
SandboxFilesystem = synchronize_api(_SandboxFilesystem)
|