modal 0.66.43__py3-none-any.whl → 0.66.45__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/cli/launch.py CHANGED
@@ -25,7 +25,7 @@ launch_cli = Typer(
25
25
  )
26
26
 
27
27
 
28
- def _launch_program(name: str, filename: str, args: Dict[str, Any]) -> None:
28
+ def _launch_program(name: str, filename: str, detach: bool, args: Dict[str, Any]) -> None:
29
29
  os.environ["MODAL_LAUNCH_ARGS"] = json.dumps(args)
30
30
 
31
31
  program_path = str(Path(__file__).parent / "programs" / filename)
@@ -37,7 +37,7 @@ def _launch_program(name: str, filename: str, args: Dict[str, Any]) -> None:
37
37
  func = entrypoint.info.raw_f
38
38
  isasync = inspect.iscoroutinefunction(func)
39
39
  with enable_output():
40
- with run_app(app):
40
+ with run_app(app, detach=detach):
41
41
  try:
42
42
  if isasync:
43
43
  asyncio.run(func())
@@ -57,6 +57,7 @@ def jupyter(
57
57
  add_python: Optional[str] = "3.11",
58
58
  mount: Optional[str] = None, # Create a `modal.Mount` from a local directory.
59
59
  volume: Optional[str] = None, # Attach a persisted `modal.Volume` by name (creating if missing).
60
+ detach: bool = False, # Run the app in "detached" mode to persist after local client disconnects
60
61
  ):
61
62
  args = {
62
63
  "cpu": cpu,
@@ -68,7 +69,7 @@ def jupyter(
68
69
  "mount": mount,
69
70
  "volume": volume,
70
71
  }
71
- _launch_program("jupyter", "run_jupyter.py", args)
72
+ _launch_program("jupyter", "run_jupyter.py", detach, args)
72
73
 
73
74
 
74
75
  @launch_cli.command(name="vscode", help="Start Visual Studio Code on Modal.")
@@ -79,6 +80,7 @@ def vscode(
79
80
  timeout: int = 3600,
80
81
  mount: Optional[str] = None, # Create a `modal.Mount` from a local directory.
81
82
  volume: Optional[str] = None, # Attach a persisted `modal.Volume` by name (creating if missing).
83
+ detach: bool = False, # Run the app in "detached" mode to persist after local client disconnects
82
84
  ):
83
85
  args = {
84
86
  "cpu": cpu,
@@ -88,4 +90,4 @@ def vscode(
88
90
  "mount": mount,
89
91
  "volume": volume,
90
92
  }
91
- _launch_program("vscode", "vscode.py", args)
93
+ _launch_program("vscode", "vscode.py", detach, args)
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[typing.Tuple[str, str]],
34
- version: str = "0.66.43",
34
+ version: str = "0.66.45",
35
35
  ): ...
36
36
  def is_closed(self) -> bool: ...
37
37
  @property
@@ -90,7 +90,7 @@ class Client:
90
90
  server_url: str,
91
91
  client_type: int,
92
92
  credentials: typing.Optional[typing.Tuple[str, str]],
93
- version: str = "0.66.43",
93
+ version: str = "0.66.45",
94
94
  ): ...
95
95
  def is_closed(self) -> bool: ...
96
96
  @property
modal/functions.pyi CHANGED
@@ -446,11 +446,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
446
446
 
447
447
  _call_generator_nowait: ___call_generator_nowait_spec
448
448
 
449
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
449
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
450
450
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
451
451
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
452
452
 
453
- remote: __remote_spec[P, ReturnType]
453
+ remote: __remote_spec[ReturnType, P]
454
454
 
455
455
  class __remote_gen_spec(typing_extensions.Protocol):
456
456
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -462,17 +462,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
462
462
  def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
463
463
  def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
464
464
 
465
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
465
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
466
466
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
467
467
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
468
468
 
469
- _experimental_spawn: ___experimental_spawn_spec[P, ReturnType]
469
+ _experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
470
470
 
471
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
471
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
472
472
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
473
473
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
474
474
 
