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.
@@ -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 BytesIOPayload
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(BytesIOPayload):
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
- chunk = await loop.run_in_executor(None, self._value.read, num_bytes)
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.2.dev7",
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.2.dev7",
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__(self, cron_string: str) -> None:
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.2.dev7
3
+ Version: 1.0.3.dev1
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -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=IFHVoiQH4U-2TuDXrc8PryTzufS663548yLTM6QNvhQ,8457
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=zUldeCA98GLObLT1N9v1IILomcPYeLrQJRCRUMTtbLE,35734
69
- modal/sandbox.pyi,sha256=stxwoLcyQNToPISj6umlU8sDUgqzeooLdMs3BwIr740,28195
70
- modal/schedule.py,sha256=ewa7hb9NKYnoeSCW2PujZAbGGJL8btX6X3KalCFpc_M,2626
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=DT4roLCafjexASGyM1lPOR0HlwOYLA9UQqlxzTgUttE,3614
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.2.dev7.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
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=LrqnzPGRtSa2NTB6joxhCn57VhaIcPxD3pO9FGvf-1o,120
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.2.dev7.dist-info/METADATA,sha256=LL0K520dCRhZwQOf3adi1EvRvswmmg3DU3hG9Bz6SR4,2454
176
- modal-1.0.2.dev7.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
177
- modal-1.0.2.dev7.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
178
- modal-1.0.2.dev7.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
179
- modal-1.0.2.dev7.dist-info/RECORD,,
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
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.0.2.dev7"
4
+ __version__ = "1.0.3.dev1"