modal 1.1.5.dev86__py3-none-any.whl → 1.1.5.dev88__py3-none-any.whl
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.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- modal/_billing.py +80 -0
- modal/billing.py +5 -0
- modal/client.pyi +2 -2
- {modal-1.1.5.dev86.dist-info → modal-1.1.5.dev88.dist-info}/METADATA +1 -1
- {modal-1.1.5.dev86.dist-info → modal-1.1.5.dev88.dist-info}/RECORD +23 -15
- modal_proto/api.proto +15 -0
- modal_proto/api_grpc.py +16 -0
- modal_proto/api_pb2.py +201 -181
- modal_proto/api_pb2.pyi +38 -0
- modal_proto/api_pb2_grpc.py +33 -0
- modal_proto/api_pb2_grpc.pyi +10 -0
- modal_proto/modal_api_grpc.py +1 -0
- modal_proto/task_command_router.proto +144 -0
- modal_proto/task_command_router_grpc.py +105 -0
- modal_proto/task_command_router_pb2.py +149 -0
- modal_proto/task_command_router_pb2.pyi +333 -0
- modal_proto/task_command_router_pb2_grpc.py +203 -0
- modal_proto/task_command_router_pb2_grpc.pyi +75 -0
- modal_version/__init__.py +1 -1
- {modal-1.1.5.dev86.dist-info → modal-1.1.5.dev88.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev86.dist-info → modal-1.1.5.dev88.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev86.dist-info → modal-1.1.5.dev88.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev86.dist-info → modal-1.1.5.dev88.dist-info}/top_level.txt +0 -0
modal/_billing.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Any, Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
from modal_proto import api_pb2
|
|
7
|
+
|
|
8
|
+
from .client import _Client
|
|
9
|
+
from .exception import InvalidError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WorkspaceBillingReportItem(TypedDict):
|
|
13
|
+
object_id: str
|
|
14
|
+
description: str
|
|
15
|
+
environment_name: str
|
|
16
|
+
interval_start: datetime
|
|
17
|
+
cost: Decimal
|
|
18
|
+
tags: dict[str, str]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def _workspace_billing_report(
|
|
22
|
+
*,
|
|
23
|
+
start: datetime, # Start of the report, inclusive
|
|
24
|
+
end: Optional[datetime] = None, # End of the report, exclusive
|
|
25
|
+
resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
|
|
26
|
+
tag_names: Optional[list[str]] = None, # Optional additional metadata to include
|
|
27
|
+
client: Optional[_Client] = None,
|
|
28
|
+
) -> list[dict[str, Any]]:
|
|
29
|
+
"""Generate a tabular report of workspace usage by object and time.
|
|
30
|
+
|
|
31
|
+
The result will be a list of dictionaries for each interval (determined by `resolution`)
|
|
32
|
+
between the `start` and `end` limits. The dictionary represents a single Modal object
|
|
33
|
+
that billing can be attributed to (e.g., an App) along with metadata (including user-defined
|
|
34
|
+
tags) for identifying that object.
|
|
35
|
+
|
|
36
|
+
The `start` and `end` parameters are required to either have a UTC timezone or to be
|
|
37
|
+
timezone-naive (which will be interpreted as UTC times). The timestamps in the result will
|
|
38
|
+
be in UTC. Cost will be reported for full intervals, even if the provided `start` or `end`
|
|
39
|
+
parameters are partial: `start` will be rounded to the beginning of its interval, while
|
|
40
|
+
partial `end` intervals will be excluded.
|
|
41
|
+
|
|
42
|
+
Additional user-provided metadata can be included in the report if the objects have tags
|
|
43
|
+
and `tag_names` (i.e., keys) are specified in the request. Note that tags will be attributed
|
|
44
|
+
to the entire interval even if they were added or removed at some point within it.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
if client is None:
|
|
48
|
+
client = await _Client.from_env()
|
|
49
|
+
|
|
50
|
+
tag_names = tag_names or []
|
|
51
|
+
|
|
52
|
+
if end is None:
|
|
53
|
+
end = datetime.now(timezone.utc)
|
|
54
|
+
|
|
55
|
+
for dt in (start, end):
|
|
56
|
+
if dt.tzinfo is None:
|
|
57
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
58
|
+
elif dt.tzinfo != timezone.utc:
|
|
59
|
+
raise InvalidError("Timezone-aware start/end limits must be in UTC.")
|
|
60
|
+
|
|
61
|
+
request = api_pb2.WorkspaceBillingReportRequest(
|
|
62
|
+
resolution=resolution,
|
|
63
|
+
tag_names=tag_names,
|
|
64
|
+
)
|
|
65
|
+
request.start_timestamp.FromDatetime(start)
|
|
66
|
+
request.end_timestamp.FromDatetime(end)
|
|
67
|
+
|
|
68
|
+
rows = []
|
|
69
|
+
async for pb_item in client.stub.WorkspaceBillingReport.unary_stream(request):
|
|
70
|
+
item = {
|
|
71
|
+
"object_id": pb_item.object_id,
|
|
72
|
+
"description": pb_item.description,
|
|
73
|
+
"environment_name": pb_item.environment_name,
|
|
74
|
+
"interval_start": pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
|
|
75
|
+
"cost": Decimal(pb_item.cost),
|
|
76
|
+
"tags": dict(pb_item.tags),
|
|
77
|
+
}
|
|
78
|
+
rows.append(item)
|
|
79
|
+
|
|
80
|
+
return rows
|
modal/billing.py
ADDED
modal/client.pyi
CHANGED
|
@@ -33,7 +33,7 @@ class _Client:
|
|
|
33
33
|
server_url: str,
|
|
34
34
|
client_type: int,
|
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
-
version: str = "1.1.5.
|
|
36
|
+
version: str = "1.1.5.dev88",
|
|
37
37
|
):
|
|
38
38
|
"""mdmd:hidden
|
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -164,7 +164,7 @@ class Client:
|
|
|
164
164
|
server_url: str,
|
|
165
165
|
client_type: int,
|
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
|
167
|
-
version: str = "1.1.5.
|
|
167
|
+
version: str = "1.1.5.dev88",
|
|
168
168
|
):
|
|
169
169
|
"""mdmd:hidden
|
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
modal/__init__.py,sha256=WMaRW-2IJRGA9ioNAaBhJYuyLvu-GS01L8wQD90fKBs,2682
|
|
2
2
|
modal/__main__.py,sha256=45H-GtwzaDfN-1nP4_HYvzN3s7AG_HXR4-ynrsjO_OI,2803
|
|
3
|
+
modal/_billing.py,sha256=C1jUN9f_1WqozSZAt9EOk1nImXUdiLrgFeeAu3R23cI,3012
|
|
3
4
|
modal/_clustered_functions.py,sha256=Sy4Sf_17EO8OL-FUe8LYcm4hrqLyQFCssNhr3p0SroU,3013
|
|
4
5
|
modal/_clustered_functions.pyi,sha256=JmYwAGOLEnD5AF-gYF9O5tu-SgGjeoJz-X1j48b1Ijg,1157
|
|
5
6
|
modal/_container_entrypoint.py,sha256=B_fIKKjWposiNsYOePifX7S6cR9hf5LRPhDfVums5O8,27867
|
|
@@ -20,9 +21,10 @@ modal/_type_manager.py,sha256=DWjgmjYJuOagw2erin506UUbG2H5UzZCFEekS-7hmfA,9087
|
|
|
20
21
|
modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
|
|
21
22
|
modal/app.py,sha256=W-24GIIBTKBqBWF6n4aB_heUhCb8d4KJfGF8oWteZn8,52454
|
|
22
23
|
modal/app.pyi,sha256=6wlYK9nNp0GuHXyUxGmcZ6J92EjsWS-KK6KRuejqXEY,49718
|
|
24
|
+
modal/billing.py,sha256=zmQ3bcCJlwa4KD1IA_QgdWpm1pn13c-7qfy79iEauYI,195
|
|
23
25
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
|
24
26
|
modal/client.py,sha256=kyAIVB3Ay-XKJizQ_1ufUFB__EagV0MLmHJpyYyJ7J0,18636
|
|
25
|
-
modal/client.pyi,sha256=
|
|
27
|
+
modal/client.pyi,sha256=1SD-1xFGRj7-jx0ofSh0gVb07hS9qR3xmpJb18PdTto,15831
|
|
26
28
|
modal/cloud_bucket_mount.py,sha256=I2GRXYhOWLIz2kJZjXu75jAm9EJkBNcutGc6jR2ReUw,5928
|
|
27
29
|
modal/cloud_bucket_mount.pyi,sha256=VuUOipMIHqFXMkD-3g2bsoqpSxV5qswlFHDOqPQzYAo,7405
|
|
28
30
|
modal/cls.py,sha256=IZG9gLlssbhTgIn6iSEmBSKkbbkst3skASMae-59FII,40239
|
|
@@ -153,7 +155,7 @@ modal/experimental/__init__.py,sha256=9gkVuDmu3m4TlKoU3MzEtTOemUSs8EEOWba40s7Aa0
|
|
|
153
155
|
modal/experimental/flash.py,sha256=C4sef08rARYFllsgtqukFmYL18SZW0_JpMS0BejDcUs,28552
|
|
154
156
|
modal/experimental/flash.pyi,sha256=vV_OQhtdrPn8SW0XrBK-aLLHHIvxAzLzwFbWrke-m74,15463
|
|
155
157
|
modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
|
|
156
|
-
modal-1.1.5.
|
|
158
|
+
modal-1.1.5.dev88.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
157
159
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
|
158
160
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
|
159
161
|
modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
|
|
@@ -161,13 +163,13 @@ modal_docs/mdmd/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,2
|
|
|
161
163
|
modal_docs/mdmd/mdmd.py,sha256=tUTImNd4UMFk1opkaw8J672gX8AkBO5gbY2S_NMxsxs,7140
|
|
162
164
|
modal_docs/mdmd/signatures.py,sha256=XJaZrK7Mdepk5fdX51A8uENiLFNil85Ud0d4MH8H5f0,3218
|
|
163
165
|
modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
|
164
|
-
modal_proto/api.proto,sha256=
|
|
165
|
-
modal_proto/api_grpc.py,sha256=
|
|
166
|
-
modal_proto/api_pb2.py,sha256=
|
|
167
|
-
modal_proto/api_pb2.pyi,sha256=
|
|
168
|
-
modal_proto/api_pb2_grpc.py,sha256=
|
|
169
|
-
modal_proto/api_pb2_grpc.pyi,sha256=
|
|
170
|
-
modal_proto/modal_api_grpc.py,sha256=
|
|
166
|
+
modal_proto/api.proto,sha256=V3XYevM1T8Btc8GJmV5i4I3faI6c62xE0sfwvjx3QKY,109406
|
|
167
|
+
modal_proto/api_grpc.py,sha256=Kgv6E88imUNNWyOA0foC1NEzgPWTXW4mJ3IKg_f0Dek,136233
|
|
168
|
+
modal_proto/api_pb2.py,sha256=QGUGKLLqnAQPrdpXtWUDmKuNyEdeMyBAYKvOrOBcLG8,382752
|
|
169
|
+
modal_proto/api_pb2.pyi,sha256=5P6K_B-zCILsKFk9XgtvvPu5gqUrVxVkVzzdV9CBWrU,533421
|
|
170
|
+
modal_proto/api_pb2_grpc.py,sha256=_M7mQCqZ3BhuCgL4vRrinA2oOjWeJSMXeb5iOFKHi2g,293349
|
|
171
|
+
modal_proto/api_pb2_grpc.pyi,sha256=4Jub1tY9CWdUXRvC7LxAiTCWuu-tuLTLBAEcpoDQMDw,68760
|
|
172
|
+
modal_proto/modal_api_grpc.py,sha256=6w-9wV6QLE3KFSzPerMtSjzbsunVEfCjOOmplKudjuw,20528
|
|
171
173
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
172
174
|
modal_proto/sandbox_router.proto,sha256=NWmNYbxgcGoV3XlR-pp8cQEgyVNn43U83e4gyWjKtmQ,5296
|
|
173
175
|
modal_proto/sandbox_router_grpc.py,sha256=27daOTX2N5hADDG-5Qnn4Yj3VfekyJwrDUkrQ12mPuU,5004
|
|
@@ -175,10 +177,16 @@ modal_proto/sandbox_router_pb2.py,sha256=QTT32WuusbyZwciNo-UUa6wl9-juOE2xRlv0JrB
|
|
|
175
177
|
modal_proto/sandbox_router_pb2.pyi,sha256=Ihf7LHmh0V5XfMHwYQobZiQ1_3xPoDJgIueyUxC4fMQ,15088
|
|
176
178
|
modal_proto/sandbox_router_pb2_grpc.py,sha256=8kPFo84rpRHfeRdZuHyoWuuIAphGmjM0s3ygGJSBVck,10006
|
|
177
179
|
modal_proto/sandbox_router_pb2_grpc.pyi,sha256=iAr1ornlIEYP7pfrFMrw91-F3PNs7amH6z6J2oCwYxk,3276
|
|
178
|
-
|
|
180
|
+
modal_proto/task_command_router.proto,sha256=AzgrpOQo0ux4axnxG_akIvUPsi2zjZwrv6qpw-mseXk,5013
|
|
181
|
+
modal_proto/task_command_router_grpc.py,sha256=K4jx9zr97twm3wrJa00ENoEntGcGz67ColcAeTZPrNc,5013
|
|
182
|
+
modal_proto/task_command_router_pb2.py,sha256=sHuO2zhdF1IcjoYOFdZQR2SRxG4pKqFZv1D-AsqpOsw,10619
|
|
183
|
+
modal_proto/task_command_router_pb2.pyi,sha256=EyDgXPLr7alqjXYERV8w_MPuO404x0uCppmSkrfE9IE,14589
|
|
184
|
+
modal_proto/task_command_router_pb2_grpc.py,sha256=-IgssABfiboRRsW08JwWWSeJxHvDdOUxZd0fiN9xq4Q,10009
|
|
185
|
+
modal_proto/task_command_router_pb2_grpc.pyi,sha256=V_uvtFjVDfqien0x820hQ0W5ZbNAqm_uSn0F3RSqz58,3273
|
|
186
|
+
modal_version/__init__.py,sha256=kpk1mIJ0ez-b0kJrSKuArOR_g3mP5vWqiKryck72F6Q,121
|
|
179
187
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
|
180
|
-
modal-1.1.5.
|
|
181
|
-
modal-1.1.5.
|
|
182
|
-
modal-1.1.5.
|
|
183
|
-
modal-1.1.5.
|
|
184
|
-
modal-1.1.5.
|
|
188
|
+
modal-1.1.5.dev88.dist-info/METADATA,sha256=XO_0TSuepAYSgvazA8eyGnixnVCALwIzcBQNx7Oc204,2481
|
|
189
|
+
modal-1.1.5.dev88.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
190
|
+
modal-1.1.5.dev88.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
|
191
|
+
modal-1.1.5.dev88.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
|
192
|
+
modal-1.1.5.dev88.dist-info/RECORD,,
|
modal_proto/api.proto
CHANGED
|
@@ -2760,6 +2760,8 @@ message SandboxCreateResponse {
|
|
|
2760
2760
|
// Used to get a JWT and URL for direct access to a sandbox router server
|
|
2761
2761
|
// running on the modal-worker, so the client can issue exec commands (and other
|
|
2762
2762
|
// operations as they become available) directly to the worker.
|
|
2763
|
+
// DEPRECATED: Use TaskGetCommandRouterAccessRequest instead.
|
|
2764
|
+
// TODO(saltzm): Remove this.
|
|
2763
2765
|
message SandboxGetCommandRouterAccessRequest {
|
|
2764
2766
|
string sandbox_id = 1;
|
|
2765
2767
|
}
|
|
@@ -3163,6 +3165,18 @@ message TaskGetAutoscalingMetricsResponse {
|
|
|
3163
3165
|
AutoscalingMetrics metrics = 1;
|
|
3164
3166
|
}
|
|
3165
3167
|
|
|
3168
|
+
// Used to get a JWT and URL for direct access to a task command router
|
|
3169
|
+
// running on the modal-worker, so the client can issue exec commands (and other
|
|
3170
|
+
// operations as they become available) directly to the worker.
|
|
3171
|
+
message TaskGetCommandRouterAccessRequest {
|
|
3172
|
+
string task_id = 1;
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
message TaskGetCommandRouterAccessResponse {
|
|
3176
|
+
string jwt = 1;
|
|
3177
|
+
string url = 2;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3166
3180
|
message TaskInfo {
|
|
3167
3181
|
string id = 1;
|
|
3168
3182
|
double started_at = 2;
|
|
@@ -3780,6 +3794,7 @@ service ModalClient {
|
|
|
3780
3794
|
rpc TaskClusterHello(TaskClusterHelloRequest) returns (TaskClusterHelloResponse);
|
|
3781
3795
|
rpc TaskCurrentInputs(google.protobuf.Empty) returns (TaskCurrentInputsResponse);
|
|
3782
3796
|
rpc TaskGetAutoscalingMetrics(TaskGetAutoscalingMetricsRequest) returns (TaskGetAutoscalingMetricsResponse); // Used for flash autoscaling
|
|
3797
|
+
rpc TaskGetCommandRouterAccess(TaskGetCommandRouterAccessRequest) returns (TaskGetCommandRouterAccessResponse);
|
|
3783
3798
|
rpc TaskList(TaskListRequest) returns (TaskListResponse);
|
|
3784
3799
|
rpc TaskResult(TaskResultRequest) returns (google.protobuf.Empty);
|
|
3785
3800
|
|
modal_proto/api_grpc.py
CHANGED
|
@@ -618,6 +618,10 @@ class ModalClientBase(abc.ABC):
|
|
|
618
618
|
async def TaskGetAutoscalingMetrics(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.TaskGetAutoscalingMetricsRequest, modal_proto.api_pb2.TaskGetAutoscalingMetricsResponse]') -> None:
|
|
619
619
|
pass
|
|
620
620
|
|
|
621
|
+
@abc.abstractmethod
|
|
622
|
+
async def TaskGetCommandRouterAccess(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.TaskGetCommandRouterAccessRequest, modal_proto.api_pb2.TaskGetCommandRouterAccessResponse]') -> None:
|
|
623
|
+
pass
|
|
624
|
+
|
|
621
625
|
@abc.abstractmethod
|
|
622
626
|
async def TaskList(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.TaskListRequest, modal_proto.api_pb2.TaskListResponse]') -> None:
|
|
623
627
|
pass
|
|
@@ -1620,6 +1624,12 @@ class ModalClientBase(abc.ABC):
|
|
|
1620
1624
|
modal_proto.api_pb2.TaskGetAutoscalingMetricsRequest,
|
|
1621
1625
|
modal_proto.api_pb2.TaskGetAutoscalingMetricsResponse,
|
|
1622
1626
|
),
|
|
1627
|
+
'/modal.client.ModalClient/TaskGetCommandRouterAccess': grpclib.const.Handler(
|
|
1628
|
+
self.TaskGetCommandRouterAccess,
|
|
1629
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
1630
|
+
modal_proto.api_pb2.TaskGetCommandRouterAccessRequest,
|
|
1631
|
+
modal_proto.api_pb2.TaskGetCommandRouterAccessResponse,
|
|
1632
|
+
),
|
|
1623
1633
|
'/modal.client.ModalClient/TaskList': grpclib.const.Handler(
|
|
1624
1634
|
self.TaskList,
|
|
1625
1635
|
grpclib.const.Cardinality.UNARY_UNARY,
|
|
@@ -2676,6 +2686,12 @@ class ModalClientStub:
|
|
|
2676
2686
|
modal_proto.api_pb2.TaskGetAutoscalingMetricsRequest,
|
|
2677
2687
|
modal_proto.api_pb2.TaskGetAutoscalingMetricsResponse,
|
|
2678
2688
|
)
|
|
2689
|
+
self.TaskGetCommandRouterAccess = grpclib.client.UnaryUnaryMethod(
|
|
2690
|
+
channel,
|
|
2691
|
+
'/modal.client.ModalClient/TaskGetCommandRouterAccess',
|
|
2692
|
+
modal_proto.api_pb2.TaskGetCommandRouterAccessRequest,
|
|
2693
|
+
modal_proto.api_pb2.TaskGetCommandRouterAccessResponse,
|
|
2694
|
+
)
|
|
2679
2695
|
self.TaskList = grpclib.client.UnaryUnaryMethod(
|
|
2680
2696
|
channel,
|
|
2681
2697
|
'/modal.client.ModalClient/TaskList',
|