modal 1.5.6.dev13__tar.gz → 1.5.6.dev14__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.dev13 → modal-1.5.6.dev14}/PKG-INFO +1 -1
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_functions.py +49 -5
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/_logs.py +0 -7
- modal-1.5.6.dev14/modal/cli/_stats.py +141 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/function.py +24 -137
- modal-1.5.6.dev14/modal/cli/server.py +519 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/utils.py +85 -10
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/client.pyi +2 -2
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/config.py +4 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/functions.pyi +64 -12
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/sandbox_fs.py +62 -54
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/sandbox_fs.pyi +182 -158
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/types.py +61 -2
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/volume.py +101 -48
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/volume.pyi +16 -2
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_grpc.py +16 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_pb2.py +1523 -1396
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_pb2.pyi +284 -20
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_pb2_grpc.py +34 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_pb2_grpc.pyi +12 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_version/__init__.py +1 -1
- modal-1.5.6.dev13/modal/cli/_stats.py +0 -50
- modal-1.5.6.dev13/modal/cli/server.py +0 -136
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/LICENSE +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/README.md +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/__main__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_billing.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_clustered_functions.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_environments.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_function_variants.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_grpc_client.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_image.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_ipython.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_load_context.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_location.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_logs.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_logs_manager.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_object.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/manager.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/pty.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/rich.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/status.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_partial_function.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_resolver.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_resources.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_serialization.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_server.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_supports_logs.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_traceback.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_tunnel.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_tunnel.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_type_manager.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/logger.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/version.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_watcher.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_workspace.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/app.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/app.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/billing.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/billing.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/README.md +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/base-images.json +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/call_graph.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/_download.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/_help.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/_traceback.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/app.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/billing.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/changelog.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/cluster.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/config.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/container.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/curl.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/dashboard.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/dict.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/endpoint.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/entry_point.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/environment.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/image.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/import_refs.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/launch.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/logo.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/profile.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/queues.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/run.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/secret.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/selector.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/shell.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/skills.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/token.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/volume.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/workspace.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/client.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cls.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cls.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/container_process.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/container_process.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/dict.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/dict.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/environments.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/environments.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/exception.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/experimental/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/experimental/flash.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/experimental/ipython.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/functions.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/image.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/image.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/io_streams.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/io_streams.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/mount.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/mount.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/network_file_system.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/network_file_system.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/object.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/object.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/output.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/parallel_map.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/parallel_map.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/partial_function.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/partial_function.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/proxy.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/proxy.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/py.typed +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/queue.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/queue.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/retries.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/runner.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/runner.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/running_app.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/sandbox.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/sandbox.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/schedule.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/scheduler_placement.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/secret.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/secret.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/server.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/server.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/serving.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/serving.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/snapshot.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/snapshot.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/stream_type.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/token_flow.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/token_flow.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/workspace.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/workspace.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/__init__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/py.typed +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_version/__main__.py +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/pyproject.toml +0 -0
- {modal-1.5.6.dev13 → modal-1.5.6.dev14}/setup.cfg +0 -0
|
@@ -8,6 +8,7 @@ import warnings
|
|
|
8
8
|
from collections import OrderedDict
|
|
9
9
|
from collections.abc import AsyncGenerator, AsyncIterator, Callable, Collection, Sequence
|
|
10
10
|
from dataclasses import dataclass
|
|
11
|
+
from datetime import datetime, timedelta, timezone
|
|
11
12
|
from pathlib import PurePosixPath
|
|
12
13
|
from typing import TYPE_CHECKING, Any
|
|
13
14
|
|
|
@@ -99,7 +100,12 @@ from .proxy import _Proxy
|
|
|
99
100
|
from .retries import Retries, RetryManager
|
|
100
101
|
from .schedule import Schedule
|
|
101
102
|
from .secret import _Secret
|
|
102
|
-
from .types import
|
|
103
|
+
from .types import (
|
|
104
|
+
FunctionAutoscalerSettings,
|
|
105
|
+
FunctionCurrentStats,
|
|
106
|
+
FunctionStats,
|
|
107
|
+
ServerAutoscalerSettings,
|
|
108
|
+
)
|
|
103
109
|
from .volume import _Volume, _volume_to_mount_proto
|
|
104
110
|
|
|
105
111
|
if TYPE_CHECKING:
|
|
@@ -2148,23 +2154,61 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
2148
2154
|
return self._raw_f
|
|
2149
2155
|
|
|
2150
2156
|
@live_method
|
|
2151
|
-
async def get_current_stats(self) ->
|
|
2152
|
-
"""Return a
|
|
2157
|
+
async def get_current_stats(self) -> FunctionCurrentStats:
|
|
2158
|
+
"""Return a snapshot of the Function's current input and container state.
|
|
2153
2159
|
|
|
2154
2160
|
Returns:
|
|
2155
|
-
|
|
2161
|
+
A `FunctionCurrentStats` object containing live input and container counts.
|
|
2156
2162
|
"""
|
|
2157
2163
|
resp = await self.client.stub.FunctionGetCurrentStats(
|
|
2158
2164
|
api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
|
|
2159
2165
|
retry=Retry(total_timeout=10.0),
|
|
2160
2166
|
)
|
|
2161
|
-
return
|
|
2167
|
+
return FunctionCurrentStats(
|
|
2162
2168
|
backlog=resp.backlog,
|
|
2163
2169
|
num_total_runners=resp.num_total_tasks,
|
|
2164
2170
|
num_running_inputs=resp.num_running_inputs,
|
|
2165
2171
|
input_headroom=resp.input_headroom,
|
|
2166
2172
|
)
|
|
2167
2173
|
|
|
2174
|
+
@live_method
|
|
2175
|
+
async def stats(
|
|
2176
|
+
self, *, since: datetime | None = None, until: datetime | None = None, container: str | None = None
|
|
2177
|
+
) -> FunctionStats:
|
|
2178
|
+
"""Return statistics for a modal Function.
|
|
2179
|
+
|
|
2180
|
+
The default time range is the most recent hour. The maximum time range is 7 days.
|
|
2181
|
+
|
|
2182
|
+
Args:
|
|
2183
|
+
since: The beginning of the time range, inclusive. If omitted, this defaults to an hour before `until`.
|
|
2184
|
+
Values without a timezone are interpeted as local time.
|
|
2185
|
+
until: The end of the time range, exclusive. If omitted, this defaults to current time.
|
|
2186
|
+
Values without a timezone are interpeted as local time.
|
|
2187
|
+
container: If passed in, the stats are computed for only this container. Default None.
|
|
2188
|
+
|
|
2189
|
+
Returns:
|
|
2190
|
+
A `FunctionStats` object
|
|
2191
|
+
"""
|
|
2192
|
+
until = until or datetime.now(timezone.utc)
|
|
2193
|
+
if until.tzinfo is None:
|
|
2194
|
+
until = until.astimezone()
|
|
2195
|
+
until = until.astimezone(timezone.utc)
|
|
2196
|
+
|
|
2197
|
+
since = since or until - timedelta(hours=1)
|
|
2198
|
+
if since.tzinfo is None:
|
|
2199
|
+
since = since.astimezone()
|
|
2200
|
+
since = since.astimezone(timezone.utc)
|
|
2201
|
+
if since >= until:
|
|
2202
|
+
raise InvalidError("`since` must be before `until`.")
|
|
2203
|
+
|
|
2204
|
+
historical_request = api_pb2.FunctionGetTimeRangeStatsRequest(function_id=self.object_id)
|
|
2205
|
+
historical_request.since.FromDatetime(since)
|
|
2206
|
+
historical_request.until.FromDatetime(until)
|
|
2207
|
+
if container:
|
|
2208
|
+
historical_request.container_id = container
|
|
2209
|
+
stats = await self.client.stub.FunctionGetTimeRangeStats(historical_request)
|
|
2210
|
+
return FunctionStats._from_proto(stats)
|
|
2211
|
+
|
|
2168
2212
|
@live_method
|
|
2169
2213
|
async def _get_schema(self) -> api_pb2.FunctionSchema:
|
|
2170
2214
|
"""Returns recorded schema for function, internal use only for now"""
|
|
@@ -5,7 +5,6 @@ from click import UsageError
|
|
|
5
5
|
|
|
6
6
|
from modal._logs import _FETCH_LIMIT, _MAX_FETCH_RANGE, LogsFilters
|
|
7
7
|
from modal._utils.time_utils import locale_tz, parse_duration
|
|
8
|
-
from modal.client import _Client
|
|
9
8
|
from modal_proto import api_pb2
|
|
10
9
|
|
|
11
10
|
from .utils import _fetch_app_logs, _stream_app_logs, _tail_app_logs
|
|
@@ -19,12 +18,6 @@ _SOURCE_OPTIONS = {
|
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
async def _get_app_id_for_function_id(function_id: str) -> str:
|
|
23
|
-
client = await _Client.from_env()
|
|
24
|
-
response = await client.stub.FunctionGetById(api_pb2.FunctionGetByIdRequest(function_id=function_id))
|
|
25
|
-
return response.handle_metadata.app_id
|
|
26
|
-
|
|
27
|
-
|
|
28
21
|
def _parse_time_arg(value: str | None, default: datetime) -> datetime:
|
|
29
22
|
"""Parse a time argument that can be a relative duration (e.g. '2h', '30m') or ISO 8601 datetime.
|
|
30
23
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
"""Shared helpers for stats CLI commands."""
|
|
3
|
+
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
|
|
6
|
+
from google.protobuf.timestamp_pb2 import Timestamp
|
|
7
|
+
from rich.style import Style
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
from modal_proto import api_pb2
|
|
12
|
+
|
|
13
|
+
STATS_HEADING_STYLE = "white"
|
|
14
|
+
STATS_METADATA_STYLE = "bright_black"
|
|
15
|
+
STATS_SECTION_STYLE = "bold white"
|
|
16
|
+
STATS_SUCCESS_STYLE = "green"
|
|
17
|
+
STATS_PROGRESS_STYLE = "cyan"
|
|
18
|
+
STATS_WARNING_STYLE = "yellow"
|
|
19
|
+
STATS_PROBLEM_STYLE = "red"
|
|
20
|
+
|
|
21
|
+
_HIGH_PROBLEM_RATE = 0.05
|
|
22
|
+
|
|
23
|
+
_DEFAULT_STATS_WINDOW = timedelta(hours=1)
|
|
24
|
+
_DEFAULT_METRIC_PRECISION = 2
|
|
25
|
+
_DISPLAYED_PERCENTILES = ((5000, "p50"), (9000, "p90"), (9900, "p99"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def stats_style(style: str, use_color: bool) -> str | Style:
|
|
29
|
+
if use_color:
|
|
30
|
+
return style
|
|
31
|
+
if style == STATS_SECTION_STYLE:
|
|
32
|
+
return "bold"
|
|
33
|
+
return Style()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def success_style(count: int, total: int, use_color: bool) -> str | None:
|
|
37
|
+
if use_color and total and count / total > 0.5:
|
|
38
|
+
return STATS_SUCCESS_STYLE
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def progress_style(count: int, use_color: bool) -> str | None:
|
|
43
|
+
if use_color and count:
|
|
44
|
+
return STATS_PROGRESS_STYLE
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def problem_style(count: int, total: int, use_color: bool) -> str | None:
|
|
49
|
+
if not use_color or count == 0:
|
|
50
|
+
return None
|
|
51
|
+
if not total or count / total >= _HIGH_PROBLEM_RATE:
|
|
52
|
+
return STATS_PROBLEM_STYLE
|
|
53
|
+
return STATS_WARNING_STYLE
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def percentile_style(p50: float | None, p99: float | None, use_color: bool) -> str | Style:
|
|
57
|
+
if not use_color or p50 is None or p99 is None or p99 <= 0:
|
|
58
|
+
return Style()
|
|
59
|
+
if p50 == 0 or p99 >= 2 * p50:
|
|
60
|
+
return STATS_WARNING_STYLE
|
|
61
|
+
return Style()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _count_with_percentage(count: int, total: int, label: str) -> str:
|
|
65
|
+
percentage = count / total if total else 0
|
|
66
|
+
return f"{count:,} {label} ({percentage:.1%})"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _timestamp(value: datetime) -> Timestamp:
|
|
70
|
+
timestamp = Timestamp()
|
|
71
|
+
timestamp.FromDatetime(value.astimezone(timezone.utc))
|
|
72
|
+
return timestamp
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _percentile_table(rows: list[tuple[Text, Text, Text, Text]], use_color: bool) -> Table:
|
|
76
|
+
table = Table(box=None, pad_edge=False, padding=(0, 2), header_style="")
|
|
77
|
+
table.add_column("", min_width=30)
|
|
78
|
+
percentile_header_style = stats_style(STATS_METADATA_STYLE, use_color)
|
|
79
|
+
table.add_column(Text("p50", style=percentile_header_style), justify="right", min_width=8)
|
|
80
|
+
table.add_column(Text("p90", style=percentile_header_style), justify="right", min_width=8)
|
|
81
|
+
table.add_column(Text("p99", style=percentile_header_style), justify="right", min_width=8)
|
|
82
|
+
for row in rows:
|
|
83
|
+
table.add_row(*row)
|
|
84
|
+
return table
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _percentile_name(percentile_basis_points: int) -> str:
|
|
88
|
+
return f"p{percentile_basis_points / 100:g}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _distribution_json(distribution: api_pb2.StatsPercentileDistribution) -> dict[str, object]:
|
|
92
|
+
return {
|
|
93
|
+
"unit": distribution.unit,
|
|
94
|
+
"percentiles": {
|
|
95
|
+
_percentile_name(percentile.percentile_basis_points): percentile.value
|
|
96
|
+
for percentile in distribution.percentiles
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _distribution_map_json(
|
|
102
|
+
distributions: dict[str, api_pb2.StatsPercentileDistribution],
|
|
103
|
+
) -> dict[str, object]:
|
|
104
|
+
return {name: _distribution_json(distribution) for name, distribution in distributions.items()}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _metric_rows(
|
|
108
|
+
distributions: dict[str, api_pb2.StatsPercentileDistribution],
|
|
109
|
+
expected_order: tuple[str, ...],
|
|
110
|
+
use_color: bool,
|
|
111
|
+
) -> list[tuple[Text, Text, Text, Text]]:
|
|
112
|
+
rows: list[tuple[Text, Text, Text, Text]] = []
|
|
113
|
+
ordered_names = [name for name in expected_order if name in distributions]
|
|
114
|
+
ordered_names.extend(sorted(set(distributions) - set(expected_order)))
|
|
115
|
+
for name in ordered_names:
|
|
116
|
+
distribution = distributions[name]
|
|
117
|
+
values_by_percentile = {
|
|
118
|
+
percentile.percentile_basis_points: percentile.value for percentile in distribution.percentiles
|
|
119
|
+
}
|
|
120
|
+
if not any(basis_points in values_by_percentile for basis_points, _ in _DISPLAYED_PERCENTILES):
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
values = [
|
|
124
|
+
(
|
|
125
|
+
f"{values_by_percentile[basis_points]:.{_DEFAULT_METRIC_PRECISION}f}"
|
|
126
|
+
if basis_points in values_by_percentile
|
|
127
|
+
else "—"
|
|
128
|
+
)
|
|
129
|
+
for basis_points, _ in _DISPLAYED_PERCENTILES
|
|
130
|
+
]
|
|
131
|
+
p50 = values_by_percentile.get(5000)
|
|
132
|
+
p99 = values_by_percentile.get(9900)
|
|
133
|
+
rows.append(
|
|
134
|
+
(
|
|
135
|
+
Text(f" {name}", style=stats_style(STATS_METADATA_STYLE, use_color)),
|
|
136
|
+
Text(values[0]),
|
|
137
|
+
Text(values[1]),
|
|
138
|
+
Text(values[2], style=percentile_style(p50, p99, use_color)),
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return rows
|
|
@@ -5,41 +5,37 @@ from typing import cast
|
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
7
|
from click import UsageError
|
|
8
|
-
from google.protobuf.timestamp_pb2 import Timestamp
|
|
9
|
-
from rich.style import Style
|
|
10
|
-
from rich.table import Table
|
|
11
8
|
from rich.text import Text
|
|
12
9
|
|
|
13
10
|
from modal._environments import ensure_env
|
|
14
11
|
from modal._object import _get_environment_name
|
|
15
12
|
from modal._utils.async_utils import synchronizer
|
|
16
|
-
from modal._utils.grpc_utils import is_class_function_lookup_error
|
|
17
13
|
from modal._utils.time_utils import parse_duration
|
|
18
14
|
from modal.client import _Client
|
|
19
|
-
from modal.exception import NotFoundError
|
|
20
|
-
from modal.functions import Function
|
|
21
15
|
from modal.output import OutputManager
|
|
22
16
|
from modal_proto import api_pb2
|
|
23
17
|
|
|
24
18
|
from ._help import ModalGroup
|
|
25
|
-
from ._logs import
|
|
19
|
+
from ._logs import _parse_time_arg, _run_logs_command, _validate_logs_args
|
|
26
20
|
from ._stats import (
|
|
21
|
+
_DEFAULT_STATS_WINDOW,
|
|
27
22
|
STATS_HEADING_STYLE,
|
|
28
23
|
STATS_METADATA_STYLE,
|
|
29
24
|
STATS_SECTION_STYLE,
|
|
30
|
-
|
|
25
|
+
_count_with_percentage,
|
|
26
|
+
_distribution_map_json,
|
|
27
|
+
_metric_rows,
|
|
28
|
+
_percentile_table,
|
|
29
|
+
_timestamp,
|
|
31
30
|
problem_style,
|
|
32
31
|
progress_style,
|
|
33
32
|
stats_style,
|
|
34
33
|
success_style,
|
|
35
34
|
)
|
|
36
|
-
from .utils import
|
|
35
|
+
from .utils import _resolve_function_id, env_option
|
|
37
36
|
|
|
38
37
|
function_cli = ModalGroup(name="function", help="Inspect Modal Functions.")
|
|
39
38
|
|
|
40
|
-
_DEFAULT_STATS_WINDOW = timedelta(hours=1)
|
|
41
|
-
_DEFAULT_METRIC_PRECISION = 2
|
|
42
|
-
_DISPLAYED_PERCENTILES = ((5000, "p50"), (9000, "p90"), (9900, "p99"))
|
|
43
39
|
_INPUT_METRIC_ORDER = ("Execution time (s)", "End-to-end latency (s)")
|
|
44
40
|
_CONTAINER_METRIC_ORDER = (
|
|
45
41
|
"Startup time (s)",
|
|
@@ -49,83 +45,6 @@ _CONTAINER_METRIC_ORDER = (
|
|
|
49
45
|
)
|
|
50
46
|
|
|
51
47
|
|
|
52
|
-
async def _resolve_function_id(client: _Client, function_identifier: str, environment_name: str) -> str:
|
|
53
|
-
if function_identifier == "":
|
|
54
|
-
raise UsageError("FUNCTION must be a Function ID (fu-…) or a deployed Function name (APP_NAME/FUNCTION_NAME).")
|
|
55
|
-
|
|
56
|
-
if "/" in function_identifier:
|
|
57
|
-
app_name, function_name = function_identifier.split("/", 1)
|
|
58
|
-
|
|
59
|
-
if not (app_name and function_name):
|
|
60
|
-
raise UsageError(
|
|
61
|
-
"FUNCTION must be a Function ID (fu-…) or a deployed Function name (APP_NAME/FUNCTION_NAME)."
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
response = await client.stub.FunctionGet(
|
|
66
|
-
api_pb2.FunctionGetRequest(
|
|
67
|
-
app_name=app_name,
|
|
68
|
-
object_tag=function_name,
|
|
69
|
-
environment_name=environment_name,
|
|
70
|
-
)
|
|
71
|
-
)
|
|
72
|
-
except NotFoundError as exc:
|
|
73
|
-
if is_class_function_lookup_error(exc):
|
|
74
|
-
raise NotFoundError(
|
|
75
|
-
f"'{function_identifier}' is a modal.Cls. Use\n modal function stats '{function_identifier}.*'"
|
|
76
|
-
) from None
|
|
77
|
-
raise
|
|
78
|
-
return response.function_id
|
|
79
|
-
|
|
80
|
-
if function_identifier.startswith("fu-"):
|
|
81
|
-
return function_identifier
|
|
82
|
-
|
|
83
|
-
raise UsageError("FUNCTION must be a Function ID (fu-…) or a deployed Function name (APP_NAME/FUNCTION_NAME).")
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _timestamp(value: datetime) -> Timestamp:
|
|
87
|
-
timestamp = Timestamp()
|
|
88
|
-
timestamp.FromDatetime(value.astimezone(timezone.utc))
|
|
89
|
-
return timestamp
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def _percentile_table(rows: list[tuple[Text, Text, Text, Text]], use_color: bool) -> Table:
|
|
93
|
-
table = Table(box=None, pad_edge=False, padding=(0, 2), header_style="")
|
|
94
|
-
table.add_column("", min_width=30)
|
|
95
|
-
percentile_header_style = stats_style(STATS_METADATA_STYLE, use_color)
|
|
96
|
-
table.add_column(Text("p50", style=percentile_header_style), justify="right", min_width=8)
|
|
97
|
-
table.add_column(Text("p90", style=percentile_header_style), justify="right", min_width=8)
|
|
98
|
-
table.add_column(Text("p99", style=percentile_header_style), justify="right", min_width=8)
|
|
99
|
-
for row in rows:
|
|
100
|
-
table.add_row(*row)
|
|
101
|
-
return table
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def _count_with_percentage(count: int, total: int, label: str) -> str:
|
|
105
|
-
percentage = count / total if total else 0
|
|
106
|
-
return f"{count:,} {label} ({percentage:.1%})"
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _percentile_name(percentile_basis_points: int) -> str:
|
|
110
|
-
return f"p{percentile_basis_points / 100:g}"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _distribution_json(distribution: api_pb2.StatsPercentileDistribution) -> dict[str, object]:
|
|
114
|
-
return {
|
|
115
|
-
"unit": distribution.unit,
|
|
116
|
-
"percentiles": {
|
|
117
|
-
_percentile_name(percentile.percentile_basis_points): percentile.value
|
|
118
|
-
for percentile in distribution.percentiles
|
|
119
|
-
},
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _distribution_map_json(
|
|
124
|
-
distributions: dict[str, api_pb2.StatsPercentileDistribution],
|
|
125
|
-
) -> dict[str, object]:
|
|
126
|
-
return {name: _distribution_json(distribution) for name, distribution in distributions.items()}
|
|
127
|
-
|
|
128
|
-
|
|
129
48
|
def _time_range_stats_json(response: api_pb2.FunctionGetTimeRangeStatsResponse) -> dict[str, object]:
|
|
130
49
|
return {
|
|
131
50
|
"since": response.since.ToDatetime(tzinfo=timezone.utc).isoformat(),
|
|
@@ -159,53 +78,24 @@ def _stats_json(
|
|
|
159
78
|
}
|
|
160
79
|
|
|
161
80
|
|
|
162
|
-
def _metric_rows(
|
|
163
|
-
distributions: dict[str, api_pb2.StatsPercentileDistribution],
|
|
164
|
-
expected_order: tuple[str, ...],
|
|
165
|
-
use_color: bool,
|
|
166
|
-
) -> list[tuple[Text, Text, Text, Text]]:
|
|
167
|
-
rows: list[tuple[Text, Text, Text, Text]] = []
|
|
168
|
-
ordered_names = [name for name in expected_order if name in distributions]
|
|
169
|
-
ordered_names.extend(sorted(set(distributions) - set(expected_order)))
|
|
170
|
-
for name in ordered_names:
|
|
171
|
-
distribution = distributions[name]
|
|
172
|
-
values_by_percentile = {
|
|
173
|
-
percentile.percentile_basis_points: percentile.value for percentile in distribution.percentiles
|
|
174
|
-
}
|
|
175
|
-
if not any(basis_points in values_by_percentile for basis_points, _ in _DISPLAYED_PERCENTILES):
|
|
176
|
-
continue
|
|
177
|
-
|
|
178
|
-
p50 = values_by_percentile.get(5000)
|
|
179
|
-
p99 = values_by_percentile.get(9900)
|
|
180
|
-
values = []
|
|
181
|
-
for basis_points, _ in _DISPLAYED_PERCENTILES:
|
|
182
|
-
value = values_by_percentile.get(basis_points)
|
|
183
|
-
value_text = "—" if value is None else f"{value:.{_DEFAULT_METRIC_PRECISION}f}"
|
|
184
|
-
style = percentile_style(p50, p99, use_color) if basis_points == 9900 else Style()
|
|
185
|
-
values.append(Text(value_text, style=style))
|
|
186
|
-
rows.append(
|
|
187
|
-
(
|
|
188
|
-
Text(f" {name}", style=stats_style(STATS_METADATA_STYLE, use_color)),
|
|
189
|
-
values[0],
|
|
190
|
-
values[1],
|
|
191
|
-
values[2],
|
|
192
|
-
)
|
|
193
|
-
)
|
|
194
|
-
return rows
|
|
195
|
-
|
|
196
|
-
|
|
197
81
|
@function_cli.command("stats", no_args_is_help=True)
|
|
198
82
|
@click.argument("function_identifier", metavar="FUNCTION")
|
|
199
83
|
@click.option(
|
|
200
84
|
"--since",
|
|
201
85
|
default=None,
|
|
202
|
-
help=
|
|
203
|
-
|
|
86
|
+
help=(
|
|
87
|
+
"Start of time range. Treated as local time "
|
|
88
|
+
"if a timezone is not supplied."
|
|
89
|
+
"Accepts an ISO 8601 datetime or relative time such as '2h' or '30m'."
|
|
90
|
+
),
|
|
204
91
|
)
|
|
205
92
|
@click.option(
|
|
206
93
|
"--until",
|
|
207
94
|
default=None,
|
|
208
|
-
help=
|
|
95
|
+
help=(
|
|
96
|
+
"End of time range. Treated as local time if a "
|
|
97
|
+
"timezone is not supplied. Accepts the same argument types as --since."
|
|
98
|
+
),
|
|
209
99
|
)
|
|
210
100
|
@click.option(
|
|
211
101
|
"--all-variants",
|
|
@@ -303,7 +193,9 @@ async def stats(
|
|
|
303
193
|
|
|
304
194
|
environment_name = _get_environment_name(ensure_env(env))
|
|
305
195
|
client = await _Client.from_env()
|
|
306
|
-
function_id = await _resolve_function_id(
|
|
196
|
+
function_id = await _resolve_function_id(
|
|
197
|
+
client, function_identifier, environment_name, command="stats", include_app_id=False
|
|
198
|
+
)
|
|
307
199
|
req = api_pb2.FunctionGetTimeRangeStatsRequest(
|
|
308
200
|
function_id=function_id,
|
|
309
201
|
since=_timestamp(since_dt),
|
|
@@ -507,15 +399,10 @@ async def logs(
|
|
|
507
399
|
env = ensure_env(env)
|
|
508
400
|
_validate_logs_args(follow=follow, since=since, until=until, tail=tail)
|
|
509
401
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
app_name, function_name = _parse_function_or_server_ref(function_ref, "Function")
|
|
515
|
-
function = Function.from_name(app_name, function_name, environment_name=env)
|
|
516
|
-
query_data = await function._get_log_query_data.aio()
|
|
517
|
-
function_id = query_data.source_object_id
|
|
518
|
-
app_id = query_data.app_id
|
|
402
|
+
client = await _Client.from_env()
|
|
403
|
+
function_id, app_id = await _resolve_function_id(
|
|
404
|
+
client, function_ref, env, object_type="Function", command="logs", include_app_id=True
|
|
405
|
+
)
|
|
519
406
|
|
|
520
407
|
prefix_fields: list[str] = []
|
|
521
408
|
if show_function_id:
|