modal 1.0.2.dev7__py3-none-any.whl → 1.0.3.dev1__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/bytes_io_segment_payload.py +17 -3
- modal/client.pyi +2 -2
- modal/sandbox.py +2 -3
- modal/sandbox.pyi +6 -6
- modal/schedule.py +17 -4
- {modal-1.0.2.dev7.dist-info → modal-1.0.3.dev1.dist-info}/METADATA +1 -1
- {modal-1.0.2.dev7.dist-info → modal-1.0.3.dev1.dist-info}/RECORD +12 -12
- modal_version/__init__.py +1 -1
- {modal-1.0.2.dev7.dist-info → modal-1.0.3.dev1.dist-info}/WHEEL +0 -0
- {modal-1.0.2.dev7.dist-info → modal-1.0.3.dev1.dist-info}/entry_points.txt +0 -0
- {modal-1.0.2.dev7.dist-info → modal-1.0.3.dev1.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.2.dev7.dist-info → modal-1.0.3.dev1.dist-info}/top_level.txt +0 -0
@@ -8,14 +8,14 @@ from typing import BinaryIO, Callable, Optional
|
|
8
8
|
# Note: this module needs to import aiohttp in global scope
|
9
9
|
# This takes about 50ms and isn't needed in many cases for Modal execution
|
10
10
|
# To avoid this, we import it in local scope when needed (blob_utils.py)
|
11
|
-
from aiohttp import
|
11
|
+
from aiohttp import Payload
|
12
12
|
from aiohttp.abc import AbstractStreamWriter
|
13
13
|
|
14
14
|
# read ~16MiB chunks by default
|
15
15
|
DEFAULT_SEGMENT_CHUNK_SIZE = 2**24
|
16
16
|
|
17
17
|
|
18
|
-
class BytesIOSegmentPayload(
|
18
|
+
class BytesIOSegmentPayload(Payload):
|
19
19
|
"""Modified bytes payload for concurrent sends of chunks from the same file.
|
20
20
|
|
21
21
|
Adds:
|
@@ -26,6 +26,8 @@ class BytesIOSegmentPayload(BytesIOPayload):
|
|
26
26
|
Feels like this should be in some standard lib...
|
27
27
|
"""
|
28
28
|
|
29
|
+
_value: BinaryIO
|
30
|
+
|
29
31
|
def __init__(
|
30
32
|
self,
|
31
33
|
bytes_io: BinaryIO, # should *not* be shared as IO position modification is not locked
|
@@ -36,6 +38,7 @@ class BytesIOSegmentPayload(BytesIOPayload):
|
|
36
38
|
):
|
37
39
|
# not thread safe constructor!
|
38
40
|
super().__init__(bytes_io)
|
41
|
+
self._size = segment_length
|
39
42
|
self.initial_seek_pos = bytes_io.tell()
|
40
43
|
self.segment_start = segment_start
|
41
44
|
self.segment_length = segment_length
|
@@ -46,6 +49,10 @@ class BytesIOSegmentPayload(BytesIOPayload):
|
|
46
49
|
self.progress_report_cb = progress_report_cb or (lambda *_, **__: None)
|
47
50
|
self.reset_state()
|
48
51
|
|
52
|
+
def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
|
53
|
+
self._value.seek(self.initial_seek_pos)
|
54
|
+
return self._value.read().decode(encoding, errors)
|
55
|
+
|
49
56
|
def reset_state(self):
|
50
57
|
self._md5_checksum = hashlib.md5()
|
51
58
|
self.num_bytes_read = 0
|
@@ -76,14 +83,21 @@ class BytesIOSegmentPayload(BytesIOPayload):
|
|
76
83
|
return self._md5_checksum
|
77
84
|
|
78
85
|
async def write(self, writer: "AbstractStreamWriter"):
|
86
|
+
# On aiohttp < 3.12.0 - this is the method that's being called on a custom payload,
|
87
|
+
# but on aiohttp 3.12+ `write_with_length` is called directly.
|
88
|
+
await self.write_with_length(writer, None)
|
89
|
+
|
90
|
+
async def write_with_length(self, writer: AbstractStreamWriter, content_length: Optional[int]):
|
79
91
|
loop = asyncio.get_event_loop()
|
80
92
|
|
81
93
|
async def safe_read():
|
82
94
|
read_start = self.initial_seek_pos + self.segment_start + self.num_bytes_read
|
83
95
|
self._value.seek(read_start)
|
84
96
|
num_bytes = min(self.chunk_size, self.remaining_bytes())
|
85
|
-
|
97
|
+
if content_length is not None:
|
98
|
+
num_bytes = min(num_bytes, content_length)
|
86
99
|
|
100
|
+
chunk = await loop.run_in_executor(None, self._value.read, num_bytes)
|
87
101
|
await loop.run_in_executor(None, self._md5_checksum.update, chunk)
|
88
102
|
self.num_bytes_read += len(chunk)
|
89
103
|
return chunk
|
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 = "1.0.
|
34
|
+
version: str = "1.0.3.dev1",
|
35
35
|
): ...
|
36
36
|
def is_closed(self) -> bool: ...
|
37
37
|
@property
|
@@ -94,7 +94,7 @@ class Client:
|
|
94
94
|
server_url: str,
|
95
95
|
client_type: int,
|
96
96
|
credentials: typing.Optional[tuple[str, str]],
|
97
|
-
version: str = "1.0.
|
97
|
+
version: str = "1.0.3.dev1",
|
98
98
|
): ...
|
99
99
|
def is_closed(self) -> bool: ...
|
100
100
|
@property
|
modal/sandbox.py
CHANGED
@@ -517,7 +517,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
517
517
|
|
518
518
|
return self._tunnels
|
519
519
|
|
520
|
-
async def terminate(self):
|
520
|
+
async def terminate(self) -> None:
|
521
521
|
"""Terminate Sandbox execution.
|
522
522
|
|
523
523
|
This is a no-op if the Sandbox has already finished running."""
|
@@ -525,7 +525,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
525
525
|
await retry_transient_errors(
|
526
526
|
self._client.stub.SandboxTerminate, api_pb2.SandboxTerminateRequest(sandbox_id=self.object_id)
|
527
527
|
)
|
528
|
-
await self.wait(raise_on_termination=False)
|
529
528
|
|
530
529
|
async def poll(self) -> Optional[int]:
|
531
530
|
"""Check if the Sandbox has finished running.
|
@@ -541,7 +540,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
541
540
|
|
542
541
|
return self.returncode
|
543
542
|
|
544
|
-
async def _get_task_id(self):
|
543
|
+
async def _get_task_id(self) -> str:
|
545
544
|
while not self._task_id:
|
546
545
|
resp = await self._client.stub.SandboxGetTaskId(api_pb2.SandboxGetTaskIdRequest(sandbox_id=self.object_id))
|
547
546
|
self._task_id = resp.task_id
|
modal/sandbox.pyi
CHANGED
@@ -129,9 +129,9 @@ class _Sandbox(modal._object._Object):
|
|
129
129
|
async def snapshot_filesystem(self, timeout: int = 55) -> modal.image._Image: ...
|
130
130
|
async def wait(self, raise_on_termination: bool = True): ...
|
131
131
|
async def tunnels(self, timeout: int = 50) -> dict[int, modal._tunnel.Tunnel]: ...
|
132
|
-
async def terminate(self): ...
|
132
|
+
async def terminate(self) -> None: ...
|
133
133
|
async def poll(self) -> typing.Optional[int]: ...
|
134
|
-
async def _get_task_id(self): ...
|
134
|
+
async def _get_task_id(self) -> str: ...
|
135
135
|
@typing.overload
|
136
136
|
async def exec(
|
137
137
|
self,
|
@@ -405,8 +405,8 @@ class Sandbox(modal.object.Object):
|
|
405
405
|
tunnels: __tunnels_spec[typing_extensions.Self]
|
406
406
|
|
407
407
|
class __terminate_spec(typing_extensions.Protocol[SUPERSELF]):
|
408
|
-
def __call__(self, /): ...
|
409
|
-
async def aio(self, /): ...
|
408
|
+
def __call__(self, /) -> None: ...
|
409
|
+
async def aio(self, /) -> None: ...
|
410
410
|
|
411
411
|
terminate: __terminate_spec[typing_extensions.Self]
|
412
412
|
|
@@ -417,8 +417,8 @@ class Sandbox(modal.object.Object):
|
|
417
417
|
poll: __poll_spec[typing_extensions.Self]
|
418
418
|
|
419
419
|
class ___get_task_id_spec(typing_extensions.Protocol[SUPERSELF]):
|
420
|
-
def __call__(self, /): ...
|
421
|
-
async def aio(self, /): ...
|
420
|
+
def __call__(self, /) -> str: ...
|
421
|
+
async def aio(self, /) -> str: ...
|
422
422
|
|
423
423
|
_get_task_id: ___get_task_id_spec[typing_extensions.Self]
|
424
424
|
|
modal/schedule.py
CHANGED
@@ -30,15 +30,28 @@ class Cron(Schedule):
|
|
30
30
|
We can specify different schedules with cron strings, for example:
|
31
31
|
|
32
32
|
```python
|
33
|
-
modal.Cron("5 4 * * *") # run at 4:05am every night
|
34
|
-
modal.Cron("0 9 * * 4") # runs every Thursday 9am
|
33
|
+
modal.Cron("5 4 * * *") # run at 4:05am UTC every night
|
34
|
+
modal.Cron("0 9 * * 4") # runs every Thursday at 9am UTC
|
35
35
|
```
|
36
36
|
|
37
|
+
We can also optionally specify a timezone, for example:
|
38
|
+
|
39
|
+
```python
|
40
|
+
# Run daily at 6am New York time, regardless of whether daylight saving
|
41
|
+
# is in effect (i.e. at 11am UTC in the winter, and 10am UTC in the summer):
|
42
|
+
modal.Cron("0 6 * * *", timezone="America/New_York")
|
43
|
+
```
|
44
|
+
|
45
|
+
If no timezone is specified, the default is UTC.
|
37
46
|
"""
|
38
47
|
|
39
|
-
def __init__(
|
48
|
+
def __init__(
|
49
|
+
self,
|
50
|
+
cron_string: str,
|
51
|
+
timezone: str = "UTC",
|
52
|
+
) -> None:
|
40
53
|
"""Construct a schedule that runs according to a cron expression string."""
|
41
|
-
cron = api_pb2.Schedule.Cron(cron_string=cron_string)
|
54
|
+
cron = api_pb2.Schedule.Cron(cron_string=cron_string, timezone=timezone)
|
42
55
|
super().__init__(api_pb2.Schedule(cron=cron))
|
43
56
|
|
44
57
|
|
@@ -22,7 +22,7 @@ modal/app.py,sha256=NZ_rJ9TuMfiNiLg8-gOFgufD5flGtXWPHOZI0gdD3hE,46585
|
|
22
22
|
modal/app.pyi,sha256=4-b_vbe3lNAqQPcMRpQCEDsE1zsVkQRJGUql9B7HvbM,22659
|
23
23
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
24
24
|
modal/client.py,sha256=OwISJvkgMb-rHm9Gc4i-7YcDgGiZgwJ7F_PzwZH7a6Q,16847
|
25
|
-
modal/client.pyi,sha256=
|
25
|
+
modal/client.pyi,sha256=iv4ItfqPD-p4vLR2Z0qt8UKnfmM_trZzim8JZzZrZxo,8457
|
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=dBbeARwOWftlKd1cwtM0cHFtQWSWkwVXwVmOV4w0SyI,37907
|
@@ -65,9 +65,9 @@ modal/retries.py,sha256=IvNLDM0f_GLUDD5VgEDoN09C88yoxSrCquinAuxT1Sc,5205
|
|
65
65
|
modal/runner.py,sha256=nvpnU7U2O5d2WqME1QUTTwu-NkSLLwblytlGk7HXPAw,24152
|
66
66
|
modal/runner.pyi,sha256=1AnEu48SUPnLWp3raQ2zJCV5lc85EGLkX2nL0bHWaB0,5162
|
67
67
|
modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
|
68
|
-
modal/sandbox.py,sha256=
|
69
|
-
modal/sandbox.pyi,sha256=
|
70
|
-
modal/schedule.py,sha256=
|
68
|
+
modal/sandbox.py,sha256=OGNgK4l7iuO-p3kC0bwj7Ehh-Qm2m-Odj0iWOpQb-1I,35697
|
69
|
+
modal/sandbox.pyi,sha256=hTAnOmXyGZOaUjjqiKfc6B_uU93N1Uxwj_D-o7qwbbY,28240
|
70
|
+
modal/schedule.py,sha256=SdH8jk6S0zoc1bTRVblrVw0zBsNwPlSC2gNpVxMet9g,3061
|
71
71
|
modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
|
72
72
|
modal/secret.py,sha256=I2z-rgKWl_Ix107d2_Y2OWGXdFOuJ7zMOyDfIOdFI1A,10374
|
73
73
|
modal/secret.pyi,sha256=NY_dz0UjiYyn4u4LaBZwPP3Ji7SlTLpEyzrYK2sj9HQ,3103
|
@@ -93,7 +93,7 @@ modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
|
|
93
93
|
modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
|
94
94
|
modal/_utils/async_utils.py,sha256=zjdtdA54zvNL_RuREmN5NWFhhiRcNh8z0jT2rBc5RgY,28001
|
95
95
|
modal/_utils/blob_utils.py,sha256=IexC2Jbtqp_Tkmy62ayfgzTYte0UPCNufB_v-DO21g8,18585
|
96
|
-
modal/_utils/bytes_io_segment_payload.py,sha256=
|
96
|
+
modal/_utils/bytes_io_segment_payload.py,sha256=vaXPq8b52-x6G2hwE7SrjS58pg_aRm7gV3bn3yjmTzQ,4261
|
97
97
|
modal/_utils/deprecation.py,sha256=EXP1beU4pmEqEzWMLw6E3kUfNfpmNA_VOp6i0EHi93g,4856
|
98
98
|
modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,3722
|
99
99
|
modal/_utils/function_utils.py,sha256=bhrjyOHPPXm6fAyJx3bzI1Yh56j6xh8eeMSFKdAWrHQ,26978
|
@@ -147,7 +147,7 @@ modal/requirements/2024.10.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddR
|
|
147
147
|
modal/requirements/PREVIEW.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddRo,296
|
148
148
|
modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
|
149
149
|
modal/requirements/base-images.json,sha256=57vMSqzMbLBxw5tFWSaMiIkkVEps4JfX5PAtXGnkS4U,740
|
150
|
-
modal-1.0.
|
150
|
+
modal-1.0.3.dev1.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
151
151
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
152
152
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
153
153
|
modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
|
@@ -170,10 +170,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
|
|
170
170
|
modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
171
171
|
modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
|
172
172
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
173
|
-
modal_version/__init__.py,sha256
|
173
|
+
modal_version/__init__.py,sha256=-hKMo-FD_l2eMd31PqxjXyxP0vhUZAJH-NoFqCjuMyU,120
|
174
174
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
175
|
-
modal-1.0.
|
176
|
-
modal-1.0.
|
177
|
-
modal-1.0.
|
178
|
-
modal-1.0.
|
179
|
-
modal-1.0.
|
175
|
+
modal-1.0.3.dev1.dist-info/METADATA,sha256=RkvyYAet_IIc_aj4AyINWE5EDRIOX8nspvziQhbpwdg,2454
|
176
|
+
modal-1.0.3.dev1.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
177
|
+
modal-1.0.3.dev1.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
178
|
+
modal-1.0.3.dev1.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
179
|
+
modal-1.0.3.dev1.dist-info/RECORD,,
|
modal_version/__init__.py
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|