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 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
 
@@ -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
@@ -1,18 +1,18 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import contextlib
3
- import socket
4
- import ssl
5
- from typing import Optional
3
+ from typing import TYPE_CHECKING, Optional
6
4
 
7
- import certifi
8
- from aiohttp import ClientSession, ClientTimeout, TCPConnector
9
- from aiohttp.web import Application
10
- from aiohttp.web_runner import AppRunner, SockSite
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.14"
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.14"
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[P_INNER, ReturnType_INNER]):
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[P, ReturnType]
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[P_INNER, ReturnType_INNER]):
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[P, ReturnType]
480
+ _experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
481
481
 
482
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
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[P, ReturnType]
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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.68.14
3
+ Version: 0.68.16
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=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=4x5j0yBlE_sZf8cS2eChs-pYuxr-_SsRiKrf1fSJmfA,7280
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=EYH4w4VgQtdbEWLGarnU5QtYVfuM2_tnovKFEbYyg2c,25068
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=rirVfihV6kp1SMTacrsfE5rNXV5JnTLpU_Zd4xukNg0,28529
55
- modal/partial_function.pyi,sha256=mt9kUeeMSVTo5qJ6JF6qeQzr41zykYBi2Yt6R8dxtWk,8948
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=GvuxZqWnIHMIR-Bx5f7toCQlkERaJO8CHjTPNM9IFIw,21537
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=4fI0F9OIaNOcO_S4Tx2JcnYwZwZq6JdhMAkUzYN6je4,14629
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=1D_dXspFdsVxkW3gsYH-wJUUHiCpYvlwhmCgYZZgN9k,17237
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=VKXYNPJtrSwZ1ttcXVGQUWmn8cLAXiOTv05g2ac3GbU,2179
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=41iACETQzxnecGOJgoeyKPQOJVZcw8iHJNtiB6SMuWg,79317
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=316-2KJCwcoJm-oFQ7tG42LZjlA3lNhEUYsbbzoikUs,290107
150
- modal_proto/api_pb2.pyi,sha256=kuvlkVay68o4OQTW8UGwH10Xvxejr-uQjcb2tT_Neso,388133
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=SWqJPFBaGp3gDChTb6Zx02oI_iQyIRtKpGgcw69Yegs,149
165
- modal-0.68.14.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
166
- modal-0.68.14.dist-info/METADATA,sha256=gHSGyvKb5gQiL1YbO3mRis3T8En0t19ug9mfOY4HYos,2329
167
- modal-0.68.14.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
168
- modal-0.68.14.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
169
- modal-0.68.14.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
170
- modal-0.68.14.dist-info/RECORD,,
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
@@ -2677,6 +2677,7 @@ message WebhookConfig {
2677
2677
  uint32 web_server_port = 7;
2678
2678
  float web_server_startup_timeout = 8;
2679
2679
  bool web_endpoint_docs = 9;
2680
+ bool requires_proxy_auth = 10;
2680
2681
  }
2681
2682
 
2682
2683
  message WorkspaceNameLookupResponse {