modal 0.68.14__py3-none-any.whl → 0.68.16__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/_runtime/asgi.py +4 -0
- modal/_runtime/user_code_imports.py +13 -18
- modal/_utils/blob_utils.py +9 -87
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/http_utils.py +19 -10
- modal/client.pyi +2 -2
- modal/functions.pyi +6 -6
- modal/partial_function.py +8 -0
- modal/partial_function.pyi +8 -0
- {modal-0.68.14.dist-info → modal-0.68.16.dist-info}/METADATA +1 -1
- {modal-0.68.14.dist-info → modal-0.68.16.dist-info}/RECORD +19 -18
- modal_proto/api.proto +1 -0
- modal_proto/api_pb2.py +64 -64
- modal_proto/api_pb2.pyi +4 -1
- modal_version/_version_generated.py +1 -1
- {modal-0.68.14.dist-info → modal-0.68.16.dist-info}/LICENSE +0 -0
- {modal-0.68.14.dist-info → modal-0.68.16.dist-info}/WHEEL +0 -0
- {modal-0.68.14.dist-info → modal-0.68.16.dist-info}/entry_points.txt +0 -0
- {modal-0.68.14.dist-info → modal-0.68.16.dist-info}/top_level.txt +0 -0
modal/_runtime/asgi.py
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
|
+
|
3
|
+
# Note: this module isn't imported unless it's needed.
|
4
|
+
# This is because aiohttp is a pretty big dependency that adds significant latency when imported
|
5
|
+
|
2
6
|
import asyncio
|
3
7
|
from collections.abc import AsyncGenerator
|
4
8
|
from typing import Any, Callable, NoReturn, Optional, cast
|
@@ -9,15 +9,6 @@ import modal._runtime.container_io_manager
|
|
9
9
|
import modal.cls
|
10
10
|
import modal.object
|
11
11
|
from modal import Function
|
12
|
-
from modal._runtime.asgi import (
|
13
|
-
LifespanManager,
|
14
|
-
asgi_app_wrapper,
|
15
|
-
get_ip_address,
|
16
|
-
wait_for_web_server,
|
17
|
-
web_server_proxy,
|
18
|
-
webhook_asgi_app,
|
19
|
-
wsgi_app_wrapper,
|
20
|
-
)
|
21
12
|
from modal._utils.async_utils import synchronizer
|
22
13
|
from modal._utils.function_utils import LocalFunctionError, is_async as get_is_async, is_global_object
|
23
14
|
from modal.exception import ExecutionError, InvalidError
|
@@ -28,6 +19,7 @@ from modal_proto import api_pb2
|
|
28
19
|
if typing.TYPE_CHECKING:
|
29
20
|
import modal.app
|
30
21
|
import modal.partial_function
|
22
|
+
from modal._runtime.asgi import LifespanManager
|
31
23
|
|
32
24
|
|
33
25
|
@dataclass
|
@@ -36,7 +28,7 @@ class FinalizedFunction:
|
|
36
28
|
is_async: bool
|
37
29
|
is_generator: bool
|
38
30
|
data_format: int # api_pb2.DataFormat
|
39
|
-
lifespan_manager: Optional[LifespanManager] = None
|
31
|
+
lifespan_manager: Optional["LifespanManager"] = None
|
40
32
|
|
41
33
|
|
42
34
|
class Service(metaclass=ABCMeta):
|
@@ -63,19 +55,22 @@ def construct_webhook_callable(
|
|
63
55
|
webhook_config: api_pb2.WebhookConfig,
|
64
56
|
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
65
57
|
):
|
58
|
+
# Note: aiohttp is a significant dependency of the `asgi` module, so we import it locally
|
59
|
+
from modal._runtime import asgi
|
60
|
+
|
66
61
|
# For webhooks, the user function is used to construct an asgi app:
|
67
62
|
if webhook_config.type == api_pb2.WEBHOOK_TYPE_ASGI_APP:
|
68
63
|
# Function returns an asgi_app, which we can use as a callable.
|
69
|
-
return asgi_app_wrapper(user_defined_callable(), container_io_manager)
|
64
|
+
return asgi.asgi_app_wrapper(user_defined_callable(), container_io_manager)
|
70
65
|
|
71
66
|
elif webhook_config.type == api_pb2.WEBHOOK_TYPE_WSGI_APP:
|
72
|
-
# Function returns an wsgi_app, which we can use as a callable
|
73
|
-
return wsgi_app_wrapper(user_defined_callable(), container_io_manager)
|
67
|
+
# Function returns an wsgi_app, which we can use as a callable
|
68
|
+
return asgi.wsgi_app_wrapper(user_defined_callable(), container_io_manager)
|
74
69
|
|
75
70
|
elif webhook_config.type == api_pb2.WEBHOOK_TYPE_FUNCTION:
|
76
71
|
# Function is a webhook without an ASGI app. Create one for it.
|
77
|
-
return asgi_app_wrapper(
|
78
|
-
webhook_asgi_app(user_defined_callable, webhook_config.method, webhook_config.web_endpoint_docs),
|
72
|
+
return asgi.asgi_app_wrapper(
|
73
|
+
asgi.webhook_asgi_app(user_defined_callable, webhook_config.method, webhook_config.web_endpoint_docs),
|
79
74
|
container_io_manager,
|
80
75
|
)
|
81
76
|
|
@@ -86,11 +81,11 @@ def construct_webhook_callable(
|
|
86
81
|
# We intentionally try to connect to the external interface instead of the loopback
|
87
82
|
# interface here so users are forced to expose the server. This allows us to potentially
|
88
83
|
# change the implementation to use an external bridge in the future.
|
89
|
-
host = get_ip_address(b"eth0")
|
84
|
+
host = asgi.get_ip_address(b"eth0")
|
90
85
|
port = webhook_config.web_server_port
|
91
86
|
startup_timeout = webhook_config.web_server_startup_timeout
|
92
|
-
wait_for_web_server(host, port, timeout=startup_timeout)
|
93
|
-
return asgi_app_wrapper(web_server_proxy(host, port), container_io_manager)
|
87
|
+
asgi.wait_for_web_server(host, port, timeout=startup_timeout)
|
88
|
+
return asgi.asgi_app_wrapper(asgi.web_server_proxy(host, port), container_io_manager)
|
94
89
|
else:
|
95
90
|
raise InvalidError(f"Unrecognized web endpoint type {webhook_config.type}")
|
96
91
|
|
modal/_utils/blob_utils.py
CHANGED
@@ -9,12 +9,9 @@ import time
|
|
9
9
|
from collections.abc import AsyncIterator
|
10
10
|
from contextlib import AbstractContextManager, contextmanager
|
11
11
|
from pathlib import Path, PurePosixPath
|
12
|
-
from typing import Any, BinaryIO, Callable, Optional, Union
|
12
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Callable, Optional, Union
|
13
13
|
from urllib.parse import urlparse
|
14
14
|
|
15
|
-
from aiohttp import BytesIOPayload
|
16
|
-
from aiohttp.abc import AbstractStreamWriter
|
17
|
-
|
18
15
|
from modal_proto import api_pb2
|
19
16
|
from modal_proto.modal_api_grpc import ModalClientModal
|
20
17
|
|
@@ -25,6 +22,9 @@ from .hash_utils import UploadHashes, get_upload_hashes
|
|
25
22
|
from .http_utils import ClientSessionRegistry
|
26
23
|
from .logger import logger
|
27
24
|
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from .bytes_io_segment_payload import BytesIOSegmentPayload
|
27
|
+
|
28
28
|
# Max size for function inputs and outputs.
|
29
29
|
MAX_OBJECT_SIZE_BYTES = 2 * 1024 * 1024 # 2 MiB
|
30
30
|
|
@@ -44,92 +44,10 @@ DEFAULT_SEGMENT_CHUNK_SIZE = 2**24
|
|
44
44
|
MULTIPART_UPLOAD_THRESHOLD = 1024**3
|
45
45
|
|
46
46
|
|
47
|
-
class BytesIOSegmentPayload(BytesIOPayload):
|
48
|
-
"""Modified bytes payload for concurrent sends of chunks from the same file.
|
49
|
-
|
50
|
-
Adds:
|
51
|
-
* read limit using remaining_bytes, in order to split files across streams
|
52
|
-
* larger read chunk (to prevent excessive read contention between parts)
|
53
|
-
* calculates an md5 for the segment
|
54
|
-
|
55
|
-
Feels like this should be in some standard lib...
|
56
|
-
"""
|
57
|
-
|
58
|
-
def __init__(
|
59
|
-
self,
|
60
|
-
bytes_io: BinaryIO, # should *not* be shared as IO position modification is not locked
|
61
|
-
segment_start: int,
|
62
|
-
segment_length: int,
|
63
|
-
chunk_size: int = DEFAULT_SEGMENT_CHUNK_SIZE,
|
64
|
-
progress_report_cb: Optional[Callable] = None,
|
65
|
-
):
|
66
|
-
# not thread safe constructor!
|
67
|
-
super().__init__(bytes_io)
|
68
|
-
self.initial_seek_pos = bytes_io.tell()
|
69
|
-
self.segment_start = segment_start
|
70
|
-
self.segment_length = segment_length
|
71
|
-
# seek to start of file segment we are interested in, in order to make .size() evaluate correctly
|
72
|
-
self._value.seek(self.initial_seek_pos + segment_start)
|
73
|
-
assert self.segment_length <= super().size
|
74
|
-
self.chunk_size = chunk_size
|
75
|
-
self.progress_report_cb = progress_report_cb or (lambda *_, **__: None)
|
76
|
-
self.reset_state()
|
77
|
-
|
78
|
-
def reset_state(self):
|
79
|
-
self._md5_checksum = hashlib.md5()
|
80
|
-
self.num_bytes_read = 0
|
81
|
-
self._value.seek(self.initial_seek_pos)
|
82
|
-
|
83
|
-
@contextmanager
|
84
|
-
def reset_on_error(self):
|
85
|
-
try:
|
86
|
-
yield
|
87
|
-
except Exception as exc:
|
88
|
-
try:
|
89
|
-
self.progress_report_cb(reset=True)
|
90
|
-
except Exception as cb_exc:
|
91
|
-
raise cb_exc from exc
|
92
|
-
raise exc
|
93
|
-
finally:
|
94
|
-
self.reset_state()
|
95
|
-
|
96
|
-
@property
|
97
|
-
def size(self) -> int:
|
98
|
-
return self.segment_length
|
99
|
-
|
100
|
-
def md5_checksum(self):
|
101
|
-
return self._md5_checksum
|
102
|
-
|
103
|
-
async def write(self, writer: AbstractStreamWriter):
|
104
|
-
loop = asyncio.get_event_loop()
|
105
|
-
|
106
|
-
async def safe_read():
|
107
|
-
read_start = self.initial_seek_pos + self.segment_start + self.num_bytes_read
|
108
|
-
self._value.seek(read_start)
|
109
|
-
num_bytes = min(self.chunk_size, self.remaining_bytes())
|
110
|
-
chunk = await loop.run_in_executor(None, self._value.read, num_bytes)
|
111
|
-
|
112
|
-
await loop.run_in_executor(None, self._md5_checksum.update, chunk)
|
113
|
-
self.num_bytes_read += len(chunk)
|
114
|
-
return chunk
|
115
|
-
|
116
|
-
chunk = await safe_read()
|
117
|
-
while chunk and self.remaining_bytes() > 0:
|
118
|
-
await writer.write(chunk)
|
119
|
-
self.progress_report_cb(advance=len(chunk))
|
120
|
-
chunk = await safe_read()
|
121
|
-
if chunk:
|
122
|
-
await writer.write(chunk)
|
123
|
-
self.progress_report_cb(advance=len(chunk))
|
124
|
-
|
125
|
-
def remaining_bytes(self):
|
126
|
-
return self.segment_length - self.num_bytes_read
|
127
|
-
|
128
|
-
|
129
47
|
@retry(n_attempts=5, base_delay=0.5, timeout=None)
|
130
48
|
async def _upload_to_s3_url(
|
131
49
|
upload_url,
|
132
|
-
payload: BytesIOSegmentPayload,
|
50
|
+
payload: "BytesIOSegmentPayload",
|
133
51
|
content_md5_b64: Optional[str] = None,
|
134
52
|
content_type: Optional[str] = "application/octet-stream", # set to None to force omission of ContentType header
|
135
53
|
) -> str:
|
@@ -185,6 +103,8 @@ async def perform_multipart_upload(
|
|
185
103
|
upload_chunk_size: int = DEFAULT_SEGMENT_CHUNK_SIZE,
|
186
104
|
progress_report_cb: Optional[Callable] = None,
|
187
105
|
) -> None:
|
106
|
+
from .bytes_io_segment_payload import BytesIOSegmentPayload
|
107
|
+
|
188
108
|
upload_coros = []
|
189
109
|
file_offset = 0
|
190
110
|
num_bytes_left = content_length
|
@@ -278,6 +198,8 @@ async def _blob_upload(
|
|
278
198
|
progress_report_cb=progress_report_cb,
|
279
199
|
)
|
280
200
|
else:
|
201
|
+
from .bytes_io_segment_payload import BytesIOSegmentPayload
|
202
|
+
|
281
203
|
payload = BytesIOSegmentPayload(
|
282
204
|
data, segment_start=0, segment_length=content_length, progress_report_cb=progress_report_cb
|
283
205
|
)
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import hashlib
|
5
|
+
from contextlib import contextmanager
|
6
|
+
from typing import BinaryIO, Callable, Optional
|
7
|
+
|
8
|
+
# Note: this module needs to import aiohttp in global scope
|
9
|
+
# This takes about 50ms and isn't needed in many cases for Modal execution
|
10
|
+
# To avoid this, we import it in local scope when needed (blob_utils.py)
|
11
|
+
from aiohttp import BytesIOPayload
|
12
|
+
from aiohttp.abc import AbstractStreamWriter
|
13
|
+
|
14
|
+
# read ~16MiB chunks by default
|
15
|
+
DEFAULT_SEGMENT_CHUNK_SIZE = 2**24
|
16
|
+
|
17
|
+
|
18
|
+
class BytesIOSegmentPayload(BytesIOPayload):
|
19
|
+
"""Modified bytes payload for concurrent sends of chunks from the same file.
|
20
|
+
|
21
|
+
Adds:
|
22
|
+
* read limit using remaining_bytes, in order to split files across streams
|
23
|
+
* larger read chunk (to prevent excessive read contention between parts)
|
24
|
+
* calculates an md5 for the segment
|
25
|
+
|
26
|
+
Feels like this should be in some standard lib...
|
27
|
+
"""
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
bytes_io: BinaryIO, # should *not* be shared as IO position modification is not locked
|
32
|
+
segment_start: int,
|
33
|
+
segment_length: int,
|
34
|
+
chunk_size: int = DEFAULT_SEGMENT_CHUNK_SIZE,
|
35
|
+
progress_report_cb: Optional[Callable] = None,
|
36
|
+
):
|
37
|
+
# not thread safe constructor!
|
38
|
+
super().__init__(bytes_io)
|
39
|
+
self.initial_seek_pos = bytes_io.tell()
|
40
|
+
self.segment_start = segment_start
|
41
|
+
self.segment_length = segment_length
|
42
|
+
# seek to start of file segment we are interested in, in order to make .size() evaluate correctly
|
43
|
+
self._value.seek(self.initial_seek_pos + segment_start)
|
44
|
+
assert self.segment_length <= super().size
|
45
|
+
self.chunk_size = chunk_size
|
46
|
+
self.progress_report_cb = progress_report_cb or (lambda *_, **__: None)
|
47
|
+
self.reset_state()
|
48
|
+
|
49
|
+
def reset_state(self):
|
50
|
+
self._md5_checksum = hashlib.md5()
|
51
|
+
self.num_bytes_read = 0
|
52
|
+
self._value.seek(self.initial_seek_pos)
|
53
|
+
|
54
|
+
@contextmanager
|
55
|
+
def reset_on_error(self):
|
56
|
+
try:
|
57
|
+
yield
|
58
|
+
except Exception as exc:
|
59
|
+
try:
|
60
|
+
self.progress_report_cb(reset=True)
|
61
|
+
except Exception as cb_exc:
|
62
|
+
raise cb_exc from exc
|
63
|
+
raise exc
|
64
|
+
finally:
|
65
|
+
self.reset_state()
|
66
|
+
|
67
|
+
@property
|
68
|
+
def size(self) -> int:
|
69
|
+
return self.segment_length
|
70
|
+
|
71
|
+
def md5_checksum(self):
|
72
|
+
return self._md5_checksum
|
73
|
+
|
74
|
+
async def write(self, writer: "AbstractStreamWriter"):
|
75
|
+
loop = asyncio.get_event_loop()
|
76
|
+
|
77
|
+
async def safe_read():
|
78
|
+
read_start = self.initial_seek_pos + self.segment_start + self.num_bytes_read
|
79
|
+
self._value.seek(read_start)
|
80
|
+
num_bytes = min(self.chunk_size, self.remaining_bytes())
|
81
|
+
chunk = await loop.run_in_executor(None, self._value.read, num_bytes)
|
82
|
+
|
83
|
+
await loop.run_in_executor(None, self._md5_checksum.update, chunk)
|
84
|
+
self.num_bytes_read += len(chunk)
|
85
|
+
return chunk
|
86
|
+
|
87
|
+
chunk = await safe_read()
|
88
|
+
while chunk and self.remaining_bytes() > 0:
|
89
|
+
await writer.write(chunk)
|
90
|
+
self.progress_report_cb(advance=len(chunk))
|
91
|
+
chunk = await safe_read()
|
92
|
+
if chunk:
|
93
|
+
await writer.write(chunk)
|
94
|
+
self.progress_report_cb(advance=len(chunk))
|
95
|
+
|
96
|
+
def remaining_bytes(self):
|
97
|
+
return self.segment_length - self.num_bytes_read
|
modal/_utils/http_utils.py
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
import contextlib
|
3
|
-
import
|
4
|
-
import ssl
|
5
|
-
from typing import Optional
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
# Note: importing aiohttp seems to take about 100ms, and it's not really necessarily,
|
6
|
+
# unless we need to work with blobs. So that's why we import it lazily instead.
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from aiohttp import ClientSession
|
10
|
+
from aiohttp.web import Application
|
11
11
|
|
12
12
|
from .async_utils import on_shutdown
|
13
13
|
|
14
14
|
|
15
|
-
def _http_client_with_tls(timeout: Optional[float]) -> ClientSession:
|
15
|
+
def _http_client_with_tls(timeout: Optional[float]) -> "ClientSession":
|
16
16
|
"""Create a new HTTP client session with standard, bundled TLS certificates.
|
17
17
|
|
18
18
|
This is necessary to prevent client issues on some system where Python does
|
@@ -22,13 +22,18 @@ def _http_client_with_tls(timeout: Optional[float]) -> ClientSession:
|
|
22
22
|
Specifically: the error "unable to get local issuer certificate" when making
|
23
23
|
an aiohttp request.
|
24
24
|
"""
|
25
|
+
import ssl
|
26
|
+
|
27
|
+
import certifi
|
28
|
+
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
29
|
+
|
25
30
|
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
26
31
|
connector = TCPConnector(ssl=ssl_context)
|
27
32
|
return ClientSession(connector=connector, timeout=ClientTimeout(total=timeout))
|
28
33
|
|
29
34
|
|
30
35
|
class ClientSessionRegistry:
|
31
|
-
_client_session: ClientSession
|
36
|
+
_client_session: "ClientSession"
|
32
37
|
_client_session_active: bool = False
|
33
38
|
|
34
39
|
@staticmethod
|
@@ -47,9 +52,13 @@ class ClientSessionRegistry:
|
|
47
52
|
|
48
53
|
|
49
54
|
@contextlib.asynccontextmanager
|
50
|
-
async def run_temporary_http_server(app: Application):
|
55
|
+
async def run_temporary_http_server(app: "Application"):
|
51
56
|
# Allocates a random port, runs a server in a context manager
|
52
57
|
# This is used in various tests
|
58
|
+
import socket
|
59
|
+
|
60
|
+
from aiohttp.web_runner import AppRunner, SockSite
|
61
|
+
|
53
62
|
sock = socket.socket()
|
54
63
|
sock.bind(("", 0))
|
55
64
|
port = sock.getsockname()[1]
|
modal/client.pyi
CHANGED
@@ -26,7 +26,7 @@ class _Client:
|
|
26
26
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
27
27
|
|
28
28
|
def __init__(
|
29
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.16"
|
30
30
|
): ...
|
31
31
|
def is_closed(self) -> bool: ...
|
32
32
|
@property
|
@@ -81,7 +81,7 @@ class Client:
|
|
81
81
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
82
82
|
|
83
83
|
def __init__(
|
84
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.16"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/functions.pyi
CHANGED
@@ -456,11 +456,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
456
456
|
|
457
457
|
_call_generator_nowait: ___call_generator_nowait_spec
|
458
458
|
|
459
|
-
class __remote_spec(typing_extensions.Protocol[
|
459
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
460
460
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
461
461
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
462
462
|
|
463
|
-
remote: __remote_spec[
|
463
|
+
remote: __remote_spec[ReturnType, P]
|
464
464
|
|
465
465
|
class __remote_gen_spec(typing_extensions.Protocol):
|
466
466
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -473,17 +473,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
473
473
|
def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
|
474
474
|
def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
|
475
475
|
|
476
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
476
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
477
477
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
478
478
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
479
479
|
|
480
|
-
_experimental_spawn: ___experimental_spawn_spec[
|
480
|
+
_experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
|
481
481
|
|
482
|
-
class __spawn_spec(typing_extensions.Protocol[
|
482
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
483
483
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
484
484
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
485
485
|
|
486
|
-
spawn: __spawn_spec[
|
486
|
+
spawn: __spawn_spec[ReturnType, P]
|
487
487
|
|
488
488
|
def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
|
489
489
|
|
modal/partial_function.py
CHANGED
@@ -275,6 +275,7 @@ def _web_endpoint(
|
|
275
275
|
custom_domains: Optional[
|
276
276
|
Iterable[str]
|
277
277
|
] = None, # Create an endpoint using a custom domain fully-qualified domain name (FQDN).
|
278
|
+
requires_proxy_auth: bool = False, # Require Proxy-Authorization HTTP Headers on requests to the endpoint
|
278
279
|
wait_for_response: bool = True, # DEPRECATED: this must always be True now
|
279
280
|
) -> Callable[[Callable[P, ReturnType]], _PartialFunction[P, ReturnType, ReturnType]]:
|
280
281
|
"""Register a basic web endpoint with this application.
|
@@ -325,6 +326,7 @@ def _web_endpoint(
|
|
325
326
|
requested_suffix=label,
|
326
327
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
327
328
|
custom_domains=_parse_custom_domains(custom_domains),
|
329
|
+
requires_proxy_auth=requires_proxy_auth,
|
328
330
|
),
|
329
331
|
)
|
330
332
|
|
@@ -336,6 +338,7 @@ def _asgi_app(
|
|
336
338
|
*,
|
337
339
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
338
340
|
custom_domains: Optional[Iterable[str]] = None, # Deploy this endpoint on a custom domain.
|
341
|
+
requires_proxy_auth: bool = False, # Require Proxy-Authorization HTTP Headers on requests to the endpoint
|
339
342
|
wait_for_response: bool = True, # DEPRECATED: this must always be True now
|
340
343
|
) -> Callable[[Callable[..., Any]], _PartialFunction]:
|
341
344
|
"""Decorator for registering an ASGI app with a Modal function.
|
@@ -399,6 +402,7 @@ def _asgi_app(
|
|
399
402
|
requested_suffix=label,
|
400
403
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
401
404
|
custom_domains=_parse_custom_domains(custom_domains),
|
405
|
+
requires_proxy_auth=requires_proxy_auth,
|
402
406
|
),
|
403
407
|
)
|
404
408
|
|
@@ -410,6 +414,7 @@ def _wsgi_app(
|
|
410
414
|
*,
|
411
415
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
412
416
|
custom_domains: Optional[Iterable[str]] = None, # Deploy this endpoint on a custom domain.
|
417
|
+
requires_proxy_auth: bool = False, # Require Proxy-Authorization HTTP Headers on requests to the endpoint
|
413
418
|
wait_for_response: bool = True, # DEPRECATED: this must always be True now
|
414
419
|
) -> Callable[[Callable[..., Any]], _PartialFunction]:
|
415
420
|
"""Decorator for registering a WSGI app with a Modal function.
|
@@ -473,6 +478,7 @@ def _wsgi_app(
|
|
473
478
|
requested_suffix=label,
|
474
479
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
475
480
|
custom_domains=_parse_custom_domains(custom_domains),
|
481
|
+
requires_proxy_auth=requires_proxy_auth,
|
476
482
|
),
|
477
483
|
)
|
478
484
|
|
@@ -485,6 +491,7 @@ def _web_server(
|
|
485
491
|
startup_timeout: float = 5.0, # Maximum number of seconds to wait for the web server to start.
|
486
492
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
487
493
|
custom_domains: Optional[Iterable[str]] = None, # Deploy this endpoint on a custom domain.
|
494
|
+
requires_proxy_auth: bool = False, # Require Proxy-Authorization HTTP Headers on requests to the endpoint
|
488
495
|
) -> Callable[[Callable[..., Any]], _PartialFunction]:
|
489
496
|
"""Decorator that registers an HTTP web server inside the container.
|
490
497
|
|
@@ -528,6 +535,7 @@ def _web_server(
|
|
528
535
|
custom_domains=_parse_custom_domains(custom_domains),
|
529
536
|
web_server_port=port,
|
530
537
|
web_server_startup_timeout=startup_timeout,
|
538
|
+
requires_proxy_auth=requires_proxy_auth,
|
531
539
|
),
|
532
540
|
)
|
533
541
|
|
modal/partial_function.pyi
CHANGED
@@ -118,6 +118,7 @@ def _web_endpoint(
|
|
118
118
|
label: typing.Optional[str] = None,
|
119
119
|
docs: bool = False,
|
120
120
|
custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
|
121
|
+
requires_proxy_auth: bool = False,
|
121
122
|
wait_for_response: bool = True,
|
122
123
|
) -> typing.Callable[[typing.Callable[P, ReturnType]], _PartialFunction[P, ReturnType, ReturnType]]: ...
|
123
124
|
def _asgi_app(
|
@@ -125,6 +126,7 @@ def _asgi_app(
|
|
125
126
|
*,
|
126
127
|
label: typing.Optional[str] = None,
|
127
128
|
custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
|
129
|
+
requires_proxy_auth: bool = False,
|
128
130
|
wait_for_response: bool = True,
|
129
131
|
) -> typing.Callable[[typing.Callable[..., typing.Any]], _PartialFunction]: ...
|
130
132
|
def _wsgi_app(
|
@@ -132,6 +134,7 @@ def _wsgi_app(
|
|
132
134
|
*,
|
133
135
|
label: typing.Optional[str] = None,
|
134
136
|
custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
|
137
|
+
requires_proxy_auth: bool = False,
|
135
138
|
wait_for_response: bool = True,
|
136
139
|
) -> typing.Callable[[typing.Callable[..., typing.Any]], _PartialFunction]: ...
|
137
140
|
def _web_server(
|
@@ -140,6 +143,7 @@ def _web_server(
|
|
140
143
|
startup_timeout: float = 5.0,
|
141
144
|
label: typing.Optional[str] = None,
|
142
145
|
custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
|
146
|
+
requires_proxy_auth: bool = False,
|
143
147
|
) -> typing.Callable[[typing.Callable[..., typing.Any]], _PartialFunction]: ...
|
144
148
|
def _disallow_wrapping_method(f: _PartialFunction, wrapper: str) -> None: ...
|
145
149
|
def _build(
|
@@ -178,6 +182,7 @@ def web_endpoint(
|
|
178
182
|
label: typing.Optional[str] = None,
|
179
183
|
docs: bool = False,
|
180
184
|
custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
|
185
|
+
requires_proxy_auth: bool = False,
|
181
186
|
wait_for_response: bool = True,
|
182
187
|
) -> typing.Callable[[typing.Callable[P, ReturnType]], PartialFunction[P, ReturnType, ReturnType]]: ...
|
183
188
|
def asgi_app(
|
@@ -185,6 +190,7 @@ def asgi_app(
|
|
185
190
|
*,
|
186
191
|
label: typing.Optional[str] = None,
|
187
192
|
custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
|
193
|
+
requires_proxy_auth: bool = False,
|
188
194
|
wait_for_response: bool = True,
|
189
195
|
) -> typing.Callable[[typing.Callable[..., typing.Any]], PartialFunction]: ...
|
190
196
|
def wsgi_app(
|
@@ -192,6 +198,7 @@ def wsgi_app(
|
|
192
198
|
*,
|
193
199
|
label: typing.Optional[str] = None,
|
194
200
|
custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
|
201
|
+
requires_proxy_auth: bool = False,
|
195
202
|
wait_for_response: bool = True,
|
196
203
|
) -> typing.Callable[[typing.Callable[..., typing.Any]], PartialFunction]: ...
|
197
204
|
def web_server(
|
@@ -200,6 +207,7 @@ def web_server(
|
|
200
207
|
startup_timeout: float = 5.0,
|
201
208
|
label: typing.Optional[str] = None,
|
202
209
|
custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
|
210
|
+
requires_proxy_auth: bool = False,
|
203
211
|
) -> typing.Callable[[typing.Callable[..., typing.Any]], PartialFunction]: ...
|
204
212
|
def build(
|
205
213
|
_warn_parentheses_missing=None, *, force: bool = False, timeout: int = 86400
|
@@ -19,7 +19,7 @@ modal/app.py,sha256=EJ7FUN6rWnSwLJoYJh8nmKg_t-8hdN8_rt0OrkP7JvQ,46084
|
|
19
19
|
modal/app.pyi,sha256=BE5SlR5tRECuc6-e2lUuOknDdov3zxgZ4N0AsLb5ZVQ,25270
|
20
20
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
21
21
|
modal/client.py,sha256=nyPjfromWBeOyurexpFP2QLQNk822RPggMCLyX9j1jA,15247
|
22
|
-
modal/client.pyi,sha256=
|
22
|
+
modal/client.pyi,sha256=457zwWltZmkWx6kL2KgLC69XMQYUSrLXVhYsT-Rcit8,7280
|
23
23
|
modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
|
24
24
|
modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
|
25
25
|
modal/cls.py,sha256=ONnrfZ2vPcaY2JuKypPiBA9eTiyg8Qfg-Ull40nn9zs,30956
|
@@ -36,7 +36,7 @@ modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
|
|
36
36
|
modal/file_io.py,sha256=q8s872qf6Ntdw7dPogDlpYbixxGkwCA0BlQn2UUoVhY,14637
|
37
37
|
modal/file_io.pyi,sha256=pfkmJiaBpMCZReE6-KCjYOzB1dVtyYDYokJoYX8ARK4,6932
|
38
38
|
modal/functions.py,sha256=IIdHw0FNOdoMksG1b2zvkn8f-xskhJu07ZvHMey9iq4,67667
|
39
|
-
modal/functions.pyi,sha256=
|
39
|
+
modal/functions.pyi,sha256=bHbJiWW5TbFKKjDn7bSCFvOcUcAjPFqTStS-NAHPSeM,25068
|
40
40
|
modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
|
41
41
|
modal/image.py,sha256=cQ6WP1xHXZT_nY8z3aEFiGwKzrTV0yxi3Ab8JzF91eo,79653
|
42
42
|
modal/image.pyi,sha256=PIKH6JBA4L5TfdJrQu3pm2ykyIITmiP920TpP8cdyQA,24585
|
@@ -51,8 +51,8 @@ modal/object.pyi,sha256=MO78H9yFSE5i1gExPEwyyQzLdlshkcGHN1aQ0ylyvq0,8802
|
|
51
51
|
modal/output.py,sha256=N0xf4qeudEaYrslzdAl35VKV8rapstgIM2e9wO8_iy0,1967
|
52
52
|
modal/parallel_map.py,sha256=4aoMXIrlG3wl5Ifk2YDNOQkXsGRsm6Xbfm6WtJ2t3WY,16002
|
53
53
|
modal/parallel_map.pyi,sha256=pOhT0P3DDYlwLx0fR3PTsecA7DI8uOdXC1N8i-ZkyOY,2328
|
54
|
-
modal/partial_function.py,sha256=
|
55
|
-
modal/partial_function.pyi,sha256=
|
54
|
+
modal/partial_function.py,sha256=zr_tFmTdoFxpTDw8cSwo_IjiWwZAyROCmQyl2z-Z6MY,29201
|
55
|
+
modal/partial_function.pyi,sha256=pO6kf8i5HVsZ7CF0z_KkzLk4Aeq7NJhFJ_VNIycRXaU,9260
|
56
56
|
modal/proxy.py,sha256=ZrOsuQP7dSZFq1OrIxalNnt0Zvsnp1h86Th679sSL40,1417
|
57
57
|
modal/proxy.pyi,sha256=UvygdOYneLTuoDY6hVaMNCyZ947Tmx93IdLjErUqkvM,368
|
58
58
|
modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -76,20 +76,21 @@ modal/token_flow.pyi,sha256=gOYtYujrWt_JFZeiI8EmfahXPx5GCR5Na-VaPQcWgEY,1937
|
|
76
76
|
modal/volume.py,sha256=PGzbninvRU-IhSwJgM2jZKzD8llRhZhadsOxZ-YNwaM,29316
|
77
77
|
modal/volume.pyi,sha256=St0mDiaojfep6Bs4sBbkRJmeacYHF6lh6FKOWGmheHA,11182
|
78
78
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
79
|
-
modal/_runtime/asgi.py,sha256=
|
79
|
+
modal/_runtime/asgi.py,sha256=H68KAN8bz8Zp7EcRl2c_ite1Y3kP1MHvjQAf-uUpCx8,21691
|
80
80
|
modal/_runtime/container_io_manager.py,sha256=ctgyNFiHjq1brCrabXmlurkAXjnrCeWPRvTVa735vRw,44215
|
81
81
|
modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
|
82
82
|
modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
|
83
|
-
modal/_runtime/user_code_imports.py,sha256=
|
83
|
+
modal/_runtime/user_code_imports.py,sha256=n4CQOojzSdf0jwSKSy6MEnVX3IWl3t3Dq54-x9VS2Ds,14663
|
84
84
|
modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
|
85
85
|
modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
|
86
86
|
modal/_utils/async_utils.py,sha256=9ubwMkwiDB4gzOYG2jL9j7Fs-5dxHjcifZe3r7JRg-k,25091
|
87
|
-
modal/_utils/blob_utils.py,sha256=
|
87
|
+
modal/_utils/blob_utils.py,sha256=N66LtZI8PpCkZ7maA7GLW5CAmYUoNJdG-GjaAUR4_NQ,14509
|
88
|
+
modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWRqQo_bxEj5TU,3385
|
88
89
|
modal/_utils/function_utils.py,sha256=LgcveUUb4XU_dWxtqgK_3ujZBvS3cGVzcDOkljyFZ2w,25066
|
89
90
|
modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
|
90
91
|
modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
|
91
92
|
modal/_utils/hash_utils.py,sha256=zg3J6OGxTFGSFri1qQ12giDz90lWk8bzaxCTUCRtiX4,3034
|
92
|
-
modal/_utils/http_utils.py,sha256=
|
93
|
+
modal/_utils/http_utils.py,sha256=yeTFsXYr0rYMEhB7vBP7audG9Uc7OLhzKBANFDZWVt0,2451
|
93
94
|
modal/_utils/logger.py,sha256=ePzdudrtx9jJCjuO6-bcL_kwUJfi4AwloUmIiNtqkY0,1330
|
94
95
|
modal/_utils/mount_utils.py,sha256=J-FRZbPQv1i__Tob-FIpbB1oXWpFLAwZiB4OCiJpFG0,3206
|
95
96
|
modal/_utils/name_utils.py,sha256=TW1iyJedvDNPEJ5UVp93u8xuD5J2gQL_CUt1mgov_aI,1939
|
@@ -144,10 +145,10 @@ modal_global_objects/mounts/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0
|
|
144
145
|
modal_global_objects/mounts/modal_client_package.py,sha256=W0E_yShsRojPzWm6LtIQqNVolapdnrZkm2hVEQuZK_4,767
|
145
146
|
modal_global_objects/mounts/python_standalone.py,sha256=SL_riIxpd8mP4i4CLDCWiFFNj0Ltknm9c_UIGfX5d60,1836
|
146
147
|
modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
147
|
-
modal_proto/api.proto,sha256=
|
148
|
+
modal_proto/api.proto,sha256=8lkerFXbR4V9ehuMchuJN0qkWDnQhR2hqZVIkqL4IpQ,79350
|
148
149
|
modal_proto/api_grpc.py,sha256=DveC4ejFYEhCLiWbQShnmY31_FWGYU675Bmr7nHhsgs,101342
|
149
|
-
modal_proto/api_pb2.py,sha256=
|
150
|
-
modal_proto/api_pb2.pyi,sha256=
|
150
|
+
modal_proto/api_pb2.py,sha256=iesj4DdMPBSsURqpMHsZvJD6xLfWAzVH8RndkN5hcbw,290156
|
151
|
+
modal_proto/api_pb2.pyi,sha256=FqZjV1yc9-gbhDgQkh8V52pKBNbobTm7AzATJ1FtCn8,388320
|
151
152
|
modal_proto/api_pb2_grpc.py,sha256=2PEP6JPOoTw2rDC5qYjLNuumP68ZwAouRhCoayisAhY,219162
|
152
153
|
modal_proto/api_pb2_grpc.pyi,sha256=uWtCxVEd0cFpOZ1oOGfZNO7Dv45OP4kp09jMnNyx9D4,51098
|
153
154
|
modal_proto/modal_api_grpc.py,sha256=-8mLby_om5MYo6yu1zA_hqhz0yLsQW7k2YWBVZW1iVs,13546
|
@@ -161,10 +162,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
161
162
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
162
163
|
modal_version/__init__.py,sha256=RT6zPoOdFO99u5Wcxxaoir4ZCuPTbQ22cvzFAXl3vUY,470
|
163
164
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
164
|
-
modal_version/_version_generated.py,sha256=
|
165
|
-
modal-0.68.
|
166
|
-
modal-0.68.
|
167
|
-
modal-0.68.
|
168
|
-
modal-0.68.
|
169
|
-
modal-0.68.
|
170
|
-
modal-0.68.
|
165
|
+
modal_version/_version_generated.py,sha256=_78tFHpocB2j13QmoHx8RyBXjJbeKL88F2EToSPlNsw,149
|
166
|
+
modal-0.68.16.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
167
|
+
modal-0.68.16.dist-info/METADATA,sha256=5v1HulhtrgeBwxV3CH-IpYMZb07HnnixgU-58vam_ow,2329
|
168
|
+
modal-0.68.16.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
169
|
+
modal-0.68.16.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
170
|
+
modal-0.68.16.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
171
|
+
modal-0.68.16.dist-info/RECORD,,
|
modal_proto/api.proto
CHANGED