modal 1.5.6.dev23__tar.gz → 1.5.6.dev24__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.dev23 → modal-1.5.6.dev24}/PKG-INFO +1 -1
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_functions.py +68 -2
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_output/manager.py +8 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_output/rich.py +5 -1
- modal-1.5.6.dev24/modal/_output/status.py +92 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_server.py +32 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/config.py +8 -6
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/function.py +225 -1
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/server.py +127 -1
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/utils.py +28 -3
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/client.pyi +2 -2
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/config.py +57 -46
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/functions.pyi +36 -6
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/mount.py +1 -1
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/server.pyi +19 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_version/__init__.py +1 -1
- modal-1.5.6.dev23/modal/_output/status.py +0 -98
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/LICENSE +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/README.md +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/__main__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_billing.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_clustered_functions.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_environments.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_function_variants.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_grpc_client.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_image.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_ipython.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_load_context.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_location.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_logs.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_logs_manager.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_object.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_output/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_output/pty.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_partial_function.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_resolver.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_resources.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_serialization.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_supports_logs.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_traceback.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_tunnel.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_tunnel.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_type_manager.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/logger.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_vendor/version.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_watcher.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/_workspace.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/app.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/app.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/billing.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/billing.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/builder/README.md +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/builder/base-images.json +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/call_graph.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/_download.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/_help.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/_logs.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/_stats.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/_traceback.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/app.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/billing.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/changelog.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/cluster.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/container.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/curl.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/dashboard.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/dict.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/endpoint.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/entry_point.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/environment.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/image.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/import_refs.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/logo.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/profile.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/queues.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/run.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/secret.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/selector.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/shell.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/skills.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/token.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/volume.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cli/workspace.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/client.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cls.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/cls.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/container_process.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/container_process.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/dict.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/dict.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/environments.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/environments.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/exception.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/experimental/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/experimental/_flash_container_entry.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/experimental/flash.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/experimental/flash_server.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/experimental/flash_server.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/experimental/ipython.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/functions.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/image.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/image.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/io_streams.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/io_streams.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/mount.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/network_file_system.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/network_file_system.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/object.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/object.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/outbound_policy.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/outbound_policy.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/output.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/parallel_map.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/parallel_map.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/partial_function.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/partial_function.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/proxy.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/proxy.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/py.typed +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/queue.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/queue.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/retries.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/runner.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/runner.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/running_app.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/sandbox.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/sandbox.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/sandbox_fs.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/schedule.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/scheduler_placement.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/secret.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/secret.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/server.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/serving.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/serving.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/snapshot.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/snapshot.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/stream_type.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/token_flow.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/token_flow.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/types.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/volume.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/volume.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/workspace.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal/workspace.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/__init__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/api_pb2.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/py.typed +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/modal_version/__main__.py +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/pyproject.toml +0 -0
- {modal-1.5.6.dev23 → modal-1.5.6.dev24}/setup.cfg +0 -0
|
@@ -10,7 +10,7 @@ from collections.abc import AsyncGenerator, AsyncIterator, Callable, Collection,
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from datetime import datetime, timedelta, timezone
|
|
12
12
|
from pathlib import PurePosixPath
|
|
13
|
-
from typing import TYPE_CHECKING, Any
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
14
14
|
|
|
15
15
|
import typing_extensions
|
|
16
16
|
from google.protobuf.message import Message
|
|
@@ -945,7 +945,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
945
945
|
self._hydrate(response.function_id, load_context.client, response.handle_metadata)
|
|
946
946
|
|
|
947
947
|
async def _load(self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
|
|
948
|
-
with FunctionCreationStatus(tag) as function_creation_status:
|
|
948
|
+
with FunctionCreationStatus("Server" if is_server else "Function", tag) as function_creation_status:
|
|
949
949
|
timeout_secs = timeout
|
|
950
950
|
|
|
951
951
|
if app and app._is_interactive_ and not is_builder_function:
|
|
@@ -1388,6 +1388,72 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1388
1388
|
_load_remote, rep, skip_reload=True, hydrate_lazily=True, load_context_overrides=load_context_overrides
|
|
1389
1389
|
)
|
|
1390
1390
|
|
|
1391
|
+
@classmethod
|
|
1392
|
+
def _from_id(
|
|
1393
|
+
cls,
|
|
1394
|
+
function_id: str,
|
|
1395
|
+
*,
|
|
1396
|
+
load_context_overrides: LoadContext,
|
|
1397
|
+
called_from: Literal["Function", "Server"] = "Function",
|
|
1398
|
+
):
|
|
1399
|
+
# internal function lookup implementation that allows lookup of class "service functions"
|
|
1400
|
+
# in addition to non-class functions
|
|
1401
|
+
async def _load_remote(
|
|
1402
|
+
self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None
|
|
1403
|
+
):
|
|
1404
|
+
request = api_pb2.FunctionGetByIdRequest(function_id=function_id)
|
|
1405
|
+
try:
|
|
1406
|
+
response = await load_context.client.stub.FunctionGetById(request)
|
|
1407
|
+
except NotFoundError as exc:
|
|
1408
|
+
# refine the error message
|
|
1409
|
+
msg = f"Lookup failed for {called_from} '{function_id}': {exc}."
|
|
1410
|
+
raise NotFoundError(msg) from None
|
|
1411
|
+
if called_from == "Function" and (response.function.is_server or response.function.is_class):
|
|
1412
|
+
if response.function.is_server:
|
|
1413
|
+
raise InvalidError(f"{function_id} is a Server. Use \n ``modal.Server.from_id('{function_id}')``")
|
|
1414
|
+
raise InvalidError(f"{function_id} is a Cls and cannot be loaded with Function.from_id().")
|
|
1415
|
+
if called_from == "Server" and not response.function.is_server:
|
|
1416
|
+
if response.function.is_class:
|
|
1417
|
+
raise InvalidError(f"{function_id} is a Cls and cannot be loaded with Server.from_id().")
|
|
1418
|
+
raise InvalidError(f"{function_id} is a Function. Use \n ``modal.Function.from_id('{function_id}')``")
|
|
1419
|
+
self._hydrate(function_id, load_context.client, response.handle_metadata)
|
|
1420
|
+
self._function_info = FunctionInfo._from_function_proto(response.function)
|
|
1421
|
+
|
|
1422
|
+
rep = f"modal.Function.from_id('{function_id}')"
|
|
1423
|
+
return cls._from_loader(
|
|
1424
|
+
_load_remote, rep, skip_reload=True, hydrate_lazily=True, load_context_overrides=load_context_overrides
|
|
1425
|
+
)
|
|
1426
|
+
|
|
1427
|
+
@classmethod
|
|
1428
|
+
def from_id(
|
|
1429
|
+
cls: type["_Function"],
|
|
1430
|
+
function_id: str,
|
|
1431
|
+
*,
|
|
1432
|
+
client: _Client | None = None,
|
|
1433
|
+
):
|
|
1434
|
+
"""Reference a Function from a deployed or running App by its ID.
|
|
1435
|
+
|
|
1436
|
+
This is a lazy method that defers hydrating the local
|
|
1437
|
+
object with metadata from Modal servers until the first
|
|
1438
|
+
time it is actually used.
|
|
1439
|
+
|
|
1440
|
+
Args:
|
|
1441
|
+
function_id: ID of the function.
|
|
1442
|
+
client: Modal client to use; defaults to `Client.from_env()` when omitted.
|
|
1443
|
+
|
|
1444
|
+
Returns:
|
|
1445
|
+
A lazy `Function` handle.
|
|
1446
|
+
|
|
1447
|
+
Examples:
|
|
1448
|
+
```python
|
|
1449
|
+
f = modal.Function.from_id("fu-123")
|
|
1450
|
+
```
|
|
1451
|
+
"""
|
|
1452
|
+
return cls._from_id(
|
|
1453
|
+
function_id,
|
|
1454
|
+
load_context_overrides=LoadContext(client=client),
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1391
1457
|
@classmethod
|
|
1392
1458
|
def from_name(
|
|
1393
1459
|
cls: type["_Function"],
|
|
@@ -58,6 +58,11 @@ class StatusRow(ABC):
|
|
|
58
58
|
"""Append a warning message associated with this row."""
|
|
59
59
|
...
|
|
60
60
|
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def details(self, message: str) -> None:
|
|
63
|
+
"""Append additional details to the row."""
|
|
64
|
+
...
|
|
65
|
+
|
|
61
66
|
@abstractmethod
|
|
62
67
|
def finish(self, message: str) -> None:
|
|
63
68
|
"""Mark the row as finished with the given message."""
|
|
@@ -73,6 +78,9 @@ class DisabledStatusRow(StatusRow):
|
|
|
73
78
|
def warn(self, warning: "api_pb2.Warning") -> None:
|
|
74
79
|
pass
|
|
75
80
|
|
|
81
|
+
def details(self, message: str) -> None:
|
|
82
|
+
pass
|
|
83
|
+
|
|
76
84
|
def finish(self, message: str) -> None:
|
|
77
85
|
pass
|
|
78
86
|
|
|
@@ -194,6 +194,10 @@ class RichStatusRow(StatusRow):
|
|
|
194
194
|
if self._spinner is not None:
|
|
195
195
|
self._spinner.update(text=message)
|
|
196
196
|
|
|
197
|
+
def details(self, message: str) -> None:
|
|
198
|
+
if self._step_node is not None:
|
|
199
|
+
self._step_node.add(f"{message}")
|
|
200
|
+
|
|
197
201
|
def warn(self, warning: api_pb2.Warning) -> None:
|
|
198
202
|
if self._step_node is not None:
|
|
199
203
|
self._step_node.add(f"[yellow]:warning:[/yellow] {warning.message}")
|
|
@@ -201,7 +205,7 @@ class RichStatusRow(StatusRow):
|
|
|
201
205
|
def finish(self, message: str) -> None:
|
|
202
206
|
if self._step_node is not None and self._spinner is not None:
|
|
203
207
|
self._spinner.update(text=message)
|
|
204
|
-
self._step_node.label = f"
|
|
208
|
+
self._step_node.label = f"[green]✓[/green] {message}"
|
|
205
209
|
|
|
206
210
|
|
|
207
211
|
_LOG_FLUSH_THRESHOLD = 32768 # 32KB — flush batched log output roughly every ~400 lines
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Copyright Modal Labs 2022
|
|
2
|
+
"""Function creation status tracking for Modal CLI.
|
|
3
|
+
|
|
4
|
+
This module contains the FunctionCreationStatus context manager used to
|
|
5
|
+
track and display function creation progress.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from modal_proto import api_pb2
|
|
11
|
+
|
|
12
|
+
from .manager import OutputManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_annotation_for_web_url(url_info: api_pb2.WebUrlInfo) -> str:
|
|
16
|
+
if url_info.truncated:
|
|
17
|
+
suffix = " [grey70](label truncated)[/grey70]"
|
|
18
|
+
elif url_info.label_stolen:
|
|
19
|
+
suffix = " [grey70](label stolen)[/grey70]"
|
|
20
|
+
else:
|
|
21
|
+
suffix = ""
|
|
22
|
+
return suffix
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FunctionCreationStatus:
|
|
26
|
+
"""Context manager for tracking and displaying function creation progress."""
|
|
27
|
+
|
|
28
|
+
type: str
|
|
29
|
+
name: str
|
|
30
|
+
response: api_pb2.FunctionCreateResponse | None = None
|
|
31
|
+
|
|
32
|
+
def __init__(self, type: str, name: str):
|
|
33
|
+
self.type = type
|
|
34
|
+
self.name = name
|
|
35
|
+
self._output_mgr = OutputManager.get()
|
|
36
|
+
|
|
37
|
+
def __enter__(self):
|
|
38
|
+
self.status_row = self._output_mgr.add_status_row()
|
|
39
|
+
self.status_row.message(f"Creating {self.type} {self.name}...")
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
def set_response(self, resp: api_pb2.FunctionCreateResponse):
|
|
43
|
+
self.response = resp
|
|
44
|
+
|
|
45
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
46
|
+
if exc_type:
|
|
47
|
+
raise exc_val
|
|
48
|
+
|
|
49
|
+
if not self.response:
|
|
50
|
+
self.status_row.finish(f"Unknown error when creating {self.type} {self.name}")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
for warning in self.response.server_warnings:
|
|
54
|
+
self.status_row.warn(warning)
|
|
55
|
+
|
|
56
|
+
if web_url := self.response.handle_metadata.web_url:
|
|
57
|
+
url_info = self.response.function.web_url_info
|
|
58
|
+
authenticated = self.response.function.webhook_config.requires_proxy_auth
|
|
59
|
+
auth_suffix = " 🔑" if authenticated else " [yellow](unauthenticated)[/yellow]"
|
|
60
|
+
suffix = _get_annotation_for_web_url(url_info)
|
|
61
|
+
|
|
62
|
+
self.status_row.finish(f"Created {self.type} [green]{self.name}[/green]")
|
|
63
|
+
self.status_row.details(f"[dim underline]{web_url}[/dim underline]{auth_suffix}{suffix}")
|
|
64
|
+
for custom_domain in self.response.function.custom_domain_info:
|
|
65
|
+
self.status_row.details(f"Custom domain: [dim underline]{custom_domain.url}[/dim underline]")
|
|
66
|
+
|
|
67
|
+
elif self.response.function.flash_service_urls:
|
|
68
|
+
# Despite the gRPC API types, Servers only have one URL
|
|
69
|
+
authenticated = self.response.function.is_server and not self.response.function.http_config.unauthenticated
|
|
70
|
+
auth_suffix = " 🔑" if authenticated else " [yellow](unauthenticated)[/yellow]"
|
|
71
|
+
self.status_row.finish(f"Created {self.type} [green]{self.name}[/green]")
|
|
72
|
+
url = self.response.function.flash_service_urls[0]
|
|
73
|
+
self.status_row.details(f"[dim underline]{url}[/dim underline]{auth_suffix}")
|
|
74
|
+
|
|
75
|
+
else:
|
|
76
|
+
self.status_row.finish(f"Created {self.type} [green]{self.name}[/green].")
|
|
77
|
+
if self.response.function.method_definitions_set:
|
|
78
|
+
for method_definition in self.response.function.method_definitions.values():
|
|
79
|
+
if method_definition.web_url:
|
|
80
|
+
url_info = method_definition.web_url_info
|
|
81
|
+
suffix = _get_annotation_for_web_url(url_info)
|
|
82
|
+
authenticated = method_definition.webhook_config.requires_proxy_auth
|
|
83
|
+
auth_suffix = " 🔑" if authenticated else " [yellow](unauthenticated)[/yellow]"
|
|
84
|
+
self.status_row.details(
|
|
85
|
+
f"{method_definition.function_name} -> "
|
|
86
|
+
f"[dim underline]{method_definition.web_url}[/dim underline]{auth_suffix}{suffix}"
|
|
87
|
+
)
|
|
88
|
+
for custom_domain in method_definition.custom_domain_info:
|
|
89
|
+
indent = len(method_definition.function_name) * " "
|
|
90
|
+
self.status_row.details(
|
|
91
|
+
f"{indent} -> [dim underline]{custom_domain.url}[/dim underline]{auth_suffix}"
|
|
92
|
+
)
|
|
@@ -273,6 +273,38 @@ class _Server:
|
|
|
273
273
|
)
|
|
274
274
|
return server
|
|
275
275
|
|
|
276
|
+
@classmethod
|
|
277
|
+
def from_id(
|
|
278
|
+
cls: type["_Server"],
|
|
279
|
+
server_id: str,
|
|
280
|
+
*,
|
|
281
|
+
client: _Client | None = None,
|
|
282
|
+
):
|
|
283
|
+
"""Reference a Server from a deployed or running App by its ID.
|
|
284
|
+
|
|
285
|
+
This is a lazy method that defers hydrating the local
|
|
286
|
+
object with metadata from Modal servers until the first
|
|
287
|
+
time it is actually used.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
server_id: The ID of the server.
|
|
291
|
+
client: Modal client instance for this session.
|
|
292
|
+
|
|
293
|
+
Examples:
|
|
294
|
+
```python notest
|
|
295
|
+
server = modal.Server.from_id("fu-456")
|
|
296
|
+
```
|
|
297
|
+
"""
|
|
298
|
+
load_context_overrides = LoadContext(client=client)
|
|
299
|
+
|
|
300
|
+
server = _Server()
|
|
301
|
+
server._service_function = _Function._from_id(
|
|
302
|
+
server_id,
|
|
303
|
+
load_context_overrides=load_context_overrides,
|
|
304
|
+
called_from="Server",
|
|
305
|
+
)
|
|
306
|
+
return server
|
|
307
|
+
|
|
276
308
|
def _is_local(self) -> bool:
|
|
277
309
|
"""Returns True if this Server has local source code available."""
|
|
278
310
|
return self._user_cls is not None
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
-
import
|
|
2
|
+
from json import dumps
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
5
|
|
|
@@ -21,11 +21,12 @@ config_cli = ModalGroup(
|
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
@config_cli.command("show", help="Show current configuration values
|
|
24
|
+
@config_cli.command("show", help="Show current configuration values.")
|
|
25
25
|
@click.option("--redact/--no-redact", default=True, help="Redact secret credential values.")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
@click.option("--include-internal", is_flag=True, help="Include internal feature flags.", hidden=True)
|
|
27
|
+
@click.option("--json", is_flag=True, default=False)
|
|
28
|
+
def show(redact: bool, include_internal: bool, json: bool):
|
|
29
|
+
config_dict = config.to_dict(include_internal=include_internal)
|
|
29
30
|
if redact and config_dict.get("token_secret"):
|
|
30
31
|
config_dict["token_secret"] = "***"
|
|
31
32
|
if redact and config_dict.get("oauth_refresh_token"):
|
|
@@ -33,7 +34,8 @@ def show(redact: bool):
|
|
|
33
34
|
if redact and config_dict.get("oauth_client_secret"):
|
|
34
35
|
config_dict["oauth_client_secret"] = "***"
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
# Accept --json as a no-op for consistency; we may change the default formatting later
|
|
38
|
+
OutputManager.get().print_json(dumps(config_dict))
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
SET_DEFAULT_ENV_HELP = """Set the default Modal environment for the active profile
|
|
@@ -37,7 +37,14 @@ from ._stats import (
|
|
|
37
37
|
stats_style,
|
|
38
38
|
success_style,
|
|
39
39
|
)
|
|
40
|
-
from .utils import
|
|
40
|
+
from .utils import (
|
|
41
|
+
HEADER_ONLY,
|
|
42
|
+
_resolve_function_id,
|
|
43
|
+
display_table,
|
|
44
|
+
env_option,
|
|
45
|
+
grouped_utc_timestamp,
|
|
46
|
+
humanize_filesize,
|
|
47
|
+
)
|
|
41
48
|
|
|
42
49
|
function_cli = ModalGroup(name="function", help="Inspect Modal Functions.")
|
|
43
50
|
|
|
@@ -48,6 +55,46 @@ _CONTAINER_METRIC_ORDER = (
|
|
|
48
55
|
"Memory Usage (GiB)",
|
|
49
56
|
"GPU Utilization (%)",
|
|
50
57
|
)
|
|
58
|
+
_DEFAULT_CALL_TAIL = 100
|
|
59
|
+
_MAX_CALL_TAIL = 1000
|
|
60
|
+
_FAILURE_STATUSES = {
|
|
61
|
+
api_pb2.FUNCTION_CALL_INPUT_STATUS_FAILURE: "Failure",
|
|
62
|
+
api_pb2.FUNCTION_CALL_INPUT_STATUS_TIMEOUT: "Timeout",
|
|
63
|
+
api_pb2.FUNCTION_CALL_INPUT_STATUS_TERMINATED: "Terminated",
|
|
64
|
+
api_pb2.FUNCTION_CALL_INPUT_STATUS_INIT_FAILURE: "Failure",
|
|
65
|
+
api_pb2.FUNCTION_CALL_INPUT_STATUS_INTERNAL_FAILURE: "Failure",
|
|
66
|
+
api_pb2.FUNCTION_CALL_INPUT_STATUS_IDLE_TIMEOUT: "Timeout (idle)",
|
|
67
|
+
api_pb2.FUNCTION_CALL_INPUT_STATUS_MEMORY_MANAGER_EVICTION: "Evicted (over memory request)",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _optional_duration(value: float, present: bool) -> str:
|
|
72
|
+
if not present:
|
|
73
|
+
return "—"
|
|
74
|
+
return f"{value:.2f}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _function_call_status(status: api_pb2.FunctionCallInputStatus.ValueType, json_output: bool = False) -> str | int:
|
|
78
|
+
try:
|
|
79
|
+
name = api_pb2.FunctionCallInputStatus.Name(status)
|
|
80
|
+
except ValueError:
|
|
81
|
+
return status
|
|
82
|
+
label = name.removeprefix("FUNCTION_CALL_INPUT_STATUS_").replace("_", " ")
|
|
83
|
+
if json_output:
|
|
84
|
+
return label.lower()
|
|
85
|
+
return _FAILURE_STATUSES.get(status, label.title())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _function_call_status_cell(status: api_pb2.FunctionCallInputStatus.ValueType, no_color: bool = False) -> Text:
|
|
89
|
+
style = None
|
|
90
|
+
if status == api_pb2.FUNCTION_CALL_INPUT_STATUS_PENDING:
|
|
91
|
+
style = "yellow"
|
|
92
|
+
elif status == api_pb2.FUNCTION_CALL_INPUT_STATUS_RUNNING:
|
|
93
|
+
style = "green"
|
|
94
|
+
elif status in _FAILURE_STATUSES:
|
|
95
|
+
style = "red"
|
|
96
|
+
label = str(_function_call_status(status))
|
|
97
|
+
return Text(label, style=style) if style and not no_color else Text(label)
|
|
51
98
|
|
|
52
99
|
|
|
53
100
|
def _time_range_stats_json(response: api_pb2.FunctionGetTimeRangeStatsResponse) -> dict[str, object]:
|
|
@@ -307,6 +354,183 @@ async def stats(
|
|
|
307
354
|
output.print(_percentile_table(container_rows, use_color))
|
|
308
355
|
|
|
309
356
|
|
|
357
|
+
@function_cli.command("calls", no_args_is_help=True)
|
|
358
|
+
@click.argument("function_identifier", metavar="FUNCTION")
|
|
359
|
+
@click.option(
|
|
360
|
+
"-n",
|
|
361
|
+
"--tail",
|
|
362
|
+
type=click.IntRange(min=1, max=_MAX_CALL_TAIL),
|
|
363
|
+
default=_DEFAULT_CALL_TAIL,
|
|
364
|
+
show_default=True,
|
|
365
|
+
help="Show up to the last N Function inputs.",
|
|
366
|
+
)
|
|
367
|
+
@click.option(
|
|
368
|
+
"--all-variants",
|
|
369
|
+
is_flag=True,
|
|
370
|
+
default=False,
|
|
371
|
+
help="Include inputs from the base Function and all its variants.",
|
|
372
|
+
)
|
|
373
|
+
@click.option(
|
|
374
|
+
"--show-function-call-id",
|
|
375
|
+
is_flag=True,
|
|
376
|
+
default=False,
|
|
377
|
+
help="Include the Function Call ID in table output.",
|
|
378
|
+
)
|
|
379
|
+
@click.option("--json", "json_output", is_flag=True, default=False, help="Output calls as JSON.")
|
|
380
|
+
@click.option("--no-color", "no_color", is_flag=True, default=False, help="Disable colors in the output.")
|
|
381
|
+
@env_option
|
|
382
|
+
@synchronizer.create_blocking
|
|
383
|
+
async def calls(
|
|
384
|
+
function_identifier: str,
|
|
385
|
+
tail: int = _DEFAULT_CALL_TAIL,
|
|
386
|
+
all_variants: bool = False,
|
|
387
|
+
show_function_call_id: bool = False,
|
|
388
|
+
json_output: bool = False,
|
|
389
|
+
no_color: bool = False,
|
|
390
|
+
*,
|
|
391
|
+
env: str | None = None,
|
|
392
|
+
) -> None:
|
|
393
|
+
"""Show recent inputs for a Modal Function.
|
|
394
|
+
|
|
395
|
+
FUNCTION may be a Function ID or a deployed Function name in the form
|
|
396
|
+
``APP_NAME/FUNCTION_NAME``. Each unique input corresponds to one entry
|
|
397
|
+
in the output.
|
|
398
|
+
|
|
399
|
+
Examples:
|
|
400
|
+
|
|
401
|
+
```
|
|
402
|
+
modal function calls my-app/my-function
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Show recent calls across all variants of a Cls:
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
modal function calls 'my-app/MyClass.*' --all-variants --tail 500
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Disable color in the output:
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
modal function calls my-app/my-function --no-color
|
|
415
|
+
```
|
|
416
|
+
"""
|
|
417
|
+
environment_name = _get_environment_name(ensure_env(env))
|
|
418
|
+
client = await _Client.from_env()
|
|
419
|
+
function_id, _ = await _resolve_function_id(
|
|
420
|
+
client,
|
|
421
|
+
function_identifier,
|
|
422
|
+
environment_name,
|
|
423
|
+
command="calls",
|
|
424
|
+
)
|
|
425
|
+
response = await client.stub.FunctionCallFetch(
|
|
426
|
+
api_pb2.FunctionCallFetchRequest(
|
|
427
|
+
function_id=function_id,
|
|
428
|
+
tail=api_pb2.FunctionCallFetchRequest.Tail(count=tail),
|
|
429
|
+
all_variants=all_variants,
|
|
430
|
+
)
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
if json_output:
|
|
434
|
+
OutputManager.get().print_json(
|
|
435
|
+
json_lib.dumps(
|
|
436
|
+
[
|
|
437
|
+
{
|
|
438
|
+
"function_call_id": call.function_call_id,
|
|
439
|
+
"service_method_name": (
|
|
440
|
+
call.service_method_name if call.HasField("service_method_name") else None
|
|
441
|
+
),
|
|
442
|
+
"enqueued_at": (call.enqueued_at.ToJsonString() if call.HasField("enqueued_at") else None),
|
|
443
|
+
"started_at": (call.started_at.ToJsonString() if call.HasField("started_at") else None),
|
|
444
|
+
"container_id": call.container_id if call.HasField("container_id") else None,
|
|
445
|
+
"startup_time_seconds": (
|
|
446
|
+
call.startup_time_seconds if call.HasField("startup_time_seconds") else None
|
|
447
|
+
),
|
|
448
|
+
"execution_time_seconds": (
|
|
449
|
+
call.execution_time_seconds if call.HasField("execution_time_seconds") else None
|
|
450
|
+
),
|
|
451
|
+
"status": _function_call_status(call.status, True),
|
|
452
|
+
}
|
|
453
|
+
for call in response.function_call_inputs
|
|
454
|
+
]
|
|
455
|
+
)
|
|
456
|
+
)
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
show_service_method_name = bool(response.function_call_inputs) and all(
|
|
460
|
+
call.HasField("service_method_name") for call in response.function_call_inputs
|
|
461
|
+
)
|
|
462
|
+
rows: list[list[Text | str]] = []
|
|
463
|
+
previous_enqueued_date = None
|
|
464
|
+
for call in response.function_call_inputs:
|
|
465
|
+
if call.HasField("enqueued_at"):
|
|
466
|
+
enqueued_at, enqueued_date = grouped_utc_timestamp(call.enqueued_at, previous_enqueued_date)
|
|
467
|
+
previous_enqueued_date = enqueued_date
|
|
468
|
+
else:
|
|
469
|
+
enqueued_at = "—"
|
|
470
|
+
enqueued_date = None
|
|
471
|
+
|
|
472
|
+
if call.HasField("started_at") and enqueued_date:
|
|
473
|
+
started_secs = (
|
|
474
|
+
call.started_at.ToDatetime(tzinfo=timezone.utc) - call.enqueued_at.ToDatetime(tzinfo=timezone.utc)
|
|
475
|
+
).total_seconds()
|
|
476
|
+
started_at = f"{started_secs:.2f}"
|
|
477
|
+
else:
|
|
478
|
+
started_at = "—"
|
|
479
|
+
|
|
480
|
+
row: list[Text | str] = [Text(enqueued_at), Text(started_at)]
|
|
481
|
+
if show_function_call_id:
|
|
482
|
+
row.append(Text(call.function_call_id or "—"))
|
|
483
|
+
if show_service_method_name:
|
|
484
|
+
row.append(Text(call.service_method_name))
|
|
485
|
+
row.extend(
|
|
486
|
+
[
|
|
487
|
+
Text(
|
|
488
|
+
call.container_id if call.HasField("container_id") else "—",
|
|
489
|
+
),
|
|
490
|
+
Text(
|
|
491
|
+
_optional_duration(call.execution_time_seconds, call.HasField("execution_time_seconds")),
|
|
492
|
+
),
|
|
493
|
+
_function_call_status_cell(call.status, no_color=no_color),
|
|
494
|
+
]
|
|
495
|
+
)
|
|
496
|
+
rows.append(row)
|
|
497
|
+
|
|
498
|
+
title = f"Function calls for {function_id}"
|
|
499
|
+
if all_variants:
|
|
500
|
+
title += " · all variants"
|
|
501
|
+
output = OutputManager.get()
|
|
502
|
+
output.print("")
|
|
503
|
+
output.print(Text(title))
|
|
504
|
+
output.print(
|
|
505
|
+
Text(
|
|
506
|
+
f"Displaying {len(rows):,} {'row' if len(rows) == 1 else 'rows'}",
|
|
507
|
+
style=STATS_METADATA_STYLE if not no_color else "",
|
|
508
|
+
)
|
|
509
|
+
)
|
|
510
|
+
output.print("")
|
|
511
|
+
columns: list[str | Column] = [
|
|
512
|
+
Column("Enqueued (UTC)", no_wrap=True, justify="right"),
|
|
513
|
+
Column("Queue Time (s)", justify="right"),
|
|
514
|
+
]
|
|
515
|
+
if show_function_call_id:
|
|
516
|
+
columns.append("Function Call ID")
|
|
517
|
+
if show_service_method_name:
|
|
518
|
+
columns.append("Method")
|
|
519
|
+
columns.extend(
|
|
520
|
+
[
|
|
521
|
+
Column("Container", width=29),
|
|
522
|
+
Column("Execution (s)", justify="right"),
|
|
523
|
+
"Status",
|
|
524
|
+
]
|
|
525
|
+
)
|
|
526
|
+
display_table(
|
|
527
|
+
columns,
|
|
528
|
+
rows,
|
|
529
|
+
table_box=HEADER_ONLY,
|
|
530
|
+
border_style=STATS_METADATA_STYLE if not no_color else "",
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
|
|
310
534
|
@function_cli.command("logs", no_args_is_help=True)
|
|
311
535
|
@click.argument("function_ref")
|
|
312
536
|
@click.option("-f", "--follow", is_flag=True, default=False, help="Stream log output until interrupted")
|