modAL 1.6.3.dev8__tar.gz → 1.6.3.dev9__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.3.dev8 → modal-1.6.3.dev9}/PKG-INFO +1 -1
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_environments.py +5 -1
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_image.py +65 -23
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/docker_utils.py +27 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_workspace.py +65 -1
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/app.py +39 -19
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/app.pyi +59 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/builder/PREVIEW.txt +0 -1
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/environment.py +21 -5
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/workspace.py +100 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/client.pyi +2 -2
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/environments.pyi +2 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/experimental/__init__.py +6 -5
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/functions.pyi +6 -6
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/mount.py +0 -1
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/types.py +23 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/workspace.py +3 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/workspace.pyi +93 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/api_grpc.py +64 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/api_pb2.py +885 -843
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/api_pb2.pyi +96 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/api_pb2_grpc.py +133 -1
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/api_pb2_grpc.pyi +43 -3
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/modal_api_grpc.py +4 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_version/__init__.py +1 -1
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/LICENSE +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/README.md +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/__main__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_billing.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_cluster.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_cluster.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_container_entrypoint.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_function_variants.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_functions.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_grpc_client.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_ipython.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_load_context.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_location.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_logs.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_logs_manager.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_logs_manager.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_metrics.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_metrics.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_object.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_outbound_policy.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_outbound_policy.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_output/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_output/manager.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_output/pty.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_output/rich.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_output/status.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_partial_function.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_resolver.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_resources.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/asgi.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/execution_context.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/telemetry.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_serialization.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_server.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_server.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_supports_logs.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_traceback.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_tunnel.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_tunnel.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_type_manager.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/app_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/async_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/blob_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/browser_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/curl_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/deprecation.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/display_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/function_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/git_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/hash_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/http_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/logger.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/mount_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/name_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/package_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/proxy_support.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/shell_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_utils/time_utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_vendor/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_vendor/tblib.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_vendor/version.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/_watcher.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/billing.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/billing.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/builder/2023.12.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/builder/2024.04.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/builder/2024.10.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/builder/2025.06.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/builder/README.md +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/builder/base-images.json +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/call_graph.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/_download.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/_help.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/_logs.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/_stats.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/_traceback.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/app.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/billing.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/changelog.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/config.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/container.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/curl.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/dashboard.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/dict.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/endpoint.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/entry_point.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/function.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/image.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/import_refs.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/logo.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/network_file_system.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/profile.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/queues.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/run.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/secret.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/selector.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/server.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/shell.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/skills.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/token.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/utils.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cli/volume.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/client.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cls.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cls.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cluster.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/cluster.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/config.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/container_process.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/container_process.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/dict.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/dict.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/environments.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/exception.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/experimental/_flash_container_entry.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/experimental/flash.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/experimental/flash.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/experimental/flash_server.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/experimental/flash_server.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/experimental/ipython.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/file_pattern_matcher.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/functions.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/image.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/image.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/io_streams.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/io_streams.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/mount.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/network_file_system.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/network_file_system.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/object.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/object.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/output.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/parallel_map.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/parallel_map.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/partial_function.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/partial_function.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/proxy.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/proxy.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/py.typed +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/queue.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/queue.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/retries.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/runner.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/runner.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/running_app.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_common.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_filesystem.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_impl_persistent.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_impl_v1.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_impl_v2.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_sandbox.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_sidecar.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_snapshot.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/_task_command_router_client.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/sandbox.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/sandbox/sandbox.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/schedule.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/scheduler_placement.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/secret.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/secret.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/server.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/server.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/serving.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/serving.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/stream_type.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/token_flow.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/token_flow.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/volume.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal/volume.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal.egg-info/requires.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal.egg-info/top_level.txt +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/mdmd/types.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_docs/reference_intro.md +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/__init__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/py.typed +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/modal_version/__main__.py +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/pyproject.toml +0 -0
- {modal-1.6.3.dev8 → modal-1.6.3.dev9}/setup.cfg +0 -0
|
@@ -648,6 +648,7 @@ async def _update_environment(
|
|
|
648
648
|
*,
|
|
649
649
|
new_name: str | None = None,
|
|
650
650
|
new_web_suffix: str | None = None,
|
|
651
|
+
new_image_builder_version: str | None = None,
|
|
651
652
|
client: _Client | None = None,
|
|
652
653
|
):
|
|
653
654
|
new_name_pb2 = None
|
|
@@ -662,7 +663,10 @@ async def _update_environment(
|
|
|
662
663
|
new_web_suffix_pb2 = StringValue(value=new_web_suffix)
|
|
663
664
|
|
|
664
665
|
update_payload = api_pb2.EnvironmentUpdateRequest(
|
|
665
|
-
current_name=current_name,
|
|
666
|
+
current_name=current_name,
|
|
667
|
+
name=new_name_pb2,
|
|
668
|
+
web_suffix=new_web_suffix_pb2,
|
|
669
|
+
image_builder_version=new_image_builder_version,
|
|
666
670
|
)
|
|
667
671
|
if client is None:
|
|
668
672
|
client = await _Client.from_env()
|
|
@@ -40,6 +40,7 @@ from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
|
|
40
40
|
from ._utils.docker_utils import (
|
|
41
41
|
extract_copy_command_patterns,
|
|
42
42
|
find_dockerignore_file,
|
|
43
|
+
splittable_from_line_index,
|
|
43
44
|
)
|
|
44
45
|
from ._utils.function_utils import FunctionSourceInfo, parse_gpu_config
|
|
45
46
|
from ._utils.mount_utils import validate_only_modal_volumes, validate_volumes_by_object_id
|
|
@@ -77,7 +78,7 @@ LATEST_IMAGE_BUILDER_VERSION: ImageBuilderVersion = "2025.06"
|
|
|
77
78
|
# Python versions in mount.py where we specify the "standalone Python versions" we create mounts for.
|
|
78
79
|
# Consider consolidating these multiple sources of truth?
|
|
79
80
|
SUPPORTED_PYTHON_SERIES: dict[ImageBuilderVersion, list[str]] = {
|
|
80
|
-
"PREVIEW": ["3.
|
|
81
|
+
"PREVIEW": ["3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"],
|
|
81
82
|
"2025.06": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"],
|
|
82
83
|
"2024.10": ["3.10", "3.11", "3.12", "3.13"],
|
|
83
84
|
"2024.04": ["3.10", "3.11", "3.12"],
|
|
@@ -174,6 +175,7 @@ def _validate_python_version(
|
|
|
174
175
|
allow_free_threading: bool = False,
|
|
175
176
|
caller_name: str = "",
|
|
176
177
|
) -> str:
|
|
178
|
+
python_version_unspecified = python_version is None
|
|
177
179
|
if python_version is None:
|
|
178
180
|
# If Python version is unspecified, match the local version, up to the minor component
|
|
179
181
|
python_version = series_version = "{}.{}".format(*sys.version_info)
|
|
@@ -196,6 +198,12 @@ def _validate_python_version(
|
|
|
196
198
|
|
|
197
199
|
supported_series = SUPPORTED_PYTHON_SERIES[builder_version]
|
|
198
200
|
if series_version not in supported_series:
|
|
201
|
+
if python_version_unspecified:
|
|
202
|
+
raise InvalidError(
|
|
203
|
+
f"Python {series_version} is not supported by the {builder_version!r} Image builder."
|
|
204
|
+
f" Since `python_version` was not set, Modal tried to match your local Python ({series_version})."
|
|
205
|
+
f" Set `python_version` to one of the supported series: {supported_series!r}."
|
|
206
|
+
)
|
|
199
207
|
raise InvalidError(
|
|
200
208
|
f"Unsupported Python version: {python_version!r}."
|
|
201
209
|
f" When using the {builder_version!r} Image builder, Modal supports the following series:"
|
|
@@ -2538,25 +2546,22 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2538
2546
|
if env:
|
|
2539
2547
|
secrets = [*secrets, _Secret.from_dict(env)]
|
|
2540
2548
|
|
|
2541
|
-
# --- Build the base dockerfile
|
|
2542
|
-
|
|
2543
|
-
def build_dockerfile_base(version: ImageBuilderVersion) -> DockerfileSpec:
|
|
2544
|
-
with open(os.path.expanduser(path)) as f:
|
|
2545
|
-
commands = f.read().split("\n")
|
|
2546
|
-
return DockerfileSpec(commands=commands, context_files={})
|
|
2547
|
-
|
|
2548
2549
|
gpu_config = parse_gpu_config(gpu)
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
context_mount_function=_create_context_mount_function(
|
|
2552
|
-
ignore=ignore, dockerfile_path=Path(path), context_dir=context_dir
|
|
2553
|
-
),
|
|
2554
|
-
gpu_config=gpu_config,
|
|
2555
|
-
secrets=secrets,
|
|
2556
|
-
build_args=build_args,
|
|
2557
|
-
force_build=force_build,
|
|
2550
|
+
context_mount_function = _create_context_mount_function(
|
|
2551
|
+
ignore=ignore, dockerfile_path=Path(path), context_dir=context_dir
|
|
2558
2552
|
)
|
|
2559
2553
|
|
|
2554
|
+
def dockerfile_image(commands: list[str], base_images: dict[str, _Image] | None = None) -> _Image:
|
|
2555
|
+
return _Image._from_args(
|
|
2556
|
+
base_images=base_images,
|
|
2557
|
+
dockerfile_function=lambda version: DockerfileSpec(commands=commands, context_files={}),
|
|
2558
|
+
context_mount_function=context_mount_function,
|
|
2559
|
+
gpu_config=gpu_config,
|
|
2560
|
+
secrets=secrets,
|
|
2561
|
+
build_args=build_args,
|
|
2562
|
+
force_build=force_build,
|
|
2563
|
+
)
|
|
2564
|
+
|
|
2560
2565
|
# --- Now add in the modal dependencies, and, optionally a Python distribution
|
|
2561
2566
|
# This happening in two steps is probably a vestigial consequence of previous limitations,
|
|
2562
2567
|
# but it will be difficult to merge them without forcing rebuilds of images.
|
|
@@ -2579,12 +2584,49 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2579
2584
|
context_files = {CONTAINER_REQUIREMENTS_PATH: requirements_path}
|
|
2580
2585
|
return DockerfileSpec(commands=commands, context_files=context_files)
|
|
2581
2586
|
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2587
|
+
def python_image(base_image: _Image) -> _Image:
|
|
2588
|
+
return _Image._from_args(
|
|
2589
|
+
base_images={"base": base_image},
|
|
2590
|
+
dockerfile_function=build_dockerfile_python,
|
|
2591
|
+
context_mount_function=add_python_mount,
|
|
2592
|
+
force_build=force_build,
|
|
2593
|
+
)
|
|
2594
|
+
|
|
2595
|
+
async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
|
|
2596
|
+
builder_version = await _resolve_image_builder_version(load_context)
|
|
2597
|
+
if add_python:
|
|
2598
|
+
# Validate before any Dockerfile layer is built.
|
|
2599
|
+
_validate_python_version(
|
|
2600
|
+
add_python, builder_version, allow_micro_granularity=False, allow_free_threading=True
|
|
2601
|
+
)
|
|
2602
|
+
with open(os.path.expanduser(path)) as f:
|
|
2603
|
+
dockerfile_lines = f.read().split("\n")
|
|
2604
|
+
|
|
2605
|
+
# Builder versions after 2025.06 put the FROM line of a single-stage Dockerfile in its own image.
|
|
2606
|
+
# Dockerfiles the split could change (see splittable_from_line_index) stay in one layer.
|
|
2607
|
+
from_index = splittable_from_line_index(dockerfile_lines) if builder_version > "2025.06" else None
|
|
2608
|
+
if from_index is None:
|
|
2609
|
+
image = python_image(dockerfile_image(dockerfile_lines))
|
|
2610
|
+
else:
|
|
2611
|
+
from_lines = dockerfile_lines[: from_index + 1]
|
|
2612
|
+
from_image = _Image._from_args(
|
|
2613
|
+
dockerfile_function=lambda version: DockerfileSpec(commands=from_lines, context_files={}),
|
|
2614
|
+
force_build=force_build,
|
|
2615
|
+
)
|
|
2616
|
+
rest = dockerfile_lines[from_index + 1 :]
|
|
2617
|
+
rest_is_empty = all(not line.strip() or line.strip().startswith("#") for line in rest)
|
|
2618
|
+
if rest_is_empty:
|
|
2619
|
+
image = python_image(from_image)
|
|
2620
|
+
else:
|
|
2621
|
+
image = python_image(dockerfile_image(["FROM base", *rest], base_images={"base": from_image}))
|
|
2622
|
+
await resolver.load(image, load_context)
|
|
2623
|
+
self._hydrate_from_other(image)
|
|
2624
|
+
self._serve_mounts = image._serve_mounts
|
|
2625
|
+
|
|
2626
|
+
rep = f"Image.from_dockerfile({str(path)!r})"
|
|
2627
|
+
obj = _Image._from_loader(_load, rep, load_context_overrides=LoadContext.empty())
|
|
2628
|
+
obj.force_build = force_build
|
|
2629
|
+
return obj
|
|
2588
2630
|
|
|
2589
2631
|
@staticmethod
|
|
2590
2632
|
def from_scratch(force_build: bool = False) -> "_Image":
|
|
@@ -74,6 +74,33 @@ def extract_copy_command_patterns(dockerfile_lines: Sequence[str]) -> list[str]:
|
|
|
74
74
|
return list(copy_source_patterns)
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
_FROM_PATTERN = re.compile(r"^\s*FROM\s", re.IGNORECASE)
|
|
78
|
+
_FROM_ALIAS_PATTERN = re.compile(r"\sAS\s+\S+\s*$", re.IGNORECASE)
|
|
79
|
+
_PARSER_DIRECTIVE_PATTERN = re.compile(r"^#\s*[a-zA-Z][a-zA-Z0-9]*\s*=")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def splittable_from_line_index(dockerfile_lines: Sequence[str]) -> int | None:
|
|
83
|
+
"""Return the line index of the FROM instruction if it can be built as its own image.
|
|
84
|
+
|
|
85
|
+
Returns None unless the first instruction is a one-line FROM without a stage alias (no global ARGs), no
|
|
86
|
+
parser directives precede it, and no other line starts with FROM. Lines inside heredocs or continuations
|
|
87
|
+
count too, so a multi-stage Dockerfile is never split.
|
|
88
|
+
"""
|
|
89
|
+
start = next(
|
|
90
|
+
(i for i, line in enumerate(dockerfile_lines) if line.strip() and not line.strip().startswith("#")),
|
|
91
|
+
None,
|
|
92
|
+
)
|
|
93
|
+
if start is None or not _FROM_PATTERN.match(dockerfile_lines[start]):
|
|
94
|
+
return None
|
|
95
|
+
if dockerfile_lines[start].rstrip().endswith("\\") or _FROM_ALIAS_PATTERN.search(dockerfile_lines[start]):
|
|
96
|
+
return None
|
|
97
|
+
if any(_PARSER_DIRECTIVE_PATTERN.match(line.strip()) for line in dockerfile_lines[:start]):
|
|
98
|
+
return None
|
|
99
|
+
if any(_FROM_PATTERN.match(line) for line in dockerfile_lines[start + 1 :]):
|
|
100
|
+
return None
|
|
101
|
+
return start
|
|
102
|
+
|
|
103
|
+
|
|
77
104
|
def find_dockerignore_file(context_directory: Path, dockerfile_path: Path | None = None) -> Path | None:
|
|
78
105
|
"""
|
|
79
106
|
Find dockerignore file relative to current context directory
|
|
@@ -9,7 +9,7 @@ from typing import Optional
|
|
|
9
9
|
from google.protobuf.empty_pb2 import Empty
|
|
10
10
|
|
|
11
11
|
from modal.exception import DeprecationError, InvalidError, NotFoundError
|
|
12
|
-
from modal.types import ServiceUserInfo
|
|
12
|
+
from modal.types import MemberTokenInfo, ServiceUserInfo
|
|
13
13
|
from modal_proto import api_pb2
|
|
14
14
|
|
|
15
15
|
from ._load_context import LoadContext
|
|
@@ -139,6 +139,11 @@ class _Workspace(_Object, type_prefix="ac"):
|
|
|
139
139
|
"""
|
|
140
140
|
return _WorkspaceServiceUserManager(self)
|
|
141
141
|
|
|
142
|
+
@property
|
|
143
|
+
def member_tokens(self) -> "_WorkspaceMemberTokenManager":
|
|
144
|
+
"""Namespace for managing Member API Tokens in a Workspace."""
|
|
145
|
+
return _WorkspaceMemberTokenManager(self)
|
|
146
|
+
|
|
142
147
|
|
|
143
148
|
class _WorkspaceProxyTokenManager:
|
|
144
149
|
"""mdmd:namespace"""
|
|
@@ -735,3 +740,62 @@ class _WorkspaceServiceUserManager:
|
|
|
735
740
|
except NotFoundError:
|
|
736
741
|
if not allow_missing:
|
|
737
742
|
raise
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
class _WorkspaceMemberTokenManager:
|
|
746
|
+
"""mdmd:namespace"""
|
|
747
|
+
|
|
748
|
+
def __init__(self, workspace: _Workspace):
|
|
749
|
+
self._workspace = workspace
|
|
750
|
+
|
|
751
|
+
async def list(self) -> list[MemberTokenInfo]:
|
|
752
|
+
"""List Member Tokens in the specified Workspace.
|
|
753
|
+
|
|
754
|
+
If you are a Manager or an Owner of this Workspace, this method will return a list of all the
|
|
755
|
+
Member Tokens in the Workspace. Otherwise, this method will only return the Tokens you have
|
|
756
|
+
created.
|
|
757
|
+
|
|
758
|
+
Returns:
|
|
759
|
+
A list of `MemberTokenInfo` dataclasses.
|
|
760
|
+
|
|
761
|
+
Examples:
|
|
762
|
+
|
|
763
|
+
```python notest
|
|
764
|
+
ws = modal.Workspace.from_context()
|
|
765
|
+
for member_token in ws.member_tokens.list():
|
|
766
|
+
print(member_token)
|
|
767
|
+
```
|
|
768
|
+
"""
|
|
769
|
+
if not self._workspace._is_hydrated:
|
|
770
|
+
await self._workspace.hydrate()
|
|
771
|
+
|
|
772
|
+
resp = await self._workspace.client._stub.TokensList(Empty())
|
|
773
|
+
|
|
774
|
+
return [MemberTokenInfo._from_proto(token) for token in resp.tokens]
|
|
775
|
+
|
|
776
|
+
async def delete(self, token_id: str, *, allow_missing: bool = False) -> None:
|
|
777
|
+
"""Delete a Member Token in the specified Workspace.
|
|
778
|
+
|
|
779
|
+
If you are a Workspace Manager or Owner, you can use this method to delete any Token in the
|
|
780
|
+
Workspace. Otherwise, you can only delete Member Tokens that you have created.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
token_id: The ID of the Member Token you would like to delete.
|
|
784
|
+
allow_missing: If `True`, do not throw an exception if the Member Token does not exist.
|
|
785
|
+
|
|
786
|
+
Examples:
|
|
787
|
+
|
|
788
|
+
```python notest
|
|
789
|
+
ws = modal.Workspace.from_context()
|
|
790
|
+
ws.member_tokens.delete(token_id="ak-0123456789abcdefghijkl")
|
|
791
|
+
```
|
|
792
|
+
"""
|
|
793
|
+
|
|
794
|
+
if not self._workspace._is_hydrated:
|
|
795
|
+
await self._workspace.hydrate()
|
|
796
|
+
|
|
797
|
+
try:
|
|
798
|
+
await self._workspace.client._stub.TokenDelete(req=api_pb2.TokenDeleteRequest(token_id=token_id))
|
|
799
|
+
except NotFoundError:
|
|
800
|
+
if not allow_missing:
|
|
801
|
+
raise
|
|
@@ -178,7 +178,7 @@ class _App:
|
|
|
178
178
|
# Running apps only (container apps or running local)
|
|
179
179
|
_app_id: str | None # Kept after app finishes
|
|
180
180
|
_running_app: RunningApp | None # Various app info
|
|
181
|
-
_client: _Client | None
|
|
181
|
+
_client: _Client | None # Kept after app finishes
|
|
182
182
|
|
|
183
183
|
# Metadata for loading objects within this app
|
|
184
184
|
# passed by reference to functions and classes so it can be updated by run()/deploy()
|
|
@@ -400,9 +400,13 @@ class _App:
|
|
|
400
400
|
print(app.get_dashboard_url())
|
|
401
401
|
```
|
|
402
402
|
"""
|
|
403
|
+
app_id = self._ensure_app_id("get_dashboard_url")
|
|
404
|
+
return f"https://modal.com/id/{app_id}"
|
|
405
|
+
|
|
406
|
+
def _ensure_app_id(self, method: str) -> str:
|
|
403
407
|
if self._app_id is None:
|
|
404
|
-
raise InvalidError("App
|
|
405
|
-
return
|
|
408
|
+
raise InvalidError(f"`App.{method}` requires the App to have an ID.")
|
|
409
|
+
return self._app_id
|
|
406
410
|
|
|
407
411
|
def _validate_blueprint_value(self, key: str, value: Any):
|
|
408
412
|
if not isinstance(value, _Object):
|
|
@@ -425,7 +429,6 @@ class _App:
|
|
|
425
429
|
yield
|
|
426
430
|
finally:
|
|
427
431
|
self._running_app = None
|
|
428
|
-
self._client = None
|
|
429
432
|
self._uncreate_all_objects()
|
|
430
433
|
|
|
431
434
|
@asynccontextmanager
|
|
@@ -582,6 +585,7 @@ class _App:
|
|
|
582
585
|
self, name=name, environment_name=environment_name, tag=tag, client=client, deployment_strategy=strategy
|
|
583
586
|
)
|
|
584
587
|
self._app_id = result.app_id
|
|
588
|
+
self._client = self._root_load_context.client
|
|
585
589
|
return self
|
|
586
590
|
|
|
587
591
|
def _get_default_image(self):
|
|
@@ -1596,10 +1600,9 @@ class _App:
|
|
|
1596
1600
|
# when the App is running, but lazy (and potentially ignored) otherwise. There would be
|
|
1597
1601
|
# other complications, like what do you do with any tags set in the constructor, and
|
|
1598
1602
|
# what should `.get_tags()` do when it's called before the App is running?
|
|
1599
|
-
|
|
1600
|
-
raise InvalidError("`App.set_tags` cannot be called before the App is running.")
|
|
1603
|
+
app_id = self._ensure_app_id("set_tags")
|
|
1601
1604
|
check_tag_dict(tags)
|
|
1602
|
-
req = api_pb2.AppSetTagsRequest(app_id=
|
|
1605
|
+
req = api_pb2.AppSetTagsRequest(app_id=app_id, tags=tags)
|
|
1603
1606
|
|
|
1604
1607
|
client = client or self._client or await _Client.from_env()
|
|
1605
1608
|
await client._stub.AppSetTags(req)
|
|
@@ -1613,13 +1616,33 @@ class _App:
|
|
|
1613
1616
|
Returns:
|
|
1614
1617
|
Tags as a map from key to value.
|
|
1615
1618
|
"""
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
req = api_pb2.AppGetTagsRequest(app_id=self._app_id)
|
|
1619
|
+
app_id = self._ensure_app_id("get_tags")
|
|
1620
|
+
req = api_pb2.AppGetTagsRequest(app_id=app_id)
|
|
1619
1621
|
client = client or self._client or await _Client.from_env()
|
|
1620
1622
|
resp = await client._stub.AppGetTags(req)
|
|
1621
1623
|
return dict(resp.tags)
|
|
1622
1624
|
|
|
1625
|
+
async def stop(self, *, client: _Client | None = None) -> None:
|
|
1626
|
+
"""Stop the App.
|
|
1627
|
+
|
|
1628
|
+
Stopping an App terminates all of its running containers and sandboxes and prevents
|
|
1629
|
+
new inputs from being processed.
|
|
1630
|
+
|
|
1631
|
+
**Examples:**
|
|
1632
|
+
|
|
1633
|
+
```python notest
|
|
1634
|
+
app = modal.App.lookup("my-app")
|
|
1635
|
+
app.stop()
|
|
1636
|
+
```
|
|
1637
|
+
|
|
1638
|
+
Args:
|
|
1639
|
+
client: Modal client to use for the RPC.
|
|
1640
|
+
"""
|
|
1641
|
+
app_id = self._ensure_app_id("stop")
|
|
1642
|
+
req = api_pb2.AppStopRequest(app_id=app_id, source=api_pb2.APP_STOP_SOURCE_PYTHON_CLIENT)
|
|
1643
|
+
client = client or self._client or await _Client.from_env()
|
|
1644
|
+
await client._stub.AppStop(req)
|
|
1645
|
+
|
|
1623
1646
|
@classmethod
|
|
1624
1647
|
def _get_container_app(cls) -> "_App | None":
|
|
1625
1648
|
"""Returns the `App` running inside a container.
|
|
@@ -1634,11 +1657,10 @@ class _App:
|
|
|
1634
1657
|
|
|
1635
1658
|
async def _get_log_query_data(self) -> _LogQueryData:
|
|
1636
1659
|
"""Get the data needed to query logs for this app."""
|
|
1637
|
-
|
|
1638
|
-
raise InvalidError("`app.logs` requires a running/stopped app.")
|
|
1660
|
+
app_id = self._ensure_app_id("logs")
|
|
1639
1661
|
client = self._client or await _Client.from_env()
|
|
1640
1662
|
filters = LogsFilters()
|
|
1641
|
-
return _LogQueryData(client=client, app_id=
|
|
1663
|
+
return _LogQueryData(client=client, app_id=app_id, filters=filters, source_object_id=app_id)
|
|
1642
1664
|
|
|
1643
1665
|
@property
|
|
1644
1666
|
def logs(self) -> _AppLogsManager:
|
|
@@ -1653,8 +1675,7 @@ class _App:
|
|
|
1653
1675
|
- [`modal app logs`](https://modal.com/docs/cli/latest/app#modal-app-logs):
|
|
1654
1676
|
CLI access to logs for an App.
|
|
1655
1677
|
"""
|
|
1656
|
-
|
|
1657
|
-
raise InvalidError("`app.logs` requires a running/stopped app.")
|
|
1678
|
+
self._ensure_app_id("logs")
|
|
1658
1679
|
|
|
1659
1680
|
return _AppLogsManager(self)
|
|
1660
1681
|
|
|
@@ -1682,12 +1703,11 @@ class _App:
|
|
|
1682
1703
|
servers=self._info.servers,
|
|
1683
1704
|
lifecycle=self._info.lifecycle,
|
|
1684
1705
|
)
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
request = api_pb2.AppGetInfoRequest(app_id=self._app_id)
|
|
1706
|
+
app_id = self._ensure_app_id("info")
|
|
1707
|
+
request = api_pb2.AppGetInfoRequest(app_id=app_id)
|
|
1688
1708
|
client = self._client or await _Client.from_env()
|
|
1689
1709
|
resp = await client._stub.AppGetInfo(request)
|
|
1690
|
-
self._info = AppInfo._from_proto(resp.info,
|
|
1710
|
+
self._info = AppInfo._from_proto(resp.info, app_id)
|
|
1691
1711
|
return self._info
|
|
1692
1712
|
|
|
1693
1713
|
|
|
@@ -298,6 +298,7 @@ class _App:
|
|
|
298
298
|
"""
|
|
299
299
|
...
|
|
300
300
|
|
|
301
|
+
def _ensure_app_id(self, method: str) -> str: ...
|
|
301
302
|
def _validate_blueprint_value(self, key: str, value: typing.Any): ...
|
|
302
303
|
def _uncreate_all_objects(self): ...
|
|
303
304
|
def _set_local_app(
|
|
@@ -902,6 +903,24 @@ class _App:
|
|
|
902
903
|
"""
|
|
903
904
|
...
|
|
904
905
|
|
|
906
|
+
async def stop(self, *, client: typing.Optional[modal.client._Client] = None) -> None:
|
|
907
|
+
"""Stop the App.
|
|
908
|
+
|
|
909
|
+
Stopping an App terminates all of its running containers and sandboxes and prevents
|
|
910
|
+
new inputs from being processed.
|
|
911
|
+
|
|
912
|
+
**Examples:**
|
|
913
|
+
|
|
914
|
+
```python notest
|
|
915
|
+
app = modal.App.lookup("my-app")
|
|
916
|
+
app.stop()
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
Args:
|
|
920
|
+
client: Modal client to use for the RPC.
|
|
921
|
+
"""
|
|
922
|
+
...
|
|
923
|
+
|
|
905
924
|
@classmethod
|
|
906
925
|
def _get_container_app(cls) -> typing.Optional[_App]:
|
|
907
926
|
"""Returns the `App` running inside a container.
|
|
@@ -1187,6 +1206,7 @@ class App:
|
|
|
1187
1206
|
|
|
1188
1207
|
get_dashboard_url: __get_dashboard_url_spec
|
|
1189
1208
|
|
|
1209
|
+
def _ensure_app_id(self, method: str) -> str: ...
|
|
1190
1210
|
def _validate_blueprint_value(self, key: str, value: typing.Any): ...
|
|
1191
1211
|
def _uncreate_all_objects(self): ...
|
|
1192
1212
|
|
|
@@ -1975,6 +1995,45 @@ class App:
|
|
|
1975
1995
|
|
|
1976
1996
|
get_tags: __get_tags_spec
|
|
1977
1997
|
|
|
1998
|
+
class __stop_spec(typing_extensions.Protocol):
|
|
1999
|
+
def __call__(self, /, *, client: typing.Optional[modal.client.Client] = None) -> None:
|
|
2000
|
+
"""Stop the App.
|
|
2001
|
+
|
|
2002
|
+
Stopping an App terminates all of its running containers and sandboxes and prevents
|
|
2003
|
+
new inputs from being processed.
|
|
2004
|
+
|
|
2005
|
+
**Examples:**
|
|
2006
|
+
|
|
2007
|
+
```python notest
|
|
2008
|
+
app = modal.App.lookup("my-app")
|
|
2009
|
+
app.stop()
|
|
2010
|
+
```
|
|
2011
|
+
|
|
2012
|
+
Args:
|
|
2013
|
+
client: Modal client to use for the RPC.
|
|
2014
|
+
"""
|
|
2015
|
+
...
|
|
2016
|
+
|
|
2017
|
+
async def aio(self, /, *, client: typing.Optional[modal.client.Client] = None) -> None:
|
|
2018
|
+
"""Stop the App.
|
|
2019
|
+
|
|
2020
|
+
Stopping an App terminates all of its running containers and sandboxes and prevents
|
|
2021
|
+
new inputs from being processed.
|
|
2022
|
+
|
|
2023
|
+
**Examples:**
|
|
2024
|
+
|
|
2025
|
+
```python notest
|
|
2026
|
+
app = modal.App.lookup("my-app")
|
|
2027
|
+
app.stop()
|
|
2028
|
+
```
|
|
2029
|
+
|
|
2030
|
+
Args:
|
|
2031
|
+
client: Modal client to use for the RPC.
|
|
2032
|
+
"""
|
|
2033
|
+
...
|
|
2034
|
+
|
|
2035
|
+
stop: __stop_spec
|
|
2036
|
+
|
|
1978
2037
|
@classmethod
|
|
1979
2038
|
def _get_container_app(cls) -> typing.Optional[App]:
|
|
1980
2039
|
"""Returns the `App` running inside a container.
|
|
@@ -64,9 +64,11 @@ def list_(json: bool = False):
|
|
|
64
64
|
for item in envs:
|
|
65
65
|
is_active = item.name == active_env
|
|
66
66
|
is_active_display: Text | str = str(is_active) if json else RenderableBool(is_active)
|
|
67
|
-
|
|
67
|
+
# Unset when the Environment inherits the Workspace's image builder version
|
|
68
|
+
image_builder_version = item.image_builder_version if item.HasField("image_builder_version") else None
|
|
69
|
+
row = [item.name, item.webhook_suffix, image_builder_version, is_active_display]
|
|
68
70
|
table_data.append(row)
|
|
69
|
-
display_table(["name", "web suffix", "active"], table_data, json=json)
|
|
71
|
+
display_table(["name", "web suffix", "image builder version", "active"], table_data, json=json)
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
@environment_cli.command(
|
|
@@ -108,18 +110,32 @@ Deletes all Apps in the selected Environment and deletes the Environment irrevoc
|
|
|
108
110
|
@click.argument("current_name")
|
|
109
111
|
@click.option("--set-name", default=None, help="New name of the Environment.")
|
|
110
112
|
@click.option("--set-web-suffix", default=None, help="New web suffix of the Environment (empty string is no suffix).")
|
|
113
|
+
@click.option(
|
|
114
|
+
"--set-image-builder-version",
|
|
115
|
+
default=None,
|
|
116
|
+
help="New image builder version of the Environment (empty string uses the Workspace default).",
|
|
117
|
+
)
|
|
111
118
|
def update(
|
|
112
119
|
current_name: str,
|
|
113
120
|
set_name: str | None = None,
|
|
114
121
|
set_web_suffix: str | None = None,
|
|
122
|
+
set_image_builder_version: str | None = None,
|
|
115
123
|
):
|
|
116
|
-
if set_name is None and set_web_suffix is None:
|
|
117
|
-
raise UsageError(
|
|
124
|
+
if set_name is None and set_web_suffix is None and set_image_builder_version is None:
|
|
125
|
+
raise UsageError(
|
|
126
|
+
"You need to set at least one new property"
|
|
127
|
+
" (using --set-name, --set-web-suffix, or --set-image-builder-version)"
|
|
128
|
+
)
|
|
118
129
|
|
|
119
130
|
if set_name and set_name != current_name:
|
|
120
131
|
check_environment_name(set_name, allow_periods=False)
|
|
121
132
|
|
|
122
|
-
environments.update_environment(
|
|
133
|
+
environments.update_environment(
|
|
134
|
+
current_name,
|
|
135
|
+
new_name=set_name,
|
|
136
|
+
new_web_suffix=set_web_suffix,
|
|
137
|
+
new_image_builder_version=set_image_builder_version,
|
|
138
|
+
)
|
|
123
139
|
rich.print("[green]✓[/green] Environment updated")
|
|
124
140
|
|
|
125
141
|
|
|
@@ -639,3 +639,103 @@ def service_users_set_role(
|
|
|
639
639
|
f"[green]✓[/green] Updated Service User [bold]{service_user_name}[/bold]'s role in "
|
|
640
640
|
f"Environment [bold]{environment_name}[/bold] to {role.capitalize()}."
|
|
641
641
|
)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
MEMBER_TOKENS_HELP_TEXT = """Manage Member API Tokens.
|
|
645
|
+
|
|
646
|
+
All Member Tokens are associated with a unique Member of the Workspace. Any requests to Modal using a
|
|
647
|
+
Token are authenticated as the associated Member (and as such, are governed by the permissions that
|
|
648
|
+
have been assigned to the specific Member). Workspace Owners and Managers can use [`modal environment roles update`](https://modal.com/docs/cli/latest/environment#modal-environment-roles-update)
|
|
649
|
+
to update these permissions programmatically.
|
|
650
|
+
|
|
651
|
+
This CLI allows users to list and delete the tokens associated to themselves. Workspace Owners
|
|
652
|
+
and Managers can list all tokens in their Workspace, and can delete any of them.
|
|
653
|
+
"""
|
|
654
|
+
|
|
655
|
+
member_tokens_cli = ModalGroup(name="member-tokens", help=MEMBER_TOKENS_HELP_TEXT)
|
|
656
|
+
workspace_cli.add_command(member_tokens_cli)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
@member_tokens_cli.command("list")
|
|
660
|
+
@click.option("--json", is_flag=True, default=False, help="Output as JSON.")
|
|
661
|
+
def member_tokens_list(json: bool):
|
|
662
|
+
"""List Member Tokens in the Workspace. Workspace Managers and Owners can see all tokens in the
|
|
663
|
+
Workspace.
|
|
664
|
+
|
|
665
|
+
```bash
|
|
666
|
+
modal workspace member-tokens list
|
|
667
|
+
|
|
668
|
+
# Show list in JSON format:
|
|
669
|
+
modal workspace member-tokens list --json
|
|
670
|
+
```
|
|
671
|
+
"""
|
|
672
|
+
ws = Workspace.from_context()
|
|
673
|
+
tokens = ws.member_tokens.list()
|
|
674
|
+
|
|
675
|
+
output_manager = OutputManager.get()
|
|
676
|
+
|
|
677
|
+
if json:
|
|
678
|
+
output_manager.print_json(
|
|
679
|
+
json_mod.dumps(
|
|
680
|
+
[
|
|
681
|
+
{
|
|
682
|
+
"name": tok.name,
|
|
683
|
+
"token_id": tok.token_id,
|
|
684
|
+
"user_id": tok.user_id,
|
|
685
|
+
"username": tok.username,
|
|
686
|
+
"created_at": timestamp_to_localized_str(tok.created_at.timestamp()),
|
|
687
|
+
"last_used_at": timestamp_to_localized_str(tok.last_used_at.timestamp())
|
|
688
|
+
if tok.last_used_at is not None
|
|
689
|
+
else None,
|
|
690
|
+
"expires_at": timestamp_to_localized_str(tok.expires_at.timestamp())
|
|
691
|
+
if tok.expires_at is not None
|
|
692
|
+
else None,
|
|
693
|
+
}
|
|
694
|
+
for tok in tokens
|
|
695
|
+
]
|
|
696
|
+
)
|
|
697
|
+
)
|
|
698
|
+
return
|
|
699
|
+
|
|
700
|
+
table = rich.table.Table(box=SIMPLE_HEAD)
|
|
701
|
+
table.add_column("Token ID")
|
|
702
|
+
table.add_column("Name")
|
|
703
|
+
table.add_column("Created By")
|
|
704
|
+
table.add_column("Created At")
|
|
705
|
+
table.add_column("Expires")
|
|
706
|
+
|
|
707
|
+
for tok in tokens:
|
|
708
|
+
table.add_row(
|
|
709
|
+
tok.token_id,
|
|
710
|
+
tok.name if tok.name else "-",
|
|
711
|
+
tok.username,
|
|
712
|
+
timestamp_to_localized_str(tok.created_at.timestamp(), isotz=False),
|
|
713
|
+
timestamp_to_localized_str(tok.expires_at.timestamp(), isotz=False) if tok.expires_at else "-",
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
output_manager.print(table)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
@member_tokens_cli.command("delete", no_args_is_help=True)
|
|
720
|
+
@click.argument("token_id")
|
|
721
|
+
@click.option("--allow-missing", is_flag=True, default=False, help="Don't error if the Token doesn't exist.")
|
|
722
|
+
@yes_option
|
|
723
|
+
def member_tokens_delete(token_id: str, *, allow_missing: bool, yes: bool):
|
|
724
|
+
"""Delete a Member Token in this Workspace.
|
|
725
|
+
|
|
726
|
+
Note that this action is irreversible. This will immediately invalidate the target token and will
|
|
727
|
+
render it unable to authenticate with Modal.
|
|
728
|
+
|
|
729
|
+
```bash
|
|
730
|
+
# Delete the Token with ID `ak-0123456789abcdefghijkl`
|
|
731
|
+
modal workspace member-tokens delete ak-0123456789abcdefghijkl
|
|
732
|
+
```
|
|
733
|
+
"""
|
|
734
|
+
if not yes:
|
|
735
|
+
confirm_or_suggest_yes(f"Are you sure you want to irrevocably delete the Token '{token_id}'?")
|
|
736
|
+
|
|
737
|
+
ws = Workspace.from_context()
|
|
738
|
+
ws.member_tokens.delete(token_id, allow_missing=allow_missing)
|
|
739
|
+
|
|
740
|
+
output_manager = OutputManager.get()
|
|
741
|
+
output_manager.print(f"[green]✓[/green] Deleted Token `{token_id}`.")
|
|
@@ -80,7 +80,7 @@ class _Client:
|
|
|
80
80
|
server_url: str,
|
|
81
81
|
client_type: int,
|
|
82
82
|
credentials: typing.Optional[tuple[str, str]],
|
|
83
|
-
version: str = "1.6.3.
|
|
83
|
+
version: str = "1.6.3.dev9",
|
|
84
84
|
*,
|
|
85
85
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
86
86
|
):
|
|
@@ -290,7 +290,7 @@ class Client:
|
|
|
290
290
|
server_url: str,
|
|
291
291
|
client_type: int,
|
|
292
292
|
credentials: typing.Optional[tuple[str, str]],
|
|
293
|
-
version: str = "1.6.3.
|
|
293
|
+
version: str = "1.6.3.dev9",
|
|
294
294
|
*,
|
|
295
295
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
296
296
|
):
|