modal 1.5.2.dev19__tar.gz → 1.5.2.dev21__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.dev19 → modal-1.5.2.dev21}/PKG-INFO +1 -1
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_environments.py +6 -9
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_image.py +36 -7
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_workspace.py +9 -3
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/container.py +15 -7
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/curl.py +6 -4
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/environment.py +2 -2
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/image.py +8 -1
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/shell.py +6 -2
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/client.pyi +2 -2
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/environments.pyi +6 -14
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/functions.pyi +6 -6
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/sandbox.py +47 -5
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/sandbox.pyi +114 -15
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/types.py +8 -5
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/volume.py +26 -1
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/volume.pyi +26 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/workspace.pyi +2 -2
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/mdmd/mdmd.py +34 -11
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/api_grpc.py +48 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/api_pb2.py +816 -763
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/api_pb2.pyi +106 -5
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/api_pb2_grpc.py +100 -1
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/api_pb2_grpc.pyi +33 -3
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/modal_api_grpc.py +3 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_version/__init__.py +1 -1
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/LICENSE +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/README.md +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/__main__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_billing.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_clustered_functions.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_function_variants.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_functions.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_grpc_client.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_ipython.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_load_context.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_location.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_logs.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_object.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_output/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_output/manager.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_output/pty.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_output/rich.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_output/status.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_partial_function.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_resolver.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_resources.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_serialization.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_server.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_traceback.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_tunnel.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_tunnel.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_type_manager.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/logger.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_vendor/version.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/_watcher.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/app.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/app.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/billing.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/billing.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/builder/README.md +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/builder/base-images.json +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/call_graph.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/_download.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/_help.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/_traceback.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/app.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/billing.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/changelog.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/cluster.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/config.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/dashboard.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/dict.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/endpoint.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/entry_point.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/import_refs.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/launch.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/logo.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/profile.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/queues.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/run.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/secret.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/selector.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/skills.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/token.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/utils.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/volume.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cli/workspace.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/client.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cls.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/cls.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/config.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/container_process.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/container_process.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/dict.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/dict.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/environments.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/exception.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/experimental/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/experimental/flash.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/experimental/ipython.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/file_io.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/file_io.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/functions.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/image.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/image.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/io_streams.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/io_streams.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/mount.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/mount.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/network_file_system.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/network_file_system.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/object.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/object.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/output.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/parallel_map.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/parallel_map.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/partial_function.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/partial_function.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/proxy.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/proxy.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/py.typed +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/queue.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/queue.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/retries.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/runner.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/runner.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/running_app.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/sandbox_fs.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/schedule.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/scheduler_placement.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/secret.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/secret.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/server.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/server.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/serving.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/serving.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/snapshot.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/snapshot.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/stream_type.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/token_flow.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/token_flow.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal/workspace.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/__init__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/py.typed +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/modal_version/__main__.py +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/pyproject.toml +0 -0
- {modal-1.5.2.dev19 → modal-1.5.2.dev21}/setup.cfg +0 -0
|
@@ -103,9 +103,6 @@ class _EnvironmentManager:
|
|
|
103
103
|
await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
MemberRole = Literal["viewer", "contributor"]
|
|
107
|
-
|
|
108
|
-
|
|
109
106
|
def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
|
|
110
107
|
match role:
|
|
111
108
|
case "viewer":
|
|
@@ -116,7 +113,7 @@ def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
|
|
|
116
113
|
raise InvalidError(f"Invalid Environment role: {role!r} (expected 'viewer' or 'contributor')")
|
|
117
114
|
|
|
118
115
|
|
|
119
|
-
def _role_from_proto(proto_value: int) ->
|
|
116
|
+
def _role_from_proto(proto_value: int) -> str:
|
|
120
117
|
match proto_value:
|
|
121
118
|
case api_pb2.ENVIRONMENT_ROLE_VIEWER:
|
|
122
119
|
return "viewer"
|
|
@@ -137,7 +134,7 @@ class _EnvironmentMembersManager:
|
|
|
137
134
|
"""mdmd:hidden"""
|
|
138
135
|
self._environment = environment
|
|
139
136
|
|
|
140
|
-
async def list(self) -> dict[Literal["users", "service_users"], dict[str,
|
|
137
|
+
async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
|
|
141
138
|
"""Return the members of a restricted Environment with their roles.
|
|
142
139
|
|
|
143
140
|
**Examples:**
|
|
@@ -155,8 +152,8 @@ class _EnvironmentMembersManager:
|
|
|
155
152
|
req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
|
|
156
153
|
resp = await self._environment.client.stub.EnvironmentGetManaged(req)
|
|
157
154
|
|
|
158
|
-
users: dict[str,
|
|
159
|
-
service_users: dict[str,
|
|
155
|
+
users: dict[str, str] = {}
|
|
156
|
+
service_users: dict[str, str] = {}
|
|
160
157
|
for principal in resp.principal_roles:
|
|
161
158
|
role = _role_from_proto(principal.role)
|
|
162
159
|
if principal.user_id:
|
|
@@ -169,8 +166,8 @@ class _EnvironmentMembersManager:
|
|
|
169
166
|
async def update(
|
|
170
167
|
self,
|
|
171
168
|
*,
|
|
172
|
-
users: Mapping[str,
|
|
173
|
-
service_users: Mapping[str,
|
|
169
|
+
users: Mapping[str, str] | None = None,
|
|
170
|
+
service_users: Mapping[str, str] | None = None,
|
|
174
171
|
) -> None:
|
|
175
172
|
"""Add or modify roles for members of a restricted Environment.
|
|
176
173
|
|
|
@@ -117,13 +117,34 @@ def _validate_image_tag(tag: str) -> None:
|
|
|
117
117
|
check_object_name(tag, "Image tag")
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
def _parse_named_image_ref(name: str) -> str:
|
|
120
|
+
def _parse_named_image_ref(name: str) -> tuple[str, str]:
|
|
121
|
+
"""Parse an image reference, returning (namespace_prefix, name_tag).
|
|
122
|
+
|
|
123
|
+
If the name contains a '/', the part before the last '/' is extracted as
|
|
124
|
+
a namespace prefix (intended for environment/name or workspace/env/name
|
|
125
|
+
syntax). The actual image name (after the last '/') is validated as a
|
|
126
|
+
standard object name.
|
|
127
|
+
|
|
128
|
+
Returns a tuple of (prefix, "full_name:tag") where prefix is empty string
|
|
129
|
+
if no '/' is present.
|
|
130
|
+
"""
|
|
121
131
|
image_name, sep, tag = name.partition(":")
|
|
122
132
|
if not sep:
|
|
123
133
|
tag = "latest"
|
|
134
|
+
|
|
135
|
+
prefix = ""
|
|
136
|
+
if "/" in image_name:
|
|
137
|
+
prefix, image_name = image_name.rsplit("/", 1)
|
|
138
|
+
if not prefix:
|
|
139
|
+
raise InvalidError("Invalid Image name: '/' prefix must be non-empty.")
|
|
140
|
+
if not image_name:
|
|
141
|
+
raise InvalidError("Invalid Image name: name after '/' must be non-empty.")
|
|
142
|
+
|
|
124
143
|
_validate_image_name(image_name)
|
|
125
144
|
_validate_image_tag(tag)
|
|
126
|
-
|
|
145
|
+
|
|
146
|
+
full_name = f"{prefix}/{image_name}" if prefix else image_name
|
|
147
|
+
return prefix, f"{full_name}:{tag}"
|
|
127
148
|
|
|
128
149
|
|
|
129
150
|
def _validate_python_version(
|
|
@@ -2923,12 +2944,15 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2923
2944
|
...
|
|
2924
2945
|
```
|
|
2925
2946
|
"""
|
|
2926
|
-
tag = _parse_named_image_ref(name)
|
|
2947
|
+
namespace_prefix, tag = _parse_named_image_ref(name)
|
|
2948
|
+
|
|
2949
|
+
if namespace_prefix and environment_name:
|
|
2950
|
+
raise InvalidError("Cannot specify 'environment_name' when the image name contains a '/'.")
|
|
2927
2951
|
|
|
2928
2952
|
async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
|
|
2929
2953
|
req = api_pb2.ImageGetByTagRequest(
|
|
2930
2954
|
tag=tag,
|
|
2931
|
-
environment_name=load_context.environment_name,
|
|
2955
|
+
environment_name="" if namespace_prefix else load_context.environment_name,
|
|
2932
2956
|
)
|
|
2933
2957
|
response = await load_context.client.stub.ImageGetByTag(req)
|
|
2934
2958
|
self._hydrate(response.image_id, load_context.client, None)
|
|
@@ -2963,15 +2987,20 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2963
2987
|
image.publish("my-image-with-numpy:v1")
|
|
2964
2988
|
```
|
|
2965
2989
|
"""
|
|
2966
|
-
tag = _parse_named_image_ref(name)
|
|
2990
|
+
namespace_prefix, tag = _parse_named_image_ref(name)
|
|
2991
|
+
|
|
2992
|
+
if namespace_prefix:
|
|
2993
|
+
if environment_name:
|
|
2994
|
+
raise InvalidError("Cannot specify 'environment_name' when the image name contains a '/'.")
|
|
2995
|
+
resolved_env = ""
|
|
2996
|
+
else:
|
|
2997
|
+
resolved_env = environment_name or config.get("environment") or ""
|
|
2967
2998
|
|
|
2968
2999
|
if self._object_id is None:
|
|
2969
3000
|
raise InvalidError("Cannot publish an image that has not been created yet. Call `.build()` first.")
|
|
2970
3001
|
|
|
2971
3002
|
_client = client or await _Client.from_env()
|
|
2972
3003
|
|
|
2973
|
-
resolved_env = environment_name or config.get("environment") or ""
|
|
2974
|
-
|
|
2975
3004
|
await _client.stub.ImagePublish(
|
|
2976
3005
|
api_pb2.ImagePublishRequest(
|
|
2977
3006
|
image_id=self._object_id,
|
|
@@ -13,13 +13,19 @@ from ._object import _Object
|
|
|
13
13
|
from ._resolver import Resolver
|
|
14
14
|
from ._utils.time_utils import timestamp_to_localized_dt
|
|
15
15
|
from .client import _Client
|
|
16
|
-
from .types import
|
|
16
|
+
from .types import (
|
|
17
|
+
BillingReportItem,
|
|
18
|
+
ProxyTokenInfo,
|
|
19
|
+
TokenData,
|
|
20
|
+
WorkspaceMemberInfo,
|
|
21
|
+
WorkspaceSettings,
|
|
22
|
+
)
|
|
17
23
|
|
|
18
24
|
|
|
19
|
-
def _member_role_from_proto(proto_value: int) ->
|
|
25
|
+
def _member_role_from_proto(proto_value: int) -> str:
|
|
20
26
|
match proto_value:
|
|
21
27
|
case api_pb2.MEMBER_ROLE_USER:
|
|
22
|
-
return "
|
|
28
|
+
return "member"
|
|
23
29
|
case api_pb2.MEMBER_ROLE_MANAGER:
|
|
24
30
|
return "manager"
|
|
25
31
|
case api_pb2.MEMBER_ROLE_OWNER:
|
|
@@ -202,20 +202,20 @@ async def logs(
|
|
|
202
202
|
task_info_resp = await client.stub.TaskGetInfo(api_pb2.TaskGetInfoRequest(task_id=task_id))
|
|
203
203
|
app_id = task_info_resp.app_id
|
|
204
204
|
|
|
205
|
-
if not task_info_resp.info.
|
|
206
|
-
# Unlikely race or Modal backend issue, don't treat as a usage exception
|
|
205
|
+
if not task_info_resp.info.enqueued_at:
|
|
207
206
|
return
|
|
208
|
-
|
|
207
|
+
|
|
208
|
+
container_enqueued_dt = datetime.fromtimestamp(task_info_resp.info.enqueued_at, timezone.utc)
|
|
209
209
|
|
|
210
210
|
now = datetime.now(timezone.utc)
|
|
211
211
|
if all_logs:
|
|
212
|
-
since_dt =
|
|
212
|
+
since_dt = container_enqueued_dt
|
|
213
213
|
if task_info_resp.info.finished_at:
|
|
214
214
|
until_dt = datetime.fromtimestamp(task_info_resp.info.finished_at, timezone.utc)
|
|
215
215
|
else:
|
|
216
216
|
until_dt = now
|
|
217
217
|
else:
|
|
218
|
-
since_dt = _parse_time_arg(since, default=
|
|
218
|
+
since_dt = _parse_time_arg(since, default=container_enqueued_dt)
|
|
219
219
|
if task_info_resp.info.finished_at:
|
|
220
220
|
default_until_dt = datetime.fromtimestamp(task_info_resp.info.finished_at, timezone.utc)
|
|
221
221
|
else:
|
|
@@ -269,15 +269,23 @@ async def _exec_impl(
|
|
|
269
269
|
pty: bool | None = None,
|
|
270
270
|
container_id: str = "",
|
|
271
271
|
command: tuple[str, ...] = (),
|
|
272
|
+
object_id_for_v2: str | None = None,
|
|
272
273
|
):
|
|
273
|
-
"""Execute a command in a container (implementation).
|
|
274
|
+
"""Execute a command in a container (implementation).
|
|
275
|
+
|
|
276
|
+
For tasks belonging to V2 sandboxes, `object_id_for_v2` must be set to the
|
|
277
|
+
sandbox ID.
|
|
278
|
+
"""
|
|
274
279
|
|
|
275
280
|
if pty is None:
|
|
276
281
|
pty = is_tty()
|
|
277
282
|
|
|
278
283
|
client = await _Client.from_env()
|
|
279
284
|
|
|
280
|
-
|
|
285
|
+
if object_id_for_v2 is not None:
|
|
286
|
+
command_router_client = await TaskCommandRouterClient.init_v2(client, object_id_for_v2, container_id)
|
|
287
|
+
else:
|
|
288
|
+
command_router_client = await TaskCommandRouterClient.init(client, container_id)
|
|
281
289
|
|
|
282
290
|
process_id = str(uuid.uuid4())
|
|
283
291
|
|
|
@@ -117,10 +117,12 @@ async def curl(curl_args: tuple[str, ...]):
|
|
|
117
117
|
All arguments are passed directly to `curl`, which must be installed locally.
|
|
118
118
|
|
|
119
119
|
Examples:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
modal curl https://user--my-app.us-west.modal.direct
|
|
123
|
+
modal curl -X GET https://user--my-app.us-west.modal.direct
|
|
124
|
+
```
|
|
125
|
+
|
|
124
126
|
"""
|
|
125
127
|
url = find_url(curl_args)
|
|
126
128
|
if url is None:
|
|
@@ -7,7 +7,7 @@ from click import UsageError
|
|
|
7
7
|
from rich.text import Text
|
|
8
8
|
|
|
9
9
|
from modal import environments
|
|
10
|
-
from modal._environments import
|
|
10
|
+
from modal._environments import _Environment
|
|
11
11
|
from modal._utils.async_utils import synchronizer
|
|
12
12
|
from modal._utils.name_utils import check_environment_name
|
|
13
13
|
from modal._utils.time_utils import format_interval
|
|
@@ -158,7 +158,7 @@ def members_list(environment: str, json: bool = False):
|
|
|
158
158
|
"--role", type=click.Choice(["contributor", "viewer"]), required=True, help="Role to assign to the member"
|
|
159
159
|
)
|
|
160
160
|
@service_user_option
|
|
161
|
-
def members_update(environment: str, member: str, role:
|
|
161
|
+
def members_update(environment: str, member: str, role: str, service_user: bool = False):
|
|
162
162
|
env = Environment.from_name(environment)
|
|
163
163
|
if service_user:
|
|
164
164
|
env.members.update(service_users={member: role})
|
|
@@ -168,11 +168,18 @@ async def history(
|
|
|
168
168
|
env: Optional[str] = None,
|
|
169
169
|
json: bool = False,
|
|
170
170
|
):
|
|
171
|
+
explicit_env = env # None when --env is not passed by the user
|
|
171
172
|
env = ensure_env(env)
|
|
172
173
|
try:
|
|
173
|
-
name_tag = _parse_named_image_ref(name)
|
|
174
|
+
namespace_prefix, name_tag = _parse_named_image_ref(name)
|
|
174
175
|
except InvalidError as exc:
|
|
175
176
|
raise click.UsageError(str(exc)) from exc
|
|
177
|
+
|
|
178
|
+
if namespace_prefix:
|
|
179
|
+
if explicit_env is not None:
|
|
180
|
+
raise click.UsageError("Cannot specify '--env' when the image name contains a '/'.")
|
|
181
|
+
env = ""
|
|
182
|
+
|
|
176
183
|
client = await _Client.from_env()
|
|
177
184
|
|
|
178
185
|
first_page = await _fetch_history_page(client, env, name_tag, "")
|
|
@@ -17,7 +17,7 @@ from ..functions import Function
|
|
|
17
17
|
from ..image import Image
|
|
18
18
|
from ..mount import _Mount
|
|
19
19
|
from ..runner import interactive_shell
|
|
20
|
-
from ..sandbox import _MAIN_CONTAINER_NAME, Sandbox
|
|
20
|
+
from ..sandbox import _MAIN_CONTAINER_NAME, Sandbox, _is_v2_sandbox_id
|
|
21
21
|
from ..secret import Secret
|
|
22
22
|
from ..stream_type import StreamType
|
|
23
23
|
from ..volume import Volume
|
|
@@ -135,6 +135,8 @@ def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
|
|
|
135
135
|
if container_name is not None and container_name != _MAIN_CONTAINER_NAME:
|
|
136
136
|
return _start_shell_in_sidecar_container(sandbox_id, container_name, cmd, pty)
|
|
137
137
|
|
|
138
|
+
object_id_for_v2: str | None = None
|
|
139
|
+
|
|
138
140
|
if _is_valid_modal_id(sandbox_id, "sb-"):
|
|
139
141
|
try:
|
|
140
142
|
sandbox = Sandbox.from_id(sandbox_id)
|
|
@@ -143,10 +145,12 @@ def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
|
|
|
143
145
|
raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
|
|
144
146
|
except Exception as e:
|
|
145
147
|
raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
|
|
148
|
+
if _is_v2_sandbox_id(sandbox_id):
|
|
149
|
+
object_id_for_v2 = sandbox_id
|
|
146
150
|
|
|
147
151
|
assert _is_valid_modal_id(ref, "ta-")
|
|
148
152
|
try:
|
|
149
|
-
_exec_impl(container_id=ref, command=shlex.split(cmd), pty=pty)
|
|
153
|
+
_exec_impl(container_id=ref, command=shlex.split(cmd), pty=pty, object_id_for_v2=object_id_for_v2)
|
|
150
154
|
except NotFoundError:
|
|
151
155
|
raise ClickException(f"Container '{ref}' not found (is it still running?)")
|
|
152
156
|
except Exception as e:
|
|
@@ -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.dev21",
|
|
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.dev21",
|
|
209
209
|
):
|
|
210
210
|
"""mdmd:hidden
|
|
211
211
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -112,9 +112,7 @@ class EnvironmentMembersManager:
|
|
|
112
112
|
...
|
|
113
113
|
|
|
114
114
|
class __list_spec(typing_extensions.Protocol):
|
|
115
|
-
def __call__(
|
|
116
|
-
self, /
|
|
117
|
-
) -> dict[typing.Literal["users", "service_users"], dict[str, typing.Literal["viewer", "contributor"]]]:
|
|
115
|
+
def __call__(self, /) -> dict[typing.Literal["users", "service_users"], dict[str, str]]:
|
|
118
116
|
"""Return the members of a restricted Environment with their roles.
|
|
119
117
|
|
|
120
118
|
**Examples:**
|
|
@@ -130,9 +128,7 @@ class EnvironmentMembersManager:
|
|
|
130
128
|
"""
|
|
131
129
|
...
|
|
132
130
|
|
|
133
|
-
async def aio(
|
|
134
|
-
self, /
|
|
135
|
-
) -> dict[typing.Literal["users", "service_users"], dict[str, typing.Literal["viewer", "contributor"]]]:
|
|
131
|
+
async def aio(self, /) -> dict[typing.Literal["users", "service_users"], dict[str, str]]:
|
|
136
132
|
"""Return the members of a restricted Environment with their roles.
|
|
137
133
|
|
|
138
134
|
**Examples:**
|
|
@@ -155,10 +151,8 @@ class EnvironmentMembersManager:
|
|
|
155
151
|
self,
|
|
156
152
|
/,
|
|
157
153
|
*,
|
|
158
|
-
users: typing.Optional[collections.abc.Mapping[str,
|
|
159
|
-
service_users: typing.Optional[
|
|
160
|
-
collections.abc.Mapping[str, typing.Literal["viewer", "contributor"]]
|
|
161
|
-
] = None,
|
|
154
|
+
users: typing.Optional[collections.abc.Mapping[str, str]] = None,
|
|
155
|
+
service_users: typing.Optional[collections.abc.Mapping[str, str]] = None,
|
|
162
156
|
) -> None:
|
|
163
157
|
"""Add or modify roles for members of a restricted Environment.
|
|
164
158
|
|
|
@@ -181,10 +175,8 @@ class EnvironmentMembersManager:
|
|
|
181
175
|
self,
|
|
182
176
|
/,
|
|
183
177
|
*,
|
|
184
|
-
users: typing.Optional[collections.abc.Mapping[str,
|
|
185
|
-
service_users: typing.Optional[
|
|
186
|
-
collections.abc.Mapping[str, typing.Literal["viewer", "contributor"]]
|
|
187
|
-
] = None,
|
|
178
|
+
users: typing.Optional[collections.abc.Mapping[str, str]] = None,
|
|
179
|
+
service_users: typing.Optional[collections.abc.Mapping[str, str]] = None,
|
|
188
180
|
) -> None:
|
|
189
181
|
"""Add or modify roles for members of a restricted Environment.
|
|
190
182
|
|
|
@@ -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[ReturnType_INNER, P_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.ReturnType, modal._functions.P]
|
|
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[ReturnType_INNER, P_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.ReturnType, modal._functions.P]
|
|
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[ReturnType_INNER, P_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.ReturnType, modal._functions.P]
|
|
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.
|
|
@@ -861,9 +861,10 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
861
861
|
(e.g. `region="us-east-1"`).
|
|
862
862
|
|
|
863
863
|
V2 sandboxes created with this method are not currently returned by
|
|
864
|
-
`Sandbox.list()
|
|
865
|
-
|
|
866
|
-
use `Sandbox.from_id(sandbox.object_id)` to
|
|
864
|
+
`Sandbox.list()`. A named sandbox can be looked up with
|
|
865
|
+
`Sandbox._experimental_from_name(app_name, name)`; otherwise store
|
|
866
|
+
`sandbox.object_id` and use `Sandbox.from_id(sandbox.object_id)` to
|
|
867
|
+
reattach.
|
|
867
868
|
"""
|
|
868
869
|
from .app import _App
|
|
869
870
|
|
|
@@ -1186,6 +1187,44 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1186
1187
|
resp = await client.stub.SandboxGetFromName(req)
|
|
1187
1188
|
return _Sandbox._new_hydrated(resp.sandbox_id, client, None)
|
|
1188
1189
|
|
|
1190
|
+
@staticmethod
|
|
1191
|
+
async def _experimental_from_name(
|
|
1192
|
+
app_name: str,
|
|
1193
|
+
name: str,
|
|
1194
|
+
*,
|
|
1195
|
+
environment_name: str | None = None,
|
|
1196
|
+
client: _Client | None = None,
|
|
1197
|
+
) -> "_Sandbox":
|
|
1198
|
+
"""Get a running V2 Sandbox by name from a deployed App.
|
|
1199
|
+
|
|
1200
|
+
This looks up V2 sandboxes, ie sandboxes created via `modal.Sandbox._experimental_create`.
|
|
1201
|
+
|
|
1202
|
+
Args:
|
|
1203
|
+
app_name: Name of the deployed app to look up the sandbox under.
|
|
1204
|
+
name: Sandbox name to resolve.
|
|
1205
|
+
environment_name: Optional environment name for the lookup; defaults to the configured environment.
|
|
1206
|
+
client: Modal client to use for the RPC; defaults to `Client.from_env()` when omitted.
|
|
1207
|
+
|
|
1208
|
+
Returns:
|
|
1209
|
+
A `Sandbox` handle for the running sandbox.
|
|
1210
|
+
|
|
1211
|
+
Raises:
|
|
1212
|
+
NotFoundError: If no running sandbox exists with the given name.
|
|
1213
|
+
"""
|
|
1214
|
+
if client is None:
|
|
1215
|
+
client = await _Client.from_env()
|
|
1216
|
+
env_name = _get_environment_name(environment_name)
|
|
1217
|
+
|
|
1218
|
+
req = api_pb2.SandboxGetFromNameRequest(sandbox_name=name, app_name=app_name, environment_name=env_name)
|
|
1219
|
+
assert client._auth_token_manager
|
|
1220
|
+
auth_token = await client._auth_token_manager.get_token()
|
|
1221
|
+
resp = await client.stub.SandboxGetFromNameV2(req, metadata=[("x-modal-auth-token", auth_token)])
|
|
1222
|
+
|
|
1223
|
+
obj = _Sandbox._new_hydrated(resp.sandbox_id, client, None)
|
|
1224
|
+
obj._is_v2 = True
|
|
1225
|
+
obj._hydrate_metadata_v2()
|
|
1226
|
+
return obj
|
|
1227
|
+
|
|
1189
1228
|
@staticmethod
|
|
1190
1229
|
async def from_id(sandbox_id: str, client: _Client | None = None) -> "_Sandbox":
|
|
1191
1230
|
"""Construct a Sandbox from an id and look up the Sandbox result.
|
|
@@ -2279,15 +2318,15 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2279
2318
|
|
|
2280
2319
|
@staticmethod
|
|
2281
2320
|
async def _experimental_list(
|
|
2282
|
-
*, app_id: str | None = None, client: _Client | None = None
|
|
2321
|
+
*, app_id: str | None = None, tags: dict[str, str] | None = None, client: _Client | None = None
|
|
2283
2322
|
) -> AsyncGenerator["_Sandbox", None]:
|
|
2284
2323
|
"""List v2 Sandboxes in an App.
|
|
2285
2324
|
|
|
2286
2325
|
This function lists v2 sandboxes, ie sandboxes created via modal.Sandbox._experimental_create.
|
|
2287
|
-
Filtering based on tags is not yet supported.
|
|
2288
2326
|
|
|
2289
2327
|
Args:
|
|
2290
2328
|
app_id: The App to list Sandboxes under.
|
|
2329
|
+
tags: If set, only sandboxes containing at least these tags are returned.
|
|
2291
2330
|
client: Optional client to use for the session.
|
|
2292
2331
|
|
|
2293
2332
|
Yields:
|
|
@@ -2304,12 +2343,15 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2304
2343
|
if client is None:
|
|
2305
2344
|
client = await _Client.from_env()
|
|
2306
2345
|
|
|
2346
|
+
tags_list = [api_pb2.SandboxTag(tag_name=name, tag_value=value) for name, value in tags.items()] if tags else []
|
|
2347
|
+
|
|
2307
2348
|
assert client._auth_token_manager
|
|
2308
2349
|
while True:
|
|
2309
2350
|
req = api_pb2.SandboxListRequest(
|
|
2310
2351
|
app_id=app_id,
|
|
2311
2352
|
before_timestamp=before_timestamp,
|
|
2312
2353
|
include_finished=False,
|
|
2354
|
+
tags=tags_list,
|
|
2313
2355
|
)
|
|
2314
2356
|
|
|
2315
2357
|
# Fetches a batch of sandboxes. SandboxListV2 authenticates via the
|