modal 1.6.4.dev8__tar.gz → 1.6.4.dev10__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.6.4.dev8 → modal-1.6.4.dev10}/PKG-INFO +1 -1
- modal-1.6.4.dev10/modal/_audit_logs/__init__.py +1 -0
- modal-1.6.4.dev10/modal/_audit_logs/_rpcs.py +66 -0
- modal-1.6.4.dev10/modal/_audit_logs/count.py +17 -0
- modal-1.6.4.dev10/modal/_audit_logs/list.py +83 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_image.py +5 -6
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/image.py +123 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/shell.py +1 -1
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/client.pyi +2 -2
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/image.pyi +1 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal.egg-info/SOURCES.txt +4 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/api_grpc.py +64 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/api_pb2.py +1529 -1432
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/api_pb2.pyi +232 -14
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/api_pb2_grpc.py +135 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/api_pb2_grpc.pyi +46 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/modal_api_grpc.py +4 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_version/__init__.py +1 -1
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/LICENSE +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/README.md +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/__main__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_billing.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_cluster.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_cluster.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_container_entrypoint.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_environments.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_function_variants.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_functions.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_grpc_client.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_ipython.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_load_context.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_location.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_logs.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_logs_manager.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_logs_manager.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_metrics.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_metrics.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_object.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_outbound_policy.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_outbound_policy.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_output/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_output/manager.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_output/pty.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_output/rich.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_output/status.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_partial_function.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_resolver.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_resources.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/asgi.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/execution_context.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/telemetry.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_serialization.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_server.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_server.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_supports_logs.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_traceback.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_tunnel.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_tunnel.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_type_manager.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/app_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/async_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/blob_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/browser_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/curl_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/deprecation.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/display_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/docker_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/function_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/git_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/hash_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/http_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/logger.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/mount_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/name_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/oauth.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/package_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/proxy_support.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/shell_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_utils/time_utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_vendor/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_vendor/tblib.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_vendor/version.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_watcher.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/_workspace.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/app.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/app.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/billing.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/billing.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/2023.12.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/2024.04.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/2024.10.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/2025.06.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/2026.10.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/README.md +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/builder/base-images.json +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/call_graph.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/_download.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/_help.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/_logs.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/_stats.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/_traceback.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/app.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/billing.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/changelog.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/config.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/container.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/curl.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/dashboard.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/dict.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/endpoint.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/entry_point.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/environment.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/function.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/import_refs.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/logo.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/network_file_system.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/profile.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/queues.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/run.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/secret.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/selector.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/server.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/skills.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/token.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/utils.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/volume.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cli/workspace.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/client.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cls.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cls.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cluster.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/cluster.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/config.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/container_process.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/container_process.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/dict.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/dict.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/environments.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/environments.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/exception.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/experimental/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/experimental/_flash_container_entry.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/experimental/flash.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/experimental/flash.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/experimental/flash_server.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/experimental/flash_server.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/experimental/ipython.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/file_pattern_matcher.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/functions.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/functions.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/image.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/io_streams.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/io_streams.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/mount.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/mount.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/network_file_system.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/network_file_system.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/object.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/object.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/output.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/parallel_map.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/parallel_map.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/partial_function.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/partial_function.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/proxy.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/proxy.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/py.typed +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/queue.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/queue.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/retries.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/runner.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/runner.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/running_app.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_common.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_filesystem.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_impl_persistent.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_impl_v1.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_impl_v2.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_sandbox.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_sidecar.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_snapshot.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/_task_command_router_client.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/sandbox.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/sandbox/sandbox.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/schedule.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/scheduler_placement.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/secret.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/secret.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/server.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/server.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/serving.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/serving.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/stream_type.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/token_flow.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/token_flow.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/types.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/volume.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/volume.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/workspace.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal/workspace.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal.egg-info/requires.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal.egg-info/top_level.txt +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/mdmd/types.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_docs/reference_intro.md +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/__init__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/py.typed +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/modal_version/__main__.py +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/pyproject.toml +0 -0
- {modal-1.6.4.dev8 → modal-1.6.4.dev10}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
"""The audit log RPCs, one call each, retried with backoff. Ordering and batching live in list.py."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Awaitable, Callable
|
|
7
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
8
|
+
|
|
9
|
+
from modal._utils.grpc_utils import Retry
|
|
10
|
+
from modal_proto import api_pb2
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from modal.client import _Client
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_Resp = TypeVar("_Resp")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_MAX_RETRIES = 4
|
|
20
|
+
_PLAN_MAX_RETRIES = 2
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def _call(
|
|
24
|
+
rpc: Callable[..., Awaitable[_Resp]], req: object, *, timeout: float, max_retries: int = _MAX_RETRIES
|
|
25
|
+
) -> _Resp:
|
|
26
|
+
"""Call with backoff on timeouts and transient errors; rate and resource limits wait as the server says."""
|
|
27
|
+
retry = Retry(base_delay=1.0, delay_factor=2.0, max_delay=8.0, max_retries=max_retries, attempt_timeout=timeout)
|
|
28
|
+
return await rpc(req, retry=retry)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def get_settings(client: _Client) -> api_pb2.AuditLogSettingsGetResponse:
|
|
32
|
+
return await client._stub.AuditLogSettingsGet(api_pb2.AuditLogSettingsGetRequest())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def count_entries(
|
|
36
|
+
client: _Client, q: str, oldest_ms: int, newest_ms: int, config: api_pb2.AuditLogSettingsGetResponse
|
|
37
|
+
) -> int:
|
|
38
|
+
req = api_pb2.AuditLogCountRequest(q=q, oldest_ms=oldest_ms, newest_ms=newest_ms)
|
|
39
|
+
resp = await _call(client._stub.AuditLogCount, req, timeout=config.request_timeout_secs)
|
|
40
|
+
return resp.count
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def list_page(
|
|
44
|
+
client: _Client,
|
|
45
|
+
q: str,
|
|
46
|
+
oldest_ms: int,
|
|
47
|
+
newest_ms: int,
|
|
48
|
+
limit: int,
|
|
49
|
+
config: api_pb2.AuditLogSettingsGetResponse,
|
|
50
|
+
newest_cursor: api_pb2.AuditLogCursor | None = None,
|
|
51
|
+
oldest_cursor: api_pb2.AuditLogCursor | None = None,
|
|
52
|
+
) -> api_pb2.AuditLogListResponse:
|
|
53
|
+
req = api_pb2.AuditLogListRequest(q=q, oldest_ms=oldest_ms, newest_ms=newest_ms, limit=limit)
|
|
54
|
+
if newest_cursor is not None:
|
|
55
|
+
req.newest_cursor.CopyFrom(newest_cursor)
|
|
56
|
+
if oldest_cursor is not None:
|
|
57
|
+
req.oldest_cursor.CopyFrom(oldest_cursor)
|
|
58
|
+
return await _call(client._stub.AuditLogList, req, timeout=config.request_timeout_secs)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def plan_buckets(
|
|
62
|
+
client: _Client, q: str, oldest_ms: int, newest_ms: int, config: api_pb2.AuditLogSettingsGetResponse
|
|
63
|
+
) -> list[api_pb2.AuditLogCursor]:
|
|
64
|
+
req = api_pb2.AuditLogBucketsRequest(q=q, oldest_ms=oldest_ms, newest_ms=newest_ms, bucket_size=config.bucket_size)
|
|
65
|
+
resp = await _call(client._stub.AuditLogPlan, req, timeout=config.plan_timeout_secs, max_retries=_PLAN_MAX_RETRIES)
|
|
66
|
+
return list(resp.cursors)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from modal._audit_logs import _rpcs
|
|
7
|
+
from modal_proto import api_pb2
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from modal.client import _Client
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def count_audit_logs(
|
|
14
|
+
client: _Client, q: str, oldest_ms: int, newest_ms: int, config: api_pb2.AuditLogSettingsGetResponse
|
|
15
|
+
) -> int:
|
|
16
|
+
"""Count the entries in [oldest_ms, newest_ms) that match the query."""
|
|
17
|
+
return await _rpcs.count_entries(client, q, oldest_ms, newest_ms, config)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import AsyncGenerator
|
|
5
|
+
from contextlib import aclosing
|
|
6
|
+
|
|
7
|
+
from modal._audit_logs import _rpcs
|
|
8
|
+
from modal._utils.async_utils import async_map_ordered
|
|
9
|
+
from modal.client import _Client
|
|
10
|
+
from modal_proto import api_pb2
|
|
11
|
+
|
|
12
|
+
# (newest_cursor, oldest_cursor); None where the bucket is open-ended.
|
|
13
|
+
Bucket = tuple[api_pb2.AuditLogCursor | None, api_pb2.AuditLogCursor | None]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def get_bucket_cursors(
|
|
17
|
+
client: _Client, q: str, oldest_ms: int, newest_ms: int, config: api_pb2.AuditLogSettingsGetResponse
|
|
18
|
+
) -> list[Bucket]:
|
|
19
|
+
"""Split the range into buckets, as (newest_cursor, oldest_cursor) pairs ordered oldest to newest.
|
|
20
|
+
|
|
21
|
+
The first bucket has no oldest cursor and the last has no newest cursor.
|
|
22
|
+
"""
|
|
23
|
+
cursors = await _rpcs.plan_buckets(client, q, oldest_ms, newest_ms, config)
|
|
24
|
+
return list(zip([None, *cursors], [*cursors, None]))[::-1]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def _fetch_bucket(
|
|
28
|
+
client: _Client,
|
|
29
|
+
q: str,
|
|
30
|
+
oldest_ms: int,
|
|
31
|
+
newest_ms: int,
|
|
32
|
+
config: api_pb2.AuditLogSettingsGetResponse,
|
|
33
|
+
bucket: Bucket,
|
|
34
|
+
) -> list[api_pb2.AuditLogEntry]:
|
|
35
|
+
"""Read one bucket's entries, oldest to newest, using pagination"""
|
|
36
|
+
newest_cursor, oldest_cursor = bucket
|
|
37
|
+
entries: list[api_pb2.AuditLogEntry] = []
|
|
38
|
+
while True:
|
|
39
|
+
resp = await _rpcs.list_page(
|
|
40
|
+
client, q, oldest_ms, newest_ms, config.page_size, config, newest_cursor, oldest_cursor
|
|
41
|
+
)
|
|
42
|
+
entries.extend(resp.items)
|
|
43
|
+
# Skips the empty request a full last page would otherwise trigger; an unset next_cursor also ends the loop.
|
|
44
|
+
if oldest_cursor is not None and resp.items and resp.items[-1].id == oldest_cursor.id:
|
|
45
|
+
break
|
|
46
|
+
if not resp.HasField("next_cursor"):
|
|
47
|
+
break
|
|
48
|
+
newest_cursor = resp.next_cursor
|
|
49
|
+
entries.reverse()
|
|
50
|
+
return entries
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def fetch_audit_logs(
|
|
54
|
+
q: str,
|
|
55
|
+
oldest_ms: int,
|
|
56
|
+
newest_ms: int,
|
|
57
|
+
*,
|
|
58
|
+
client: _Client | None = None,
|
|
59
|
+
config: api_pb2.AuditLogSettingsGetResponse,
|
|
60
|
+
) -> AsyncGenerator[api_pb2.AuditLogEntry]:
|
|
61
|
+
"""Yield the range's entries from the oldest to the newest, one bucket at a time.
|
|
62
|
+
|
|
63
|
+
At most `max_concurrent_requests` buckets are fetched at once, and at most `max_buffered_buckets` are fetched
|
|
64
|
+
or held at once. A new bucket is admitted only after the consumer has taken all of a bucket's entries, so a slow
|
|
65
|
+
consumer or a stalled request cannot grow memory.
|
|
66
|
+
If any request fails after its attempts, the remaining requests are cancelled and the error is raised.
|
|
67
|
+
"""
|
|
68
|
+
if client is None:
|
|
69
|
+
client = await _Client.from_env()
|
|
70
|
+
buckets = await get_bucket_cursors(client, q, oldest_ms, newest_ms, config)
|
|
71
|
+
|
|
72
|
+
async def bucket_stream() -> AsyncGenerator[Bucket]:
|
|
73
|
+
for bucket in buckets:
|
|
74
|
+
yield bucket
|
|
75
|
+
|
|
76
|
+
async def fetch(bucket: Bucket) -> list[api_pb2.AuditLogEntry]:
|
|
77
|
+
return await _fetch_bucket(client, q, oldest_ms, newest_ms, config, bucket)
|
|
78
|
+
|
|
79
|
+
fetched = async_map_ordered(bucket_stream(), fetch, config.max_concurrent_requests, config.max_buffered_buckets)
|
|
80
|
+
async with aclosing(fetched) as stream:
|
|
81
|
+
async for entries in stream:
|
|
82
|
+
for entry in entries:
|
|
83
|
+
yield entry
|
|
@@ -584,6 +584,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
584
584
|
] # added as mounts on any container referencing the Image, see `def _mount_layers`
|
|
585
585
|
_added_python_source_set: frozenset[str] # used to warn about missing mounts during auto-mount deprecation
|
|
586
586
|
_metadata: api_pb2.ImageMetadata | None = None # set on hydration, private for now
|
|
587
|
+
_cache_hit: bool | None = None # set when built, True if an existing build was reused
|
|
587
588
|
_is_empty: bool
|
|
588
589
|
_build_steps: list[api_pb2.ImageBuildStep] | None = None
|
|
589
590
|
# A failed image ID is only queryable through the client that performed the build.
|
|
@@ -832,6 +833,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
832
833
|
result: api_pb2.GenericResult
|
|
833
834
|
metadata: api_pb2.ImageMetadata | None = None
|
|
834
835
|
|
|
836
|
+
self._cache_hit = bool(resp.result.status)
|
|
835
837
|
if resp.result.status:
|
|
836
838
|
# image already built
|
|
837
839
|
result = resp.result
|
|
@@ -2062,8 +2064,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2062
2064
|
if not isinstance(entrypoint_commands, list) or not all(isinstance(x, str) for x in entrypoint_commands):
|
|
2063
2065
|
raise InvalidError("entrypoint_commands must be a list of strings.")
|
|
2064
2066
|
args = _flatten_str_args("entrypoint", "entrypoint_commands", entrypoint_commands)
|
|
2065
|
-
|
|
2066
|
-
dockerfile_cmd = f"ENTRYPOINT [{args_str}]"
|
|
2067
|
+
dockerfile_cmd = f"ENTRYPOINT {json.dumps(args, ensure_ascii=False)}"
|
|
2067
2068
|
|
|
2068
2069
|
return self.dockerfile_commands(dockerfile_cmd)
|
|
2069
2070
|
|
|
@@ -2083,8 +2084,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2083
2084
|
if not isinstance(shell_commands, list) or not all(isinstance(x, str) for x in shell_commands):
|
|
2084
2085
|
raise InvalidError("shell_commands must be a list of strings.")
|
|
2085
2086
|
args = _flatten_str_args("shell", "shell_commands", shell_commands)
|
|
2086
|
-
|
|
2087
|
-
dockerfile_cmd = f"SHELL [{args_str}]"
|
|
2087
|
+
dockerfile_cmd = f"SHELL {json.dumps(args, ensure_ascii=False)}"
|
|
2088
2088
|
|
|
2089
2089
|
return self.dockerfile_commands(dockerfile_cmd)
|
|
2090
2090
|
|
|
@@ -3074,8 +3074,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
3074
3074
|
raise InvalidError("Image CMD must be a list of strings.")
|
|
3075
3075
|
|
|
3076
3076
|
cmd_args = _flatten_str_args("cmd", "cmd", cmd)
|
|
3077
|
-
|
|
3078
|
-
dockerfile_cmd = f"CMD [{cmd_str}]"
|
|
3077
|
+
dockerfile_cmd = f"CMD {json.dumps(cmd_args, ensure_ascii=False)}"
|
|
3079
3078
|
|
|
3080
3079
|
return self.dockerfile_commands(dockerfile_cmd)
|
|
3081
3080
|
|
|
@@ -42,6 +42,26 @@ def _print_result_summary(count: int) -> None:
|
|
|
42
42
|
OutputManager.get().print(f"Showing {count} {result_label}")
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
async def _build_and_publish(image: _Image, image_ref: str, app: _App, env: str, name: Optional[str]) -> None:
|
|
46
|
+
built_image = await image.build(app)
|
|
47
|
+
|
|
48
|
+
output_mgr = OutputManager.get()
|
|
49
|
+
if built_image._cache_hit:
|
|
50
|
+
message = f"Used a cached pull of [bold]{image_ref}[/bold] as [bold]{built_image.object_id}[/bold]"
|
|
51
|
+
# A digest reference can't change in the registry, so pulling it again would be a no-op
|
|
52
|
+
if "@" not in image_ref:
|
|
53
|
+
message += " [dim](Pass --force-build to pull it from the registry again.)[/dim]"
|
|
54
|
+
output_mgr.step_completed(message)
|
|
55
|
+
else:
|
|
56
|
+
output_mgr.step_completed(f"Pulled [bold]{image_ref}[/bold] as [bold]{built_image.object_id}[/bold]")
|
|
57
|
+
if name:
|
|
58
|
+
upgraded_image_name = _upgrade_image_name(name, fallback_environment_name=env)
|
|
59
|
+
await built_image.publish(upgraded_image_name)
|
|
60
|
+
output_mgr.step_completed(f"Image published as [bold magenta]{upgraded_image_name}[/bold magenta]! 🎉")
|
|
61
|
+
output_mgr.print("\nUse it with:\n")
|
|
62
|
+
output_mgr.print(Syntax(f'modal.Image.from_name("{upgraded_image_name}")', "python"))
|
|
63
|
+
|
|
64
|
+
|
|
45
65
|
async def _registry_secret(
|
|
46
66
|
secret_name: Optional[str], secret_vars: Optional[str], env: str
|
|
47
67
|
) -> tuple[Optional[_Secret], set[str]]:
|
|
@@ -56,6 +76,15 @@ async def _registry_secret(
|
|
|
56
76
|
return _Secret.from_local_environ(keys), set(keys)
|
|
57
77
|
|
|
58
78
|
|
|
79
|
+
def _check_registry_host(image_ref: str) -> None:
|
|
80
|
+
first, sep, _ = image_ref.partition("/")
|
|
81
|
+
# A leading path component is a registry host only if it looks like one; otherwise it's a Docker Hub name
|
|
82
|
+
if not sep or ("." not in first and ":" not in first and first != "localhost"):
|
|
83
|
+
raise click.UsageError(
|
|
84
|
+
f"IMAGE_REF must start with the registry host, e.g. docker.io/{image_ref} for a Docker Hub image."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
59
88
|
@image_cli.command("build", no_args_is_help=True)
|
|
60
89
|
@click.argument("context_dir", metavar="CONTEXT_DIR")
|
|
61
90
|
@click.option(
|
|
@@ -154,6 +183,100 @@ async def build(
|
|
|
154
183
|
output_mgr.print(Syntax(f'modal.Image.from_name("{tag}")', "python"))
|
|
155
184
|
|
|
156
185
|
|
|
186
|
+
@image_cli.command("pull", no_args_is_help=True)
|
|
187
|
+
@click.argument("image_ref")
|
|
188
|
+
@click.option(
|
|
189
|
+
"--name",
|
|
190
|
+
"name",
|
|
191
|
+
metavar="[ENV/]NAME[:TAG]",
|
|
192
|
+
help="Publish the pulled Image under this name. An ENV/ prefix publishes it to that Environment.",
|
|
193
|
+
)
|
|
194
|
+
@click.option(
|
|
195
|
+
"--registry-secret",
|
|
196
|
+
"registry_secret_name",
|
|
197
|
+
default=None,
|
|
198
|
+
help="Name of a modal.Secret holding registry credentials.",
|
|
199
|
+
)
|
|
200
|
+
@click.option(
|
|
201
|
+
"--registry-secret-from-env",
|
|
202
|
+
"registry_secret_vars",
|
|
203
|
+
default=None,
|
|
204
|
+
metavar="VAR[,VAR...]",
|
|
205
|
+
help=(
|
|
206
|
+
"Comma-separated local environment variable names holding registry credentials. "
|
|
207
|
+
"Cannot be combined with --registry-secret."
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
@click.option(
|
|
211
|
+
"--force-build",
|
|
212
|
+
is_flag=True,
|
|
213
|
+
help="Pull the image from the registry again, replacing a cached Image.",
|
|
214
|
+
)
|
|
215
|
+
@env_option
|
|
216
|
+
@synchronizer.create_blocking
|
|
217
|
+
async def pull(
|
|
218
|
+
image_ref: str,
|
|
219
|
+
*,
|
|
220
|
+
name: Optional[str] = None,
|
|
221
|
+
registry_secret_name: Optional[str] = None,
|
|
222
|
+
registry_secret_vars: Optional[str] = None,
|
|
223
|
+
force_build: bool = False,
|
|
224
|
+
env: Optional[str] = None,
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Pull an Image from a container registry into Modal.
|
|
227
|
+
|
|
228
|
+
The `image_ref` is a registry image name (and optional tag), as passed to `modal.Image.from_registry`.
|
|
229
|
+
It must start with the registry host, e.g. `docker.io/node:20` for a Docker Hub image.
|
|
230
|
+
If `--name` is provided, the Modal Image will also be published under that name.
|
|
231
|
+
Use `ENV/NAME` to publish to another Environment; `--env` still selects the Environment
|
|
232
|
+
used for the pull and for looking up `--registry-secret`.
|
|
233
|
+
|
|
234
|
+
Examples:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
modal image pull docker.io/node:20
|
|
238
|
+
modal image pull nvcr.io/nvidia/pytorch:25.01-py3 --name pytorch:25
|
|
239
|
+
modal image pull docker.io/node:20 --name prod/node:20
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Pass `--force-build` to pull a tag again after it changes in the registry, e.g. `latest`.
|
|
243
|
+
|
|
244
|
+
Private registries need credentials from a `modal.Secret`, either one that already exists
|
|
245
|
+
(`--registry-secret`) or one assembled from local environment variables (`--registry-secret-from-env`).
|
|
246
|
+
The kind of authentication follows from the keys in the credentials:
|
|
247
|
+
|
|
248
|
+
- AWS: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION` (optionally `AWS_SESSION_TOKEN`),
|
|
249
|
+
or `AWS_ROLE_ARN` and `AWS_REGION` to assume a role with OIDC
|
|
250
|
+
- GCP: `SERVICE_ACCOUNT_JSON`, holding the service account key's JSON
|
|
251
|
+
- Username and password: `REGISTRY_USERNAME` and `REGISTRY_PASSWORD`
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Authenticate with a named Secret
|
|
255
|
+
modal image pull ghcr.io/my-org/my-image --registry-secret my-registry-secret
|
|
256
|
+
|
|
257
|
+
# Authenticate with credentials in environment variables
|
|
258
|
+
modal image pull $AWS_ECR_URL/my-image \\
|
|
259
|
+
--registry-secret-from-env AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
_check_registry_host(image_ref)
|
|
264
|
+
env = ensure_env(env)
|
|
265
|
+
secret, keys = await _registry_secret(registry_secret_name, registry_secret_vars, env)
|
|
266
|
+
if secret is not None:
|
|
267
|
+
# Fail before creating the app; from_registry runs the same check during the build
|
|
268
|
+
try:
|
|
269
|
+
_infer_registry_auth_type(keys)
|
|
270
|
+
except InvalidError as exc:
|
|
271
|
+
raise click.UsageError(str(exc)) from exc
|
|
272
|
+
image = _Image.from_registry(image_ref, secret, force_build=force_build)
|
|
273
|
+
|
|
274
|
+
app = _App("modal-image-pull")
|
|
275
|
+
async with _run_app(app, environment_name=env, stream_output=False):
|
|
276
|
+
OutputManager.get().print(f"Pulling [bold]{image_ref}[/bold]")
|
|
277
|
+
await _build_and_publish(image, image_ref, app, env, name)
|
|
278
|
+
|
|
279
|
+
|
|
157
280
|
@image_cli.command("logs", no_args_is_help=True)
|
|
158
281
|
@click.argument("image_id")
|
|
159
282
|
@click.option(
|
|
@@ -448,7 +448,7 @@ def shell(
|
|
|
448
448
|
app = App("modal shell")
|
|
449
449
|
|
|
450
450
|
# NB: invoking under bash makes --cmd a lot more flexible.
|
|
451
|
-
cmds =
|
|
451
|
+
cmds = ["/bin/bash", "-c", cmd]
|
|
452
452
|
timeout = 3600
|
|
453
453
|
parsed_experimental_options = _parse_experimental_options(experimental_options)
|
|
454
454
|
|
|
@@ -87,7 +87,7 @@ class _Client:
|
|
|
87
87
|
server_url: str,
|
|
88
88
|
client_type: int,
|
|
89
89
|
credentials: typing.Optional[tuple[str, str]],
|
|
90
|
-
version: str = "1.6.4.
|
|
90
|
+
version: str = "1.6.4.dev10",
|
|
91
91
|
*,
|
|
92
92
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
93
93
|
):
|
|
@@ -304,7 +304,7 @@ class Client:
|
|
|
304
304
|
server_url: str,
|
|
305
305
|
client_type: int,
|
|
306
306
|
credentials: typing.Optional[tuple[str, str]],
|
|
307
|
-
version: str = "1.6.4.
|
|
307
|
+
version: str = "1.6.4.dev10",
|
|
308
308
|
*,
|
|
309
309
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
310
310
|
):
|
|
@@ -32,6 +32,7 @@ class Image(modal.object.Object):
|
|
|
32
32
|
_deferred_mounts: collections.abc.Sequence[modal.mount.Mount]
|
|
33
33
|
_added_python_source_set: frozenset[str]
|
|
34
34
|
_metadata: typing.Optional[modal_proto.api_pb2.ImageMetadata]
|
|
35
|
+
_cache_hit: typing.Optional[bool]
|
|
35
36
|
_is_empty: bool
|
|
36
37
|
_build_steps: typing.Optional[list[modal_proto.api_pb2.ImageBuildStep]]
|
|
37
38
|
_failed_build_attempt: typing.Optional[modal._image._FailedBuildAttempt]
|
|
@@ -106,6 +106,10 @@ modal.egg-info/dependency_links.txt
|
|
|
106
106
|
modal.egg-info/entry_points.txt
|
|
107
107
|
modal.egg-info/requires.txt
|
|
108
108
|
modal.egg-info/top_level.txt
|
|
109
|
+
modal/_audit_logs/__init__.py
|
|
110
|
+
modal/_audit_logs/_rpcs.py
|
|
111
|
+
modal/_audit_logs/count.py
|
|
112
|
+
modal/_audit_logs/list.py
|
|
109
113
|
modal/_output/__init__.py
|
|
110
114
|
modal/_output/manager.py
|
|
111
115
|
modal/_output/pty.py
|
|
@@ -163,6 +163,10 @@ class ModalClientBase(abc.ABC):
|
|
|
163
163
|
async def ClassGet(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.ClassGetRequest, modal_proto.api_pb2.ClassGetResponse]') -> None:
|
|
164
164
|
pass
|
|
165
165
|
|
|
166
|
+
@abc.abstractmethod
|
|
167
|
+
async def CliCommandSuggest(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.CliCommandSuggestRequest, modal_proto.api_pb2.CliCommandSuggestResponse]') -> None:
|
|
168
|
+
pass
|
|
169
|
+
|
|
166
170
|
@abc.abstractmethod
|
|
167
171
|
async def ClientHello(self, stream: 'grpclib.server.Stream[google.protobuf.empty_pb2.Empty, modal_proto.api_pb2.ClientHelloResponse]') -> None:
|
|
168
172
|
pass
|
|
@@ -587,6 +591,10 @@ class ModalClientBase(abc.ABC):
|
|
|
587
591
|
async def MountPutFile(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.MountPutFileRequest, modal_proto.api_pb2.MountPutFileResponse]') -> None:
|
|
588
592
|
pass
|
|
589
593
|
|
|
594
|
+
@abc.abstractmethod
|
|
595
|
+
async def ObjectSuggest(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.ObjectSuggestRequest, modal_proto.api_pb2.ObjectSuggestResponse]') -> None:
|
|
596
|
+
pass
|
|
597
|
+
|
|
590
598
|
@abc.abstractmethod
|
|
591
599
|
async def PersistentSandboxCreate(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.PersistentSandboxCreateRequest, modal_proto.api_pb2.PersistentSandboxCreateResponse]') -> None:
|
|
592
600
|
pass
|
|
@@ -719,6 +727,14 @@ class ModalClientBase(abc.ABC):
|
|
|
719
727
|
async def SandboxGetResourceUsage(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxGetResourceUsageRequest, modal_proto.api_pb2.SandboxGetResourceUsageResponse]') -> None:
|
|
720
728
|
pass
|
|
721
729
|
|
|
730
|
+
@abc.abstractmethod
|
|
731
|
+
async def SandboxGetStats(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxGetStatsRequest, modal_proto.api_pb2.SandboxGetStatsResponse]') -> None:
|
|
732
|
+
pass
|
|
733
|
+
|
|
734
|
+
@abc.abstractmethod
|
|
735
|
+
async def SandboxGetStatsV2(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxGetStatsRequest, modal_proto.api_pb2.SandboxGetStatsResponse]') -> None:
|
|
736
|
+
pass
|
|
737
|
+
|
|
722
738
|
@abc.abstractmethod
|
|
723
739
|
async def SandboxGetTaskId(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxGetTaskIdRequest, modal_proto.api_pb2.SandboxGetTaskIdResponse]') -> None:
|
|
724
740
|
pass
|
|
@@ -1341,6 +1357,12 @@ class ModalClientBase(abc.ABC):
|
|
|
1341
1357
|
modal_proto.api_pb2.ClassGetRequest,
|
|
1342
1358
|
modal_proto.api_pb2.ClassGetResponse,
|
|
1343
1359
|
),
|
|
1360
|
+
'/modal.client.ModalClient/CliCommandSuggest': grpclib.const.Handler(
|
|
1361
|
+
self.CliCommandSuggest,
|
|
1362
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
1363
|
+
modal_proto.api_pb2.CliCommandSuggestRequest,
|
|
1364
|
+
modal_proto.api_pb2.CliCommandSuggestResponse,
|
|
1365
|
+
),
|
|
1344
1366
|
'/modal.client.ModalClient/ClientHello': grpclib.const.Handler(
|
|
1345
1367
|
self.ClientHello,
|
|
1346
1368
|
grpclib.const.Cardinality.UNARY_UNARY,
|
|
@@ -1977,6 +1999,12 @@ class ModalClientBase(abc.ABC):
|
|
|
1977
1999
|
modal_proto.api_pb2.MountPutFileRequest,
|
|
1978
2000
|
modal_proto.api_pb2.MountPutFileResponse,
|
|
1979
2001
|
),
|
|
2002
|
+
'/modal.client.ModalClient/ObjectSuggest': grpclib.const.Handler(
|
|
2003
|
+
self.ObjectSuggest,
|
|
2004
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
2005
|
+
modal_proto.api_pb2.ObjectSuggestRequest,
|
|
2006
|
+
modal_proto.api_pb2.ObjectSuggestResponse,
|
|
2007
|
+
),
|
|
1980
2008
|
'/modal.client.ModalClient/PersistentSandboxCreate': grpclib.const.Handler(
|
|
1981
2009
|
self.PersistentSandboxCreate,
|
|
1982
2010
|
grpclib.const.Cardinality.UNARY_UNARY,
|
|
@@ -2175,6 +2203,18 @@ class ModalClientBase(abc.ABC):
|
|
|
2175
2203
|
modal_proto.api_pb2.SandboxGetResourceUsageRequest,
|
|
2176
2204
|
modal_proto.api_pb2.SandboxGetResourceUsageResponse,
|
|
2177
2205
|
),
|
|
2206
|
+
'/modal.client.ModalClient/SandboxGetStats': grpclib.const.Handler(
|
|
2207
|
+
self.SandboxGetStats,
|
|
2208
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
2209
|
+
modal_proto.api_pb2.SandboxGetStatsRequest,
|
|
2210
|
+
modal_proto.api_pb2.SandboxGetStatsResponse,
|
|
2211
|
+
),
|
|
2212
|
+
'/modal.client.ModalClient/SandboxGetStatsV2': grpclib.const.Handler(
|
|
2213
|
+
self.SandboxGetStatsV2,
|
|
2214
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
2215
|
+
modal_proto.api_pb2.SandboxGetStatsRequest,
|
|
2216
|
+
modal_proto.api_pb2.SandboxGetStatsResponse,
|
|
2217
|
+
),
|
|
2178
2218
|
'/modal.client.ModalClient/SandboxGetTaskId': grpclib.const.Handler(
|
|
2179
2219
|
self.SandboxGetTaskId,
|
|
2180
2220
|
grpclib.const.Cardinality.UNARY_UNARY,
|
|
@@ -3003,6 +3043,12 @@ class ModalClientStub:
|
|
|
3003
3043
|
modal_proto.api_pb2.ClassGetRequest,
|
|
3004
3044
|
modal_proto.api_pb2.ClassGetResponse,
|
|
3005
3045
|
)
|
|
3046
|
+
self.CliCommandSuggest = grpclib.client.UnaryUnaryMethod(
|
|
3047
|
+
channel,
|
|
3048
|
+
'/modal.client.ModalClient/CliCommandSuggest',
|
|
3049
|
+
modal_proto.api_pb2.CliCommandSuggestRequest,
|
|
3050
|
+
modal_proto.api_pb2.CliCommandSuggestResponse,
|
|
3051
|
+
)
|
|
3006
3052
|
self.ClientHello = grpclib.client.UnaryUnaryMethod(
|
|
3007
3053
|
channel,
|
|
3008
3054
|
'/modal.client.ModalClient/ClientHello',
|
|
@@ -3639,6 +3685,12 @@ class ModalClientStub:
|
|
|
3639
3685
|
modal_proto.api_pb2.MountPutFileRequest,
|
|
3640
3686
|
modal_proto.api_pb2.MountPutFileResponse,
|
|
3641
3687
|
)
|
|
3688
|
+
self.ObjectSuggest = grpclib.client.UnaryUnaryMethod(
|
|
3689
|
+
channel,
|
|
3690
|
+
'/modal.client.ModalClient/ObjectSuggest',
|
|
3691
|
+
modal_proto.api_pb2.ObjectSuggestRequest,
|
|
3692
|
+
modal_proto.api_pb2.ObjectSuggestResponse,
|
|
3693
|
+
)
|
|
3642
3694
|
self.PersistentSandboxCreate = grpclib.client.UnaryUnaryMethod(
|
|
3643
3695
|
channel,
|
|
3644
3696
|
'/modal.client.ModalClient/PersistentSandboxCreate',
|
|
@@ -3837,6 +3889,18 @@ class ModalClientStub:
|
|
|
3837
3889
|
modal_proto.api_pb2.SandboxGetResourceUsageRequest,
|
|
3838
3890
|
modal_proto.api_pb2.SandboxGetResourceUsageResponse,
|
|
3839
3891
|
)
|
|
3892
|
+
self.SandboxGetStats = grpclib.client.UnaryUnaryMethod(
|
|
3893
|
+
channel,
|
|
3894
|
+
'/modal.client.ModalClient/SandboxGetStats',
|
|
3895
|
+
modal_proto.api_pb2.SandboxGetStatsRequest,
|
|
3896
|
+
modal_proto.api_pb2.SandboxGetStatsResponse,
|
|
3897
|
+
)
|
|
3898
|
+
self.SandboxGetStatsV2 = grpclib.client.UnaryUnaryMethod(
|
|
3899
|
+
channel,
|
|
3900
|
+
'/modal.client.ModalClient/SandboxGetStatsV2',
|
|
3901
|
+
modal_proto.api_pb2.SandboxGetStatsRequest,
|
|
3902
|
+
modal_proto.api_pb2.SandboxGetStatsResponse,
|
|
3903
|
+
)
|
|
3840
3904
|
self.SandboxGetTaskId = grpclib.client.UnaryUnaryMethod(
|
|
3841
3905
|
channel,
|
|
3842
3906
|
'/modal.client.ModalClient/SandboxGetTaskId',
|