modal 0.62.115__py3-none-any.whl → 0.72.13__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/__init__.py +13 -9
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +402 -398
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -60
- modal/_resources.py +26 -7
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1025 -0
- modal/{execution_context.py → _runtime/execution_context.py} +11 -2
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +123 -6
- modal/_traceback.py +47 -187
- modal/_tunnel.py +50 -14
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +386 -104
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +299 -98
- modal/_utils/grpc_testing.py +47 -34
- modal/_utils/grpc_utils.py +54 -21
- modal/_utils/hash_utils.py +51 -10
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +3 -3
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +12 -10
- modal/app.py +561 -323
- modal/app.pyi +474 -262
- modal/call_graph.py +7 -6
- modal/cli/_download.py +22 -6
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +203 -42
- modal/cli/config.py +12 -5
- modal/cli/container.py +61 -13
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +21 -48
- modal/cli/launch.py +28 -14
- modal/cli/network_file_system.py +57 -21
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +34 -9
- modal/cli/programs/vscode.py +58 -8
- modal/cli/queues.py +131 -0
- modal/cli/run.py +199 -96
- modal/cli/secret.py +5 -4
- modal/cli/token.py +7 -2
- modal/cli/utils.py +74 -8
- modal/cli/volume.py +97 -56
- modal/client.py +248 -144
- modal/client.pyi +156 -124
- modal/cloud_bucket_mount.py +43 -30
- modal/cloud_bucket_mount.pyi +32 -25
- modal/cls.py +528 -141
- modal/cls.pyi +189 -145
- modal/config.py +32 -15
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +50 -54
- modal/dict.pyi +120 -164
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +30 -43
- modal/experimental.py +62 -2
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +196 -0
- modal/functions.py +846 -428
- modal/functions.pyi +446 -387
- modal/gpu.py +57 -44
- modal/image.py +943 -417
- modal/image.pyi +584 -245
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +223 -90
- modal/mount.pyi +241 -243
- modal/network_file_system.py +85 -86
- modal/network_file_system.pyi +151 -110
- modal/object.py +66 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +73 -47
- modal/parallel_map.pyi +51 -63
- modal/partial_function.py +272 -107
- modal/partial_function.pyi +219 -120
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +96 -72
- modal/queue.pyi +210 -135
- modal/requirements/2024.04.txt +2 -1
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +45 -4
- modal/runner.py +325 -203
- modal/runner.pyi +124 -110
- modal/running_app.py +27 -4
- modal/sandbox.py +509 -231
- modal/sandbox.pyi +396 -169
- modal/schedule.py +2 -2
- modal/scheduler_placement.py +20 -3
- modal/secret.py +41 -25
- modal/secret.pyi +62 -42
- modal/serving.py +39 -49
- modal/serving.pyi +37 -43
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +123 -137
- modal/volume.pyi +228 -221
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/METADATA +5 -5
- modal-0.72.13.dist-info/RECORD +174 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +1 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1231 -531
- modal_proto/api_grpc.py +750 -430
- modal_proto/api_pb2.py +2102 -1176
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1329 -675
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_exec.py +0 -128
- modal/_container_io_manager.py +0 -646
- modal/_container_io_manager.pyi +0 -412
- modal/_sandbox_shell.py +0 -49
- modal/app_utils.py +0 -20
- modal/app_utils.pyi +0 -17
- modal/execution_context.pyi +0 -37
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal-0.62.115.dist-info/RECORD +0 -207
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -279
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -674
- test/client_test.py +0 -203
- test/cloud_bucket_mount_test.py +0 -22
- test/cls_test.py +0 -636
- test/config_test.py +0 -149
- test/conftest.py +0 -1485
- test/container_app_test.py +0 -50
- test/container_test.py +0 -1405
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -51
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -791
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -82
- test/helpers.py +0 -47
- test/image_test.py +0 -814
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -327
- test/network_file_system_test.py +0 -188
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -115
- test/resolver_test.py +0 -59
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -57
- test/secret_test.py +0 -89
- test/serialization_test.py +0 -50
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -361
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -397
- test/watcher_test.py +0 -58
- test/webhook_test.py +0 -145
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/LICENSE +0 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/WHEEL +0 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/entry_points.txt +0 -0
test/client_test.py
DELETED
@@ -1,203 +0,0 @@
|
|
1
|
-
# Copyright Modal Labs 2022
|
2
|
-
import platform
|
3
|
-
import pytest
|
4
|
-
import subprocess
|
5
|
-
import sys
|
6
|
-
|
7
|
-
from google.protobuf.empty_pb2 import Empty
|
8
|
-
|
9
|
-
import modal.exception
|
10
|
-
from modal import Client
|
11
|
-
from modal.exception import AuthError, ConnectionError, DeprecationError, InvalidError, VersionError
|
12
|
-
from modal_proto import api_pb2
|
13
|
-
|
14
|
-
from .supports.skip import skip_windows_unix_socket
|
15
|
-
|
16
|
-
TEST_TIMEOUT = 4.0 # align this with the container client timeout in client.py
|
17
|
-
|
18
|
-
|
19
|
-
def test_client_type(servicer, client):
|
20
|
-
assert len(servicer.requests) == 1
|
21
|
-
assert isinstance(servicer.requests[0], Empty)
|
22
|
-
assert servicer.client_create_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CLIENT)
|
23
|
-
|
24
|
-
|
25
|
-
def test_client_platform_string(servicer, client):
|
26
|
-
platform_str = servicer.client_create_metadata["x-modal-platform"]
|
27
|
-
system, release, machine = platform_str.split("-")
|
28
|
-
if platform.system() == "Darwin":
|
29
|
-
assert system == "macOS"
|
30
|
-
assert release == platform.mac_ver()[0].replace("-", "_")
|
31
|
-
else:
|
32
|
-
assert system == platform.system().replace("-", "_")
|
33
|
-
assert release == platform.release().replace("-", "_")
|
34
|
-
assert machine == platform.machine().replace("-", "_")
|
35
|
-
|
36
|
-
|
37
|
-
@pytest.mark.asyncio
|
38
|
-
@skip_windows_unix_socket
|
39
|
-
async def test_container_client_type(unix_servicer, container_client):
|
40
|
-
assert len(unix_servicer.requests) == 1 # no heartbeat, just ClientHello
|
41
|
-
assert isinstance(unix_servicer.requests[0], Empty)
|
42
|
-
assert unix_servicer.client_create_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CONTAINER)
|
43
|
-
|
44
|
-
|
45
|
-
@pytest.mark.asyncio
|
46
|
-
@pytest.mark.timeout(TEST_TIMEOUT)
|
47
|
-
async def test_client_dns_failure():
|
48
|
-
with pytest.raises(ConnectionError) as excinfo:
|
49
|
-
async with Client("https://xyz.invalid", api_pb2.CLIENT_TYPE_CONTAINER, None):
|
50
|
-
pass
|
51
|
-
assert excinfo.value
|
52
|
-
|
53
|
-
|
54
|
-
@pytest.mark.asyncio
|
55
|
-
@pytest.mark.timeout(TEST_TIMEOUT)
|
56
|
-
@skip_windows_unix_socket
|
57
|
-
async def test_client_connection_failure():
|
58
|
-
with pytest.raises(ConnectionError) as excinfo:
|
59
|
-
async with Client("https://localhost:443", api_pb2.CLIENT_TYPE_CONTAINER, None):
|
60
|
-
pass
|
61
|
-
assert excinfo.value
|
62
|
-
|
63
|
-
|
64
|
-
@pytest.mark.asyncio
|
65
|
-
@pytest.mark.timeout(TEST_TIMEOUT)
|
66
|
-
@skip_windows_unix_socket
|
67
|
-
async def test_client_connection_failure_unix_socket():
|
68
|
-
with pytest.raises(ConnectionError) as excinfo:
|
69
|
-
async with Client("unix:/tmp/xyz.txt", api_pb2.CLIENT_TYPE_CONTAINER, None):
|
70
|
-
pass
|
71
|
-
assert excinfo.value
|
72
|
-
|
73
|
-
|
74
|
-
@pytest.mark.asyncio
|
75
|
-
@pytest.mark.timeout(TEST_TIMEOUT)
|
76
|
-
@skip_windows_unix_socket
|
77
|
-
async def test_client_connection_timeout(unix_servicer, monkeypatch):
|
78
|
-
monkeypatch.setattr("modal.client.CLIENT_CREATE_ATTEMPT_TIMEOUT", 1.0)
|
79
|
-
monkeypatch.setattr("modal.client.CLIENT_CREATE_TOTAL_TIMEOUT", 3.0)
|
80
|
-
with pytest.raises(ConnectionError) as excinfo:
|
81
|
-
async with Client(unix_servicer.remote_addr, api_pb2.CLIENT_TYPE_CONTAINER, None, version="timeout"):
|
82
|
-
pass
|
83
|
-
|
84
|
-
# The HTTP lookup will return 400 because the GRPC server rejects the http request
|
85
|
-
assert "deadline" in str(excinfo.value).lower()
|
86
|
-
|
87
|
-
|
88
|
-
@pytest.mark.asyncio
|
89
|
-
@pytest.mark.timeout(TEST_TIMEOUT)
|
90
|
-
async def test_client_server_error(servicer):
|
91
|
-
with pytest.raises(ConnectionError) as excinfo:
|
92
|
-
async with Client("https://github.com", api_pb2.CLIENT_TYPE_CLIENT, None):
|
93
|
-
pass
|
94
|
-
# Can't connect over gRPC, but the HTTP lookup should succeed
|
95
|
-
assert "HTTP status: 200" in str(excinfo.value)
|
96
|
-
|
97
|
-
|
98
|
-
@pytest.mark.asyncio
|
99
|
-
async def test_client_old_version(servicer):
|
100
|
-
with pytest.raises(VersionError):
|
101
|
-
async with Client(servicer.remote_addr, api_pb2.CLIENT_TYPE_CLIENT, ("foo-id", "foo-secret"), version="0.0.0"):
|
102
|
-
pass
|
103
|
-
|
104
|
-
|
105
|
-
@pytest.mark.asyncio
|
106
|
-
async def test_client_deprecated(servicer):
|
107
|
-
with pytest.warns(modal.exception.DeprecationError):
|
108
|
-
async with Client(
|
109
|
-
servicer.remote_addr, api_pb2.CLIENT_TYPE_CLIENT, ("foo-id", "foo-secret"), version="deprecated"
|
110
|
-
):
|
111
|
-
pass
|
112
|
-
|
113
|
-
|
114
|
-
@pytest.mark.asyncio
|
115
|
-
async def test_client_unauthenticated(servicer):
|
116
|
-
with pytest.raises(AuthError):
|
117
|
-
async with Client(servicer.remote_addr, api_pb2.CLIENT_TYPE_CLIENT, None, version="unauthenticated"):
|
118
|
-
pass
|
119
|
-
|
120
|
-
|
121
|
-
def client_from_env(remote_addr):
|
122
|
-
_override_config = {
|
123
|
-
"server_url": remote_addr,
|
124
|
-
"token_id": "foo-id",
|
125
|
-
"token_secret": "foo-secret",
|
126
|
-
"task_id": None,
|
127
|
-
"task_secret": None,
|
128
|
-
}
|
129
|
-
return Client.from_env(_override_config=_override_config)
|
130
|
-
|
131
|
-
|
132
|
-
def test_client_from_env(servicer):
|
133
|
-
try:
|
134
|
-
# First, a failing one
|
135
|
-
with pytest.raises(ConnectionError):
|
136
|
-
client_from_env("https://foo.invalid")
|
137
|
-
|
138
|
-
# Make sure later clients can still succeed
|
139
|
-
client_1 = client_from_env(servicer.remote_addr)
|
140
|
-
client_2 = client_from_env(servicer.remote_addr)
|
141
|
-
assert isinstance(client_1, Client)
|
142
|
-
assert isinstance(client_2, Client)
|
143
|
-
assert client_1 == client_2
|
144
|
-
|
145
|
-
finally:
|
146
|
-
Client.set_env_client(None)
|
147
|
-
|
148
|
-
try:
|
149
|
-
# After stopping, creating a new client should return a new one
|
150
|
-
client_3 = client_from_env(servicer.remote_addr)
|
151
|
-
client_4 = client_from_env(servicer.remote_addr)
|
152
|
-
assert client_3 != client_1
|
153
|
-
assert client_4 == client_3
|
154
|
-
finally:
|
155
|
-
Client.set_env_client(None)
|
156
|
-
|
157
|
-
|
158
|
-
def test_multiple_profile_error(servicer, modal_config):
|
159
|
-
config = """
|
160
|
-
[prof-1]
|
161
|
-
token_id = 'ak-abc'
|
162
|
-
token_secret = 'as_xyz'
|
163
|
-
active = true
|
164
|
-
|
165
|
-
[prof-2]
|
166
|
-
token_id = 'ak-abc'
|
167
|
-
token_secret = 'as_xyz'
|
168
|
-
active = true
|
169
|
-
"""
|
170
|
-
with modal_config(config):
|
171
|
-
with pytest.raises(InvalidError, match="More than one Modal profile is active"):
|
172
|
-
Client.verify(servicer.remote_addr, None)
|
173
|
-
|
174
|
-
|
175
|
-
def test_implicit_default_profile_warning(servicer, modal_config):
|
176
|
-
config = """
|
177
|
-
[default]
|
178
|
-
token_id = 'ak-abc'
|
179
|
-
token_secret = 'as_xyz'
|
180
|
-
|
181
|
-
[other]
|
182
|
-
token_id = 'ak-abc'
|
183
|
-
token_secret = 'as_xyz'
|
184
|
-
"""
|
185
|
-
with modal_config(config):
|
186
|
-
with pytest.warns(DeprecationError, match="Support for using an implicit 'default' profile is deprecated."):
|
187
|
-
Client.verify(servicer.remote_addr, None)
|
188
|
-
|
189
|
-
config = """
|
190
|
-
[default]
|
191
|
-
token_id = 'ak-abc'
|
192
|
-
token_secret = 'as_xyz'
|
193
|
-
"""
|
194
|
-
with modal_config(config):
|
195
|
-
# A single profile should be fine, even if not explicitly active and named 'default'
|
196
|
-
Client.verify(servicer.remote_addr, None)
|
197
|
-
|
198
|
-
|
199
|
-
def test_import_modal_from_thread(supports_dir):
|
200
|
-
# this mainly ensures that we don't make any assumptions about which thread *imports* modal
|
201
|
-
# For example, in Python <3.10, creating loop-bound asyncio primitives in global scope would
|
202
|
-
# trigger an exception if there is no event loop in the thread (and it's not the main thread)
|
203
|
-
subprocess.check_call([sys.executable, supports_dir / "import_modal_from_thread.py"])
|
test/cloud_bucket_mount_test.py
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
# Copyright Modal Labs 2024
|
2
|
-
import modal
|
3
|
-
|
4
|
-
|
5
|
-
def dummy():
|
6
|
-
pass
|
7
|
-
|
8
|
-
|
9
|
-
def test_volume_mount(client, servicer):
|
10
|
-
app = modal.App()
|
11
|
-
secret = modal.Secret.from_dict({"AWS_ACCESS_KEY_ID": "1", "AWS_SECRET_ACCESS_KEY": "2"})
|
12
|
-
cld_bckt_mnt = modal.CloudBucketMount(
|
13
|
-
bucket_name="foo",
|
14
|
-
bucket_endpoint_url="https://1234.r2.cloudflarestorage.com",
|
15
|
-
secret=secret,
|
16
|
-
read_only=False,
|
17
|
-
)
|
18
|
-
|
19
|
-
_ = app.function(volumes={"/root/foo": cld_bckt_mnt})(dummy)
|
20
|
-
|
21
|
-
with app.run(client=client):
|
22
|
-
pass
|