modal 1.4.1.dev4__tar.gz → 1.4.2.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.1.dev4 → modal-1.4.2.dev0}/PKG-INFO +2 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/sandbox_fs_utils.py +35 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/task_command_router_client.py +5 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/time_utils.py +55 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/import_refs.py +13 -4
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/shell.py +5 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/client.py +1 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/client.pyi +2 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/dict.py +2 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/dict.pyi +4 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/exception.py +6 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/queue.py +2 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/queue.pyi +4 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/sandbox.py +19 -18
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/sandbox.pyi +19 -18
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/sandbox_fs.py +48 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/sandbox_fs.pyi +120 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/secret.py +2 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/secret.pyi +4 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/volume.py +2 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/volume.pyi +4 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/PKG-INFO +2 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/requires.txt +1 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_grpc.py +16 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_pb2.py +375 -355
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_pb2.pyi +35 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2.py +43 -30
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2.pyi +21 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_version/__init__.py +1 -1
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/pyproject.toml +2 -2
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/LICENSE +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/README.md +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/__main__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_billing.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_functions.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_grpc_client.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_ipython.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_load_context.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_location.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_logs.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_object.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/manager.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/pty.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/rich.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_output/status.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_partial_function.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_resolver.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_resources.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_serialization.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_server.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_traceback.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_tunnel.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_type_manager.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_vendor/version.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/_watcher.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/app.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/app.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/billing.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/README.md +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/call_graph.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/_download.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/app.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/billing.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/changelog.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/config.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/container.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/dashboard.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/dict.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/entry_point.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/environment.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/launch.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/profile.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/queues.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/run.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/secret.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/selector.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/token.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/utils.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cli/volume.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cls.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/cls.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/config.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/container_process.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/container_process.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/environments.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/environments.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/experimental/flash.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/experimental/flash.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/file_io.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/file_io.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/functions.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/functions.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/image.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/image.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/io_streams.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/mount.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/mount.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/network_file_system.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/object.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/object.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/output.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/parallel_map.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/partial_function.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/proxy.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/proxy.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/py.typed +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/retries.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/runner.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/runner.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/running_app.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/schedule.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/server.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/server.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/serving.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/serving.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/snapshot.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/stream_type.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/token_flow.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/py.typed +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/modal_version/__main__.py +0 -0
- {modal-1.4.1.dev4 → modal-1.4.2.dev0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2.dev0
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -23,7 +23,7 @@ Requires-Dist: grpclib<0.4.10,>=0.4.7; python_version < "3.14"
|
|
|
23
23
|
Requires-Dist: grpclib<0.4.10,>=0.4.9; python_version >= "3.14"
|
|
24
24
|
Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
|
|
25
25
|
Requires-Dist: rich>=12.0.0
|
|
26
|
-
Requires-Dist: synchronicity~=0.
|
|
26
|
+
Requires-Dist: synchronicity~=0.12.1
|
|
27
27
|
Requires-Dist: toml
|
|
28
28
|
Requires-Dist: typer>=0.9
|
|
29
29
|
Requires-Dist: types-certifi
|
|
@@ -12,6 +12,7 @@ from ..exception import (
|
|
|
12
12
|
Error as ModalError,
|
|
13
13
|
InvalidError,
|
|
14
14
|
NotFoundError,
|
|
15
|
+
SandboxFilesystemDirectoryNotEmptyError,
|
|
15
16
|
SandboxFilesystemError,
|
|
16
17
|
SandboxFilesystemFileTooLargeError,
|
|
17
18
|
SandboxFilesystemIsADirectoryError,
|
|
@@ -153,6 +154,40 @@ def make_read_file_command(remote_path: str) -> str:
|
|
|
153
154
|
return json.dumps({"ReadFile": {"path": remote_path}})
|
|
154
155
|
|
|
155
156
|
|
|
157
|
+
def make_remove_command(remote_path: str, recursive: bool) -> str:
|
|
158
|
+
"""Build the JSON command string for a Remove operation.
|
|
159
|
+
|
|
160
|
+
The returned JSON must match the `Command` enum in the modal-sandbox-fs-tools
|
|
161
|
+
Rust crate (crates/modal-sandbox-fs-tools/src/lib.rs). Treat changes to
|
|
162
|
+
this schema like protobuf changes: fields must not be removed or renamed,
|
|
163
|
+
only added with backwards-compatible defaults.
|
|
164
|
+
"""
|
|
165
|
+
return json.dumps({"Remove": {"path": remote_path, "recursive": recursive}})
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def raise_remove_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
|
|
169
|
+
if payload := try_parse_error_payload(stderr):
|
|
170
|
+
logger.debug(
|
|
171
|
+
f"sandbox-fs-tools remove error: path={remote_path}, "
|
|
172
|
+
f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
|
|
173
|
+
)
|
|
174
|
+
if payload.error_kind == "NotFound":
|
|
175
|
+
raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
|
|
176
|
+
if payload.error_kind == "DirectoryNotEmpty":
|
|
177
|
+
raise SandboxFilesystemDirectoryNotEmptyError(f"{payload.message}: {remote_path}")
|
|
178
|
+
if payload.error_kind == "NotSupported":
|
|
179
|
+
raise InvalidError(
|
|
180
|
+
f"{payload.message}: {remote_path} - this operation is not supported for CloudBucketMounts"
|
|
181
|
+
)
|
|
182
|
+
if payload.error_kind == "PermissionDenied":
|
|
183
|
+
raise SandboxFilesystemPermissionError(f"{payload.message}: {remote_path}")
|
|
184
|
+
raise SandboxFilesystemError(payload.message)
|
|
185
|
+
|
|
186
|
+
if stderr_text := _stderr_to_text(stderr):
|
|
187
|
+
logger.debug(f"Unstructured modal-sandbox-fs-tools stderr: {stderr_text}")
|
|
188
|
+
raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
|
|
189
|
+
|
|
190
|
+
|
|
156
191
|
def validate_absolute_remote_path(remote_path: str, operation: str) -> None:
|
|
157
192
|
if not PurePosixPath(remote_path).is_absolute():
|
|
158
193
|
raise InvalidError(f"Sandbox.filesystem.{operation}() currently only supports absolute remote_path values")
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import asyncio
|
|
3
3
|
import base64
|
|
4
4
|
import json
|
|
5
|
+
import socket
|
|
5
6
|
import ssl
|
|
6
7
|
import time
|
|
7
8
|
import urllib.parse
|
|
@@ -181,7 +182,10 @@ class TaskCommandRouterClient:
|
|
|
181
182
|
closed_error_message="Unable to perform operation on a detached sandbox",
|
|
182
183
|
)
|
|
183
184
|
|
|
184
|
-
|
|
185
|
+
try:
|
|
186
|
+
await _connect_channel(channel)
|
|
187
|
+
except socket.gaierror as exc:
|
|
188
|
+
raise ConnectionError(f"Could not resolve hostname '{host}': {exc}") from exc
|
|
185
189
|
loop = asyncio.get_running_loop()
|
|
186
190
|
jwt_refresh_lock = asyncio.Lock()
|
|
187
191
|
|
|
@@ -164,6 +164,61 @@ def parse_date_range(s: str, tz: Optional[tzinfo] = None) -> tuple[datetime, dat
|
|
|
164
164
|
raise ValueError(f"Unrecognized range: '{s}'. Accepted values: {accepted}")
|
|
165
165
|
|
|
166
166
|
|
|
167
|
+
def relative_timestamp(dt: datetime) -> str:
|
|
168
|
+
"""Convert a tz-aware datetime to a human-readable relative time string.
|
|
169
|
+
|
|
170
|
+
Examples: "just now", "30 seconds ago", "5 minutes ago", "2 hours ago",
|
|
171
|
+
"yesterday", "3 days ago", "2 weeks ago", "3 months ago", "1 year ago".
|
|
172
|
+
|
|
173
|
+
Raises ValueError if the datetime is naive (no tzinfo).
|
|
174
|
+
"""
|
|
175
|
+
if dt.tzinfo is None:
|
|
176
|
+
raise ValueError("datetime must be timezone-aware")
|
|
177
|
+
|
|
178
|
+
now = datetime.now(timezone.utc)
|
|
179
|
+
delta = now - dt
|
|
180
|
+
total_seconds = int(delta.total_seconds())
|
|
181
|
+
|
|
182
|
+
if total_seconds < 0:
|
|
183
|
+
return "just now"
|
|
184
|
+
|
|
185
|
+
if total_seconds < 10:
|
|
186
|
+
return "just now"
|
|
187
|
+
if total_seconds < 60:
|
|
188
|
+
return f"{total_seconds} seconds ago"
|
|
189
|
+
if total_seconds < 120:
|
|
190
|
+
return "1 minute ago"
|
|
191
|
+
|
|
192
|
+
minutes = total_seconds // 60
|
|
193
|
+
if minutes < 60:
|
|
194
|
+
return f"{minutes} minutes ago"
|
|
195
|
+
if minutes < 120:
|
|
196
|
+
return "1 hour ago"
|
|
197
|
+
|
|
198
|
+
hours = minutes // 60
|
|
199
|
+
if hours < 24:
|
|
200
|
+
return f"{hours} hours ago"
|
|
201
|
+
if hours < 48:
|
|
202
|
+
return "yesterday"
|
|
203
|
+
|
|
204
|
+
days = hours // 24
|
|
205
|
+
if days < 14:
|
|
206
|
+
return f"{days} days ago"
|
|
207
|
+
|
|
208
|
+
weeks = days // 7
|
|
209
|
+
if days < 60:
|
|
210
|
+
return f"{weeks} weeks ago"
|
|
211
|
+
|
|
212
|
+
months = days // 30
|
|
213
|
+
if days < 365:
|
|
214
|
+
return f"{months} months ago"
|
|
215
|
+
|
|
216
|
+
years = days // 365
|
|
217
|
+
if years == 1:
|
|
218
|
+
return "1 year ago"
|
|
219
|
+
return f"{years} years ago"
|
|
220
|
+
|
|
221
|
+
|
|
167
222
|
def locale_tz() -> tzinfo:
|
|
168
223
|
return datetime.now().astimezone().tzinfo
|
|
169
224
|
|
|
@@ -127,6 +127,7 @@ class AutoRunPriority:
|
|
|
127
127
|
|
|
128
128
|
def list_cli_commands(
|
|
129
129
|
module_members: dict[str, typing.Any],
|
|
130
|
+
include_service_functions: bool = False,
|
|
130
131
|
) -> list[CLICommand]:
|
|
131
132
|
"""
|
|
132
133
|
Extracts all runnables found either directly in the input module, or in any of the Apps listed in that module
|
|
@@ -149,8 +150,8 @@ def list_cli_commands(
|
|
|
149
150
|
all_runnables[local_entrypoint].append(f"{app_name}.{name}")
|
|
150
151
|
priorities[local_entrypoint] = AutoRunPriority.APP_LOCAL_ENTRYPOINT
|
|
151
152
|
for name, function in app.registered_functions.items():
|
|
152
|
-
if function.info.is_service_class():
|
|
153
|
-
continue # Skip class and server service functions
|
|
153
|
+
if function.info.is_service_class() and not include_service_functions:
|
|
154
|
+
continue # Skip class and server service functions for all commands except `modal shell`.
|
|
154
155
|
all_runnables[function].append(f"{app_name}.{name}")
|
|
155
156
|
priorities[function] = AutoRunPriority.APP_FUNCTION
|
|
156
157
|
for cls_name, cls in app.registered_classes.items():
|
|
@@ -322,7 +323,12 @@ def _get_runnable_app(runnable: Runnable) -> App:
|
|
|
322
323
|
|
|
323
324
|
|
|
324
325
|
def import_and_filter(
|
|
325
|
-
import_ref: ImportRef,
|
|
326
|
+
import_ref: ImportRef,
|
|
327
|
+
*,
|
|
328
|
+
base_cmd: str,
|
|
329
|
+
accept_local_entrypoint=True,
|
|
330
|
+
accept_webhook=False,
|
|
331
|
+
accept_service_functions=False,
|
|
326
332
|
) -> tuple[Optional[Runnable], list[CLICommand]]:
|
|
327
333
|
"""Takes a function ref string and returns a single determined "runnable" to use, and a list of all available
|
|
328
334
|
runnables.
|
|
@@ -339,7 +345,10 @@ def import_and_filter(
|
|
|
339
345
|
"""
|
|
340
346
|
# all commands:
|
|
341
347
|
module = import_file_or_module(import_ref, base_cmd)
|
|
342
|
-
cli_commands = list_cli_commands(
|
|
348
|
+
cli_commands = list_cli_commands(
|
|
349
|
+
dict(inspect.getmembers(module)),
|
|
350
|
+
include_service_functions=accept_service_functions,
|
|
351
|
+
)
|
|
343
352
|
|
|
344
353
|
# all commands that satisfy local entrypoint/accept webhook limitations AND object path prefix
|
|
345
354
|
|
|
@@ -142,7 +142,11 @@ def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
|
|
|
142
142
|
def _function_spec_from_ref(ref: str, use_module_mode: bool) -> _FunctionSpec:
|
|
143
143
|
import_ref = parse_import_ref(ref, use_module_mode=use_module_mode)
|
|
144
144
|
runnable, all_usable_commands = import_and_filter(
|
|
145
|
-
import_ref,
|
|
145
|
+
import_ref,
|
|
146
|
+
base_cmd="modal shell",
|
|
147
|
+
accept_local_entrypoint=False,
|
|
148
|
+
accept_webhook=True,
|
|
149
|
+
accept_service_functions=True,
|
|
146
150
|
)
|
|
147
151
|
if not runnable:
|
|
148
152
|
help_header = (
|
|
@@ -56,8 +56,7 @@ def _get_metadata(client_type: int, credentials: Optional[tuple[str, str]], vers
|
|
|
56
56
|
"x-modal-token-secret": token_secret,
|
|
57
57
|
}
|
|
58
58
|
)
|
|
59
|
-
agent_env
|
|
60
|
-
if agent_env:
|
|
59
|
+
if agent_env := _agent_environment():
|
|
61
60
|
metadata["x-modal-agent-harness"] = urllib.parse.quote(agent_env)
|
|
62
61
|
return metadata
|
|
63
62
|
|
|
@@ -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.2.dev0",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -171,7 +171,7 @@ class Client:
|
|
|
171
171
|
server_url: str,
|
|
172
172
|
client_type: int,
|
|
173
173
|
credentials: typing.Optional[tuple[str, str]],
|
|
174
|
-
version: str = "1.4.
|
|
174
|
+
version: str = "1.4.2.dev0",
|
|
175
175
|
):
|
|
176
176
|
"""mdmd:hidden
|
|
177
177
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -412,7 +412,8 @@ class _Dict(modal._object._Object):
|
|
|
412
412
|
...
|
|
413
413
|
|
|
414
414
|
@synchronicity.classproperty
|
|
415
|
-
|
|
415
|
+
@classmethod
|
|
416
|
+
def objects(cls) -> type[_DictManager]: ...
|
|
416
417
|
@property
|
|
417
418
|
def name(self) -> typing.Optional[str]: ...
|
|
418
419
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
@@ -653,7 +654,8 @@ class Dict(modal.object.Object):
|
|
|
653
654
|
...
|
|
654
655
|
|
|
655
656
|
@synchronicity.classproperty
|
|
656
|
-
|
|
657
|
+
@classmethod
|
|
658
|
+
def objects(cls) -> type[DictManager]: ...
|
|
657
659
|
@property
|
|
658
660
|
def name(self) -> typing.Optional[str]: ...
|
|
659
661
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
@@ -351,6 +351,12 @@ class SandboxFilesystemNotFoundError(SandboxFilesystemError):
|
|
|
351
351
|
pass
|
|
352
352
|
|
|
353
353
|
|
|
354
|
+
class SandboxFilesystemDirectoryNotEmptyError(SandboxFilesystemError):
|
|
355
|
+
"""Raised when a directory is not empty."""
|
|
356
|
+
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
|
|
354
360
|
class SandboxFilesystemIsADirectoryError(SandboxFilesystemError):
|
|
355
361
|
"""Raised when a file operation in the sandbox targets a directory when it should target a non-directory file."""
|
|
356
362
|
|
|
@@ -291,7 +291,8 @@ class _Queue(_Object, type_prefix="qu"):
|
|
|
291
291
|
raise RuntimeError("Queue() is not allowed. Please use `Queue.from_name(...)` or `Queue.ephemeral()` instead.")
|
|
292
292
|
|
|
293
293
|
@classproperty
|
|
294
|
-
|
|
294
|
+
@classmethod
|
|
295
|
+
def objects(cls) -> type[_QueueManager]:
|
|
295
296
|
return _QueueManager
|
|
296
297
|
|
|
297
298
|
@property
|
|
@@ -431,7 +431,8 @@ class _Queue(modal._object._Object):
|
|
|
431
431
|
...
|
|
432
432
|
|
|
433
433
|
@synchronicity.classproperty
|
|
434
|
-
|
|
434
|
+
@classmethod
|
|
435
|
+
def objects(cls) -> type[_QueueManager]: ...
|
|
435
436
|
@property
|
|
436
437
|
def name(self) -> typing.Optional[str]: ...
|
|
437
438
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
@@ -726,7 +727,8 @@ class Queue(modal.object.Object):
|
|
|
726
727
|
...
|
|
727
728
|
|
|
728
729
|
@synchronicity.classproperty
|
|
729
|
-
|
|
730
|
+
@classmethod
|
|
731
|
+
def objects(cls) -> type[QueueManager]: ...
|
|
730
732
|
@property
|
|
731
733
|
def name(self) -> typing.Optional[str]: ...
|
|
732
734
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
@@ -123,24 +123,25 @@ class SandboxConnectCredentials:
|
|
|
123
123
|
class Probe:
|
|
124
124
|
"""Probe configuration for the Sandbox Readiness Probe.
|
|
125
125
|
|
|
126
|
-
Usage
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
126
|
+
**Usage**
|
|
127
|
+
|
|
128
|
+
```python notest
|
|
129
|
+
# Wait until a file exists.
|
|
130
|
+
readiness_probe = modal.Probe.with_exec(
|
|
131
|
+
"sh", "-c", "test -f /tmp/ready",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Wait until a TCP port is accepting connections.
|
|
135
|
+
readiness_probe = modal.Probe.with_tcp(8080)
|
|
136
|
+
|
|
137
|
+
app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
|
|
138
|
+
sandbox = modal.Sandbox.create(
|
|
139
|
+
"python3", "-m", "http.server", "8080",
|
|
140
|
+
readiness_probe=readiness_probe,
|
|
141
|
+
app=app,
|
|
142
|
+
)
|
|
143
|
+
sandbox.wait_until_ready()
|
|
144
|
+
```
|
|
144
145
|
"""
|
|
145
146
|
|
|
146
147
|
tcp_port: Optional[int] = None
|
|
@@ -74,24 +74,25 @@ class SandboxConnectCredentials:
|
|
|
74
74
|
class Probe:
|
|
75
75
|
"""Probe configuration for the Sandbox Readiness Probe.
|
|
76
76
|
|
|
77
|
-
Usage
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
77
|
+
**Usage**
|
|
78
|
+
|
|
79
|
+
```python notest
|
|
80
|
+
# Wait until a file exists.
|
|
81
|
+
readiness_probe = modal.Probe.with_exec(
|
|
82
|
+
"sh", "-c", "test -f /tmp/ready",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Wait until a TCP port is accepting connections.
|
|
86
|
+
readiness_probe = modal.Probe.with_tcp(8080)
|
|
87
|
+
|
|
88
|
+
app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
|
|
89
|
+
sandbox = modal.Sandbox.create(
|
|
90
|
+
"python3", "-m", "http.server", "8080",
|
|
91
|
+
readiness_probe=readiness_probe,
|
|
92
|
+
app=app,
|
|
93
|
+
)
|
|
94
|
+
sandbox.wait_until_ready()
|
|
95
|
+
```
|
|
95
96
|
"""
|
|
96
97
|
|
|
97
98
|
tcp_port: typing.Optional[int]
|
|
@@ -12,8 +12,10 @@ from ._utils.async_utils import synchronize_api
|
|
|
12
12
|
from ._utils.logger import logger
|
|
13
13
|
from ._utils.sandbox_fs_utils import (
|
|
14
14
|
make_read_file_command,
|
|
15
|
+
make_remove_command,
|
|
15
16
|
make_write_file_command,
|
|
16
17
|
raise_read_file_error,
|
|
18
|
+
raise_remove_error,
|
|
17
19
|
raise_write_file_error,
|
|
18
20
|
translate_exec_errors,
|
|
19
21
|
validate_absolute_remote_path,
|
|
@@ -303,5 +305,51 @@ class _SandboxFilesystem:
|
|
|
303
305
|
dur_s = max(time.monotonic() - t0, 0.001)
|
|
304
306
|
_log_throughput(f"copy_from_local {local_path} -> {remote_path}", total_bytes, dur_s)
|
|
305
307
|
|
|
308
|
+
async def remove(self, remote_path: str, *, recursive: bool = False) -> None:
|
|
309
|
+
"""Remove a file or directory in the Sandbox.
|
|
310
|
+
|
|
311
|
+
`remote_path` must be an absolute path in the Sandbox.
|
|
312
|
+
|
|
313
|
+
When `remote_path` is a directory and `recursive` is `False` (the
|
|
314
|
+
default), removes it only if it is empty. When `recursive` is `True`,
|
|
315
|
+
removes the directory and all its contents.
|
|
316
|
+
|
|
317
|
+
Recursive directory removal is not supported on all mounts.
|
|
318
|
+
In particular, `CloudBucketMount` does not support it. An
|
|
319
|
+
`InvalidError` is raised in that case.
|
|
320
|
+
|
|
321
|
+
**Raises**
|
|
322
|
+
|
|
323
|
+
- `SandboxFilesystemNotFoundError`: the path does not exist.
|
|
324
|
+
- `SandboxFilesystemDirectoryNotEmptyError`: `recursive` is `False` and the directory is not empty.
|
|
325
|
+
- `SandboxFilesystemPermissionError`: removal is not permitted.
|
|
326
|
+
- `InvalidError`: the operation is not supported by the mount.
|
|
327
|
+
- `SandboxFilesystemError`: the command fails for any other reason.
|
|
328
|
+
|
|
329
|
+
**Usage**
|
|
330
|
+
|
|
331
|
+
To remove a file:
|
|
332
|
+
|
|
333
|
+
```python fixture:sandbox
|
|
334
|
+
sandbox.filesystem.write_bytes(b"Hello, world!\\n", "/tmp/hello.bin")
|
|
335
|
+
sandbox.filesystem.remove("/tmp/hello.bin")
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
To remove a directory and all its contents:
|
|
339
|
+
|
|
340
|
+
```python fixture:sandbox
|
|
341
|
+
sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
|
|
342
|
+
sandbox.filesystem.remove("/tmp/mydir", recursive=True)
|
|
343
|
+
```
|
|
344
|
+
"""
|
|
345
|
+
validate_absolute_remote_path(remote_path, "remove")
|
|
346
|
+
|
|
347
|
+
with translate_exec_errors("remove", remote_path):
|
|
348
|
+
process = await self._sandbox.exec(_SANDBOX_FS_TOOLS_PATH, make_remove_command(remote_path, recursive))
|
|
349
|
+
stderr, returncode = await asyncio.gather(process.stderr.read(), process.wait())
|
|
350
|
+
|
|
351
|
+
if returncode != 0:
|
|
352
|
+
raise_remove_error(returncode, stderr, remote_path)
|
|
353
|
+
|
|
306
354
|
|
|
307
355
|
SandboxFilesystem = synchronize_api(_SandboxFilesystem)
|
|
@@ -168,6 +168,45 @@ class _SandboxFilesystem:
|
|
|
168
168
|
"""
|
|
169
169
|
...
|
|
170
170
|
|
|
171
|
+
async def remove(self, remote_path: str, *, recursive: bool = False) -> None:
|
|
172
|
+
"""Remove a file or directory in the Sandbox.
|
|
173
|
+
|
|
174
|
+
`remote_path` must be an absolute path in the Sandbox.
|
|
175
|
+
|
|
176
|
+
When `remote_path` is a directory and `recursive` is `False` (the
|
|
177
|
+
default), removes it only if it is empty. When `recursive` is `True`,
|
|
178
|
+
removes the directory and all its contents.
|
|
179
|
+
|
|
180
|
+
Recursive directory removal is not supported on all mounts.
|
|
181
|
+
In particular, `CloudBucketMount` does not support it. An
|
|
182
|
+
`InvalidError` is raised in that case.
|
|
183
|
+
|
|
184
|
+
**Raises**
|
|
185
|
+
|
|
186
|
+
- `SandboxFilesystemNotFoundError`: the path does not exist.
|
|
187
|
+
- `SandboxFilesystemDirectoryNotEmptyError`: `recursive` is `False` and the directory is not empty.
|
|
188
|
+
- `SandboxFilesystemPermissionError`: removal is not permitted.
|
|
189
|
+
- `InvalidError`: the operation is not supported by the mount.
|
|
190
|
+
- `SandboxFilesystemError`: the command fails for any other reason.
|
|
191
|
+
|
|
192
|
+
**Usage**
|
|
193
|
+
|
|
194
|
+
To remove a file:
|
|
195
|
+
|
|
196
|
+
```python fixture:sandbox
|
|
197
|
+
sandbox.filesystem.write_bytes(b"Hello, world!\n", "/tmp/hello.bin")
|
|
198
|
+
sandbox.filesystem.remove("/tmp/hello.bin")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
To remove a directory and all its contents:
|
|
202
|
+
|
|
203
|
+
```python fixture:sandbox
|
|
204
|
+
sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
|
|
205
|
+
sandbox.filesystem.remove("/tmp/mydir", recursive=True)
|
|
206
|
+
```
|
|
207
|
+
"""
|
|
208
|
+
...
|
|
209
|
+
|
|
171
210
|
class SandboxFilesystem:
|
|
172
211
|
"""mdmd:namespace
|
|
173
212
|
Namespace for Sandbox filesystem APIs.
|
|
@@ -505,3 +544,84 @@ class SandboxFilesystem:
|
|
|
505
544
|
...
|
|
506
545
|
|
|
507
546
|
copy_from_local: __copy_from_local_spec
|
|
547
|
+
|
|
548
|
+
class __remove_spec(typing_extensions.Protocol):
|
|
549
|
+
def __call__(self, /, remote_path: str, *, recursive: bool = False) -> None:
|
|
550
|
+
"""Remove a file or directory in the Sandbox.
|
|
551
|
+
|
|
552
|
+
`remote_path` must be an absolute path in the Sandbox.
|
|
553
|
+
|
|
554
|
+
When `remote_path` is a directory and `recursive` is `False` (the
|
|
555
|
+
default), removes it only if it is empty. When `recursive` is `True`,
|
|
556
|
+
removes the directory and all its contents.
|
|
557
|
+
|
|
558
|
+
Recursive directory removal is not supported on all mounts.
|
|
559
|
+
In particular, `CloudBucketMount` does not support it. An
|
|
560
|
+
`InvalidError` is raised in that case.
|
|
561
|
+
|
|
562
|
+
**Raises**
|
|
563
|
+
|
|
564
|
+
- `SandboxFilesystemNotFoundError`: the path does not exist.
|
|
565
|
+
- `SandboxFilesystemDirectoryNotEmptyError`: `recursive` is `False` and the directory is not empty.
|
|
566
|
+
- `SandboxFilesystemPermissionError`: removal is not permitted.
|
|
567
|
+
- `InvalidError`: the operation is not supported by the mount.
|
|
568
|
+
- `SandboxFilesystemError`: the command fails for any other reason.
|
|
569
|
+
|
|
570
|
+
**Usage**
|
|
571
|
+
|
|
572
|
+
To remove a file:
|
|
573
|
+
|
|
574
|
+
```python fixture:sandbox
|
|
575
|
+
sandbox.filesystem.write_bytes(b"Hello, world!\n", "/tmp/hello.bin")
|
|
576
|
+
sandbox.filesystem.remove("/tmp/hello.bin")
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
To remove a directory and all its contents:
|
|
580
|
+
|
|
581
|
+
```python fixture:sandbox
|
|
582
|
+
sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
|
|
583
|
+
sandbox.filesystem.remove("/tmp/mydir", recursive=True)
|
|
584
|
+
```
|
|
585
|
+
"""
|
|
586
|
+
...
|
|
587
|
+
|
|
588
|
+
async def aio(self, /, remote_path: str, *, recursive: bool = False) -> None:
|
|
589
|
+
"""Remove a file or directory in the Sandbox.
|
|
590
|
+
|
|
591
|
+
`remote_path` must be an absolute path in the Sandbox.
|
|
592
|
+
|
|
593
|
+
When `remote_path` is a directory and `recursive` is `False` (the
|
|
594
|
+
default), removes it only if it is empty. When `recursive` is `True`,
|
|
595
|
+
removes the directory and all its contents.
|
|
596
|
+
|
|
597
|
+
Recursive directory removal is not supported on all mounts.
|
|
598
|
+
In particular, `CloudBucketMount` does not support it. An
|
|
599
|
+
`InvalidError` is raised in that case.
|
|
600
|
+
|
|
601
|
+
**Raises**
|
|
602
|
+
|
|
603
|
+
- `SandboxFilesystemNotFoundError`: the path does not exist.
|
|
604
|
+
- `SandboxFilesystemDirectoryNotEmptyError`: `recursive` is `False` and the directory is not empty.
|
|
605
|
+
- `SandboxFilesystemPermissionError`: removal is not permitted.
|
|
606
|
+
- `InvalidError`: the operation is not supported by the mount.
|
|
607
|
+
- `SandboxFilesystemError`: the command fails for any other reason.
|
|
608
|
+
|
|
609
|
+
**Usage**
|
|
610
|
+
|
|
611
|
+
To remove a file:
|
|
612
|
+
|
|
613
|
+
```python fixture:sandbox
|
|
614
|
+
sandbox.filesystem.write_bytes(b"Hello, world!\n", "/tmp/hello.bin")
|
|
615
|
+
sandbox.filesystem.remove("/tmp/hello.bin")
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
To remove a directory and all its contents:
|
|
619
|
+
|
|
620
|
+
```python fixture:sandbox
|
|
621
|
+
sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
|
|
622
|
+
sandbox.filesystem.remove("/tmp/mydir", recursive=True)
|
|
623
|
+
```
|
|
624
|
+
"""
|
|
625
|
+
...
|
|
626
|
+
|
|
627
|
+
remove: __remove_spec
|
|
@@ -237,7 +237,8 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
237
237
|
_metadata: Optional[api_pb2.SecretMetadata] = None
|
|
238
238
|
|
|
239
239
|
@classproperty
|
|
240
|
-
|
|
240
|
+
@classmethod
|
|
241
|
+
def objects(cls) -> type[_SecretManager]:
|
|
241
242
|
return _SecretManager
|
|
242
243
|
|
|
243
244
|
@property
|
|
@@ -375,7 +375,8 @@ class _Secret(modal._object._Object):
|
|
|
375
375
|
_metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
|
|
376
376
|
|
|
377
377
|
@synchronicity.classproperty
|
|
378
|
-
|
|
378
|
+
@classmethod
|
|
379
|
+
def objects(cls) -> type[_SecretManager]: ...
|
|
379
380
|
@property
|
|
380
381
|
def name(self) -> typing.Optional[str]: ...
|
|
381
382
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
@@ -500,7 +501,8 @@ class Secret(modal.object.Object):
|
|
|
500
501
|
...
|
|
501
502
|
|
|
502
503
|
@synchronicity.classproperty
|
|
503
|
-
|
|
504
|
+
@classmethod
|
|
505
|
+
def objects(cls) -> type[SecretManager]: ...
|
|
504
506
|
@property
|
|
505
507
|
def name(self) -> typing.Optional[str]: ...
|
|
506
508
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
@@ -454,7 +454,8 @@ class _Volume(modal._object._Object):
|
|
|
454
454
|
_read_only: bool
|
|
455
455
|
|
|
456
456
|
@synchronicity.classproperty
|
|
457
|
-
|
|
457
|
+
@classmethod
|
|
458
|
+
def objects(cls) -> type[_VolumeManager]: ...
|
|
458
459
|
@property
|
|
459
460
|
def name(self) -> typing.Optional[str]: ...
|
|
460
461
|
def read_only(self) -> _Volume:
|
|
@@ -789,7 +790,8 @@ class Volume(modal.object.Object):
|
|
|
789
790
|
...
|
|
790
791
|
|
|
791
792
|
@synchronicity.classproperty
|
|
792
|
-
|
|
793
|
+
@classmethod
|
|
794
|
+
def objects(cls) -> type[VolumeManager]: ...
|
|
793
795
|
@property
|
|
794
796
|
def name(self) -> typing.Optional[str]: ...
|
|
795
797
|
def read_only(self) -> Volume:
|