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.
- modal/_clustered_functions.pyi +13 -3
- modal/_functions.py +5 -4
- modal/_partial_function.py +1 -1
- modal/_runtime/container_io_manager.pyi +222 -40
- modal/_runtime/execution_context.pyi +60 -6
- modal/_tunnel.pyi +380 -12
- modal/app.py +4 -4
- modal/app.pyi +658 -48
- modal/cli/run.py +2 -1
- modal/client.pyi +224 -28
- modal/cloud_bucket_mount.pyi +192 -4
- modal/cls.py +3 -3
- modal/cls.pyi +442 -35
- modal/container_process.pyi +103 -14
- modal/dict.py +1 -1
- modal/dict.pyi +453 -51
- modal/environments.pyi +41 -9
- modal/exception.py +2 -2
- modal/file_io.pyi +236 -45
- modal/functions.pyi +545 -56
- modal/gpu.py +1 -1
- modal/image.py +1 -1
- modal/image.pyi +1256 -74
- modal/io_streams.pyi +342 -39
- modal/mount.pyi +261 -31
- modal/network_file_system.pyi +307 -26
- modal/object.pyi +48 -9
- modal/parallel_map.pyi +144 -14
- modal/partial_function.pyi +255 -14
- modal/proxy.py +1 -1
- modal/proxy.pyi +28 -3
- modal/queue.py +1 -1
- modal/queue.pyi +447 -30
- modal/runner.pyi +160 -22
- modal/sandbox.py +7 -7
- modal/sandbox.pyi +310 -50
- modal/schedule.py +1 -1
- modal/secret.py +2 -2
- modal/secret.pyi +164 -15
- modal/snapshot.pyi +25 -4
- modal/token_flow.pyi +28 -8
- modal/volume.py +1 -1
- modal/volume.pyi +649 -59
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/METADATA +1 -1
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/RECORD +50 -50
- modal_version/__init__.py +1 -1
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/WHEEL +0 -0
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/entry_points.txt +0 -0
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def
|
22
|
-
|
23
|
-
|
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
|
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()`.")
|