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 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
@@ -0,0 +1,5 @@
1
+ # Copyright Modal Labs 2025
2
+ from ._billing import _workspace_billing_report
3
+ from ._utils.async_utils import synchronize_api
4
+
5
+ workspace_billing_report = synchronize_api(_workspace_billing_report)
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.dev86",
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.dev86",
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.5.dev86
3
+ Version: 1.1.5.dev88
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -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=eaiV3x4_xzXKVlKx1zXZ3WnYs3z7wGdEJ92xO7B6fzM,15831
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.dev86.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
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=BJvChnmHwPHqAzPctoawlFbH_UNNw8Fut88FNfbRSHk,108830
165
- modal_proto/api_grpc.py,sha256=plTpsjLCcRpciQdTxwh1Y6fZaAMBbSzoUYFOTF9uMIE,135333
166
- modal_proto/api_pb2.py,sha256=AXfiWULiO4EcttRDWnqZVImhXIvZVqv0Xbf1ojRAc_k,381192
167
- modal_proto/api_pb2.pyi,sha256=5qCe0VixKCd2TMSmtpgJuiF9pMbfP9ZLG76z7M5iN98,532059
168
- modal_proto/api_pb2_grpc.py,sha256=KGm4HHBQsge8Ww3eSVr9KWbl1IPomf-l3qqbfXWrej0,291534
169
- modal_proto/api_pb2_grpc.pyi,sha256=FVAP6dDZgGHVR3xP7ZxrUVQZLXY0PgT9EG3l2UXh10w,68311
170
- modal_proto/modal_api_grpc.py,sha256=WDbmh9XNSXIsnQ3KePkDlclEHRQpeDtRBuBIdGsvvHU,20394
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
- modal_version/__init__.py,sha256=PYnkwPhjnrPa0phIwaK7GG2qfKb9Hk16t28x3rRfiDE,121
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.dev86.dist-info/METADATA,sha256=X0DsidEkq4lS8zvPWMxd0dai3ClesoWgLfcW4FzV5Sg,2481
181
- modal-1.1.5.dev86.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
182
- modal-1.1.5.dev86.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
183
- modal-1.1.5.dev86.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
184
- modal-1.1.5.dev86.dist-info/RECORD,,
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',