modal 0.68.15__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.15"
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.15"
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.68.15
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=mjSotEeVvuR11P-WpdTPblxpbhS21NjCSQBcWRoAMaU,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
@@ -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
@@ -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=7XpspEFf5FGas8vdYLv-tDI0ybtS-XdOb7wYBs7iRy8,149
165
- modal-0.68.15.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
166
- modal-0.68.15.dist-info/METADATA,sha256=pcVrgPsUNSuUoUs0CRjOoRorritv2Qs9nh-G186YeTY,2329
167
- modal-0.68.15.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
168
- modal-0.68.15.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
169
- modal-0.68.15.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
170
- modal-0.68.15.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,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2024
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 15 # git: ee4d47a
4
+ build_number = 16 # git: da30174