modal 0.73.106__py3-none-any.whl → 0.73.107__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.
- modal/_utils/git_utils.py +72 -0
- modal/client.pyi +2 -2
- modal/functions.pyi +6 -6
- modal/runner.py +22 -2
- modal/runner.pyi +1 -0
- {modal-0.73.106.dist-info → modal-0.73.107.dist-info}/METADATA +1 -1
- {modal-0.73.106.dist-info → modal-0.73.107.dist-info}/RECORD +15 -14
- modal_proto/api.proto +13 -0
- modal_proto/api_pb2.py +834 -824
- modal_proto/api_pb2.pyi +50 -2
- modal_version/_version_generated.py +1 -1
- {modal-0.73.106.dist-info → modal-0.73.107.dist-info}/LICENSE +0 -0
- {modal-0.73.106.dist-info → modal-0.73.107.dist-info}/WHEEL +0 -0
- {modal-0.73.106.dist-info → modal-0.73.107.dist-info}/entry_points.txt +0 -0
- {modal-0.73.106.dist-info → modal-0.73.107.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright Modal Labs 2025
|
2
|
+
import asyncio
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from modal.config import logger
|
6
|
+
from modal_proto import api_pb2
|
7
|
+
|
8
|
+
|
9
|
+
async def run_command_fallible(args: list[str]) -> Optional[str]:
|
10
|
+
try:
|
11
|
+
process = await asyncio.create_subprocess_exec(
|
12
|
+
*args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
13
|
+
)
|
14
|
+
stdout_bytes, _ = await process.communicate()
|
15
|
+
|
16
|
+
if process.returncode != 0:
|
17
|
+
logger.debug(f"Command {args} exited with code {process.returncode}")
|
18
|
+
return None
|
19
|
+
|
20
|
+
return stdout_bytes.decode("utf-8").strip()
|
21
|
+
|
22
|
+
except Exception as e:
|
23
|
+
logger.debug(f"Command {args} failed", exc_info=e)
|
24
|
+
return None
|
25
|
+
|
26
|
+
|
27
|
+
async def get_git_commit_info() -> Optional[api_pb2.CommitInfo]:
|
28
|
+
"""Collect git information about the current repository asynchronously."""
|
29
|
+
git_info: api_pb2.CommitInfo = api_pb2.CommitInfo(vcs="git")
|
30
|
+
|
31
|
+
commands = [
|
32
|
+
# Get commit hash, timestamp, author name, and author email
|
33
|
+
["git", "log", "-1", "--format=%H%n%ct%n%an%n%ae", "HEAD"],
|
34
|
+
# Get branch name
|
35
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
36
|
+
# Check if working directory is dirty
|
37
|
+
["git", "status", "--porcelain"],
|
38
|
+
["git", "remote", "get-url", "origin"],
|
39
|
+
]
|
40
|
+
|
41
|
+
tasks = (run_command_fallible(cmd) for cmd in commands)
|
42
|
+
(log_info, branch, status, origin_url) = await asyncio.gather(*tasks)
|
43
|
+
|
44
|
+
if not branch:
|
45
|
+
return None
|
46
|
+
|
47
|
+
git_info.branch = branch
|
48
|
+
|
49
|
+
if not log_info:
|
50
|
+
return None
|
51
|
+
|
52
|
+
info_lines = log_info.split("\n")
|
53
|
+
if len(info_lines) < 4:
|
54
|
+
# If we didn't get all expected lines, bail
|
55
|
+
logger.debug(f"Log info returned only {len(info_lines)} lines")
|
56
|
+
return None
|
57
|
+
|
58
|
+
try:
|
59
|
+
git_info.commit_hash = info_lines[0]
|
60
|
+
git_info.commit_timestamp = int(info_lines[1])
|
61
|
+
git_info.author_name = info_lines[2]
|
62
|
+
git_info.author_email = info_lines[3]
|
63
|
+
except (ValueError, IndexError):
|
64
|
+
logger.debug(f"Failed to parse git log info: {log_info}")
|
65
|
+
return None
|
66
|
+
|
67
|
+
git_info.dirty = bool(status)
|
68
|
+
|
69
|
+
if origin_url:
|
70
|
+
git_info.repo_url = origin_url
|
71
|
+
|
72
|
+
return git_info
|
modal/client.pyi
CHANGED
@@ -31,7 +31,7 @@ class _Client:
|
|
31
31
|
server_url: str,
|
32
32
|
client_type: int,
|
33
33
|
credentials: typing.Optional[tuple[str, str]],
|
34
|
-
version: str = "0.73.
|
34
|
+
version: str = "0.73.107",
|
35
35
|
): ...
|
36
36
|
def is_closed(self) -> bool: ...
|
37
37
|
@property
|
@@ -93,7 +93,7 @@ class Client:
|
|
93
93
|
server_url: str,
|
94
94
|
client_type: int,
|
95
95
|
credentials: typing.Optional[tuple[str, str]],
|
96
|
-
version: str = "0.73.
|
96
|
+
version: str = "0.73.107",
|
97
97
|
): ...
|
98
98
|
def is_closed(self) -> bool: ...
|
99
99
|
@property
|
modal/functions.pyi
CHANGED
@@ -198,11 +198,11 @@ class Function(
|
|
198
198
|
|
199
199
|
_call_generator_nowait: ___call_generator_nowait_spec[typing_extensions.Self]
|
200
200
|
|
201
|
-
class __remote_spec(typing_extensions.Protocol[
|
201
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
202
202
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
203
203
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
204
204
|
|
205
|
-
remote: __remote_spec[modal._functions.
|
205
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
206
206
|
|
207
207
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
208
208
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -217,19 +217,19 @@ class Function(
|
|
217
217
|
self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
|
218
218
|
) -> modal._functions.OriginalReturnType: ...
|
219
219
|
|
220
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
220
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
221
221
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
222
222
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
223
223
|
|
224
224
|
_experimental_spawn: ___experimental_spawn_spec[
|
225
|
-
modal._functions.
|
225
|
+
modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
|
226
226
|
]
|
227
227
|
|
228
|
-
class __spawn_spec(typing_extensions.Protocol[
|
228
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
229
229
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
230
230
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
231
231
|
|
232
|
-
spawn: __spawn_spec[modal._functions.
|
232
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
233
233
|
|
234
234
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
|
235
235
|
|
modal/runner.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright Modal Labs
|
1
|
+
# Copyright Modal Labs 2025
|
2
2
|
import asyncio
|
3
3
|
import dataclasses
|
4
4
|
import os
|
@@ -23,6 +23,7 @@ from ._runtime.execution_context import is_local
|
|
23
23
|
from ._traceback import print_server_warnings, traceback_contains_remote_call
|
24
24
|
from ._utils.async_utils import TaskContext, gather_cancel_on_exc, synchronize_api
|
25
25
|
from ._utils.deprecation import deprecation_error
|
26
|
+
from ._utils.git_utils import get_git_commit_info
|
26
27
|
from ._utils.grpc_utils import retry_transient_errors
|
27
28
|
from ._utils.name_utils import check_object_name, is_valid_tag
|
28
29
|
from .client import HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT, _Client
|
@@ -182,6 +183,7 @@ async def _publish_app(
|
|
182
183
|
classes: dict[str, _Cls],
|
183
184
|
name: str = "", # Only relevant for deployments
|
184
185
|
tag: str = "", # Only relevant for deployments
|
186
|
+
commit_info: Optional[api_pb2.CommitInfo] = None, # Git commit information
|
185
187
|
) -> tuple[str, list[api_pb2.Warning]]:
|
186
188
|
"""Wrapper for AppPublish RPC."""
|
187
189
|
|
@@ -195,7 +197,9 @@ async def _publish_app(
|
|
195
197
|
function_ids=running_app.function_ids,
|
196
198
|
class_ids=running_app.class_ids,
|
197
199
|
definition_ids=definition_ids,
|
200
|
+
commit_info=commit_info,
|
198
201
|
)
|
202
|
+
|
199
203
|
try:
|
200
204
|
response = await retry_transient_errors(client.stub.AppPublish, request)
|
201
205
|
except GRPCError as exc:
|
@@ -521,6 +525,9 @@ async def _deploy_app(
|
|
521
525
|
|
522
526
|
t0 = time.time()
|
523
527
|
|
528
|
+
# Get git information to track deployment history
|
529
|
+
commit_info_task = asyncio.create_task(get_git_commit_info())
|
530
|
+
|
524
531
|
running_app: RunningApp = await _init_local_app_from_name(
|
525
532
|
client, name, namespace, environment_name=environment_name
|
526
533
|
)
|
@@ -542,8 +549,21 @@ async def _deploy_app(
|
|
542
549
|
environment_name=environment_name,
|
543
550
|
)
|
544
551
|
|
552
|
+
commit_info = None
|
553
|
+
try:
|
554
|
+
commit_info = await commit_info_task
|
555
|
+
except Exception as e:
|
556
|
+
logger.debug("Failed to get git commit info", exc_info=e)
|
557
|
+
|
545
558
|
app_url, warnings = await _publish_app(
|
546
|
-
client,
|
559
|
+
client,
|
560
|
+
running_app,
|
561
|
+
api_pb2.APP_STATE_DEPLOYED,
|
562
|
+
app._functions,
|
563
|
+
app._classes,
|
564
|
+
name,
|
565
|
+
tag,
|
566
|
+
commit_info,
|
547
567
|
)
|
548
568
|
except Exception as e:
|
549
569
|
# Note that AppClientDisconnect only stops the app if it's still initializing, and is a no-op otherwise.
|
modal/runner.pyi
CHANGED
@@ -41,6 +41,7 @@ async def _publish_app(
|
|
41
41
|
classes: dict[str, modal.cls._Cls],
|
42
42
|
name: str = "",
|
43
43
|
tag: str = "",
|
44
|
+
commit_info: typing.Optional[modal_proto.api_pb2.CommitInfo] = None,
|
44
45
|
) -> tuple[str, list[modal_proto.api_pb2.Warning]]: ...
|
45
46
|
async def _disconnect(client: modal.client._Client, app_id: str, reason: int, exc_str: str = "") -> None: ...
|
46
47
|
async def _status_based_disconnect(
|
@@ -22,7 +22,7 @@ modal/app.py,sha256=ojhuLZuNZAQ1OsbDH0k6G4pm1W7bOIvZfXbaKlvQ-Ao,45622
|
|
22
22
|
modal/app.pyi,sha256=tZFbcsu20SuvfB2puxCyuXLFNJ9bQulzag55rVpgZmc,26827
|
23
23
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
24
24
|
modal/client.py,sha256=j9D3hNis1lfhnz9lVFGgJgowbH3PaGUzNKgHPWYG778,15372
|
25
|
-
modal/client.pyi,sha256=
|
25
|
+
modal/client.pyi,sha256=zk_fqo5hUeiYvbtxSBT5tnvf9bjiFZVBtNGH7Rltxzc,7661
|
26
26
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
27
27
|
modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
|
28
28
|
modal/cls.py,sha256=JhDbaZZHN52lqA_roY1BCbcN9BvbkUcdXiM2Kg9lIc0,31717
|
@@ -41,7 +41,7 @@ modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
|
|
41
41
|
modal/file_io.pyi,sha256=NTRft1tbPSWf9TlWVeZmTlgB5AZ_Zhu2srWIrWr7brk,9445
|
42
42
|
modal/file_pattern_matcher.py,sha256=trosX-Bp7dOubudN1bLLhRAoidWy1TcoaR4Pv8CedWw,6497
|
43
43
|
modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
|
44
|
-
modal/functions.pyi,sha256=
|
44
|
+
modal/functions.pyi,sha256=D-PDJfSbwqMDXdq7Bxu2ErZRENo-tRgu_zPoB-jl0OU,14377
|
45
45
|
modal/gpu.py,sha256=Kbhs_u49FaC2Zi0TjCdrpstpRtT5eZgecynmQi5IZVE,6752
|
46
46
|
modal/image.py,sha256=o9wnpB03agSoErzh-odrKU2BprROxRZOWGJidjZeeds,91811
|
47
47
|
modal/image.pyi,sha256=DQ4DLOCPr6_yV7z4LS0bTY0rOyvQP9-dQOrzaW7pPG8,25260
|
@@ -64,8 +64,8 @@ modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
64
|
modal/queue.py,sha256=OIYmve1a4GTP54Vj2CcLatLPIAWToU7hWBNeu7IJiBY,18985
|
65
65
|
modal/queue.pyi,sha256=sgvELCK4bJXMZIZw7gllooGFZNipGjI3BT4rmUuyD9M,10282
|
66
66
|
modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
|
67
|
-
modal/runner.py,sha256=
|
68
|
-
modal/runner.pyi,sha256=
|
67
|
+
modal/runner.py,sha256=2VoA5jR9JwgPlmaGFWWOsSCGivS9Pv4-e-YFP9l92iE,25073
|
68
|
+
modal/runner.pyi,sha256=HW2pvC_PLwg1Es_EkrfQgMZsktIr9zzVEtmjOVFG6Dw,5351
|
69
69
|
modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
|
70
70
|
modal/sandbox.py,sha256=bE7nLMge7TEjNFWo_7W4dRft-jYW9AqWHzCjiSi-Afs,32427
|
71
71
|
modal/sandbox.pyi,sha256=cLmSwI1ab-2DgEuXNf6S1PiK63wfUR9dHtxlZtSOuX8,22719
|
@@ -99,6 +99,7 @@ modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWR
|
|
99
99
|
modal/_utils/deprecation.py,sha256=EXP1beU4pmEqEzWMLw6E3kUfNfpmNA_VOp6i0EHi93g,4856
|
100
100
|
modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,3722
|
101
101
|
modal/_utils/function_utils.py,sha256=OqsmLKOqSenxkXiNSPUWpKqD5KotySa_Omw1tmt97K0,27380
|
102
|
+
modal/_utils/git_utils.py,sha256=-bTeR3RCZydqduVwmraBlQG11KHvzsY5H48VKS8coGQ,2202
|
102
103
|
modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
|
103
104
|
modal/_utils/grpc_utils.py,sha256=wmMydVKN9YbugTwUXuOuzxbpzYvxkTDaFRxlBtIDE_0,8526
|
104
105
|
modal/_utils/hash_utils.py,sha256=zg3J6OGxTFGSFri1qQ12giDz90lWk8bzaxCTUCRtiX4,3034
|
@@ -152,10 +153,10 @@ modal_docs/mdmd/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,2
|
|
152
153
|
modal_docs/mdmd/mdmd.py,sha256=Irx49MCCTlBOP4FBdLR--JrpA3-WhsVeriq0LGgsRic,6232
|
153
154
|
modal_docs/mdmd/signatures.py,sha256=XJaZrK7Mdepk5fdX51A8uENiLFNil85Ud0d4MH8H5f0,3218
|
154
155
|
modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
155
|
-
modal_proto/api.proto,sha256=
|
156
|
+
modal_proto/api.proto,sha256=mFNn1_wjeGCXhuact35aPBT8csBfEebQv4q0c9BTVB8,87354
|
156
157
|
modal_proto/api_grpc.py,sha256=FYGqDegM_w_qxdtlxum8k31mDibKoMvmNxv_p9cKdKs,109056
|
157
|
-
modal_proto/api_pb2.py,sha256=
|
158
|
-
modal_proto/api_pb2.pyi,sha256=
|
158
|
+
modal_proto/api_pb2.py,sha256=dCXCNx6x7PJogjWkpC4U2i3Xx-itbyY4jNkVKOmsQbU,313828
|
159
|
+
modal_proto/api_pb2.pyi,sha256=L4BRZZi50OFroqDBpQz6jYoQTT6zinY42acCxjmHpKw,424645
|
159
160
|
modal_proto/api_pb2_grpc.py,sha256=DNp0Et5i_Ey4dKx_1o1LRtYhyWYyT0NzTcAY4EcHn-c,235765
|
160
161
|
modal_proto/api_pb2_grpc.pyi,sha256=RI6tWC3L8EIN4-izFSEGPPJl5Ta0lXPNuHUJaWAr35s,54892
|
161
162
|
modal_proto/modal_api_grpc.py,sha256=UG8WJU81afrWPwItWB4Ag64E9EpyREMpBbAVGVEYJiM,14550
|
@@ -169,10 +170,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
169
170
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
170
171
|
modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
|
171
172
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
172
|
-
modal_version/_version_generated.py,sha256=
|
173
|
-
modal-0.73.
|
174
|
-
modal-0.73.
|
175
|
-
modal-0.73.
|
176
|
-
modal-0.73.
|
177
|
-
modal-0.73.
|
178
|
-
modal-0.73.
|
173
|
+
modal_version/_version_generated.py,sha256=nfjrv63ua8knwBgcK91Et-6cu5foto7KNinAK4c83q4,150
|
174
|
+
modal-0.73.107.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
175
|
+
modal-0.73.107.dist-info/METADATA,sha256=9XBg9dtDboOAYuWQOiDTTTLi-R4KZc_3lWKjB_o-2-c,2453
|
176
|
+
modal-0.73.107.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
177
|
+
modal-0.73.107.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
178
|
+
modal-0.73.107.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
179
|
+
modal-0.73.107.dist-info/RECORD,,
|
modal_proto/api.proto
CHANGED
@@ -309,6 +309,7 @@ message AppDeploymentHistory {
|
|
309
309
|
string tag = 6;
|
310
310
|
uint32 rollback_version = 7;
|
311
311
|
bool rollback_allowed = 8;
|
312
|
+
optional CommitInfo commit_info = 10;
|
312
313
|
}
|
313
314
|
|
314
315
|
message AppDeploymentHistoryRequest {
|
@@ -419,6 +420,7 @@ message AppPublishRequest {
|
|
419
420
|
map<string, string> definition_ids = 7; // function_id -> definition_id
|
420
421
|
uint32 rollback_version = 8; // Unused by client, but used internally
|
421
422
|
string client_version = 9; // Unused by client, but used internally
|
423
|
+
CommitInfo commit_info = 10; // Git information for deployment tracking
|
422
424
|
}
|
423
425
|
|
424
426
|
message AppPublishResponse {
|
@@ -745,6 +747,17 @@ message CloudBucketMount {
|
|
745
747
|
optional string oidc_auth_role_arn = 9;
|
746
748
|
}
|
747
749
|
|
750
|
+
message CommitInfo {
|
751
|
+
string vcs = 1; // Only git is supported for now
|
752
|
+
string branch = 2;
|
753
|
+
string commit_hash = 3;
|
754
|
+
int64 commit_timestamp = 4;
|
755
|
+
bool dirty = 5;
|
756
|
+
string author_name = 6;
|
757
|
+
string author_email = 7;
|
758
|
+
string repo_url = 8;
|
759
|
+
}
|
760
|
+
|
748
761
|
message ContainerArguments { // This is used to pass data from the worker to the container
|
749
762
|
string task_id = 1;
|
750
763
|
string function_id = 2;
|