modAL 1.3.4__tar.gz → 1.3.5.dev0__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.3.4 → modal-1.3.5.dev0}/PKG-INFO +1 -3
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_billing.py +7 -3
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/async_utils.py +71 -67
- modal-1.3.5.dev0/modal/_vendor/version.py +844 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/billing.py +2 -1
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/billing.py +12 -7
- modal-1.3.5.dev0/modal/cli/changelog.py +157 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/entry_point.py +2 -0
- modal-1.3.5.dev0/modal/cli/selector.py +124 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/client.pyi +10 -2
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/queue.py +3 -3
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/queue.pyi +7 -7
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/secret.py +18 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/secret.pyi +27 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal.egg-info/PKG-INFO +1 -3
- {modal-1.3.4 → modal-1.3.5.dev0}/modal.egg-info/SOURCES.txt +3 -3
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_docs/mdmd/mdmd.py +41 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/api_grpc.py +16 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/api_pb2.py +375 -354
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/api_pb2.pyi +44 -1
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_version/__init__.py +1 -1
- modal-1.3.4/LICENSE +0 -176
- modal-1.3.4/modal_proto/api.proto +0 -3968
- modal-1.3.4/modal_proto/task_command_router.proto +0 -164
- {modal-1.3.4 → modal-1.3.5.dev0}/README.md +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/__main__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_functions.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_grpc_client.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_ipython.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_load_context.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_location.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_object.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_output/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_output/manager.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_output/pty.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_output/rich.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_output/status.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_partial_function.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_resolver.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_resources.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_serialization.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_server.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_traceback.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_tunnel.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_type_manager.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/_watcher.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/app.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/app.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/builder/README.md +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/call_graph.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/_download.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/app.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/config.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/container.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/dashboard.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/dict.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/environment.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/launch.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/profile.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/queues.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/run.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/secret.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/shell.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/token.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/utils.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cli/volume.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/client.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cls.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/cls.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/config.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/container_process.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/container_process.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/dict.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/dict.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/environments.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/environments.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/exception.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/experimental/flash.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/experimental/flash.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/file_io.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/file_io.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/functions.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/functions.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/gpu.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/image.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/image.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/io_streams.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/mount.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/mount.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/network_file_system.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/object.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/object.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/output.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/parallel_map.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/partial_function.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/proxy.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/proxy.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/py.typed +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/retries.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/runner.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/runner.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/running_app.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/sandbox.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/sandbox.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/schedule.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/server.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/server.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/serving.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/serving.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/snapshot.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/stream_type.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/token_flow.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/volume.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal/volume.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/py.typed +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/modal_version/__main__.py +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/pyproject.toml +0 -0
- {modal-1.3.4 → modal-1.3.5.dev0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.5.dev0
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -15,7 +15,6 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Requires-Python: <3.15,>=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
License-File: LICENSE
|
|
19
18
|
Requires-Dist: aiohttp
|
|
20
19
|
Requires-Dist: cbor2
|
|
21
20
|
Requires-Dist: certifi
|
|
@@ -31,7 +30,6 @@ Requires-Dist: types-certifi
|
|
|
31
30
|
Requires-Dist: types-toml
|
|
32
31
|
Requires-Dist: watchfiles
|
|
33
32
|
Requires-Dist: typing_extensions~=4.6
|
|
34
|
-
Dynamic: license-file
|
|
35
33
|
|
|
36
34
|
# Modal Python SDK
|
|
37
35
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
3
|
from decimal import Decimal
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Optional, TypedDict
|
|
5
5
|
|
|
6
6
|
from modal_proto import api_pb2
|
|
7
7
|
|
|
@@ -25,7 +25,7 @@ async def _workspace_billing_report(
|
|
|
25
25
|
resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
|
|
26
26
|
tag_names: Optional[list[str]] = None, # Optional additional metadata to include
|
|
27
27
|
client: Optional[_Client] = None,
|
|
28
|
-
) -> list[
|
|
28
|
+
) -> list[WorkspaceBillingReportItem]:
|
|
29
29
|
"""Generate a tabular report of workspace usage by object and time.
|
|
30
30
|
|
|
31
31
|
The result will be a list of dictionaries for each interval (determined by `resolution`)
|
|
@@ -49,6 +49,10 @@ async def _workspace_billing_report(
|
|
|
49
49
|
minutes, although there may be collection delays. If completeness is important for your use
|
|
50
50
|
case, we recommend leaving a buffer after the end of the query interval.
|
|
51
51
|
|
|
52
|
+
It's also possible to generate reports using the
|
|
53
|
+
[`modal billing report`](https://modal.com/docs/reference/cli/billing) CLI command. The CLI
|
|
54
|
+
has a few convenience features for generating reports across relative time ranges.
|
|
55
|
+
|
|
52
56
|
"""
|
|
53
57
|
if client is None:
|
|
54
58
|
client = await _Client.from_env()
|
|
@@ -77,7 +81,7 @@ async def _workspace_billing_report(
|
|
|
77
81
|
|
|
78
82
|
rows = []
|
|
79
83
|
async for pb_item in client.stub.WorkspaceBillingReport.unary_stream(request):
|
|
80
|
-
item = {
|
|
84
|
+
item: WorkspaceBillingReportItem = {
|
|
81
85
|
"object_id": pb_item.object_id,
|
|
82
86
|
"description": pb_item.description,
|
|
83
87
|
"environment_name": pb_item.environment_name,
|
|
@@ -49,6 +49,10 @@ def rewrite_sync_to_async(code_line: str, original_func: Callable) -> tuple[bool
|
|
|
49
49
|
"""
|
|
50
50
|
Rewrite a blocking call to use async/await syntax.
|
|
51
51
|
|
|
52
|
+
Uses Python's ast module for method calls and property access to reliably
|
|
53
|
+
parse complex expressions (e.g. chained calls like SomeClass().method.spawn(q))
|
|
54
|
+
and determine exact positions for inserting `await` and `.aio`.
|
|
55
|
+
|
|
52
56
|
Handles four patterns:
|
|
53
57
|
1. __aiter__: for x in obj -> async for x in obj
|
|
54
58
|
2. __aenter__: with obj as x -> async with obj as x
|
|
@@ -64,6 +68,7 @@ def rewrite_sync_to_async(code_line: str, original_func: Callable) -> tuple[bool
|
|
|
64
68
|
- success: True if the pattern was found and rewritten, False if falling back to generic
|
|
65
69
|
- rewritten_code: The rewritten code or a generic suggestion
|
|
66
70
|
"""
|
|
71
|
+
import ast
|
|
67
72
|
import re
|
|
68
73
|
|
|
69
74
|
func_name = original_func.__name__ # type: ignore
|
|
@@ -118,83 +123,82 @@ def rewrite_sync_to_async(code_line: str, original_func: Callable) -> tuple[bool
|
|
|
118
123
|
suggestion = code_line.replace("for ", "async for ", 1)
|
|
119
124
|
return (True, suggestion)
|
|
120
125
|
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if property_match:
|
|
127
|
-
# This is a property access, rewrite to use await without .aio()
|
|
128
|
-
# Find the start of the expression (skip statement keywords and assignments)
|
|
129
|
-
statement_start = 0
|
|
130
|
-
prefix_match = re.match(r"^(\s*(?:\w+\s*=|return|yield|raise)\s+)", code_line)
|
|
131
|
-
if prefix_match:
|
|
132
|
-
statement_start = len(prefix_match.group(1))
|
|
126
|
+
# Use Python's ast module for method calls and property access.
|
|
127
|
+
# This correctly handles complex expressions like SomeClass().method.spawn(q)
|
|
128
|
+
# without needing fragile backward-walking or regex matching.
|
|
129
|
+
stripped = code_line.lstrip()
|
|
130
|
+
indent = code_line[: len(code_line) - len(stripped)]
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
try:
|
|
133
|
+
tree = ast.parse(stripped, mode="exec")
|
|
134
|
+
except SyntaxError:
|
|
135
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return (True, suggestion)
|
|
137
|
+
if not tree.body:
|
|
138
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
method_pattern = rf"\.{re.escape(func_name)}\s*\("
|
|
143
|
-
method_match = re.search(method_pattern, code_line)
|
|
140
|
+
stmt = tree.body[0]
|
|
144
141
|
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
# Bail out for control flow statements that are too complex to rewrite safely
|
|
143
|
+
if isinstance(stmt, (ast.If, ast.While, ast.For)):
|
|
147
144
|
return (False, f"await ...{func_name}.aio(...)")
|
|
148
145
|
|
|
149
|
-
#
|
|
150
|
-
|
|
146
|
+
# Walk the AST to find Call nodes and Attribute nodes matching func_name
|
|
147
|
+
target_call = None
|
|
148
|
+
call_func_ids: set[int] = set()
|
|
149
|
+
|
|
150
|
+
for node in ast.walk(tree):
|
|
151
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
|
|
152
|
+
call_func_ids.add(id(node.func))
|
|
153
|
+
if node.func.attr == func_name and target_call is None:
|
|
154
|
+
target_call = node
|
|
155
|
+
|
|
156
|
+
# If no matching Call found, check for property access (Attribute not used as a Call's func)
|
|
157
|
+
if target_call is None:
|
|
158
|
+
target_attr = None
|
|
159
|
+
for node in ast.walk(tree):
|
|
160
|
+
if isinstance(node, ast.Attribute) and node.attr == func_name and id(node) not in call_func_ids:
|
|
161
|
+
target_attr = node
|
|
162
|
+
break
|
|
151
163
|
|
|
152
|
-
|
|
153
|
-
for keyword in unsafe_keywords:
|
|
154
|
-
if re.search(rf"\b{keyword}\b", code_line):
|
|
155
|
-
# Fall back to generic suggestion for complex expressions
|
|
164
|
+
if target_attr is None:
|
|
156
165
|
return (False, f"await ...{func_name}.aio(...)")
|
|
157
166
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
#
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
167
|
+
# Property access: insert "await" before the start of the attribute expression
|
|
168
|
+
expr_start = target_attr.col_offset
|
|
169
|
+
result = stripped[:expr_start] + "await " + stripped[expr_start:]
|
|
170
|
+
return (True, indent + result)
|
|
171
|
+
|
|
172
|
+
# Check if the target Call is inside a BoolOp (and/or) - bail out for safety
|
|
173
|
+
for node in ast.walk(tree):
|
|
174
|
+
if isinstance(node, ast.BoolOp):
|
|
175
|
+
for descendant in ast.walk(node):
|
|
176
|
+
if descendant is target_call:
|
|
177
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
178
|
+
|
|
179
|
+
# Method call: insert .aio after the method name and await before the expression
|
|
180
|
+
func_attr = target_call.func
|
|
181
|
+
attr_end = func_attr.end_col_offset
|
|
182
|
+
|
|
183
|
+
if attr_end is None:
|
|
184
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
185
|
+
|
|
186
|
+
# Find the opening parenthesis (skip any whitespace between method name and paren)
|
|
187
|
+
paren_pos = attr_end
|
|
188
|
+
while paren_pos < len(stripped) and stripped[paren_pos] != "(":
|
|
189
|
+
paren_pos += 1
|
|
190
|
+
|
|
191
|
+
if paren_pos >= len(stripped):
|
|
192
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
193
|
+
|
|
194
|
+
# Insert .aio, collapsing any whitespace between method name and (
|
|
195
|
+
modified = stripped[:attr_end] + ".aio" + stripped[paren_pos:]
|
|
196
|
+
|
|
197
|
+
# Insert await before the call expression (ast gives us the exact start position)
|
|
198
|
+
call_start = target_call.col_offset
|
|
199
|
+
modified = modified[:call_start] + "await " + modified[call_start:]
|
|
178
200
|
|
|
179
|
-
|
|
180
|
-
# But we need to check if the identifier we found is actually a keyword like return/yield/raise
|
|
181
|
-
# In that case, skip over it and find the actual object
|
|
182
|
-
before_obj = code_line[:expr_start]
|
|
183
|
-
obj_and_rest = code_line[expr_start:]
|
|
184
|
-
|
|
185
|
-
# Check if what we found starts with a statement keyword
|
|
186
|
-
keyword_match = re.match(r"^(return|yield|raise)\s+", obj_and_rest)
|
|
187
|
-
if keyword_match:
|
|
188
|
-
# The "object" we found is actually a keyword, adjust to skip it
|
|
189
|
-
keyword_len = len(keyword_match.group(0))
|
|
190
|
-
before_obj = code_line[: expr_start + keyword_len]
|
|
191
|
-
obj_and_rest = code_line[expr_start + keyword_len :]
|
|
192
|
-
|
|
193
|
-
# Add .aio() after the method name and await before the object
|
|
194
|
-
rewritten_expr = re.sub(rf"(\.{re.escape(func_name)})\s*\(", r"\1.aio(", obj_and_rest, count=1)
|
|
195
|
-
suggestion = before_obj + "await " + rewritten_expr
|
|
196
|
-
|
|
197
|
-
return (True, suggestion)
|
|
201
|
+
return (True, indent + modified)
|
|
198
202
|
|
|
199
203
|
|
|
200
204
|
@dataclass
|