modal 0.67.43__py3-none-any.whl → 0.68.24__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 +2 -0
- modal/_container_entrypoint.py +4 -1
- modal/_ipython.py +3 -13
- modal/_runtime/asgi.py +4 -0
- modal/_runtime/container_io_manager.py +3 -0
- modal/_runtime/user_code_imports.py +17 -20
- modal/_traceback.py +16 -2
- modal/_utils/blob_utils.py +27 -92
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/function_utils.py +5 -1
- modal/_utils/grpc_testing.py +6 -2
- modal/_utils/hash_utils.py +51 -10
- modal/_utils/http_utils.py +19 -10
- modal/_utils/{pattern_matcher.py → pattern_utils.py} +1 -70
- modal/_utils/shell_utils.py +11 -5
- modal/cli/_traceback.py +11 -4
- modal/cli/run.py +25 -12
- modal/client.py +6 -37
- modal/client.pyi +2 -6
- modal/cls.py +132 -62
- modal/cls.pyi +13 -7
- modal/exception.py +20 -0
- modal/file_io.py +380 -0
- modal/file_io.pyi +185 -0
- modal/file_pattern_matcher.py +121 -0
- modal/functions.py +33 -11
- modal/functions.pyi +11 -9
- modal/image.py +88 -8
- modal/image.pyi +20 -4
- modal/mount.py +49 -9
- modal/mount.pyi +19 -4
- modal/network_file_system.py +4 -1
- modal/object.py +4 -2
- modal/partial_function.py +22 -10
- modal/partial_function.pyi +10 -2
- modal/runner.py +5 -4
- modal/runner.pyi +2 -1
- modal/sandbox.py +40 -0
- modal/sandbox.pyi +18 -0
- modal/volume.py +5 -1
- {modal-0.67.43.dist-info → modal-0.68.24.dist-info}/METADATA +2 -2
- {modal-0.67.43.dist-info → modal-0.68.24.dist-info}/RECORD +52 -48
- modal_docs/gen_reference_docs.py +1 -0
- modal_proto/api.proto +33 -1
- modal_proto/api_pb2.py +813 -737
- modal_proto/api_pb2.pyi +160 -13
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +1 -1
- {modal-0.67.43.dist-info → modal-0.68.24.dist-info}/LICENSE +0 -0
- {modal-0.67.43.dist-info → modal-0.68.24.dist-info}/WHEEL +0 -0
- {modal-0.67.43.dist-info → modal-0.68.24.dist-info}/entry_points.txt +0 -0
- {modal-0.67.43.dist-info → modal-0.68.24.dist-info}/top_level.txt +0 -0
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]
|
@@ -5,7 +5,7 @@ This is the same pattern-matching logic used by Docker, except it is written in
|
|
5
5
|
Python rather than Go. Also, the original Go library has a couple deprecated
|
6
6
|
functions that we don't implement in this port.
|
7
7
|
|
8
|
-
The main way to use this library is by constructing a `
|
8
|
+
The main way to use this library is by constructing a `FilePatternMatcher` object,
|
9
9
|
then asking it whether file paths match any of its patterns.
|
10
10
|
"""
|
11
11
|
|
@@ -148,75 +148,6 @@ class Pattern:
|
|
148
148
|
return False
|
149
149
|
|
150
150
|
|
151
|
-
class PatternMatcher:
|
152
|
-
"""Allows checking paths against a list of patterns."""
|
153
|
-
|
154
|
-
def __init__(self, patterns: list[str]) -> None:
|
155
|
-
"""Initialize a new PatternMatcher instance.
|
156
|
-
|
157
|
-
Args:
|
158
|
-
patterns (list): A list of pattern strings.
|
159
|
-
|
160
|
-
Raises:
|
161
|
-
ValueError: If an illegal exclusion pattern is provided.
|
162
|
-
"""
|
163
|
-
self.patterns: list[Pattern] = []
|
164
|
-
self.exclusions = False
|
165
|
-
for pattern in patterns:
|
166
|
-
pattern = pattern.strip()
|
167
|
-
if not pattern:
|
168
|
-
continue
|
169
|
-
pattern = os.path.normpath(pattern)
|
170
|
-
new_pattern = Pattern()
|
171
|
-
if pattern[0] == "!":
|
172
|
-
if len(pattern) == 1:
|
173
|
-
raise ValueError('Illegal exclusion pattern: "!"')
|
174
|
-
new_pattern.exclusion = True
|
175
|
-
pattern = pattern[1:]
|
176
|
-
self.exclusions = True
|
177
|
-
# In Python, we can proceed without explicit syntax checking
|
178
|
-
new_pattern.cleaned_pattern = pattern
|
179
|
-
new_pattern.dirs = pattern.split(os.path.sep)
|
180
|
-
self.patterns.append(new_pattern)
|
181
|
-
|
182
|
-
def matches(self, file_path: str) -> bool:
|
183
|
-
"""Check if the file path or any of its parent directories match the patterns.
|
184
|
-
|
185
|
-
This is equivalent to `MatchesOrParentMatches()` in the original Go
|
186
|
-
library. The reason is that `Matches()` in the original library is
|
187
|
-
deprecated due to buggy behavior.
|
188
|
-
"""
|
189
|
-
matched = False
|
190
|
-
file_path = os.path.normpath(file_path)
|
191
|
-
if file_path == ".":
|
192
|
-
# Don't let them exclude everything; kind of silly.
|
193
|
-
return False
|
194
|
-
parent_path = os.path.dirname(file_path)
|
195
|
-
if parent_path == "":
|
196
|
-
parent_path = "."
|
197
|
-
parent_path_dirs = parent_path.split(os.path.sep)
|
198
|
-
|
199
|
-
for pattern in self.patterns:
|
200
|
-
# Skip evaluation based on current match status and pattern exclusion
|
201
|
-
if pattern.exclusion != matched:
|
202
|
-
continue
|
203
|
-
|
204
|
-
match = pattern.match(file_path)
|
205
|
-
|
206
|
-
if not match and parent_path != ".":
|
207
|
-
# Check if the pattern matches any of the parent directories
|
208
|
-
for i in range(len(parent_path_dirs)):
|
209
|
-
dir_path = os.path.sep.join(parent_path_dirs[: i + 1])
|
210
|
-
if pattern.match(dir_path):
|
211
|
-
match = True
|
212
|
-
break
|
213
|
-
|
214
|
-
if match:
|
215
|
-
matched = not pattern.exclusion
|
216
|
-
|
217
|
-
return matched
|
218
|
-
|
219
|
-
|
220
151
|
def read_ignorefile(reader: TextIO) -> list[str]:
|
221
152
|
"""Read an ignore file from a reader and return the list of file patterns to
|
222
153
|
ignore, applying the following rules:
|
modal/_utils/shell_utils.py
CHANGED
@@ -19,14 +19,20 @@ def write_to_fd(fd: int, data: bytes):
|
|
19
19
|
future = loop.create_future()
|
20
20
|
|
21
21
|
def try_write():
|
22
|
+
nonlocal data
|
22
23
|
try:
|
23
24
|
nbytes = os.write(fd, data)
|
24
|
-
|
25
|
-
|
25
|
+
data = data[nbytes:]
|
26
|
+
if not data:
|
27
|
+
loop.remove_writer(fd)
|
28
|
+
future.set_result(None)
|
26
29
|
except OSError as e:
|
27
|
-
if e.errno
|
28
|
-
|
29
|
-
|
30
|
+
if e.errno == errno.EAGAIN:
|
31
|
+
# Wait for the next write notification
|
32
|
+
return
|
33
|
+
# Fail if it's not EAGAIN
|
34
|
+
loop.remove_writer(fd)
|
35
|
+
future.set_exception(e)
|
30
36
|
|
31
37
|
loop.add_writer(fd, try_write)
|
32
38
|
return future
|
modal/cli/_traceback.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Copyright Modal Labs 2024
|
2
2
|
"""Helper functions related to displaying tracebacks in the CLI."""
|
3
|
+
|
3
4
|
import functools
|
4
5
|
import re
|
5
6
|
import warnings
|
@@ -11,7 +12,7 @@ from rich.syntax import Syntax
|
|
11
12
|
from rich.text import Text
|
12
13
|
from rich.traceback import PathHighlighter, Stack, Traceback, install
|
13
14
|
|
14
|
-
from ..exception import DeprecationError, PendingDeprecationError
|
15
|
+
from ..exception import DeprecationError, PendingDeprecationError, ServerWarning
|
15
16
|
|
16
17
|
|
17
18
|
@group()
|
@@ -165,7 +166,7 @@ def highlight_modal_deprecation_warnings() -> None:
|
|
165
166
|
base_showwarning = warnings.showwarning
|
166
167
|
|
167
168
|
def showwarning(warning, category, filename, lineno, file=None, line=None):
|
168
|
-
if issubclass(category, (DeprecationError, PendingDeprecationError)):
|
169
|
+
if issubclass(category, (DeprecationError, PendingDeprecationError, ServerWarning)):
|
169
170
|
content = str(warning)
|
170
171
|
if re.match(r"^\d{4}-\d{2}-\d{2}", content):
|
171
172
|
date = content[:10]
|
@@ -180,10 +181,16 @@ def highlight_modal_deprecation_warnings() -> None:
|
|
180
181
|
except OSError:
|
181
182
|
# e.g., when filename is "<unknown>"; raises FileNotFoundError on posix but OSError on windows
|
182
183
|
pass
|
184
|
+
if issubclass(category, ServerWarning):
|
185
|
+
title = "Modal Warning"
|
186
|
+
else:
|
187
|
+
title = "Modal Deprecation Warning"
|
188
|
+
if date:
|
189
|
+
title += f" ({date})"
|
183
190
|
panel = Panel(
|
184
191
|
message,
|
185
|
-
|
186
|
-
title=
|
192
|
+
border_style="yellow",
|
193
|
+
title=title,
|
187
194
|
title_align="left",
|
188
195
|
)
|
189
196
|
Console().print(panel)
|
modal/cli/run.py
CHANGED
@@ -13,8 +13,6 @@ from typing import Any, Callable, Optional, get_type_hints
|
|
13
13
|
|
14
14
|
import click
|
15
15
|
import typer
|
16
|
-
from rich.console import Console
|
17
|
-
from rich.panel import Panel
|
18
16
|
from typing_extensions import TypedDict
|
19
17
|
|
20
18
|
from .. import Cls
|
@@ -135,6 +133,18 @@ def _get_clean_app_description(func_ref: str) -> str:
|
|
135
133
|
return " ".join(sys.argv)
|
136
134
|
|
137
135
|
|
136
|
+
def _write_local_result(result_path: str, res: Any):
|
137
|
+
if isinstance(res, str):
|
138
|
+
mode = "wt"
|
139
|
+
elif isinstance(res, bytes):
|
140
|
+
mode = "wb"
|
141
|
+
else:
|
142
|
+
res_type = type(res).__name__
|
143
|
+
raise InvalidError(f"Function must return str or bytes when using `--write-result`; got {res_type}.")
|
144
|
+
with open(result_path, mode) as fid:
|
145
|
+
fid.write(res)
|
146
|
+
|
147
|
+
|
138
148
|
def _get_click_command_for_function(app: App, function_tag):
|
139
149
|
function = app.registered_functions.get(function_tag)
|
140
150
|
if not function or (isinstance(function, Function) and function.info.user_cls is not None):
|
@@ -179,7 +189,7 @@ def _get_click_command_for_function(app: App, function_tag):
|
|
179
189
|
interactive=ctx.obj["interactive"],
|
180
190
|
):
|
181
191
|
if cls is None:
|
182
|
-
function.remote(**kwargs)
|
192
|
+
res = function.remote(**kwargs)
|
183
193
|
else:
|
184
194
|
# unpool class and method arguments
|
185
195
|
# TODO(erikbern): this code is a bit hacky
|
@@ -188,7 +198,10 @@ def _get_click_command_for_function(app: App, function_tag):
|
|
188
198
|
|
189
199
|
instance = cls(**cls_kwargs)
|
190
200
|
method: Function = getattr(instance, method_name)
|
191
|
-
method.remote(**fun_kwargs)
|
201
|
+
res = method.remote(**fun_kwargs)
|
202
|
+
|
203
|
+
if result_path := ctx.obj["result_path"]:
|
204
|
+
_write_local_result(result_path, res)
|
192
205
|
|
193
206
|
with_click_options = _add_click_options(f, signature)
|
194
207
|
return click.command(with_click_options)
|
@@ -216,12 +229,15 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
|
|
216
229
|
):
|
217
230
|
try:
|
218
231
|
if isasync:
|
219
|
-
asyncio.run(func(*args, **kwargs))
|
232
|
+
res = asyncio.run(func(*args, **kwargs))
|
220
233
|
else:
|
221
|
-
func(*args, **kwargs)
|
234
|
+
res = func(*args, **kwargs)
|
222
235
|
except Exception as exc:
|
223
236
|
raise _CliUserExecutionError(inspect.getsourcefile(func)) from exc
|
224
237
|
|
238
|
+
if result_path := ctx.obj["result_path"]:
|
239
|
+
_write_local_result(result_path, res)
|
240
|
+
|
225
241
|
with_click_options = _add_click_options(f, _get_signature(func))
|
226
242
|
return click.command(with_click_options)
|
227
243
|
|
@@ -250,12 +266,13 @@ class RunGroup(click.Group):
|
|
250
266
|
cls=RunGroup,
|
251
267
|
subcommand_metavar="FUNC_REF",
|
252
268
|
)
|
269
|
+
@click.option("-w", "--write-result", help="Write return value (which must be str or bytes) to this local path.")
|
253
270
|
@click.option("-q", "--quiet", is_flag=True, help="Don't show Modal progress indicators.")
|
254
271
|
@click.option("-d", "--detach", is_flag=True, help="Don't stop the app if the local process dies or disconnects.")
|
255
272
|
@click.option("-i", "--interactive", is_flag=True, help="Run the app in interactive mode.")
|
256
273
|
@click.option("-e", "--env", help=ENV_OPTION_HELP, default=None)
|
257
274
|
@click.pass_context
|
258
|
-
def run(ctx, detach, quiet, interactive, env):
|
275
|
+
def run(ctx, write_result, detach, quiet, interactive, env):
|
259
276
|
"""Run a Modal function or local entrypoint.
|
260
277
|
|
261
278
|
`FUNC_REF` should be of the format `{file or module}::{function name}`.
|
@@ -286,6 +303,7 @@ def run(ctx, detach, quiet, interactive, env):
|
|
286
303
|
```
|
287
304
|
"""
|
288
305
|
ctx.ensure_object(dict)
|
306
|
+
ctx.obj["result_path"] = write_result
|
289
307
|
ctx.obj["detach"] = detach # if subcommand would be a click command...
|
290
308
|
ctx.obj["show_progress"] = False if quiet else True
|
291
309
|
ctx.obj["interactive"] = interactive
|
@@ -308,11 +326,6 @@ def deploy(
|
|
308
326
|
|
309
327
|
with enable_output():
|
310
328
|
res = deploy_app(app, name=name, environment_name=env or "", tag=tag)
|
311
|
-
if res.warnings:
|
312
|
-
console = Console()
|
313
|
-
for warning in res.warnings:
|
314
|
-
panel = Panel(warning, title="Warning", title_align="left", border_style="yellow")
|
315
|
-
console.print(panel, highlight=False)
|
316
329
|
|
317
330
|
if stream_logs:
|
318
331
|
stream_app_logs(app_id=res.app_id, app_logs_url=res.app_logs_url)
|
modal/client.py
CHANGED
@@ -16,23 +16,21 @@ from typing import (
|
|
16
16
|
import grpclib.client
|
17
17
|
from google.protobuf import empty_pb2
|
18
18
|
from google.protobuf.message import Message
|
19
|
-
from grpclib import GRPCError, Status
|
20
19
|
from synchronicity.async_wrap import asynccontextmanager
|
21
20
|
|
22
21
|
from modal._utils.async_utils import synchronizer
|
23
22
|
from modal_proto import api_grpc, api_pb2, modal_api_grpc
|
24
23
|
from modal_version import __version__
|
25
24
|
|
25
|
+
from ._traceback import print_server_warnings
|
26
26
|
from ._utils import async_utils
|
27
27
|
from ._utils.async_utils import TaskContext, synchronize_api
|
28
28
|
from ._utils.grpc_utils import connect_channel, create_channel, retry_transient_errors
|
29
29
|
from .config import _check_config, _is_remote, config, logger
|
30
|
-
from .exception import AuthError, ClientClosed, ConnectionError
|
30
|
+
from .exception import AuthError, ClientClosed, ConnectionError
|
31
31
|
|
32
32
|
HEARTBEAT_INTERVAL: float = config.get("heartbeat_interval")
|
33
33
|
HEARTBEAT_TIMEOUT: float = HEARTBEAT_INTERVAL + 0.1
|
34
|
-
CLIENT_CREATE_ATTEMPT_TIMEOUT: float = 4.0
|
35
|
-
CLIENT_CREATE_TOTAL_TIMEOUT: float = 15.0
|
36
34
|
|
37
35
|
|
38
36
|
def _get_metadata(client_type: int, credentials: Optional[tuple[str, str]], version: str) -> dict[str, str]:
|
@@ -137,32 +135,11 @@ class _Client:
|
|
137
135
|
async def hello(self):
|
138
136
|
"""Connect to server and retrieve version information; raise appropriate error for various failures."""
|
139
137
|
logger.debug(f"Client ({id(self)}): Starting")
|
140
|
-
|
141
|
-
|
142
|
-
resp = await retry_transient_errors(
|
143
|
-
self.stub.ClientHello,
|
144
|
-
req,
|
145
|
-
attempt_timeout=CLIENT_CREATE_ATTEMPT_TIMEOUT,
|
146
|
-
total_timeout=CLIENT_CREATE_TOTAL_TIMEOUT,
|
147
|
-
)
|
148
|
-
if resp.warning:
|
149
|
-
ALARM_EMOJI = chr(0x1F6A8)
|
150
|
-
warnings.warn_explicit(f"{ALARM_EMOJI} {resp.warning} {ALARM_EMOJI}", DeprecationError, "<unknown>", 0)
|
151
|
-
except GRPCError as exc:
|
152
|
-
if exc.status == Status.FAILED_PRECONDITION:
|
153
|
-
raise VersionError(
|
154
|
-
f"The client version ({self.version}) is too old. Please update (pip install --upgrade modal)."
|
155
|
-
)
|
156
|
-
else:
|
157
|
-
raise exc
|
138
|
+
resp = await retry_transient_errors(self.stub.ClientHello, empty_pb2.Empty())
|
139
|
+
print_server_warnings(resp.server_warnings)
|
158
140
|
|
159
141
|
async def __aenter__(self):
|
160
142
|
await self._open()
|
161
|
-
try:
|
162
|
-
await self.hello()
|
163
|
-
except BaseException:
|
164
|
-
await self._close()
|
165
|
-
raise
|
166
143
|
return self
|
167
144
|
|
168
145
|
async def __aexit__(self, exc_type, exc, tb):
|
@@ -178,7 +155,6 @@ class _Client:
|
|
178
155
|
client = cls(server_url, api_pb2.CLIENT_TYPE_CLIENT, credentials=None)
|
179
156
|
try:
|
180
157
|
await client._open()
|
181
|
-
# Skip client.hello
|
182
158
|
yield client
|
183
159
|
finally:
|
184
160
|
await client._close()
|
@@ -229,7 +205,6 @@ class _Client:
|
|
229
205
|
client = _Client(server_url, client_type, credentials)
|
230
206
|
await client._open()
|
231
207
|
async_utils.on_shutdown(client._close())
|
232
|
-
await client.hello()
|
233
208
|
cls._client_from_env = client
|
234
209
|
return client
|
235
210
|
|
@@ -252,11 +227,6 @@ class _Client:
|
|
252
227
|
credentials = (token_id, token_secret)
|
253
228
|
client = _Client(server_url, client_type, credentials)
|
254
229
|
await client._open()
|
255
|
-
try:
|
256
|
-
await client.hello()
|
257
|
-
except BaseException:
|
258
|
-
await client._close()
|
259
|
-
raise
|
260
230
|
async_utils.on_shutdown(client._close())
|
261
231
|
return client
|
262
232
|
|
@@ -265,8 +235,8 @@ class _Client:
|
|
265
235
|
"""mdmd:hidden
|
266
236
|
Check whether can the client can connect to this server with these credentials; raise if not.
|
267
237
|
"""
|
268
|
-
async with cls(server_url, api_pb2.CLIENT_TYPE_CLIENT, credentials):
|
269
|
-
|
238
|
+
async with cls(server_url, api_pb2.CLIENT_TYPE_CLIENT, credentials) as client:
|
239
|
+
await client.hello() # Will call ClientHello RPC and possibly raise AuthError or ConnectionError
|
270
240
|
|
271
241
|
@classmethod
|
272
242
|
def set_env_client(cls, client: Optional["_Client"]):
|
@@ -316,7 +286,6 @@ class _Client:
|
|
316
286
|
self.set_env_client(None)
|
317
287
|
# TODO(elias): reset _cancellation_context in case ?
|
318
288
|
await self._open()
|
319
|
-
# intentionally not doing self.hello since we should already be authenticated etc.
|
320
289
|
|
321
290
|
async def _get_grpclib_method(self, method_name: str) -> Any:
|
322
291
|
# safely get grcplib method that is bound to a valid channel
|
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.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.24"
|
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.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.24"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
@@ -194,7 +194,3 @@ class UnaryStreamWrapper(typing.Generic[RequestType, ResponseType]):
|
|
194
194
|
HEARTBEAT_INTERVAL: float
|
195
195
|
|
196
196
|
HEARTBEAT_TIMEOUT: float
|
197
|
-
|
198
|
-
CLIENT_CREATE_ATTEMPT_TIMEOUT: float
|
199
|
-
|
200
|
-
CLIENT_CREATE_TOTAL_TIMEOUT: float
|