475
- spawn: __spawn_spec[P, ReturnType]
475
+ spawn: __spawn_spec[ReturnType, P]
476
476
 
477
477
  def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
478
478
 
modal/io_streams.py CHANGED
@@ -66,9 +66,10 @@ T = TypeVar("T", str, bytes)
66
66
 
67
67
 
68
68
  class _StreamReader(Generic[T]):
69
- """Provides an interface to buffer and fetch logs from a stream (`stdout` or `stderr`).
69
+ """Retrieve logs from a stream (`stdout` or `stderr`).
70
70
 
71
- As an asynchronous iterable, the object supports the async for statement.
71
+ As an asynchronous iterable, the object supports the `for` and `async for`
72
+ statements. Just loop over the object to read in chunks.
72
73
 
73
74
  **Usage**
74
75
 
@@ -140,12 +141,12 @@ class _StreamReader(Generic[T]):
140
141
  self._consume_container_process_task = asyncio.create_task(self._consume_container_process_stream())
141
142
 
142
143
  @property
143
- def file_descriptor(self):
144
+ def file_descriptor(self) -> int:
145
+ """Possible values are `1` for stdout and `2` for stderr."""
144
146
  return self._file_descriptor
145
147
 
146
148
  async def read(self) -> T:
147
- """Fetch and return contents of the entire stream. If EOF was received,
148
- return an empty string.
149
+ """Fetch the entire contents of the stream until EOF.
149
150
 
150
151
  **Usage**
151
152
 
@@ -157,7 +158,6 @@ class _StreamReader(Generic[T]):
157
158
 
158
159
  print(sandbox.stdout.read())
