modal 1.0.5.dev2__py3-none-any.whl → 1.0.5.dev4__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.
Files changed (50) hide show
  1. modal/_clustered_functions.pyi +13 -3
  2. modal/_functions.py +5 -4
  3. modal/_partial_function.py +1 -1
  4. modal/_runtime/container_io_manager.pyi +222 -40
  5. modal/_runtime/execution_context.pyi +60 -6
  6. modal/_tunnel.pyi +380 -12
  7. modal/app.py +4 -4
  8. modal/app.pyi +658 -48
  9. modal/cli/run.py +2 -1
  10. modal/client.pyi +224 -28
  11. modal/cloud_bucket_mount.pyi +192 -4
  12. modal/cls.py +3 -3
  13. modal/cls.pyi +442 -35
  14. modal/container_process.pyi +103 -14
  15. modal/dict.py +1 -1
  16. modal/dict.pyi +453 -51
  17. modal/environments.pyi +41 -9
  18. modal/exception.py +2 -2
  19. modal/file_io.pyi +236 -45
  20. modal/functions.pyi +545 -56
  21. modal/gpu.py +1 -1
  22. modal/image.py +1 -1
  23. modal/image.pyi +1256 -74
  24. modal/io_streams.pyi +342 -39
  25. modal/mount.pyi +261 -31
  26. modal/network_file_system.pyi +307 -26
  27. modal/object.pyi +48 -9
  28. modal/parallel_map.pyi +144 -14
  29. modal/partial_function.pyi +255 -14
  30. modal/proxy.py +1 -1
  31. modal/proxy.pyi +28 -3
  32. modal/queue.py +1 -1
  33. modal/queue.pyi +447 -30
  34. modal/runner.pyi +160 -22
  35. modal/sandbox.py +7 -7
  36. modal/sandbox.pyi +310 -50
  37. modal/schedule.py +1 -1
  38. modal/secret.py +2 -2
  39. modal/secret.pyi +164 -15
  40. modal/snapshot.pyi +25 -4
  41. modal/token_flow.pyi +28 -8
  42. modal/volume.py +1 -1
  43. modal/volume.pyi +649 -59
  44. {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/METADATA +1 -1
  45. {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/RECORD +50 -50
  46. modal_version/__init__.py +1 -1
  47. {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/WHEEL +0 -0
  48. {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/entry_points.txt +0 -0
  49. {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/licenses/LICENSE +0 -0
  50. {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/top_level.txt +0 -0
modal/_tunnel.pyi CHANGED
@@ -4,34 +4,402 @@ import typing
4
4
  import typing_extensions
5
5
 
6
6
  class Tunnel:
7
+ """A port forwarded from within a running Modal container. Created by `modal.forward()`.
8
+
9
+ **Important:** This is an experimental API which may change in the future.
10
+ """
11
+
7
12
  host: str
8
13
  port: int
9
14
  unencrypted_host: str
10
15
  unencrypted_port: int
11
16
 
12
17
  @property
13
- def url(self) -> str: ...
18
+ def url(self) -> str:
19
+ """Get the public HTTPS URL of the forwarded port."""
20
+ ...
21
+
14
22
  @property
15
- def tls_socket(self) -> tuple[str, int]: ...
23
+ def tls_socket(self) -> tuple[str, int]:
24
+ """Get the public TLS socket as a (host, port) tuple."""
25
+ ...
26
+
16
27
  @property
17
- def tcp_socket(self) -> tuple[str, int]: ...
18
- def __init__(self, host: str, port: int, unencrypted_host: str, unencrypted_port: int) -> None: ...
19
- def __repr__(self): ...
20
- def __eq__(self, other): ...
21
- def __setattr__(self, name, value): ...
22
- def __delattr__(self, name): ...
23
- def __hash__(self): ...
28
+ def tcp_socket(self) -> tuple[str, int]:
29
+ """Get the public TCP socket as a (host, port) tuple."""
30
+ ...
31
+
32
+ def __init__(self, host: str, port: int, unencrypted_host: str, unencrypted_port: int) -> None:
33
+ """Initialize self. See help(type(self)) for accurate signature."""
34
+ ...
35
+
36
+ def __repr__(self):
37
+ """Return repr(self)."""
38
+ ...
39
+
40
+ def __eq__(self, other):
41
+ """Return self==value."""
42
+ ...
43
+
44
+ def __setattr__(self, name, value):
45
+ """Implement setattr(self, name, value)."""
46
+ ...
47
+
48
+ def __delattr__(self, name):
49
+ """Implement delattr(self, name)."""
50
+ ...
51
+
52
+ def __hash__(self):
53
+ """Return hash(self)."""
54
+ ...
24
55
 
25
56
  def _forward(
26
57
  port: int, *, unencrypted: bool = False, client: typing.Optional[modal.client._Client] = None
27
- ) -> typing.AsyncContextManager[Tunnel]: ...
58
+ ) -> typing.AsyncContextManager[Tunnel]:
59
+ '''Expose a port publicly from inside a running Modal container, with TLS.
60
+
61
+ If `unencrypted` is set, this also exposes the TCP socket without encryption on a random port
62
+ number. This can be used to SSH into a container (see example below). Note that it is on the public Internet, so
63
+ make sure you are using a secure protocol over TCP.
64
+
65
+ **Important:** This is an experimental API which may change in the future.
66
+
67
+ **Usage:**
68
+
69
+ ```python notest
70
+ import modal
71
+ from flask import Flask
72
+
73
+ app = modal.App(image=modal.Image.debian_slim().pip_install("Flask"))
74
+ flask_app = Flask(__name__)
75
+
76
+
77
+ @flask_app.route("/")
78
+ def hello_world():
79
+ return "Hello, World!"
80
+
81
+
82
+ @app.function()
83
+ def run_app():
84
+ # Start a web server inside the container at port 8000. `modal.forward(8000)` lets us
85
+ # expose that port to the world at a random HTTPS URL.
86
+ with modal.forward(8000) as tunnel:
87
+ print("Server listening at", tunnel.url)
88
+ flask_app.run("0.0.0.0", 8000)
89
+
90
+ # When the context manager exits, the port is no longer exposed.
91
+ ```
92
+
93
+ **Raw TCP usage:**
94
+
95
+ ```python
96
+ import socket
97
+ import threading
98
+
99
+ import modal
100
+
101
+
102
+ def run_echo_server(port: int):
103
+ """Run a TCP echo server listening on the given port."""
104
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
105
+ sock.bind(("0.0.0.0", port))
106
+ sock.listen(1)
107
+
108
+ while True:
109
+ conn, addr = sock.accept()
110
+ print("Connection from:", addr)
111
+
112
+ # Start a new thread to handle the connection
113
+ def handle(conn):
114
+ with conn:
115
+ while True:
116
+ data = conn.recv(1024)
117
+ if not data:
118
+ break
119
+ conn.sendall(data)
120
+
121
+ threading.Thread(target=handle, args=(conn,)).start()
122
+
123
+
124
+ app = modal.App()
125
+
126
+
127
+ @app.function()
128
+ def tcp_tunnel():
129
+ # This exposes port 8000 to public Internet traffic over TCP.
130
+ with modal.forward(8000, unencrypted=True) as tunnel:
131
+ # You can connect to this TCP socket from outside the container, for example, using `nc`:
132
+ # nc <HOST> <PORT>
133
+ print("TCP tunnel listening at:", tunnel.tcp_socket)
134
+ run_echo_server(8000)
135
+ ```
136
+
137
+ **SSH example:**
138
+ This assumes you have a rsa keypair in `~/.ssh/id_rsa{.pub}`, this is a bare-bones example
139
+ letting you SSH into a Modal container.
140
+
141
+ ```python
142
+ import subprocess
143
+ import time
144
+
145
+ import modal
146
+
147
+ app = modal.App()
148
+ image = (
149
+ modal.Image.debian_slim()
150
+ .apt_install("openssh-server")
151
+ .run_commands("mkdir /run/sshd")
152
+ .add_local_file("~/.ssh/id_rsa.pub", "/root/.ssh/authorized_keys", copy=True)
153
+ )
154
+
155
+
156
+ @app.function(image=image, timeout=3600)
157
+ def some_function():
158
+ subprocess.Popen(["/usr/sbin/sshd", "-D", "-e"])
159
+ with modal.forward(port=22, unencrypted=True) as tunnel:
160
+ hostname, port = tunnel.tcp_socket
161
+ connection_cmd = f'ssh -p {port} root@{hostname}'
162
+ print(f"ssh into container using: {connection_cmd}")
163
+ time.sleep(3600) # keep alive for 1 hour or until killed
164
+ ```
165
+
166
+ If you intend to use this more generally, a suggestion is to put the subprocess and port
167
+ forwarding code in an `@enter` lifecycle method of an @app.cls, to only make a single
168
+ ssh server and port for each container (and not one for each input to the function).
169
+ '''
170
+ ...
28
171
 
29
172
  class __forward_spec(typing_extensions.Protocol):
30
173
  def __call__(
31
174
  self, /, port: int, *, unencrypted: bool = False, client: typing.Optional[modal.client.Client] = None
32
- ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Tunnel]: ...
175
+ ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Tunnel]:
176
+ '''Expose a port publicly from inside a running Modal container, with TLS.
177
+
178
+ If `unencrypted` is set, this also exposes the TCP socket without encryption on a random port
179
+ number. This can be used to SSH into a container (see example below). Note that it is on the public Internet, so
180
+ make sure you are using a secure protocol over TCP.
181
+
182
+ **Important:** This is an experimental API which may change in the future.
183
+
184
+ **Usage:**
185
+
186
+ ```python notest
187
+ import modal
188
+ from flask import Flask
189
+
190
+ app = modal.App(image=modal.Image.debian_slim().pip_install("Flask"))
191
+ flask_app = Flask(__name__)
192
+
193
+
194
+ @flask_app.route("/")
195
+ def hello_world():
196
+ return "Hello, World!"
197
+
198
+
199
+ @app.function()
200
+ def run_app():
201
+ # Start a web server inside the container at port 8000. `modal.forward(8000)` lets us
202
+ # expose that port to the world at a random HTTPS URL.
203
+ with modal.forward(8000) as tunnel:
204
+ print("Server listening at", tunnel.url)
205
+ flask_app.run("0.0.0.0", 8000)
206
+
207
+ # When the context manager exits, the port is no longer exposed.
208
+ ```
209
+
210
+ **Raw TCP usage:**
211
+
212
+ ```python
213
+ import socket
214
+ import threading
215
+
216
+ import modal
217
+
218
+
219
+ def run_echo_server(port: int):
220
+ """Run a TCP echo server listening on the given port."""
221
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
222
+ sock.bind(("0.0.0.0", port))
223
+ sock.listen(1)
224
+
225
+ while True:
226
+ conn, addr = sock.accept()
227
+ print("Connection from:", addr)
228
+
229
+ # Start a new thread to handle the connection
230
+ def handle(conn):
231
+ with conn:
232
+ while True:
233
+ data = conn.recv(1024)
234
+ if not data:
235
+ break
236
+ conn.sendall(data)
237
+
238
+ threading.Thread(target=handle, args=(conn,)).start()
239
+
240
+
241
+ app = modal.App()
242
+
243
+
244
+ @app.function()
245
+ def tcp_tunnel():
246
+ # This exposes port 8000 to public Internet traffic over TCP.
247
+ with modal.forward(8000, unencrypted=True) as tunnel:
248
+ # You can connect to this TCP socket from outside the container, for example, using `nc`:
249
+ # nc <HOST> <PORT>
250
+ print("TCP tunnel listening at:", tunnel.tcp_socket)
251
+ run_echo_server(8000)
252
+ ```
253
+
254
+ **SSH example:**
255
+ This assumes you have a rsa keypair in `~/.ssh/id_rsa{.pub}`, this is a bare-bones example
256
+ letting you SSH into a Modal container.
257
+
258
+ ```python
259
+ import subprocess
260
+ import time
261
+
262
+ import modal
263
+
264
+ app = modal.App()
265
+ image = (
266
+ modal.Image.debian_slim()
267
+ .apt_install("openssh-server")
268
+ .run_commands("mkdir /run/sshd")
269
+ .add_local_file("~/.ssh/id_rsa.pub", "/root/.ssh/authorized_keys", copy=True)
270
+ )
271
+
272
+
273
+ @app.function(image=image, timeout=3600)
274
+ def some_function():
275
+ subprocess.Popen(["/usr/sbin/sshd", "-D", "-e"])
276
+ with modal.forward(port=22, unencrypted=True) as tunnel:
277
+ hostname, port = tunnel.tcp_socket
278
+ connection_cmd = f'ssh -p {port} root@{hostname}'
279
+ print(f"ssh into container using: {connection_cmd}")
280
+ time.sleep(3600) # keep alive for 1 hour or until killed
281
+ ```
282
+
283
+ If you intend to use this more generally, a suggestion is to put the subprocess and port
284
+ forwarding code in an `@enter` lifecycle method of an @app.cls, to only make a single
285
+ ssh server and port for each container (and not one for each input to the function).
286
+ '''
287
+ ...
288
+
33
289
  def aio(
34
290
  self, /, port: int, *, unencrypted: bool = False, client: typing.Optional[modal.client.Client] = None
35
- ) -> typing.AsyncContextManager[Tunnel]: ...
291
+ ) -> typing.AsyncContextManager[Tunnel]:
292
+ '''Expose a port publicly from inside a running Modal container, with TLS.
293
+
294
+ If `unencrypted` is set, this also exposes the TCP socket without encryption on a random port
295
+ number. This can be used to SSH into a container (see example below). Note that it is on the public Internet, so
296
+ make sure you are using a secure protocol over TCP.
297
+
298
+ **Important:** This is an experimental API which may change in the future.
299
+
300
+ **Usage:**
301
+
302
+ ```python notest
303
+ import modal
304
+ from flask import Flask
305
+
306
+ app = modal.App(image=modal.Image.debian_slim().pip_install("Flask"))
307
+ flask_app = Flask(__name__)
308
+
309
+
310
+ @flask_app.route("/")
311
+ def hello_world():
312
+ return "Hello, World!"
313
+
314
+
315
+ @app.function()
316
+ def run_app():
317
+ # Start a web server inside the container at port 8000. `modal.forward(8000)` lets us
318
+ # expose that port to the world at a random HTTPS URL.
319
+ with modal.forward(8000) as tunnel:
320
+ print("Server listening at", tunnel.url)
321
+ flask_app.run("0.0.0.0", 8000)
322
+
323
+ # When the context manager exits, the port is no longer exposed.
324
+ ```
325
+
326
+ **Raw TCP usage:**
327
+
328
+ ```python
329
+ import socket
330
+ import threading
331
+
332
+ import modal
333
+
334
+
335
+ def run_echo_server(port: int):
336
+ """Run a TCP echo server listening on the given port."""
337
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
338
+ sock.bind(("0.0.0.0", port))
339
+ sock.listen(1)
340
+
341
+ while True:
342
+ conn, addr = sock.accept()
343
+ print("Connection from:", addr)
344
+
345
+ # Start a new thread to handle the connection
346
+ def handle(conn):
347
+ with conn:
348
+ while True:
349
+ data = conn.recv(1024)
350
+ if not data:
351
+ break
352
+ conn.sendall(data)
353
+
354
+ threading.Thread(target=handle, args=(conn,)).start()
355
+
356
+
357
+ app = modal.App()
358
+
359
+
360
+ @app.function()
361
+ def tcp_tunnel():
362
+ # This exposes port 8000 to public Internet traffic over TCP.
363
+ with modal.forward(8000, unencrypted=True) as tunnel:
364
+ # You can connect to this TCP socket from outside the container, for example, using `nc`:
365
+ # nc <HOST> <PORT>
366
+ print("TCP tunnel listening at:", tunnel.tcp_socket)
367
+ run_echo_server(8000)
368
+ ```
369
+
370
+ **SSH example:**
371
+ This assumes you have a rsa keypair in `~/.ssh/id_rsa{.pub}`, this is a bare-bones example
372
+ letting you SSH into a Modal container.
373
+
374
+ ```python
375
+ import subprocess
376
+ import time
377
+
378
+ import modal
379
+
380
+ app = modal.App()
381
+ image = (
382
+ modal.Image.debian_slim()
383
+ .apt_install("openssh-server")
384
+ .run_commands("mkdir /run/sshd")
385
+ .add_local_file("~/.ssh/id_rsa.pub", "/root/.ssh/authorized_keys", copy=True)
386
+ )
387
+
388
+
389
+ @app.function(image=image, timeout=3600)
390
+ def some_function():
391
+ subprocess.Popen(["/usr/sbin/sshd", "-D", "-e"])
392
+ with modal.forward(port=22, unencrypted=True) as tunnel:
393
+ hostname, port = tunnel.tcp_socket
394
+ connection_cmd = f'ssh -p {port} root@{hostname}'
395
+ print(f"ssh into container using: {connection_cmd}")
396
+ time.sleep(3600) # keep alive for 1 hour or until killed
397
+ ```
398
+
399
+ If you intend to use this more generally, a suggestion is to put the subprocess and port
400
+ forwarding code in an `@enter` lifecycle method of an @app.cls, to only make a single
401
+ ssh server and port for each container (and not one for each input to the function).
402
+ '''
403
+ ...
36
404
 
37
405
  forward: __forward_spec
modal/app.py CHANGED
@@ -550,8 +550,8 @@ class _App:
550
550
  modal run app_module.py
551
551
  ```
552
552
 
553
- Note that an explicit [`app.run()`](/docs/reference/modal.App#run) is not needed, as an
554
- [app](/docs/guide/apps) is automatically created for you.
553
+ Note that an explicit [`app.run()`](https://modal.com/docs/reference/modal.App#run) is not needed, as an
554
+ [app](https://modal.com/docs/guide/apps) is automatically created for you.
555
555
 
556
556
  **Multiple Entrypoints**
557
557
 
@@ -657,7 +657,7 @@ class _App:
657
657
  _experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
658
658
  allow_cross_region_volumes: Optional[bool] = None, # Always True on the Modal backend now
659
659
  ) -> _FunctionDecoratorType:
660
- """Decorator to register a new Modal [Function](/docs/reference/modal.Function) with this App."""
660
+ """Decorator to register a new Modal Function with this App."""
661
661
  if isinstance(_warn_parentheses_missing, _Image):
662
662
  # Handle edge case where maybe (?) some users passed image as a positional arg
663
663
  raise InvalidError("`image` needs to be a keyword argument: `@app.function(image=image)`.")
@@ -881,7 +881,7 @@ class _App:
881
881
  allow_cross_region_volumes: Optional[bool] = None, # Always True on the Modal backend now
882
882
  ) -> Callable[[Union[CLS_T, _PartialFunction]], CLS_T]:
883
883
  """
884
- Decorator to register a new Modal [Cls](/docs/reference/modal.Cls) with this App.
884
+ Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App.
885
885
  """
886
886
  if _warn_parentheses_missing:
887
887
  raise InvalidError("Did you forget parentheses? Suggestion: `@app.cls()`.")