modal 1.6.4.dev1__tar.gz → 1.6.4.dev2__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.dev1 → modal-1.6.4.dev2}/PKG-INFO +1 -1
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/app.py +84 -1
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/app.pyi +154 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/endpoint.py +109 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/client.pyi +2 -2
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/types.py +21 -1
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_pb2.py +535 -534
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_pb2.pyi +35 -57
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_version/__init__.py +1 -1
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/LICENSE +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/README.md +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/__main__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_billing.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_cluster.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_cluster.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_container_entrypoint.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_environments.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_function_variants.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_functions.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_grpc_client.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_image.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_ipython.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_load_context.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_location.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_logs.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_logs_manager.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_logs_manager.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_metrics.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_metrics.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_object.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_outbound_policy.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_outbound_policy.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/manager.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/pty.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/rich.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/status.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_partial_function.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_resolver.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_resources.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/asgi.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/execution_context.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/telemetry.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_serialization.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_server.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_server.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_supports_logs.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_traceback.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_tunnel.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_tunnel.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_type_manager.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/app_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/async_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/blob_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/browser_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/curl_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/deprecation.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/display_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/docker_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/function_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/git_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/hash_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/http_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/logger.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/mount_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/name_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/oauth.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/package_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/proxy_support.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/shell_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/time_utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/tblib.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/version.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_watcher.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_workspace.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/billing.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/billing.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2023.12.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2024.04.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2024.10.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2025.06.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2026.10.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/README.md +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/base-images.json +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/call_graph.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_download.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_help.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_logs.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_stats.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_traceback.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/app.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/billing.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/changelog.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/config.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/container.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/curl.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/dashboard.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/dict.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/entry_point.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/environment.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/function.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/image.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/import_refs.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/logo.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/network_file_system.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/profile.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/queues.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/run.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/secret.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/selector.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/server.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/shell.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/skills.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/token.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/utils.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/volume.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/workspace.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/client.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cls.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cls.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cluster.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cluster.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/config.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/container_process.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/container_process.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/dict.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/dict.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/environments.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/environments.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/exception.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/_flash_container_entry.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/flash.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/flash.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/flash_server.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/flash_server.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/ipython.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/file_pattern_matcher.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/functions.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/functions.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/image.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/image.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/io_streams.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/io_streams.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/mount.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/mount.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/network_file_system.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/network_file_system.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/object.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/object.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/output.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/parallel_map.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/parallel_map.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/partial_function.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/partial_function.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/proxy.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/proxy.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/py.typed +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/queue.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/queue.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/retries.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/runner.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/runner.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/running_app.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_common.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_filesystem.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_impl_persistent.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_impl_v1.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_impl_v2.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_sandbox.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_sidecar.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_snapshot.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_task_command_router_client.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/sandbox.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/sandbox.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/schedule.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/scheduler_placement.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/secret.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/secret.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/server.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/server.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/serving.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/serving.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/stream_type.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/token_flow.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/token_flow.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/volume.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/volume.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/workspace.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/workspace.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/requires.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/top_level.txt +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/mdmd/types.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/reference_intro.md +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/__init__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_grpc.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/py.typed +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_version/__main__.py +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/pyproject.toml +0 -0
- {modal-1.6.4.dev1 → modal-1.6.4.dev2}/setup.cfg +0 -0
|
@@ -3,6 +3,7 @@ import inspect
|
|
|
3
3
|
import typing
|
|
4
4
|
from collections.abc import AsyncGenerator, Callable, Collection, Coroutine, Mapping, Sequence
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime, timezone
|
|
6
7
|
from pathlib import PurePosixPath
|
|
7
8
|
from textwrap import dedent
|
|
8
9
|
from typing import (
|
|
@@ -56,7 +57,7 @@ from .retries import Retries
|
|
|
56
57
|
from .running_app import RunningApp
|
|
57
58
|
from .schedule import Schedule
|
|
58
59
|
from .secret import _Secret
|
|
59
|
-
from .types import AppInfo
|
|
60
|
+
from .types import AppInfo, SandboxGroupStats
|
|
60
61
|
from .volume import _Volume
|
|
61
62
|
|
|
62
63
|
_default_image: _Image = _Image.debian_slim()
|
|
@@ -135,6 +136,82 @@ class _LocalAppState:
|
|
|
135
136
|
volumes_default: dict[str | PurePosixPath, _Volume]
|
|
136
137
|
|
|
137
138
|
|
|
139
|
+
class _AppSandboxesManager:
|
|
140
|
+
"""mdmd:namespace"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, app: "_App"):
|
|
143
|
+
"""mdmd:hidden"""
|
|
144
|
+
self._app = app
|
|
145
|
+
|
|
146
|
+
async def stats(
|
|
147
|
+
self,
|
|
148
|
+
*,
|
|
149
|
+
since: datetime,
|
|
150
|
+
until: datetime | None = None,
|
|
151
|
+
group_by_tags: Sequence[str] | None = None,
|
|
152
|
+
tags: dict[str, str] | None = None,
|
|
153
|
+
) -> AsyncGenerator[SandboxGroupStats, None]:
|
|
154
|
+
"""Return statistics for this App's Sandboxes over a time range, optionally grouped by tag values.
|
|
155
|
+
|
|
156
|
+
The maximum time range is 7 days.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
since: The beginning of the time range, inclusive, on a UTC hour boundary. Values without a
|
|
160
|
+
timezone are interpreted as local time.
|
|
161
|
+
until: The end of the time range, exclusive, on a UTC hour boundary. If omitted, this defaults to
|
|
162
|
+
the current time. Values without a timezone are interpreted as local time.
|
|
163
|
+
group_by_tags: Tag names to group by. Sandboxes with the same values for these names form one group;
|
|
164
|
+
a name not present on a Sandbox counts as `None`. If omitted, all matching Sandboxes are returned
|
|
165
|
+
as a single group.
|
|
166
|
+
tags: Only include Sandboxes that have at least these tags.
|
|
167
|
+
|
|
168
|
+
Yields:
|
|
169
|
+
`SandboxGroupStats` objects, one per combination of tag values, with the following fields:
|
|
170
|
+
- `tags`: the value of each name in `group_by_tags` for the group's Sandboxes, `None` where not present.
|
|
171
|
+
- `cost`: total cost in USD, the sum of `cost_by_resource`.
|
|
172
|
+
- `cost_by_resource`: cost in USD by billed resource: `CPU`, `Memory`, and GPU types such as
|
|
173
|
+
`H100`. The resource names are subject to change as Modal's billing model evolves.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
InvalidError: If the time range is invalid or there are more than 10,000 groups.
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
```python notest
|
|
180
|
+
app = modal.App.lookup("my-app")
|
|
181
|
+
since = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
|
|
182
|
+
for group in app.sandboxes.stats(since=since, group_by_tags=["team"]):
|
|
183
|
+
print(group.tags, group.cost, group.cost_by_resource)
|
|
184
|
+
```
|
|
185
|
+
"""
|
|
186
|
+
if self._app._app_id is None:
|
|
187
|
+
raise InvalidError("`App.sandboxes.stats` requires a running, deployed or looked-up App.")
|
|
188
|
+
if until is not None:
|
|
189
|
+
if until.tzinfo is None:
|
|
190
|
+
until = until.astimezone()
|
|
191
|
+
until = until.astimezone(timezone.utc)
|
|
192
|
+
if since.tzinfo is None:
|
|
193
|
+
since = since.astimezone()
|
|
194
|
+
since = since.astimezone(timezone.utc)
|
|
195
|
+
if since >= datetime.now(timezone.utc):
|
|
196
|
+
raise InvalidError("`since` must be in the past.")
|
|
197
|
+
if until is not None and since >= until:
|
|
198
|
+
raise InvalidError("`since` must be before `until`.")
|
|
199
|
+
client = self._app._client or await _Client.from_env()
|
|
200
|
+
request = api_pb2.SandboxGetTimeRangeStatsRequest(
|
|
201
|
+
group_by_tags=list(group_by_tags or []), tags=tags or {}, app_id=self._app._app_id
|
|
202
|
+
)
|
|
203
|
+
request.since.FromDatetime(since)
|
|
204
|
+
if until is not None:
|
|
205
|
+
request.until.FromDatetime(until)
|
|
206
|
+
while True:
|
|
207
|
+
response = await client._stub.SandboxGetTimeRangeStats(request)
|
|
208
|
+
for group in response.groups:
|
|
209
|
+
yield SandboxGroupStats._from_proto(group)
|
|
210
|
+
if not response.next_page_token:
|
|
211
|
+
return
|
|
212
|
+
request.page_token = response.next_page_token
|
|
213
|
+
|
|
214
|
+
|
|
138
215
|
class _App:
|
|
139
216
|
"""A Modal App is a group of functions and classes that are deployed together.
|
|
140
217
|
|
|
@@ -1679,6 +1756,11 @@ class _App:
|
|
|
1679
1756
|
|
|
1680
1757
|
return _AppLogsManager(self)
|
|
1681
1758
|
|
|
1759
|
+
@property
|
|
1760
|
+
def sandboxes(self) -> _AppSandboxesManager:
|
|
1761
|
+
"""Namespace for the Sandboxes belonging to this App."""
|
|
1762
|
+
return _AppSandboxesManager(self)
|
|
1763
|
+
|
|
1682
1764
|
async def info(self, refresh: bool = False) -> AppInfo:
|
|
1683
1765
|
"""Return information for a modal `App`.
|
|
1684
1766
|
|
|
@@ -1711,4 +1793,5 @@ class _App:
|
|
|
1711
1793
|
return self._info
|
|
1712
1794
|
|
|
1713
1795
|
|
|
1796
|
+
AppSandboxesManager = synchronize_api(_AppSandboxesManager)
|
|
1714
1797
|
App = synchronize_api(_App)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections.abc
|
|
2
|
+
import datetime
|
|
2
3
|
import modal._functions
|
|
3
4
|
import modal._image
|
|
4
5
|
import modal._load_context
|
|
@@ -115,6 +116,54 @@ class _LocalAppState:
|
|
|
115
116
|
"""Return self==value."""
|
|
116
117
|
...
|
|
117
118
|
|
|
119
|
+
class _AppSandboxesManager:
|
|
120
|
+
"""mdmd:namespace"""
|
|
121
|
+
def __init__(self, app: _App):
|
|
122
|
+
"""mdmd:hidden"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
def stats(
|
|
126
|
+
self,
|
|
127
|
+
*,
|
|
128
|
+
since: datetime.datetime,
|
|
129
|
+
until: typing.Optional[datetime.datetime] = None,
|
|
130
|
+
group_by_tags: typing.Optional[collections.abc.Sequence[str]] = None,
|
|
131
|
+
tags: typing.Optional[dict[str, str]] = None,
|
|
132
|
+
) -> collections.abc.AsyncGenerator[modal.types.SandboxGroupStats, None]:
|
|
133
|
+
"""Return statistics for this App's Sandboxes over a time range, optionally grouped by tag values.
|
|
134
|
+
|
|
135
|
+
The maximum time range is 7 days.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
since: The beginning of the time range, inclusive, on a UTC hour boundary. Values without a
|
|
139
|
+
timezone are interpreted as local time.
|
|
140
|
+
until: The end of the time range, exclusive, on a UTC hour boundary. If omitted, this defaults to
|
|
141
|
+
the current time. Values without a timezone are interpreted as local time.
|
|
142
|
+
group_by_tags: Tag names to group by. Sandboxes with the same values for these names form one group;
|
|
143
|
+
a name not present on a Sandbox counts as `None`. If omitted, all matching Sandboxes are returned
|
|
144
|
+
as a single group.
|
|
145
|
+
tags: Only include Sandboxes that have at least these tags.
|
|
146
|
+
|
|
147
|
+
Yields:
|
|
148
|
+
`SandboxGroupStats` objects, one per combination of tag values, with the following fields:
|
|
149
|
+
- `tags`: the value of each name in `group_by_tags` for the group's Sandboxes, `None` where not present.
|
|
150
|
+
- `cost`: total cost in USD, the sum of `cost_by_resource`.
|
|
151
|
+
- `cost_by_resource`: cost in USD by billed resource: `CPU`, `Memory`, and GPU types such as
|
|
152
|
+
`H100`. The resource names are subject to change as Modal's billing model evolves.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
InvalidError: If the time range is invalid or there are more than 10,000 groups.
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
```python notest
|
|
159
|
+
app = modal.App.lookup("my-app")
|
|
160
|
+
since = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
|
|
161
|
+
for group in app.sandboxes.stats(since=since, group_by_tags=["team"]):
|
|
162
|
+
print(group.tags, group.cost, group.cost_by_resource)
|
|
163
|
+
```
|
|
164
|
+
"""
|
|
165
|
+
...
|
|
166
|
+
|
|
118
167
|
class _App:
|
|
119
168
|
"""A Modal App is a group of functions and classes that are deployed together.
|
|
120
169
|
|
|
@@ -953,6 +1002,11 @@ class _App:
|
|
|
953
1002
|
"""
|
|
954
1003
|
...
|
|
955
1004
|
|
|
1005
|
+
@property
|
|
1006
|
+
def sandboxes(self) -> _AppSandboxesManager:
|
|
1007
|
+
"""Namespace for the Sandboxes belonging to this App."""
|
|
1008
|
+
...
|
|
1009
|
+
|
|
956
1010
|
async def info(self, refresh: bool = False) -> modal.types.AppInfo:
|
|
957
1011
|
"""Return information for a modal `App`.
|
|
958
1012
|
|
|
@@ -971,6 +1025,101 @@ class _App:
|
|
|
971
1025
|
"""
|
|
972
1026
|
...
|
|
973
1027
|
|
|
1028
|
+
class AppSandboxesManager:
|
|
1029
|
+
"""mdmd:namespace"""
|
|
1030
|
+
def __init__(self, app: App):
|
|
1031
|
+
"""mdmd:hidden"""
|
|
1032
|
+
...
|
|
1033
|
+
|
|
1034
|
+
class __stats_spec(typing_extensions.Protocol):
|
|
1035
|
+
def __call__(
|
|
1036
|
+
self,
|
|
1037
|
+
/,
|
|
1038
|
+
*,
|
|
1039
|
+
since: datetime.datetime,
|
|
1040
|
+
until: typing.Optional[datetime.datetime] = None,
|
|
1041
|
+
group_by_tags: typing.Optional[collections.abc.Sequence[str]] = None,
|
|
1042
|
+
tags: typing.Optional[dict[str, str]] = None,
|
|
1043
|
+
) -> typing.Generator[modal.types.SandboxGroupStats, None, None]:
|
|
1044
|
+
"""Return statistics for this App's Sandboxes over a time range, optionally grouped by tag values.
|
|
1045
|
+
|
|
1046
|
+
The maximum time range is 7 days.
|
|
1047
|
+
|
|
1048
|
+
Args:
|
|
1049
|
+
since: The beginning of the time range, inclusive, on a UTC hour boundary. Values without a
|
|
1050
|
+
timezone are interpreted as local time.
|
|
1051
|
+
until: The end of the time range, exclusive, on a UTC hour boundary. If omitted, this defaults to
|
|
1052
|
+
the current time. Values without a timezone are interpreted as local time.
|
|
1053
|
+
group_by_tags: Tag names to group by. Sandboxes with the same values for these names form one group;
|
|
1054
|
+
a name not present on a Sandbox counts as `None`. If omitted, all matching Sandboxes are returned
|
|
1055
|
+
as a single group.
|
|
1056
|
+
tags: Only include Sandboxes that have at least these tags.
|
|
1057
|
+
|
|
1058
|
+
Yields:
|
|
1059
|
+
`SandboxGroupStats` objects, one per combination of tag values, with the following fields:
|
|
1060
|
+
- `tags`: the value of each name in `group_by_tags` for the group's Sandboxes, `None` where not present.
|
|
1061
|
+
- `cost`: total cost in USD, the sum of `cost_by_resource`.
|
|
1062
|
+
- `cost_by_resource`: cost in USD by billed resource: `CPU`, `Memory`, and GPU types such as
|
|
1063
|
+
`H100`. The resource names are subject to change as Modal's billing model evolves.
|
|
1064
|
+
|
|
1065
|
+
Raises:
|
|
1066
|
+
InvalidError: If the time range is invalid or there are more than 10,000 groups.
|
|
1067
|
+
|
|
1068
|
+
Examples:
|
|
1069
|
+
```python notest
|
|
1070
|
+
app = modal.App.lookup("my-app")
|
|
1071
|
+
since = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
|
|
1072
|
+
for group in app.sandboxes.stats(since=since, group_by_tags=["team"]):
|
|
1073
|
+
print(group.tags, group.cost, group.cost_by_resource)
|
|
1074
|
+
```
|
|
1075
|
+
"""
|
|
1076
|
+
...
|
|
1077
|
+
|
|
1078
|
+
def aio(
|
|
1079
|
+
self,
|
|
1080
|
+
/,
|
|
1081
|
+
*,
|
|
1082
|
+
since: datetime.datetime,
|
|
1083
|
+
until: typing.Optional[datetime.datetime] = None,
|
|
1084
|
+
group_by_tags: typing.Optional[collections.abc.Sequence[str]] = None,
|
|
1085
|
+
tags: typing.Optional[dict[str, str]] = None,
|
|
1086
|
+
) -> collections.abc.AsyncGenerator[modal.types.SandboxGroupStats, None]:
|
|
1087
|
+
"""Return statistics for this App's Sandboxes over a time range, optionally grouped by tag values.
|
|
1088
|
+
|
|
1089
|
+
The maximum time range is 7 days.
|
|
1090
|
+
|
|
1091
|
+
Args:
|
|
1092
|
+
since: The beginning of the time range, inclusive, on a UTC hour boundary. Values without a
|
|
1093
|
+
timezone are interpreted as local time.
|
|
1094
|
+
until: The end of the time range, exclusive, on a UTC hour boundary. If omitted, this defaults to
|
|
1095
|
+
the current time. Values without a timezone are interpreted as local time.
|
|
1096
|
+
group_by_tags: Tag names to group by. Sandboxes with the same values for these names form one group;
|
|
1097
|
+
a name not present on a Sandbox counts as `None`. If omitted, all matching Sandboxes are returned
|
|
1098
|
+
as a single group.
|
|
1099
|
+
tags: Only include Sandboxes that have at least these tags.
|
|
1100
|
+
|
|
1101
|
+
Yields:
|
|
1102
|
+
`SandboxGroupStats` objects, one per combination of tag values, with the following fields:
|
|
1103
|
+
- `tags`: the value of each name in `group_by_tags` for the group's Sandboxes, `None` where not present.
|
|
1104
|
+
- `cost`: total cost in USD, the sum of `cost_by_resource`.
|
|
1105
|
+
- `cost_by_resource`: cost in USD by billed resource: `CPU`, `Memory`, and GPU types such as
|
|
1106
|
+
`H100`. The resource names are subject to change as Modal's billing model evolves.
|
|
1107
|
+
|
|
1108
|
+
Raises:
|
|
1109
|
+
InvalidError: If the time range is invalid or there are more than 10,000 groups.
|
|
1110
|
+
|
|
1111
|
+
Examples:
|
|
1112
|
+
```python notest
|
|
1113
|
+
app = modal.App.lookup("my-app")
|
|
1114
|
+
since = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
|
|
1115
|
+
for group in app.sandboxes.stats(since=since, group_by_tags=["team"]):
|
|
1116
|
+
print(group.tags, group.cost, group.cost_by_resource)
|
|
1117
|
+
```
|
|
1118
|
+
"""
|
|
1119
|
+
...
|
|
1120
|
+
|
|
1121
|
+
stats: __stats_spec
|
|
1122
|
+
|
|
974
1123
|
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
975
1124
|
|
|
976
1125
|
class App:
|
|
@@ -2073,6 +2222,11 @@ class App:
|
|
|
2073
2222
|
"""
|
|
2074
2223
|
...
|
|
2075
2224
|
|
|
2225
|
+
@property
|
|
2226
|
+
def sandboxes(self) -> AppSandboxesManager:
|
|
2227
|
+
"""Namespace for the Sandboxes belonging to this App."""
|
|
2228
|
+
...
|
|
2229
|
+
|
|
2076
2230
|
class __info_spec(typing_extensions.Protocol):
|
|
2077
2231
|
def __call__(self, /, refresh: bool = False) -> modal.types.AppInfo:
|
|
2078
2232
|
"""Return information for a modal `App`.
|
|
@@ -5,6 +5,7 @@ from json import dumps
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
|
+
from rich.box import SIMPLE
|
|
8
9
|
from rich.table import Column, Table
|
|
9
10
|
from rich.text import Text
|
|
10
11
|
|
|
@@ -21,6 +22,7 @@ from modal_proto import api_pb2
|
|
|
21
22
|
|
|
22
23
|
from ._help import ModalGroup
|
|
23
24
|
from ._logs import _validate_logs_args
|
|
25
|
+
from ._stats import STATS_METADATA_STYLE
|
|
24
26
|
from .server import _run_server_logs, _run_server_stats, _server_stats_time_range
|
|
25
27
|
|
|
26
28
|
_ENDPOINT_HELP = """
|
|
@@ -41,6 +43,11 @@ _SERVING_MODE_LABELS = {
|
|
|
41
43
|
api_pb2.ENDPOINT_SERVING_MODE_DEDICATED: "dedicated",
|
|
42
44
|
api_pb2.ENDPOINT_SERVING_MODE_SHARED: "shared",
|
|
43
45
|
}
|
|
46
|
+
_INPUT_MODALITY_LABELS = {
|
|
47
|
+
api_pb2.ENDPOINT_INPUT_MODALITY_TEXT: "text",
|
|
48
|
+
api_pb2.ENDPOINT_INPUT_MODALITY_IMAGE: "image",
|
|
49
|
+
api_pb2.ENDPOINT_INPUT_MODALITY_AUDIO: "audio",
|
|
50
|
+
}
|
|
44
51
|
_ENDPOINT_STATUS_LABELS = {
|
|
45
52
|
api_pb2.EndpointGetInfoResponse.ENDPOINT_STATUS_PROVISIONING: "provisioning",
|
|
46
53
|
api_pb2.EndpointGetInfoResponse.ENDPOINT_STATUS_LIVE: "live",
|
|
@@ -502,6 +509,108 @@ async def stop(
|
|
|
502
509
|
output.print(f"[green]✓[/green] Stopped Endpoint {endpoint_id}.")
|
|
503
510
|
|
|
504
511
|
|
|
512
|
+
@endpoint_cli.command("library", panel="Inspection")
|
|
513
|
+
@click.option(
|
|
514
|
+
"--input-modality",
|
|
515
|
+
type=click.Choice(["text", "image", "audio"]),
|
|
516
|
+
multiple=True,
|
|
517
|
+
help="Only show models that accept any of these inputs. Can be repeated.",
|
|
518
|
+
)
|
|
519
|
+
@click.option("--json", "json_output", is_flag=True, help="Output models as JSON.")
|
|
520
|
+
@synchronizer.create_blocking
|
|
521
|
+
async def library(*, input_modality: tuple[str, ...] = (), json_output: bool = False) -> None:
|
|
522
|
+
"""List models you can serve with Modal Endpoints.
|
|
523
|
+
|
|
524
|
+
Models are listed by their Hugging Face repo ID, with the input types they
|
|
525
|
+
accept and whether they're available as shared endpoints, dedicated
|
|
526
|
+
endpoints, or both.
|
|
527
|
+
|
|
528
|
+
This is Modal's catalog of supported models, not the Endpoints in your
|
|
529
|
+
workspace. To see those, run `modal endpoint list`.
|
|
530
|
+
|
|
531
|
+
Examples:
|
|
532
|
+
|
|
533
|
+
List models that support text input modality:
|
|
534
|
+
|
|
535
|
+
```
|
|
536
|
+
modal endpoint library --input-modality text
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Pick a model ID and then create a shared endpoint:
|
|
540
|
+
|
|
541
|
+
```
|
|
542
|
+
modal endpoint create --model Qwen/Qwen3.8-27B --shared
|
|
543
|
+
```
|
|
544
|
+
"""
|
|
545
|
+
client = await _Client.from_env()
|
|
546
|
+
response = await client._stub.EndpointGetLibrary(api_pb2.EndpointGetLibraryRequest())
|
|
547
|
+
modalities = {modality for modality, label in _INPUT_MODALITY_LABELS.items() if label in input_modality}
|
|
548
|
+
|
|
549
|
+
models = sorted(
|
|
550
|
+
(
|
|
551
|
+
model
|
|
552
|
+
for model in response.models
|
|
553
|
+
if (not modalities or modalities.intersection(model.supported_input_modalities))
|
|
554
|
+
),
|
|
555
|
+
key=lambda model: model.base_model_repo_id,
|
|
556
|
+
)
|
|
557
|
+
output = OutputManager.get()
|
|
558
|
+
if json_output:
|
|
559
|
+
output.print_json(
|
|
560
|
+
dumps(
|
|
561
|
+
[
|
|
562
|
+
{
|
|
563
|
+
"base_model_repo_id": model.base_model_repo_id,
|
|
564
|
+
"input_modalities": [
|
|
565
|
+
_INPUT_MODALITY_LABELS.get(modality, "unknown")
|
|
566
|
+
for modality in model.supported_input_modalities
|
|
567
|
+
],
|
|
568
|
+
"shared": api_pb2.ENDPOINT_SERVING_MODE_SHARED in model.available_serving_modes,
|
|
569
|
+
"dedicated": api_pb2.ENDPOINT_SERVING_MODE_DEDICATED in model.available_serving_modes,
|
|
570
|
+
}
|
|
571
|
+
for model in models
|
|
572
|
+
]
|
|
573
|
+
)
|
|
574
|
+
)
|
|
575
|
+
return
|
|
576
|
+
output.print(
|
|
577
|
+
Text(
|
|
578
|
+
f"Displaying {len(models):,} {'model' if len(models) == 1 else 'models'}",
|
|
579
|
+
style=STATS_METADATA_STYLE,
|
|
580
|
+
)
|
|
581
|
+
)
|
|
582
|
+
if not models:
|
|
583
|
+
output.print(
|
|
584
|
+
"No endpoint-compatible models match the filters."
|
|
585
|
+
if input_modality
|
|
586
|
+
else "No endpoint-compatible models are available."
|
|
587
|
+
)
|
|
588
|
+
return
|
|
589
|
+
display_table(
|
|
590
|
+
[
|
|
591
|
+
Column("Model ID", overflow="fold"),
|
|
592
|
+
"Input Modalities",
|
|
593
|
+
Column("Shared", justify="center"),
|
|
594
|
+
Column("Dedicated", justify="center"),
|
|
595
|
+
],
|
|
596
|
+
[
|
|
597
|
+
(
|
|
598
|
+
Text.assemble(
|
|
599
|
+
(model.base_model_repo_id, f"link https://huggingface.co/{model.base_model_repo_id}"),
|
|
600
|
+
(" ↗", "bright_blue not underline"),
|
|
601
|
+
),
|
|
602
|
+
", ".join(
|
|
603
|
+
_INPUT_MODALITY_LABELS.get(modality, "unknown") for modality in model.supported_input_modalities
|
|
604
|
+
),
|
|
605
|
+
"✓" if api_pb2.ENDPOINT_SERVING_MODE_SHARED in model.available_serving_modes else "—",
|
|
606
|
+
"✓" if api_pb2.ENDPOINT_SERVING_MODE_DEDICATED in model.available_serving_modes else "—",
|
|
607
|
+
)
|
|
608
|
+
for model in models
|
|
609
|
+
],
|
|
610
|
+
table_box=SIMPLE,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
|
|
505
614
|
@endpoint_cli.command("info", panel="Inspection", no_args_is_help=True)
|
|
506
615
|
@click.argument("endpoint_identifier")
|
|
507
616
|
@env_option
|
|
@@ -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.dev2",
|
|
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.dev2",
|
|
308
308
|
*,
|
|
309
309
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
310
310
|
):
|
|
@@ -215,7 +215,10 @@ class StatsPercentileDistribution:
|
|
|
215
215
|
|
|
216
216
|
@classmethod
|
|
217
217
|
def _from_proto(cls, proto: api_pb2.StatsPercentileDistribution) -> "StatsPercentileDistribution":
|
|
218
|
-
return cls(
|
|
218
|
+
return cls(
|
|
219
|
+
unit=proto.unit,
|
|
220
|
+
percentiles=[StatsPercentile._from_proto(p) for p in proto.percentiles],
|
|
221
|
+
)
|
|
219
222
|
|
|
220
223
|
|
|
221
224
|
@dataclass(frozen=True)
|
|
@@ -571,6 +574,23 @@ class BillingReportItem:
|
|
|
571
574
|
)
|
|
572
575
|
|
|
573
576
|
|
|
577
|
+
@dataclass(slots=True, frozen=True)
|
|
578
|
+
class SandboxGroupStats:
|
|
579
|
+
"""Statistics for a group of Sandboxes over a time range."""
|
|
580
|
+
|
|
581
|
+
tags: dict[str, str | None]
|
|
582
|
+
cost: float
|
|
583
|
+
cost_by_resource: dict[str, float]
|
|
584
|
+
|
|
585
|
+
@classmethod
|
|
586
|
+
def _from_proto(cls, pb: "api_pb2.SandboxStatsGroup") -> "SandboxGroupStats":
|
|
587
|
+
return cls(
|
|
588
|
+
tags={name: tag.value if tag.HasField("value") else None for name, tag in pb.tags.items()},
|
|
589
|
+
cost=pb.cost,
|
|
590
|
+
cost_by_resource=dict(pb.cost_by_resource),
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
|
|
574
594
|
@dataclass(slots=True, frozen=True)
|
|
575
595
|
class WorkspaceBillingSummary:
|
|
576
596
|
start: datetime
|