159
160
  ```
160
-
161
161
  """
162
162
  data_str = ""
163
163
  data_bytes = b""
@@ -175,9 +175,7 @@ class _StreamReader(Generic[T]):
175
175
  return cast(T, data_bytes)
176
176
 
177
177
  async def _consume_container_process_stream(self):
178
- """
179
- Consumes the container process stream and stores the messages in the buffer.
180
- """
178
+ """Consume the container process stream and store messages in the buffer."""
181
179
  if self._stream_type == StreamType.DEVNULL:
182
180
  return
183
181
 
@@ -211,9 +209,7 @@ class _StreamReader(Generic[T]):
211
209
  raise exc
212
210
 
213
211
  async def _stream_container_process(self) -> AsyncGenerator[Tuple[Optional[bytes], str], None]:
214
- """mdmd:hidden
215
- Streams the container process buffer to the reader.
216
- """
212
+ """Streams the container process buffer to the reader."""
217
213
  entry_id = 0
218
214
  if self._last_entry_id:
219
215
  entry_id = int(self._last_entry_id) + 1
@@ -232,8 +228,7 @@ class _StreamReader(Generic[T]):
232
228
  entry_id += 1
233
229
 
234
230
  async def _get_logs(self) -> AsyncGenerator[Optional[bytes], None]:
235
- """mdmd:hidden
236
- Streams sandbox or process logs from the server to the reader.
231
+ """Streams sandbox or process logs from the server to the reader.
237
232
 
238
233
  Logs returned by this method may contain partial or multiple lines at a time.
239
234
 
@@ -278,9 +273,7 @@ class _StreamReader(Generic[T]):
278
273
  raise
279
274
 
280
275
  async def _get_logs_by_line(self) -> AsyncGenerator[Optional[bytes], None]:
281
- """mdmd:hidden
282
- Processes logs from the server and yields complete lines only.
283
- """
276
+ """Process logs from the server and yield complete lines only."""
284
277
  async for message in self._get_logs():
285
278
  if message is None:
286
279
  if self._line_buffer:
@@ -325,7 +318,8 @@ MAX_BUFFER_SIZE = 2 * 1024 * 1024
325
318
  class _StreamWriter:
326
319
  """Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
327
320
 
328
- def __init__(self, object_id: str, object_type: Literal["sandbox", "container_process"], client: _Client):
321
+ def __init__(self, object_id: str, object_type: Literal["sandbox", "container_process"], client: _Client) -> None:
322
+ """mdmd:hidden"""
329
323
  self._index = 1
330
324
  self._object_id = object_id
331
325
  self._object_type = object_type
@@ -333,17 +327,16 @@ class _StreamWriter:
333
327
  self._is_closed = False
334
328
  self._buffer = bytearray()
335
329
 
336
- def get_next_index(self):
337
- """mdmd:hidden"""
330
+ def _get_next_index(self) -> int:
338
331
  index = self._index
339
332
  self._index += 1
340
333
  return index
341
334
 
342
- def write(self, data: Union[bytes, bytearray, memoryview, str]):
343
- """
344
- Writes data to stream's internal buffer, but does not drain/flush the write.
335
+ def write(self, data: Union[bytes, bytearray, memoryview, str]) -> None:
336
+ """Write data to the stream but does not send it immediately.
345
337
 
346
- This method needs to be used along with the `drain()` method which flushes the buffer.
338
+ This is non-blocking and queues the data to an internal buffer. Must be
339
+ used along with the `drain()` method, which flushes the buffer.
347
340
 
348
341
  **Usage**
349
342
 
@@ -375,22 +368,36 @@ class _StreamWriter:
375
368
  else:
376
369
  raise TypeError(f"data argument must be a bytes-like object, not {type(data).__name__}")
377
370
 
378
- def write_eof(self):
379
- """
380
- Closes the write end of the stream after the buffered write data is drained.
381
- If the process was blocked on input, it will become unblocked after `write_eof()`.
371
+ def write_eof(self) -> None:
372
+ """Close the write end of the stream after the buffered data is drained.
382
373
 
383
- This method needs to be used along with the `drain()` method which flushes the EOF to the process.
374
+ If the process was blocked on input, it will become unblocked after
375
+ `write_eof()`. This method needs to be used along with the `drain()`
376
+ method, which flushes the EOF to the process.
384
377
  """
385
378
  self._is_closed = True
386
379
 
387
- async def drain(self):
388
- """
389
- Flushes the write buffer to the running process. Flushes the EOF if the writer is closed.
380
+ async def drain(self) -> None:
381
+ """Flush the write buffer and send data to the running process.
382
+
383
+ This is a flow control method that blocks until data is sent. It returns
384
+ when it is appropriate to continue writing data to the stream.
385
+
386
+ **Usage**
387
+
388
+ ```python
389
+ # Synchronous
390
+ writer.write(data)
391
+ writer.drain()
392
+
393
+ # Async
394
+ writer.write(data)
395
+ await writer.drain.aio()
396
+ ```
390
397
  """
391
398
  data = bytes(self._buffer)
392
399
  self._buffer.clear()
393
- index = self.get_next_index()
400
+ index = self._get_next_index()
394
401
 
395
402
  try:
396
403
  if self._object_type == "sandbox":
modal/io_streams.pyi CHANGED
@@ -26,7 +26,7 @@ class _StreamReader(typing.Generic[T]):
26
26
  by_line: bool = False,
27
27
  ) -> None: ...
28
28
  @property
29
- def file_descriptor(self): ...
29
+ def file_descriptor(self) -> int: ...
30
30
  async def read(self) -> T: ...
31
31
  async def _consume_container_process_stream(self): ...
32
32
  def _stream_container_process(self) -> typing.AsyncGenerator[typing.Tuple[typing.Optional[bytes], str], None]: ...
@@ -38,11 +38,11 @@ class _StreamReader(typing.Generic[T]):
38
38
  class _StreamWriter:
39
39
  def __init__(
40
40
  self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client._Client
41
- ): ...
42
- def get_next_index(self): ...
43
- def write(self, data: typing.Union[bytes, bytearray, memoryview, str]): ...
44
- def write_eof(self): ...
45
- async def drain(self): ...
41
+ ) -> None: ...
42
+ def _get_next_index(self) -> int: ...
43
+ def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None: ...
44
+ def write_eof(self) -> None: ...
45
+ async def drain(self) -> None: ...
46
46
 
47
47
  T_INNER = typing.TypeVar("T_INNER", covariant=True)
48
48
 
@@ -60,7 +60,7 @@ class StreamReader(typing.Generic[T]):
60
60
  by_line: bool = False,
61
61
  ) -> None: ...
62
62
  @property
63
- def file_descriptor(self): ...
63
+ def file_descriptor(self) -> int: ...
64
64
 
65
65
  class __read_spec(typing_extensions.Protocol[T_INNER]):
66
66
  def __call__(self) -> T_INNER: ...
@@ -100,13 +100,13 @@ class StreamReader(typing.Generic[T]):
100
100
  class StreamWriter:
101
101
  def __init__(
102
102
  self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client.Client
103
- ): ...
104
- def get_next_index(self): ...
105
- def write(self, data: typing.Union[bytes, bytearray, memoryview, str]): ...
106
- def write_eof(self): ...
103
+ ) -> None: ...
104
+ def _get_next_index(self) -> int: ...
105
+ def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None: ...
106
+ def write_eof(self) -> None: ...
107
107
 
108
108
  class __drain_spec(typing_extensions.Protocol):
109
- def __call__(self): ...
110
- async def aio(self): ...
109
+ def __call__(self) -> None: ...
110
+ async def aio(self) -> None: ...
111
111
 
112
112
  drain: __drain_spec
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.66.43
3
+ Version: 0.66.45
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -19,7 +19,7 @@ modal/app.py,sha256=QEBK8qYSrux36oi3iS3msBQmcUOS1r4s2nengzzynjQ,44658
19
19
  modal/app.pyi,sha256=wHwBIDqkUb2CQzYVhxZafJ8xZ17TZ-8y-cRyOeZsEm0,25182
20
20
  modal/call_graph.py,sha256=l-Wi6vM8aosCdHTWegcCyGeVJGFdZ_fzlCmbRVPBXFI,2593
21
21
  modal/client.py,sha256=4SpWb4n0nolITR36kADZl1tYLOg6avukmzZU56UQjCo,16385
22
- modal/client.pyi,sha256=rygeOYppvKVoM_DviDsgkMWVmNq1dWUFcr2PZEX7rmg,7372
22
+ modal/client.pyi,sha256=ff5IGJxJXwH0-8hhT2-74k9Md0GGmQlcmloXCqdI2bo,7372
23
23
  modal/cloud_bucket_mount.py,sha256=eWQhCtMIczpokjfTZEgNBCGO_s5ft46PqTSLfKBykq4,5748
24
24
  modal/cloud_bucket_mount.pyi,sha256=tTF7M4FR9bTA30cFkz8qq3ZTlFL19NHU_36e_5GgAGA,1424
25
25
  modal/cls.py,sha256=apKnBOHKYEpBiMC8mRvHtCDJl1g0vP0tG1r8mUZ1yH0,24684
@@ -34,12 +34,12 @@ modal/environments.pyi,sha256=oScvFAclF55-tL9UioLIL_SPBwgy_9O-BBvJ-PLbRgY,3542
34
34
  modal/exception.py,sha256=K-czk1oK8wFvK8snWrytXSByo2WNb9SJAlgBVPGWZBs,6417
35
35
  modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
36
36
  modal/functions.py,sha256=BxccB-3a1migZQ6JA6iiHZJQ7WQ-jYpmg9DEZoTxzcc,71639
37
- modal/functions.pyi,sha256=0JsvWf9vvj2fMSNIHoXmL9FS7sWoRU4hU6911PyqEls,24800
37
+ modal/functions.pyi,sha256=5JGM4Mhpm674Ia7h3OTsPBmZA32goyOs2oBCCUG8A3I,24800
38
38
  modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
39
39
  modal/image.py,sha256=j-NH8pLWk4jd5UOGD4y6W7DHWoeb3rG_VR7zPLSqj-Q,78927
40
40
  modal/image.pyi,sha256=QEjjnl4ZSmqt7toHww5ZbhL2Re5qaFGgH7qADcJS_vA,24493
41
- modal/io_streams.py,sha256=kZ5o-aK0lPg4-NezYxpCFmjS2Vf_TbWn49S7c2xUQ6U,14255
42
- modal/io_streams.pyi,sha256=pn6UnOjCUjQwukPYiHHdCv92CH9S9YRb_WekKGoPN94,4350
41
+ modal/io_streams.py,sha256=XUsNsxRzDrhkjyb2Hx0hugCoOEz266SHQF8wP-VgsfY,14582
42
+ modal/io_streams.pyi,sha256=WJmSI1WvZITUNBO7mnIuJgYdSKdbLaHk10V4GbttAVw,4452
43
43
  modal/mount.py,sha256=QZ4nabpbNU9tjLIPCq86rlHor9CXzADMkhJWBYfKKgg,27750
44
44
  modal/mount.pyi,sha256=nywUmeUELLY2OEnAc1NNBHmSxuEylTWBzkh6nuXkkuc,9965
45
45
  modal/network_file_system.py,sha256=P_LsILecyda1SRHU76Hk4Lq3M1HSx9shFJbaLThzw0U,14071
@@ -109,7 +109,7 @@ modal/cli/dict.py,sha256=lIEl6uxygFt3omC-oF6tHUxnFjVhy4d0InC_kZrlkvM,4454
109
109
  modal/cli/entry_point.py,sha256=aaNxFAqZcmtSjwzkYIA_Ba9CkL4cL4_i2gy5VjoXxkM,4228
110
110
  modal/cli/environment.py,sha256=eq8Rixbo8u-nJPvtGwW4-I1lXZPnevsFEv65WlSxFXY,4362
111
111
  modal/cli/import_refs.py,sha256=0sYZLcgcnor_CECq-7yX3WBs1W55nz5y65sbysxxKzY,9267
112
- modal/cli/launch.py,sha256=aY1fXxZyGn1Ih0lAzuAvzpXP6_OxvVCoZCgCIyV9Vos,2692
112
+ modal/cli/launch.py,sha256=FgZ0L-e3dLl9vRJ_IVHfSRUzCbmdyS8-u_abC42tTDo,2941
113
113
  modal/cli/network_file_system.py,sha256=p_o3wu8rh2tjHXJYrjaad__pD8hv93ypeDtfSY2fSEU,7527
114
114
  modal/cli/profile.py,sha256=s4jCYHwriOorEFCKxeGZoSWX8rXTR_hDTNFZhOA565s,3109
115
115
  modal/cli/queues.py,sha256=mJ44A319sPIrysH3A0HCIz4Or0jFey6miiuQKZoEQxo,4493
@@ -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=UnAuHBPuPSstqgdCOx0SBVdfhpeJnMlY_oxEbu44Izg,470
161
161
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
162
- modal_version/_version_generated.py,sha256=7WGR5zgAcB_J0SICoQ3twGcle4mHokidCuBdi-SVPYE,149
163
- modal-0.66.43.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
164
- modal-0.66.43.dist-info/METADATA,sha256=KMqp2KMs325ypwcMgvoCDdfV1-QnMjkjJ8uxAQ07DIw,2329
165
- modal-0.66.43.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
166
- modal-0.66.43.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
167
- modal-0.66.43.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
168
- modal-0.66.43.dist-info/RECORD,,
162
+ modal_version/_version_generated.py,sha256=UXFAH4OKxboSSDBrr37s5UWM3WNwZ-Q4bjX-tfF7Hj0,149
163
+ modal-0.66.45.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
164
+ modal-0.66.45.dist-info/METADATA,sha256=sxLqxkKCOBQaGBvhzaKx-RB1ybImrlmY0lJdeOJYGDQ,2329
165
+ modal-0.66.45.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
166
+ modal-0.66.45.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
167
+ modal-0.66.45.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
168
+ modal-0.66.45.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2024
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 43 # git: f00c791
4
+ build_number = 45 # git: fe1f199