modal 1.5.6.dev21__tar.gz → 1.5.6.dev23__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/PKG-INFO +1 -1
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_function_variants.py +157 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/function.py +211 -2
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/utils.py +13 -1
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/volume.py +1 -13
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/client.pyi +2 -2
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/functions.pyi +6 -6
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_version/__init__.py +1 -1
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/LICENSE +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/README.md +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/__main__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_billing.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_clustered_functions.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_environments.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_functions.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_grpc_client.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_image.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_ipython.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_load_context.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_location.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_logs.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_logs_manager.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_object.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_output/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_output/manager.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_output/pty.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_output/rich.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_output/status.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_partial_function.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_resolver.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_resources.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_serialization.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_server.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_supports_logs.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_traceback.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_tunnel.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_tunnel.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_type_manager.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/logger.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_vendor/version.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_watcher.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/_workspace.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/app.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/app.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/billing.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/billing.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/builder/README.md +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/builder/base-images.json +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/call_graph.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/_download.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/_help.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/_logs.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/_stats.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/_traceback.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/app.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/billing.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/changelog.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/cluster.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/config.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/container.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/curl.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/dashboard.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/dict.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/endpoint.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/entry_point.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/environment.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/image.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/import_refs.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/logo.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/profile.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/queues.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/run.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/secret.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/selector.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/server.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/shell.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/skills.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/token.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cli/workspace.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/client.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cls.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/cls.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/config.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/container_process.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/container_process.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/dict.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/dict.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/environments.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/environments.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/exception.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/experimental/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/experimental/_flash_container_entry.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/experimental/flash.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/experimental/flash_server.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/experimental/flash_server.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/experimental/ipython.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/functions.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/image.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/image.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/io_streams.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/io_streams.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/mount.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/mount.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/network_file_system.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/network_file_system.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/object.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/object.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/outbound_policy.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/outbound_policy.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/output.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/parallel_map.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/parallel_map.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/partial_function.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/partial_function.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/proxy.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/proxy.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/py.typed +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/queue.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/queue.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/retries.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/runner.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/runner.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/running_app.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/sandbox.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/sandbox.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/sandbox_fs.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/schedule.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/scheduler_placement.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/secret.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/secret.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/server.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/server.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/serving.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/serving.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/snapshot.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/snapshot.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/stream_type.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/token_flow.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/token_flow.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/types.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/volume.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/volume.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/workspace.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal/workspace.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/__init__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/api_pb2.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/py.typed +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/modal_version/__main__.py +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/pyproject.toml +0 -0
- {modal-1.5.6.dev21 → modal-1.5.6.dev23}/setup.cfg +0 -0
|
@@ -4,12 +4,15 @@ from collections.abc import Collection, Sequence, Sized
|
|
|
4
4
|
from pathlib import PurePosixPath
|
|
5
5
|
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
|
+
import google.protobuf.message
|
|
8
|
+
|
|
7
9
|
from modal.types import CloudBucketMountInfo, FunctionInfo, VolumeMountInfo
|
|
8
10
|
from modal_proto import api_pb2
|
|
9
11
|
|
|
10
12
|
from ._resources import convert_fn_config_to_resources_config
|
|
11
13
|
from ._serialization import (
|
|
12
14
|
apply_defaults,
|
|
15
|
+
deserialize_proto_params,
|
|
13
16
|
serialize,
|
|
14
17
|
serialize_proto_params,
|
|
15
18
|
validate_parameter_values,
|
|
@@ -17,11 +20,14 @@ from ._serialization import (
|
|
|
17
20
|
from ._utils.function_utils import _parse_retries
|
|
18
21
|
from ._utils.mount_utils import validate_volumes, validate_volumes_by_object_id
|
|
19
22
|
from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
|
|
23
|
+
from .exception import InvalidError
|
|
20
24
|
from .retries import Retries
|
|
21
25
|
from .secret import _Secret
|
|
22
26
|
from .volume import _Volume, _volume_to_mount_proto
|
|
23
27
|
|
|
24
28
|
if TYPE_CHECKING:
|
|
29
|
+
from modal.client import _Client
|
|
30
|
+
|
|
25
31
|
from ._functions import _Function
|
|
26
32
|
from ._load_context import LoadContext
|
|
27
33
|
from ._object import _Object
|
|
@@ -190,6 +196,157 @@ class _FunctionOptions:
|
|
|
190
196
|
)
|
|
191
197
|
|
|
192
198
|
|
|
199
|
+
@dataclasses.dataclass(frozen=True)
|
|
200
|
+
class _FunctionOptionsInfo:
|
|
201
|
+
"""Configuration overrides applied to a Function variant.
|
|
202
|
+
|
|
203
|
+
Fields are named after the parameters of the builder methods that set them: `.with_options()`,
|
|
204
|
+
`.with_concurrency()` (`max_inputs`, `target_inputs`), and `.with_batching()` (`max_batch_size`,
|
|
205
|
+
`wait_ms`). Fields are None when the variant does not override that setting.
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
cpu: float | tuple[float, float] | None = None
|
|
209
|
+
memory: int | tuple[int, int] | None = None
|
|
210
|
+
gpu: str | None = None
|
|
211
|
+
secrets: list[str] | None = None
|
|
212
|
+
volumes: dict[str, VolumeMountInfo] | None = None # mount path -> volume and its mount options
|
|
213
|
+
cloud_bucket_mounts: dict[str, CloudBucketMountInfo] | None = None # mount path -> bucket mount
|
|
214
|
+
retries: Retries | None = None
|
|
215
|
+
max_containers: int | None = None
|
|
216
|
+
buffer_containers: int | None = None
|
|
217
|
+
scaledown_window: int | None = None
|
|
218
|
+
timeout: int | None = None
|
|
219
|
+
region: str | list[str] | None = None
|
|
220
|
+
cloud: str | None = None
|
|
221
|
+
routing_region: str | None = None
|
|
222
|
+
max_inputs: int | None = None
|
|
223
|
+
target_inputs: int | None = None
|
|
224
|
+
max_batch_size: int | None = None
|
|
225
|
+
wait_ms: int | None = None
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def _from_proto(cls, proto: api_pb2.FunctionOptions) -> "_FunctionOptionsInfo":
|
|
229
|
+
cpu: float | tuple[float, float] | None = None
|
|
230
|
+
memory: int | tuple[int, int] | None = None
|
|
231
|
+
gpu: str | None = None
|
|
232
|
+
if proto.HasField("resources"):
|
|
233
|
+
resources = proto.resources
|
|
234
|
+
if resources.milli_cpu_max > 0:
|
|
235
|
+
cpu = (resources.milli_cpu / 1000, resources.milli_cpu_max / 1000)
|
|
236
|
+
elif resources.milli_cpu > 0:
|
|
237
|
+
cpu = resources.milli_cpu / 1000
|
|
238
|
+
|
|
239
|
+
if resources.memory_mb_max > 0:
|
|
240
|
+
memory = (resources.memory_mb, resources.memory_mb_max)
|
|
241
|
+
elif resources.memory_mb > 0:
|
|
242
|
+
memory = resources.memory_mb
|
|
243
|
+
|
|
244
|
+
if resources.gpu_config.count > 0:
|
|
245
|
+
gpu_config = resources.gpu_config
|
|
246
|
+
gpu = gpu_config.gpu_type if gpu_config.count == 1 else f"{gpu_config.gpu_type}:{gpu_config.count}"
|
|
247
|
+
|
|
248
|
+
retries: Retries | None = None
|
|
249
|
+
if proto.HasField("retry_policy"):
|
|
250
|
+
retry_policy = proto.retry_policy
|
|
251
|
+
retries = Retries(
|
|
252
|
+
max_retries=retry_policy.retries,
|
|
253
|
+
backoff_coefficient=retry_policy.backoff_coefficient or 2.0,
|
|
254
|
+
initial_delay=retry_policy.initial_delay_ms / 1000,
|
|
255
|
+
max_delay=retry_policy.max_delay_ms / 1000 or 60.0,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
region: str | list[str] | None = None
|
|
259
|
+
if proto.HasField("scheduler_placement") and proto.scheduler_placement.regions:
|
|
260
|
+
region = list(proto.scheduler_placement.regions)
|
|
261
|
+
|
|
262
|
+
return cls(
|
|
263
|
+
cpu=cpu,
|
|
264
|
+
memory=memory,
|
|
265
|
+
gpu=gpu,
|
|
266
|
+
secrets=list(proto.secret_ids) or None,
|
|
267
|
+
volumes={
|
|
268
|
+
vm.mount_path: VolumeMountInfo(
|
|
269
|
+
name=None, # The mount records which Volume, not what it was called.
|
|
270
|
+
volume_id=vm.volume_id,
|
|
271
|
+
read_only=vm.read_only,
|
|
272
|
+
sub_path=vm.sub_path if vm.HasField("sub_path") else None,
|
|
273
|
+
)
|
|
274
|
+
for vm in proto.volume_mounts
|
|
275
|
+
}
|
|
276
|
+
or None,
|
|
277
|
+
cloud_bucket_mounts={
|
|
278
|
+
cbm.mount_path: CloudBucketMountInfo._from_proto(cbm) for cbm in proto.cloud_bucket_mounts
|
|
279
|
+
}
|
|
280
|
+
or None,
|
|
281
|
+
retries=retries,
|
|
282
|
+
max_containers=proto.concurrency_limit if proto.HasField("concurrency_limit") else None,
|
|
283
|
+
buffer_containers=proto.buffer_containers if proto.HasField("buffer_containers") else None,
|
|
284
|
+
scaledown_window=proto.task_idle_timeout_secs if proto.HasField("task_idle_timeout_secs") else None,
|
|
285
|
+
timeout=proto.timeout_secs if proto.HasField("timeout_secs") else None,
|
|
286
|
+
region=region,
|
|
287
|
+
cloud=proto.cloud_provider_str if proto.HasField("cloud_provider_str") else None,
|
|
288
|
+
routing_region=proto.routing_region if proto.HasField("routing_region") else None,
|
|
289
|
+
max_inputs=proto.max_concurrent_inputs if proto.HasField("max_concurrent_inputs") else None,
|
|
290
|
+
target_inputs=proto.target_concurrent_inputs if proto.HasField("target_concurrent_inputs") else None,
|
|
291
|
+
max_batch_size=proto.batch_max_size if proto.HasField("batch_max_size") else None,
|
|
292
|
+
wait_ms=proto.batch_linger_ms if proto.HasField("batch_linger_ms") else None,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@dataclasses.dataclass(frozen=True)
|
|
297
|
+
class _FunctionVariantInfo:
|
|
298
|
+
"""Information about a variant of a Function, created by parametrization or `.with_options()`."""
|
|
299
|
+
|
|
300
|
+
function_id: str
|
|
301
|
+
parameters: dict[str, str | int | bytes | bool] | None # None when the parameters can't be decoded
|
|
302
|
+
options: _FunctionOptionsInfo | None
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
def _from_proto(cls, proto: api_pb2.FunctionVariantInfo) -> "_FunctionVariantInfo":
|
|
306
|
+
parameters: dict[str, str | int | bytes | bool] | None
|
|
307
|
+
try:
|
|
308
|
+
parameters = deserialize_proto_params(proto.serialized_params)
|
|
309
|
+
except (google.protobuf.message.DecodeError, InvalidError):
|
|
310
|
+
parameters = None
|
|
311
|
+
|
|
312
|
+
return cls(
|
|
313
|
+
function_id=proto.function_id,
|
|
314
|
+
parameters=parameters,
|
|
315
|
+
options=_FunctionOptionsInfo._from_proto(proto.function_options)
|
|
316
|
+
if proto.HasField("function_options")
|
|
317
|
+
else None,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@dataclasses.dataclass(frozen=True)
|
|
322
|
+
class _FunctionVariantListing:
|
|
323
|
+
"""The variants of a Function, and how the server chose to order them."""
|
|
324
|
+
|
|
325
|
+
variants: list[_FunctionVariantInfo]
|
|
326
|
+
# True when the variants are ordered by how many tasks each is running, busiest first.
|
|
327
|
+
ordered_by_task_count: bool
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
async def _list_function_variants(
|
|
331
|
+
client: "_Client", function_id: str, *, limit: int | None = None
|
|
332
|
+
) -> _FunctionVariantListing:
|
|
333
|
+
variants: list[_FunctionVariantInfo] = []
|
|
334
|
+
ordered_by_task_count = False
|
|
335
|
+
cursor = None
|
|
336
|
+
while True:
|
|
337
|
+
request = api_pb2.FunctionListVariantsRequest(function_id=function_id, cursor=cursor, limit=limit or 0)
|
|
338
|
+
response = await client.stub.FunctionListVariants(request)
|
|
339
|
+
# An ordered listing arrives as a single response, so there is nothing to reconcile across
|
|
340
|
+
# pages: every response of a multi-page listing reports itself unordered.
|
|
341
|
+
ordered_by_task_count = response.ordered_by_task_count
|
|
342
|
+
variants.extend(_FunctionVariantInfo._from_proto(info) for info in response.infos)
|
|
343
|
+
if limit is not None and len(variants) >= limit:
|
|
344
|
+
return _FunctionVariantListing(variants[:limit], ordered_by_task_count)
|
|
345
|
+
if not response.HasField("next_cursor"):
|
|
346
|
+
return _FunctionVariantListing(variants, ordered_by_task_count)
|
|
347
|
+
cursor = response.next_cursor
|
|
348
|
+
|
|
349
|
+
|
|
193
350
|
async def _function_bind_params_cached(
|
|
194
351
|
base_function: "_Function",
|
|
195
352
|
req: api_pb2.FunctionBindParamsRequest,
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
# Copyright Modal Labs 2026
|
|
2
|
+
import dataclasses
|
|
2
3
|
import json as json_lib
|
|
3
4
|
from datetime import datetime, timedelta, timezone
|
|
4
|
-
from typing import cast
|
|
5
|
+
from typing import Any, cast
|
|
5
6
|
|
|
6
7
|
import click
|
|
7
8
|
from click import UsageError
|
|
9
|
+
from rich.table import Column
|
|
8
10
|
from rich.text import Text
|
|
9
11
|
|
|
10
12
|
from modal._environments import ensure_env
|
|
13
|
+
from modal._function_variants import _FunctionOptionsInfo, _list_function_variants
|
|
11
14
|
from modal._object import _get_environment_name
|
|
12
15
|
from modal._utils.async_utils import synchronizer
|
|
13
16
|
from modal._utils.time_utils import parse_duration
|
|
14
17
|
from modal.client import _Client
|
|
15
18
|
from modal.output import OutputManager
|
|
19
|
+
from modal.retries import Retries
|
|
20
|
+
from modal.types import CloudBucketMountInfo, VolumeMountInfo
|
|
16
21
|
from modal_proto import api_pb2
|
|
17
22
|
|
|
18
23
|
from ._help import ModalGroup
|
|
@@ -32,7 +37,7 @@ from ._stats import (
|
|
|
32
37
|
stats_style,
|
|
33
38
|
success_style,
|
|
34
39
|
)
|
|
35
|
-
from .utils import _resolve_function_id, env_option
|
|
40
|
+
from .utils import _resolve_function_id, display_table, env_option, humanize_filesize
|
|
36
41
|
|
|
37
42
|
function_cli = ModalGroup(name="function", help="Inspect Modal Functions.")
|
|
38
43
|
|
|
@@ -406,6 +411,7 @@ async def logs(
|
|
|
406
411
|
function_id, metadata = await _resolve_function_id(
|
|
407
412
|
client, function_ref, env, object_type="Function", command="logs"
|
|
408
413
|
)
|
|
414
|
+
app_id = metadata.app_id
|
|
409
415
|
|
|
410
416
|
prefix_fields: list[str] = []
|
|
411
417
|
if show_function_id:
|
|
@@ -430,3 +436,206 @@ async def logs(
|
|
|
430
436
|
timestamps=timestamps,
|
|
431
437
|
prefix_fields=prefix_fields,
|
|
432
438
|
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _overridden_options(options: _FunctionOptionsInfo | None) -> list[tuple[str, Any]]:
|
|
442
|
+
"""Return the settings a variant overrides, keyed by the builder-method parameter name."""
|
|
443
|
+
if options is None:
|
|
444
|
+
return []
|
|
445
|
+
fields = ((field.name, getattr(options, field.name)) for field in dataclasses.fields(options))
|
|
446
|
+
return [(name, value) for name, value in fields if value is not None]
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _variant_overrides_json(options: _FunctionOptionsInfo | None) -> dict[str, Any]:
|
|
450
|
+
"""Return the overridden settings in a form that can be serialized as JSON."""
|
|
451
|
+
overrides: dict[str, Any] = {}
|
|
452
|
+
for name, value in _overridden_options(options):
|
|
453
|
+
if isinstance(value, Retries):
|
|
454
|
+
value = {
|
|
455
|
+
"max_retries": value.max_retries,
|
|
456
|
+
"backoff_coefficient": value.backoff_coefficient,
|
|
457
|
+
"initial_delay": value.initial_delay.total_seconds(),
|
|
458
|
+
"max_delay": value.max_delay.total_seconds(),
|
|
459
|
+
}
|
|
460
|
+
elif isinstance(value, tuple):
|
|
461
|
+
value = list(value)
|
|
462
|
+
elif name in ("cloud_bucket_mounts", "volumes"):
|
|
463
|
+
value = {path: dataclasses.asdict(mount) for path, mount in value.items()}
|
|
464
|
+
overrides[name] = value
|
|
465
|
+
return overrides
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _format_bucket_mount(mount: CloudBucketMountInfo) -> str:
|
|
469
|
+
"""Render a cloud bucket mount as a URI, which is the part that identifies it at a glance."""
|
|
470
|
+
uri = f"{mount.bucket_type}://{mount.bucket_name}"
|
|
471
|
+
if mount.key_prefix:
|
|
472
|
+
uri = f"{uri}/{mount.key_prefix}"
|
|
473
|
+
return f"{uri} (ro)" if mount.read_only else uri
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _format_volume_mount(mount: VolumeMountInfo) -> str:
|
|
477
|
+
"""Render a volume mount as its ID plus whatever `.with_mount_options()` changed about it."""
|
|
478
|
+
volume = mount.volume_id or mount.name or ""
|
|
479
|
+
if mount.sub_path:
|
|
480
|
+
volume = f"{volume}:{mount.sub_path}"
|
|
481
|
+
return f"{volume} (ro)" if mount.read_only else volume
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _format_override(value: Any) -> str:
|
|
485
|
+
if isinstance(value, dict):
|
|
486
|
+
return "{" + ", ".join(f"{k}: {_format_override(v)}" for k, v in value.items()) + "}"
|
|
487
|
+
if isinstance(value, (list, tuple)):
|
|
488
|
+
return "[" + ", ".join(_format_override(v) for v in value) + "]"
|
|
489
|
+
return str(value)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _variant_cell(text: str, style: str = "") -> Text:
|
|
493
|
+
"""Render one table cell that is truncated rather than wrapped, so one variant stays in one row."""
|
|
494
|
+
return Text(text, style=style, no_wrap=True, overflow="ellipsis")
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _format_parameter(value: Any) -> str:
|
|
498
|
+
if isinstance(value, bytes):
|
|
499
|
+
# A bytes parameter is an opaque payload; its size is the only part that reads well in a table.
|
|
500
|
+
return f"<bytes: {humanize_filesize(len(value))}>"
|
|
501
|
+
return repr(value)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _parameters_cell(parameters: dict[str, Any] | None) -> Text:
|
|
505
|
+
if parameters is None:
|
|
506
|
+
return _variant_cell("unavailable", style="dim")
|
|
507
|
+
return _variant_cell(", ".join(f"{name}={_format_parameter(value)}" for name, value in parameters.items()))
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _options_cell(options: _FunctionOptionsInfo | None) -> Text:
|
|
511
|
+
parts = []
|
|
512
|
+
for name, value in _overridden_options(options):
|
|
513
|
+
if isinstance(value, Retries):
|
|
514
|
+
# The retry count is the useful part of a retry policy at a glance.
|
|
515
|
+
value = value.max_retries
|
|
516
|
+
elif name == "cloud_bucket_mounts":
|
|
517
|
+
value = {path: _format_bucket_mount(mount) for path, mount in value.items()}
|
|
518
|
+
elif name == "volumes":
|
|
519
|
+
value = {path: _format_volume_mount(mount) for path, mount in value.items()}
|
|
520
|
+
parts.append(f"{name}={_format_override(value)}")
|
|
521
|
+
return _variant_cell(", ".join(parts))
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@function_cli.command("variants", no_args_is_help=True)
|
|
525
|
+
@click.argument("function_ref", metavar="FUNCTION")
|
|
526
|
+
@click.option(
|
|
527
|
+
"-n",
|
|
528
|
+
"--limit",
|
|
529
|
+
"limit",
|
|
530
|
+
default=200,
|
|
531
|
+
show_default=True,
|
|
532
|
+
type=int,
|
|
533
|
+
help="Show at most N variants, those running the most containers first. Use 0 to list every variant, newest first.",
|
|
534
|
+
)
|
|
535
|
+
@click.option("--json", "json", is_flag=True, default=False, help="Output as JSON.")
|
|
536
|
+
@env_option
|
|
537
|
+
@synchronizer.create_blocking
|
|
538
|
+
async def variants(
|
|
539
|
+
function_ref: str,
|
|
540
|
+
limit: int = 200,
|
|
541
|
+
json: bool = False,
|
|
542
|
+
env: str | None = None,
|
|
543
|
+
):
|
|
544
|
+
"""List the variants of a modal Function.
|
|
545
|
+
|
|
546
|
+
Variants are the parameterized instances of a `modal.Cls` and the Functions created with
|
|
547
|
+
`.with_options()`. FUNCTION may be a Function ID or a deployed Function name in the form
|
|
548
|
+
``APP_NAME/FUNCTION_NAME``.
|
|
549
|
+
|
|
550
|
+
By default, the busiest variants are listed first. If you ask for more variants than can be
|
|
551
|
+
ranked, or for all of them, they are listed newest first instead.
|
|
552
|
+
|
|
553
|
+
Examples:
|
|
554
|
+
|
|
555
|
+
List the busiest variants of a deployed Function:
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
modal function variants my-app/my-function
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
List the parameterized instances of a deployed `modal.Cls`:
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
modal function variants 'my-app/MyClass.*'
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Show only the ten busiest variants:
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
modal function variants my-app/my-function --limit 10
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
List every variant of a Function ID as JSON:
|
|
574
|
+
|
|
575
|
+
```
|
|
576
|
+
modal function variants fu-abc123 --limit 0 --json
|
|
577
|
+
```
|
|
578
|
+
"""
|
|
579
|
+
if limit < 0:
|
|
580
|
+
raise UsageError("--limit cannot be negative.")
|
|
581
|
+
|
|
582
|
+
environment_name = _get_environment_name(ensure_env(env))
|
|
583
|
+
client = await _Client.from_env()
|
|
584
|
+
function_id, handle_metadata = await _resolve_function_id(
|
|
585
|
+
client,
|
|
586
|
+
function_ref,
|
|
587
|
+
environment_name,
|
|
588
|
+
command="variants",
|
|
589
|
+
)
|
|
590
|
+
# A variant has no variants of its own, so resolving one lists the family it belongs to.
|
|
591
|
+
base_function_id = handle_metadata.base_function_id or function_id
|
|
592
|
+
effective_limit = limit or None
|
|
593
|
+
listing = await _list_function_variants(client, base_function_id, limit=effective_limit)
|
|
594
|
+
function_variants = listing.variants
|
|
595
|
+
show_parameters = bool(handle_metadata.method_handle_metadata)
|
|
596
|
+
|
|
597
|
+
output = OutputManager.get()
|
|
598
|
+
if json:
|
|
599
|
+
payload: list[dict[str, Any]] = []
|
|
600
|
+
for variant in function_variants:
|
|
601
|
+
entry: dict[str, Any] = {"function_id": variant.function_id}
|
|
602
|
+
if show_parameters:
|
|
603
|
+
entry["parameters"] = variant.parameters
|
|
604
|
+
entry["options"] = _variant_overrides_json(variant.options)
|
|
605
|
+
payload.append(entry)
|
|
606
|
+
# Parameter values may include bytes, which have no JSON representation of their own.
|
|
607
|
+
output.print_json(json_lib.dumps(payload, default=repr))
|
|
608
|
+
return
|
|
609
|
+
|
|
610
|
+
if not function_variants:
|
|
611
|
+
output.print(f"No variants found for {base_function_id}.")
|
|
612
|
+
return
|
|
613
|
+
|
|
614
|
+
rows: list[list[Text | str | None]] = []
|
|
615
|
+
for variant in function_variants:
|
|
616
|
+
options_cell = _options_cell(variant.options)
|
|
617
|
+
if show_parameters:
|
|
618
|
+
rows.append(
|
|
619
|
+
[
|
|
620
|
+
variant.function_id,
|
|
621
|
+
_parameters_cell(variant.parameters),
|
|
622
|
+
options_cell,
|
|
623
|
+
]
|
|
624
|
+
)
|
|
625
|
+
else:
|
|
626
|
+
rows.append([variant.function_id, options_cell])
|
|
627
|
+
|
|
628
|
+
id_column = Column("Function ID", no_wrap=True)
|
|
629
|
+
columns = (
|
|
630
|
+
[id_column, Column("Parameters"), Column("Options")] if show_parameters else [id_column, Column("Options")]
|
|
631
|
+
)
|
|
632
|
+
ordering = f"{len(rows)} busiest" if listing.ordered_by_task_count else f"{len(rows)} newest"
|
|
633
|
+
display_table(columns, rows, title=f"Variants of {base_function_id} · {ordering}")
|
|
634
|
+
|
|
635
|
+
if effective_limit is not None and len(function_variants) == effective_limit:
|
|
636
|
+
output.print(
|
|
637
|
+
Text(
|
|
638
|
+
f"Showing {effective_limit} variants; use --limit 0 to list every variant.",
|
|
639
|
+
style="dim",
|
|
640
|
+
)
|
|
641
|
+
)
|
|
@@ -132,6 +132,18 @@ async def _fetch_app_logs(
|
|
|
132
132
|
await _drain_batches(output_mgr, batches, prefix_fields or [], filters.search_text)
|
|
133
133
|
|
|
134
134
|
|
|
135
|
+
def humanize_filesize(value: int) -> str:
|
|
136
|
+
if value < 0:
|
|
137
|
+
raise ValueError("value should be >= 0")
|
|
138
|
+
base = 1024
|
|
139
|
+
size = float(value)
|
|
140
|
+
for unit in ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"):
|
|
141
|
+
if size < base:
|
|
142
|
+
return f"{size:.0f} {unit}" if unit == "B" else f"{size:.1f} {unit}"
|
|
143
|
+
size /= base
|
|
144
|
+
return f"{size:.1f} ZiB"
|
|
145
|
+
|
|
146
|
+
|
|
135
147
|
def _plain(text: "Text | str | bool | None") -> "str | bool | None":
|
|
136
148
|
return text.plain if isinstance(text, Text) else text
|
|
137
149
|
|
|
@@ -216,7 +228,7 @@ async def _resolve_function_id(
|
|
|
216
228
|
environment_name: str,
|
|
217
229
|
*,
|
|
218
230
|
object_type: Literal["Function", "Server"] = "Function",
|
|
219
|
-
command: Literal["logs", "stats"],
|
|
231
|
+
command: Literal["logs", "stats", "variants"],
|
|
220
232
|
) -> tuple[str, api_pb2.FunctionHandleMetadata]:
|
|
221
233
|
identifier_label = object_type.upper()
|
|
222
234
|
usage = (
|
|
@@ -13,7 +13,7 @@ from modal._utils.async_utils import synchronizer
|
|
|
13
13
|
from modal._utils.browser_utils import open_url_and_display
|
|
14
14
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
15
15
|
from modal.cli._download import _volume_download
|
|
16
|
-
from modal.cli.utils import display_table, env_option, yes_option
|
|
16
|
+
from modal.cli.utils import display_table, env_option, humanize_filesize, yes_option
|
|
17
17
|
from modal.output import OutputManager
|
|
18
18
|
from modal.volume import _AbstractVolumeUploadContextManager, _Volume
|
|
19
19
|
from modal_proto import api_pb2
|
|
@@ -30,18 +30,6 @@ volume_cli = ModalGroup(
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def humanize_filesize(value: int) -> str:
|
|
34
|
-
if value < 0:
|
|
35
|
-
raise ValueError("value should be >= 0")
|
|
36
|
-
base = 1024
|
|
37
|
-
size = float(value)
|
|
38
|
-
for unit in ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"):
|
|
39
|
-
if size < base:
|
|
40
|
-
return f"{size:.0f} {unit}" if unit == "B" else f"{size:.1f} {unit}"
|
|
41
|
-
size /= base
|
|
42
|
-
return f"{size:.1f} ZiB"
|
|
43
|
-
|
|
44
|
-
|
|
45
33
|
@volume_cli.command("create", help="Create a named, persistent modal.Volume.", panel="Management", no_args_is_help=True)
|
|
46
34
|
@click.argument("name")
|
|
47
35
|
@env_option
|
|
@@ -77,7 +77,7 @@ class _Client:
|
|
|
77
77
|
server_url: str,
|
|
78
78
|
client_type: int,
|
|
79
79
|
credentials: typing.Optional[tuple[str, str]],
|
|
80
|
-
version: str = "1.5.6.
|
|
80
|
+
version: str = "1.5.6.dev23",
|
|
81
81
|
*,
|
|
82
82
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
83
83
|
):
|
|
@@ -277,7 +277,7 @@ class Client:
|
|
|
277
277
|
server_url: str,
|
|
278
278
|
client_type: int,
|
|
279
279
|
credentials: typing.Optional[tuple[str, str]],
|
|
280
|
-
version: str = "1.5.6.
|
|
280
|
+
version: str = "1.5.6.dev23",
|
|
281
281
|
*,
|
|
282
282
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
283
283
|
):
|
|
@@ -653,7 +653,7 @@ class Function(
|
|
|
653
653
|
|
|
654
654
|
_call_generator: ___call_generator_spec
|
|
655
655
|
|
|
656
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
656
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
657
657
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
658
658
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
659
659
|
|
|
@@ -678,7 +678,7 @@ class Function(
|
|
|
678
678
|
"""
|
|
679
679
|
...
|
|
680
680
|
|
|
681
|
-
remote: __remote_spec[modal._functions.
|
|
681
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
682
682
|
|
|
683
683
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
684
684
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -728,7 +728,7 @@ class Function(
|
|
|
728
728
|
"""
|
|
729
729
|
...
|
|
730
730
|
|
|
731
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
731
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
732
732
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
733
733
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
734
734
|
|
|
@@ -761,7 +761,7 @@ class Function(
|
|
|
761
761
|
"""
|
|
762
762
|
...
|
|
763
763
|
|
|
764
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
764
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
765
765
|
|
|
766
766
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
767
767
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -769,7 +769,7 @@ class Function(
|
|
|
769
769
|
|
|
770
770
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
771
771
|
|
|
772
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
772
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
773
773
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
774
774
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
775
775
|
|
|
@@ -802,7 +802,7 @@ class Function(
|
|
|
802
802
|
"""
|
|
803
803
|
...
|
|
804
804
|
|
|
805
|
-
spawn: __spawn_spec[modal._functions.
|
|
805
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
806
806
|
|
|
807
807
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
808
808
|
"""Return the inner Python object wrapped by this Modal Function.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|