modal 0.67.10__py3-none-any.whl → 0.67.13__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/client.pyi +2 -2
- modal/config.py +1 -0
- modal/functions.py +83 -6
- modal/functions.pyi +27 -1
- modal/partial_function.py +1 -1
- modal/retries.py +38 -0
- {modal-0.67.10.dist-info → modal-0.67.13.dist-info}/METADATA +1 -1
- {modal-0.67.10.dist-info → modal-0.67.13.dist-info}/RECORD +13 -13
- modal_version/_version_generated.py +1 -1
- {modal-0.67.10.dist-info → modal-0.67.13.dist-info}/LICENSE +0 -0
- {modal-0.67.10.dist-info → modal-0.67.13.dist-info}/WHEEL +0 -0
- {modal-0.67.10.dist-info → modal-0.67.13.dist-info}/entry_points.txt +0 -0
- {modal-0.67.10.dist-info → modal-0.67.13.dist-info}/top_level.txt +0 -0
modal/client.pyi
CHANGED
@@ -26,7 +26,7 @@ class _Client:
|
|
26
26
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
27
27
|
|
28
28
|
def __init__(
|
29
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.13"
|
30
30
|
): ...
|
31
31
|
def is_closed(self) -> bool: ...
|
32
32
|
@property
|
@@ -81,7 +81,7 @@ class Client:
|
|
81
81
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
82
82
|
|
83
83
|
def __init__(
|
84
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.13"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/config.py
CHANGED
@@ -221,6 +221,7 @@ _SETTINGS = {
|
|
221
221
|
"image_builder_version": _Setting(),
|
222
222
|
"strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
|
223
223
|
"snapshot_debug": _Setting(False, transform=_to_boolean),
|
224
|
+
"client_retries": _Setting(False, transform=_to_boolean), # For internal testing.
|
224
225
|
}
|
225
226
|
|
226
227
|
|
modal/functions.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# Copyright Modal Labs 2023
|
2
|
+
import dataclasses
|
2
3
|
import inspect
|
3
4
|
import textwrap
|
4
5
|
import time
|
@@ -19,6 +20,7 @@ import typing_extensions
|
|
19
20
|
from google.protobuf.message import Message
|
20
21
|
from grpclib import GRPCError, Status
|
21
22
|
from synchronicity.combined_types import MethodWithAio
|
23
|
+
from synchronicity.exceptions import UserCodeException
|
22
24
|
|
23
25
|
from modal._utils.async_utils import aclosing
|
24
26
|
from modal_proto import api_pb2
|
@@ -57,6 +59,7 @@ from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
|
|
57
59
|
from .config import config
|
58
60
|
from .exception import (
|
59
61
|
ExecutionError,
|
62
|
+
FunctionTimeoutError,
|
60
63
|
InvalidError,
|
61
64
|
NotFoundError,
|
62
65
|
OutputExpiredError,
|
@@ -79,7 +82,7 @@ from .parallel_map import (
|
|
79
82
|
_SynchronizedQueue,
|
80
83
|
)
|
81
84
|
from .proxy import _Proxy
|
82
|
-
from .retries import Retries
|
85
|
+
from .retries import Retries, RetryManager
|
83
86
|
from .schedule import Schedule
|
84
87
|
from .scheduler_placement import SchedulerPlacement
|
85
88
|
from .secret import _Secret
|
@@ -91,15 +94,32 @@ if TYPE_CHECKING:
|
|
91
94
|
import modal.partial_function
|
92
95
|
|
93
96
|
|
97
|
+
@dataclasses.dataclass
|
98
|
+
class _RetryContext:
|
99
|
+
function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType"
|
100
|
+
retry_policy: api_pb2.FunctionRetryPolicy
|
101
|
+
function_call_jwt: str
|
102
|
+
input_jwt: str
|
103
|
+
input_id: str
|
104
|
+
item: api_pb2.FunctionPutInputsItem
|
105
|
+
|
106
|
+
|
94
107
|
class _Invocation:
|
95
108
|
"""Internal client representation of a single-input call to a Modal Function or Generator"""
|
96
109
|
|
97
110
|
stub: ModalClientModal
|
98
111
|
|
99
|
-
def __init__(
|
112
|
+
def __init__(
|
113
|
+
self,
|
114
|
+
stub: ModalClientModal,
|
115
|
+
function_call_id: str,
|
116
|
+
client: _Client,
|
117
|
+
retry_context: Optional[_RetryContext] = None,
|
118
|
+
):
|
100
119
|
self.stub = stub
|
101
120
|
self.client = client # Used by the deserializer.
|
102
121
|
self.function_call_id = function_call_id # TODO: remove and use only input_id
|
122
|
+
self._retry_context = retry_context
|
103
123
|
|
104
124
|
@staticmethod
|
105
125
|
async def create(
|
@@ -125,7 +145,17 @@ class _Invocation:
|
|
125
145
|
function_call_id = response.function_call_id
|
126
146
|
|
127
147
|
if response.pipelined_inputs:
|
128
|
-
|
148
|
+
assert len(response.pipelined_inputs) == 1
|
149
|
+
input = response.pipelined_inputs[0]
|
150
|
+
retry_context = _RetryContext(
|
151
|
+
function_call_invocation_type=function_call_invocation_type,
|
152
|
+
retry_policy=response.retry_policy,
|
153
|
+
function_call_jwt=response.function_call_jwt,
|
154
|
+
input_jwt=input.input_jwt,
|
155
|
+
input_id=input.input_id,
|
156
|
+
item=item,
|
157
|
+
)
|
158
|
+
return _Invocation(client.stub, function_call_id, client, retry_context)
|
129
159
|
|
130
160
|
request_put = api_pb2.FunctionPutInputsRequest(
|
131
161
|
function_id=function_id, inputs=[item], function_call_id=function_call_id
|
@@ -137,7 +167,16 @@ class _Invocation:
|
|
137
167
|
processed_inputs = inputs_response.inputs
|
138
168
|
if not processed_inputs:
|
139
169
|
raise Exception("Could not create function call - the input queue seems to be full")
|
140
|
-
|
170
|
+
input = inputs_response.inputs[0]
|
171
|
+
retry_context = _RetryContext(
|
172
|
+
function_call_invocation_type=function_call_invocation_type,
|
173
|
+
retry_policy=response.retry_policy,
|
174
|
+
function_call_jwt=response.function_call_jwt,
|
175
|
+
input_jwt=input.input_jwt,
|
176
|
+
input_id=input.input_id,
|
177
|
+
item=item,
|
178
|
+
)
|
179
|
+
return _Invocation(client.stub, function_call_id, client, retry_context)
|
141
180
|
|
142
181
|
async def pop_function_call_outputs(
|
143
182
|
self, timeout: Optional[float], clear_on_success: bool
|
@@ -173,13 +212,46 @@ class _Invocation:
|
|
173
212
|
# return the last response to check for state of num_unfinished_inputs
|
174
213
|
return response
|
175
214
|
|
176
|
-
async def
|
215
|
+
async def _retry_input(self) -> None:
|
216
|
+
ctx = self._retry_context
|
217
|
+
if not ctx:
|
218
|
+
raise ValueError("Cannot retry input when _retry_context is empty.")
|
219
|
+
|
220
|
+
item = api_pb2.FunctionRetryInputsItem(input_jwt=ctx.input_jwt, input=ctx.item.input)
|
221
|
+
request = api_pb2.FunctionRetryInputsRequest(function_call_jwt=ctx.function_call_jwt, inputs=[item])
|
222
|
+
await retry_transient_errors(
|
223
|
+
self.client.stub.FunctionRetryInputs,
|
224
|
+
request,
|
225
|
+
)
|
226
|
+
|
227
|
+
async def _get_single_output(self) -> Any:
|
177
228
|
# waits indefinitely for a single result for the function, and clear the outputs buffer after
|
178
229
|
item: api_pb2.FunctionGetOutputsItem = (
|
179
230
|
await self.pop_function_call_outputs(timeout=None, clear_on_success=True)
|
180
231
|
).outputs[0]
|
181
232
|
return await _process_result(item.result, item.data_format, self.stub, self.client)
|
182
233
|
|
234
|
+
async def run_function(self) -> Any:
|
235
|
+
# Use retry logic only if retry policy is specified and
|
236
|
+
ctx = self._retry_context
|
237
|
+
if (
|
238
|
+
not ctx
|
239
|
+
or not ctx.retry_policy
|
240
|
+
or ctx.retry_policy.retries == 0
|
241
|
+
or ctx.function_call_invocation_type != api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC
|
242
|
+
):
|
243
|
+
return await self._get_single_output()
|
244
|
+
|
245
|
+
# User errors including timeouts are managed by the user specified retry policy.
|
246
|
+
user_retry_manager = RetryManager(ctx.retry_policy)
|
247
|
+
|
248
|
+
while True:
|
249
|
+
try:
|
250
|
+
return await self._get_single_output()
|
251
|
+
except (UserCodeException, FunctionTimeoutError) as exc:
|
252
|
+
await user_retry_manager.raise_or_sleep(exc)
|
253
|
+
await self._retry_input()
|
254
|
+
|
183
255
|
async def poll_function(self, timeout: Optional[float] = None):
|
184
256
|
"""Waits up to timeout for a result from a function.
|
185
257
|
|
@@ -1225,13 +1297,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1225
1297
|
yield item
|
1226
1298
|
|
1227
1299
|
async def _call_function(self, args, kwargs) -> ReturnType:
|
1300
|
+
if config.get("client_retries"):
|
1301
|
+
function_call_invocation_type = api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC
|
1302
|
+
else:
|
1303
|
+
function_call_invocation_type = api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY
|
1228
1304
|
invocation = await _Invocation.create(
|
1229
1305
|
self,
|
1230
1306
|
args,
|
1231
1307
|
kwargs,
|
1232
1308
|
client=self._client,
|
1233
|
-
function_call_invocation_type=
|
1309
|
+
function_call_invocation_type=function_call_invocation_type,
|
1234
1310
|
)
|
1311
|
+
|
1235
1312
|
return await invocation.run_function()
|
1236
1313
|
|
1237
1314
|
async def _call_function_nowait(
|
modal/functions.pyi
CHANGED
@@ -26,11 +26,35 @@ import pathlib
|
|
26
26
|
import typing
|
27
27
|
import typing_extensions
|
28
28
|
|
29
|
+
class _RetryContext:
|
30
|
+
function_call_invocation_type: int
|
31
|
+
retry_policy: modal_proto.api_pb2.FunctionRetryPolicy
|
32
|
+
function_call_jwt: str
|
33
|
+
input_jwt: str
|
34
|
+
input_id: str
|
35
|
+
item: modal_proto.api_pb2.FunctionPutInputsItem
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
function_call_invocation_type: int,
|
40
|
+
retry_policy: modal_proto.api_pb2.FunctionRetryPolicy,
|
41
|
+
function_call_jwt: str,
|
42
|
+
input_jwt: str,
|
43
|
+
input_id: str,
|
44
|
+
item: modal_proto.api_pb2.FunctionPutInputsItem,
|
45
|
+
) -> None: ...
|
46
|
+
def __repr__(self): ...
|
47
|
+
def __eq__(self, other): ...
|
48
|
+
|
29
49
|
class _Invocation:
|
30
50
|
stub: modal_proto.modal_api_grpc.ModalClientModal
|
31
51
|
|
32
52
|
def __init__(
|
33
|
-
self,
|
53
|
+
self,
|
54
|
+
stub: modal_proto.modal_api_grpc.ModalClientModal,
|
55
|
+
function_call_id: str,
|
56
|
+
client: modal.client._Client,
|
57
|
+
retry_context: typing.Optional[_RetryContext] = None,
|
34
58
|
): ...
|
35
59
|
@staticmethod
|
36
60
|
async def create(
|
@@ -39,6 +63,8 @@ class _Invocation:
|
|
39
63
|
async def pop_function_call_outputs(
|
40
64
|
self, timeout: typing.Optional[float], clear_on_success: bool
|
41
65
|
) -> modal_proto.api_pb2.FunctionGetOutputsResponse: ...
|
66
|
+
async def _retry_input(self) -> None: ...
|
67
|
+
async def _get_single_output(self) -> typing.Any: ...
|
42
68
|
async def run_function(self) -> typing.Any: ...
|
43
69
|
async def poll_function(self, timeout: typing.Optional[float] = None): ...
|
44
70
|
def run_generator(self): ...
|
modal/partial_function.py
CHANGED
@@ -155,7 +155,7 @@ def _find_partial_methods_for_user_cls(user_cls: type[Any], flags: int) -> dict[
|
|
155
155
|
deprecation_error((2024, 2, 21), message)
|
156
156
|
|
157
157
|
partial_functions: dict[str, PartialFunction] = {}
|
158
|
-
for parent_cls in user_cls.mro():
|
158
|
+
for parent_cls in reversed(user_cls.mro()):
|
159
159
|
if parent_cls is not object:
|
160
160
|
for k, v in parent_cls.__dict__.items():
|
161
161
|
if isinstance(v, PartialFunction):
|
modal/retries.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
|
+
import asyncio
|
2
3
|
from datetime import timedelta
|
3
4
|
|
4
5
|
from modal_proto import api_pb2
|
5
6
|
|
6
7
|
from .exception import InvalidError
|
7
8
|
|
9
|
+
MIN_INPUT_RETRY_DELAY_MS = 1000
|
10
|
+
MAX_INPUT_RETRY_DELAY_MS = 24 * 60 * 60 * 1000
|
11
|
+
|
8
12
|
|
9
13
|
class Retries:
|
10
14
|
"""Adds a retry policy to a Modal function.
|
@@ -103,3 +107,37 @@ class Retries:
|
|
103
107
|
initial_delay_ms=self.initial_delay // timedelta(milliseconds=1),
|
104
108
|
max_delay_ms=self.max_delay // timedelta(milliseconds=1),
|
105
109
|
)
|
110
|
+
|
111
|
+
|
112
|
+
class RetryManager:
|
113
|
+
"""
|
114
|
+
Helper class to apply the specified retry policy.
|
115
|
+
"""
|
116
|
+
|
117
|
+
def __init__(self, retry_policy: api_pb2.FunctionRetryPolicy):
|
118
|
+
self.retry_policy = retry_policy
|
119
|
+
self.attempt_count = 0
|
120
|
+
|
121
|
+
async def raise_or_sleep(self, exc: Exception):
|
122
|
+
"""
|
123
|
+
Raises an exception if the maximum retry count has been reached, otherwise sleeps for calculated delay.
|
124
|
+
"""
|
125
|
+
self.attempt_count += 1
|
126
|
+
if self.attempt_count > self.retry_policy.retries:
|
127
|
+
raise exc
|
128
|
+
delay_ms = self._retry_delay_ms(self.attempt_count, self.retry_policy)
|
129
|
+
await asyncio.sleep(delay_ms / 1000)
|
130
|
+
|
131
|
+
@staticmethod
|
132
|
+
def _retry_delay_ms(attempt_count: int, retry_policy: api_pb2.FunctionRetryPolicy) -> float:
|
133
|
+
"""
|
134
|
+
Computes the amount of time to sleep before retrying based on the backend_coefficient and initial_delay_ms args.
|
135
|
+
"""
|
136
|
+
if attempt_count < 1:
|
137
|
+
raise ValueError(f"Cannot compute retry delay. attempt_count must be at least 1, but was {attempt_count}")
|
138
|
+
delay_ms = retry_policy.initial_delay_ms * (retry_policy.backoff_coefficient ** (attempt_count - 1))
|
139
|
+
if delay_ms < MIN_INPUT_RETRY_DELAY_MS:
|
140
|
+
return MIN_INPUT_RETRY_DELAY_MS
|
141
|
+
if delay_ms > MAX_INPUT_RETRY_DELAY_MS:
|
142
|
+
return MAX_INPUT_RETRY_DELAY_MS
|
143
|
+
return delay_ms
|
@@ -19,12 +19,12 @@ modal/app.py,sha256=EJ7FUN6rWnSwLJoYJh8nmKg_t-8hdN8_rt0OrkP7JvQ,46084
|
|
19
19
|
modal/app.pyi,sha256=BE5SlR5tRECuc6-e2lUuOknDdov3zxgZ4N0AsLb5ZVQ,25270
|
20
20
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
21
21
|
modal/client.py,sha256=VMg_aIuo_LOEe2ttxBHEND3PLhTp5lo-onH4wELhIyY,16375
|
22
|
-
modal/client.pyi,sha256=
|
22
|
+
modal/client.pyi,sha256=eg7nuSgtNxJkB8fQLRu6VDD6b8w8fSBcW9-5wO2Phzo,7354
|
23
23
|
modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
|
24
24
|
modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
|
25
25
|
modal/cls.py,sha256=F2jk5zFCAA8h-GfM0dbdBG3Mu5wiG9k9Z9JLYRYuT2Q,24758
|
26
26
|
modal/cls.pyi,sha256=2_nbvSlkh2d0tfibTIxsThPiL0Xcrcosc5f_ET-i0sk,8147
|
27
|
-
modal/config.py,sha256=
|
27
|
+
modal/config.py,sha256=1KhNJkjYsJkX1V8RPPdRYPlM2HE-ZZs0JVSxbiXjmrw,11010
|
28
28
|
modal/container_process.py,sha256=c_jBPtyPeSxbIcbLfs_FzTrt-1eErtRSnsfxkDozFoY,5589
|
29
29
|
modal/container_process.pyi,sha256=k2kClwaSzz11eci1pzFZgCm-ptXapHAyHTOENorlazA,2594
|
30
30
|
modal/dict.py,sha256=RmJlEwFJOdSfAYcVa50hbbFccV8e7BvC5tc5g1HXF-c,12622
|
@@ -33,8 +33,8 @@ modal/environments.py,sha256=5cgA-zbm6ngKLsRA19zSOgtgo9-BarJK3FJK0BiF2Lo,6505
|
|
33
33
|
modal/environments.pyi,sha256=XalNpiPkAtHWAvOU2Cotq0ozmtl-Jv0FDsR8h9mr27Q,3521
|
34
34
|
modal/exception.py,sha256=EBkdWVved2XEPsXaoPRu56xfxFFHL9iuqvUsdj42WDA,6392
|
35
35
|
modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
|
36
|
-
modal/functions.py,sha256=
|
37
|
-
modal/functions.pyi,sha256=
|
36
|
+
modal/functions.py,sha256=Pwebl3aeEkNrniXCzuDdjfxgExykTWQo7o0VjFD4To8,69853
|
37
|
+
modal/functions.pyi,sha256=fifvDS5GDEYmXjko1UGZrKqmhfnQn6GRwCblM9hrRWo,25107
|
38
38
|
modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
|
39
39
|
modal/image.py,sha256=ZIC8tgjJnqWamN4sZ0Gch3x2VmcM671MWfRLR5SMmoc,79423
|
40
40
|
modal/image.pyi,sha256=JjicLNuaBsfuPZ_xo_eN0zKZkDrEm2alYg-szENhJjM,24591
|
@@ -49,14 +49,14 @@ modal/object.pyi,sha256=MO78H9yFSE5i1gExPEwyyQzLdlshkcGHN1aQ0ylyvq0,8802
|
|
49
49
|
modal/output.py,sha256=N0xf4qeudEaYrslzdAl35VKV8rapstgIM2e9wO8_iy0,1967
|
50
50
|
modal/parallel_map.py,sha256=4aoMXIrlG3wl5Ifk2YDNOQkXsGRsm6Xbfm6WtJ2t3WY,16002
|
51
51
|
modal/parallel_map.pyi,sha256=pOhT0P3DDYlwLx0fR3PTsecA7DI8uOdXC1N8i-ZkyOY,2328
|
52
|
-
modal/partial_function.py,sha256=
|
52
|
+
modal/partial_function.py,sha256=938kcVJHcdGXKWsO7NE_FBxPldZ304a_GyhjxD79wHE,28215
|
53
53
|
modal/partial_function.pyi,sha256=EafGOzZdEq-yE5bYRoMfnMqw-o8Hk_So8MRPDSB99_0,8982
|
54
54
|
modal/proxy.py,sha256=ZrOsuQP7dSZFq1OrIxalNnt0Zvsnp1h86Th679sSL40,1417
|
55
55
|
modal/proxy.pyi,sha256=UvygdOYneLTuoDY6hVaMNCyZ947Tmx93IdLjErUqkvM,368
|
56
56
|
modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
57
|
modal/queue.py,sha256=q0SDkrN2e2CNxsHjhs6xMwf03LZdvuUFYL7SZPZpKps,18390
|
58
58
|
modal/queue.pyi,sha256=di3ownBw4jc6d4X7ygXtbpjlUMOK69qyaD3lVsJbpoM,9900
|
59
|
-
modal/retries.py,sha256=
|
59
|
+
modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
|
60
60
|
modal/runner.py,sha256=7obU-Gq1ocpBGCuR6pvn1T-D6ggg1T48qFo2TNUGWkU,24089
|
61
61
|
modal/runner.pyi,sha256=RAtCvx_lXWjyFjIaZ3t9-X1c7rqpgAQlhl4Hww53OY8,5038
|
62
62
|
modal/running_app.py,sha256=CshNvGDJtagOdKW54uYjY8HY73j2TpnsL9jkPFZAsfA,560
|
@@ -159,10 +159,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
159
159
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
160
160
|
modal_version/__init__.py,sha256=3IY-AWLH55r35_mQXIaut0jrJvoPuf1NZJBQQfSbPuo,470
|
161
161
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
162
|
-
modal_version/_version_generated.py,sha256=
|
163
|
-
modal-0.67.
|
164
|
-
modal-0.67.
|
165
|
-
modal-0.67.
|
166
|
-
modal-0.67.
|
167
|
-
modal-0.67.
|
168
|
-
modal-0.67.
|
162
|
+
modal_version/_version_generated.py,sha256=n6vTPP069df_S0Gqp_hCTlgHvSYzFTpcSw2VEbRd6Mo,149
|
163
|
+
modal-0.67.13.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
164
|
+
modal-0.67.13.dist-info/METADATA,sha256=9cCPrWPQbv5ADr9zsoeQ0TW32a2uNY12MQdaINSIuyY,2329
|
165
|
+
modal-0.67.13.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
166
|
+
modal-0.67.13.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
167
|
+
modal-0.67.13.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
168
|
+
modal-0.67.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|