modAL 1.5.6.dev4__py3-none-any.whl → 1.5.6.dev6__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.
@@ -199,16 +199,23 @@ class ConnectionManager:
199
199
  self._client = client
200
200
  # This metadata is injected into all requests on all channels created by this manager.
201
201
  self._metadata = metadata
202
- self._channels: dict[str, grpclib.client.Channel] = {}
202
+ self._channels: dict[str, asyncio.Future[grpclib.client.Channel]] = {}
203
203
 
204
204
  async def get_or_create_channel(self, server_url: str) -> grpclib.client.Channel:
205
- if server_url not in self._channels:
206
- self._channels[server_url] = await create_channel_with_fallbacks(server_url, self._metadata)
207
- return self._channels[server_url]
205
+ fut = self._channels.get(server_url)
206
+ if fut is None or (fut.done() and (fut.cancelled() or fut.exception() is not None)):
207
+ # Cache the attempt rather than the channel, so callers racing to connect share one.
208
+ fut = asyncio.ensure_future(create_channel_with_fallbacks(server_url, self._metadata))
209
+ self._channels[server_url] = fut
210
+ # Shielded so one caller giving up doesn't cancel the connection for the others.
211
+ return await asyncio.shield(fut)
208
212
 
209
213
  def close(self):
210
- for channel in self._channels.values():
211
- channel.close()
214
+ for fut in self._channels.values():
215
+ if fut.done() and not fut.cancelled() and fut.exception() is None:
216
+ fut.result().close()
217
+ else:
218
+ fut.cancel()
212
219
  self._channels.clear()
213
220
 
214
221
 
@@ -265,18 +272,34 @@ class CustomProtoStatusDetailsCodec(StatusDetailsCodecBase):
265
272
  custom_detail_codec = CustomProtoStatusDetailsCodec()
266
273
 
267
274
 
268
- def create_channel_config() -> grpclib.config.Configuration:
275
+ def create_channel_config(*, sustained_keepalive: bool = False) -> grpclib.config.Configuration:
269
276
  """Shared grpclib channel settings for Modal client connections.
270
277
 
271
278
  HTTP/2 keepalive probes keep idle transports warm through stateful
272
279
  middleboxes and surface dead connections before retries reuse them.
280
+
281
+ `sustained_keepalive` clears the two grpclib defaults such that a probe
282
+ actually goes out every `_keepalive_time`, for as long as the connection is
283
+ open.
273
284
  """
285
+ ping_throttle_overrides: dict[str, Any] = {}
286
+ if sustained_keepalive:
287
+ ping_throttle_overrides = {
288
+ # Default 2: probing stops after two probes with no request sent in
289
+ # between. Requests reset the count, acks don't. 0 = no cap.
290
+ "_http2_max_pings_without_data": 0,
291
+ # Default 300s floor between probes, applied regardless of data
292
+ # despite the name, so it has to stay under `_keepalive_time`.
293
+ "_http2_min_sent_ping_interval_without_data": GRPC_KEEPALIVE_TIME_SECS / 2,
294
+ }
295
+
274
296
  return grpclib.config.Configuration(
275
297
  _keepalive_time=GRPC_KEEPALIVE_TIME_SECS,
276
298
  _keepalive_timeout=GRPC_KEEPALIVE_TIMEOUT_SECS,
277
299
  _keepalive_permit_without_calls=True,
278
300
  http2_connection_window_size=GRPC_WINDOW_SIZE,
279
301
  http2_stream_window_size=GRPC_WINDOW_SIZE,
302
+ **ping_throttle_overrides,
280
303
  )
281
304
 
282
305
 
@@ -251,7 +251,7 @@ class TaskCommandRouterClient:
251
251
  host,
252
252
  port,
253
253
  ssl=ssl_context,
254
- config=create_channel_config(),
254
+ config=create_channel_config(sustained_keepalive=True),
255
255
  closed_error_message="Unable to perform operation on a detached sandbox",
256
256
  )
257
257
 
modal/client.pyi CHANGED
@@ -77,7 +77,7 @@ class _Client:
77
77
  server_url: str,
78
78
  client_type: int,
79
79
  credentials: typing.Optional[tuple[str, str]],
80
- version: str = "1.5.6.dev4",
80
+ version: str = "1.5.6.dev6",
81
81
  *,
82
82
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
83
83
  ):
@@ -277,7 +277,7 @@ class Client:
277
277
  server_url: str,
278
278
  client_type: int,
279
279
  credentials: typing.Optional[tuple[str, str]],
280
- version: str = "1.5.6.dev4",
280
+ version: str = "1.5.6.dev6",
281
281
  *,
282
282
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
283
283
  ):
modal/mount.py CHANGED
@@ -851,7 +851,7 @@ async def _create_single_client_dependency_mount(
851
851
  print(f"📦 Building {mount_name}.") # noqa: T201
852
852
  requirements = os.path.join(os.path.dirname(__file__), f"builder/{builder_version}.txt")
853
853
  cmd = [
854
- "uv",
854
+ os.environ.get("MODAL_UV_PATH", "uv"),
855
855
  "pip",
856
856
  "install",
857
857
  "--strict",
@@ -867,6 +867,11 @@ async def _create_single_client_dependency_mount(
867
867
  "--python",
868
868
  python_version,
869
869
  ]
870
+ # A directory holding wheels for every pin in the requirements file, for
871
+ # building mounts where no package index is reachable.
872
+ find_links = os.environ.get("MODAL_BUILDER_WHEEL_DIR")
873
+ if find_links:
874
+ cmd += ["--no-index", "--find-links", find_links]
870
875
  # Retry uv pip install on transient failures (e.g. network blips,
871
876
  # resource contention). uv exits with non-zero on recoverable errors
872
877
  # like rate limits or connection resets.
modal/sandbox.py CHANGED
@@ -2279,7 +2279,7 @@ class _Sandbox(_Object, type_prefix="sb"):
2279
2279
  command_router_client = await self._get_command_router_client(task_id)
2280
2280
  snap_v2_resp = await command_router_client.snapshot_memory(
2281
2281
  sr_pb2.TaskSnapshotMemoryRequest(task_id=task_id, idempotency_key=str(uuid.uuid4())),
2282
- timeout=55.0,
2282
+ timeout=165.0,
2283
2283
  )
2284
2284
  snapshot_id = snap_v2_resp.snapshot_id
2285
2285
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev4
3
+ Version: 1.5.6.dev6
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -34,7 +34,7 @@ modal/billing.py,sha256=E1-P_P8bJFLp-bTaggYd4BaaViImPB5UdtXiY8VeAZQ,390
34
34
  modal/billing.pyi,sha256=4m7VLYNS8IiJ2I3CJx0awvrOM6BILWRVLfdeqtAYr8M,7189
35
35
  modal/call_graph.py,sha256=XGALIDn1bLchWo_goyX23bcPjzCZcfqKUo8INIfDapo,1775
36
36
  modal/client.py,sha256=CiBsT2XuUuKps5RYc3jwJcfI_VQKK09cbkqeHfHpx3E,21476
37
- modal/client.pyi,sha256=xkqw20bz3fiI_5-ZnD4A62-enjwyGv30XalTd0tQ61g,22281
37
+ modal/client.pyi,sha256=Am_gLzSxfCCwHwhvS4l0uBzg2Fv-xoSTucuiM1aW9Vc,22281
38
38
  modal/cloud_bucket_mount.py,sha256=NrdNV9r9aL3QFrlt-Dhtd7_K3f8v1igrHf2FRwgJ1CI,6827
39
39
  modal/cloud_bucket_mount.pyi,sha256=0E8d53pMh2KsFa-rMh_i6LxlzW4cMV8drVmmkPHSqZk,8149
40
40
  modal/cls.py,sha256=oF4hswFWv72C7_FTEVqOsLEzMTv43b5bKAScnCR14x0,41737
@@ -56,7 +56,7 @@ modal/image.py,sha256=c0lUSAoU-Ip3g5DGZ0uhdCzoWSQWh1g0i-7Rxo-Sz-I,160
56
56
  modal/image.pyi,sha256=R5rdY4P30IEm-F7A2S9CdXa6QYNk-pQwsEun9BKTyLw,61094
57
57
  modal/io_streams.py,sha256=3fhhb7SDJ69pVUu9J3PiMLBePD9bpiIxPN05SCzf8FE,29118
58
58
  modal/io_streams.pyi,sha256=5uQAgwxN3aouY8UJbRmDnRj9vOId5psRaMrU4IpyQgw,24471
59
- modal/mount.py,sha256=pWVsUwr3kPiNIHuzzj_fLvr7g9WFsPNGnhHhDiabC_k,36168
59
+ modal/mount.py,sha256=Mks6IcgK5W7q6B5NmWHc95e4az74kSZvAvRLXPT2_KU,36493
60
60
  modal/mount.pyi,sha256=5JFXGXvA9uOxh5n-xYL6ruRvrs59K84-LS7BKwwNjHU,16391
61
61
  modal/network_file_system.py,sha256=Mr7gWcxqhS-SqBkFfDgt9lt5Sh36vxb3HXpeGF2qCVE,14159
62
62
  modal/network_file_system.pyi,sha256=MS4srjGFl-ogqgNLxYRlj6Ik1ObwZKJ0WrneYKMKk-A,17927
@@ -76,7 +76,7 @@ modal/retries.py,sha256=tSGB5bGM5xZZUDXMkuelIEoQJY0vOukOssZhwc6tXAo,5014
76
76
  modal/runner.py,sha256=5auTijqKFvMleYfVaK01sRdXd1dWQLilXlSqWx9gMAM,31344
77
77
  modal/runner.pyi,sha256=jIdnTwYv5AH-c8weyeCFzJ0hIekw81QqUsxVzXbgkcc,9954
78
78
  modal/running_app.py,sha256=xSR1VVlhxEFytmjx2x697_0GpD25Vc9crhp6r117_QY,1145
79
- modal/sandbox.py,sha256=vdqjP4c7IAq5BIsobmI0IUiDgl7frCI8vBd1E0lj5q0,134875
79
+ modal/sandbox.py,sha256=AOvNw9Uoz40UtHmx2tGZ5B84PEDv6lWbiA-qBT8FBIM,134876
80
80
  modal/sandbox.pyi,sha256=shhenvSkuFYqrozmY6AbnmEuS0-Sp-zbNgeNTTQr378,157117
81
81
  modal/sandbox_fs.py,sha256=GSuPlrLSdQ7fG7c_TGMd-VWs_s6RqQxMvLivdvC6s4s,27002
82
82
  modal/sandbox_fs.pyi,sha256=TJWx8h7x3yRC7KUxeF7q62_0a7w1IHufnJL7AYEojAg,45285
@@ -129,7 +129,7 @@ modal/_utils/docker_utils.py,sha256=k8fkYT6G-X_B3iamjxiSpwSE-k58xGVHtsk2_gR7i2Q,
129
129
  modal/_utils/function_utils.py,sha256=Gmirx0Qs-xu41UF0LZjbgzqtis1i8Y73oIZWpJngsf8,26661
130
130
  modal/_utils/git_utils.py,sha256=Lat_l4O4hwOYgylEtWup3hyqWT8D7JoCIjCflXcX8BE,3148
131
131
  modal/_utils/grpc_testing.py,sha256=udyK_wRXX_yc9eLt8mIZZqx12XlaiUqORxaGNvfI_MM,8697
132
- modal/_utils/grpc_utils.py,sha256=vGL6JUNRahbe4HRWV_bS1ceCfszzKNVIgAW_bzUNo74,24249
132
+ modal/_utils/grpc_utils.py,sha256=NSoXoYoOsjvWl-x6ccmCid_McfJGCmnpRoZDA8VtcFM,25515
133
133
  modal/_utils/hash_utils.py,sha256=WHuMRwmB4fnaM-qmyWXR87C2CinvLnmRoygSzsTEunA,3008
134
134
  modal/_utils/http_utils.py,sha256=TO9nnKR0Cc-iWCmkou56wJh9MHgNZwQMosK8WK-GD5Y,3133
135
135
  modal/_utils/jwt_utils.py,sha256=KUoqGHMM4ewDyTsTKXPm1wrMu8Z6lp0l8VILHufm484,1354
@@ -142,7 +142,7 @@ modal/_utils/proxy_support.py,sha256=hLA_XojYxzPysOiLqyUbB-SgMLmn_X8WtXTk92xh7g4
142
142
  modal/_utils/rand_pb_testing.py,sha256=kwAKIros_g6XPXluiJAktQ79YPkD9SGopKGl83GQ1l0,4361
143
143
  modal/_utils/sandbox_fs_utils.py,sha256=rsfo1uDbVSoiY21XERDzsmp-DALcNmlPfJ6jn_2p5T0,15680
144
144
  modal/_utils/shell_utils.py,sha256=_2yHrlx4Crx5JWQXECLPQ51kclu8H-U3N7jOkBN7q5U,3390
145
- modal/_utils/task_command_router_client.py,sha256=Jl3ozfZ1b2w6dRVduVatm7QQn6KnjDEgnOYn6Ov7GXc,46095
145
+ modal/_utils/task_command_router_client.py,sha256=SLdKsNzZbEnIFcFQcZLhtaZpisadIjmBXdoNScADCKU,46119
146
146
  modal/_utils/time_utils.py,sha256=Q5NpNyclZWinfpI4WjcBWdqrL81JbiuVuEbQ4NMsqTc,11045
147
147
  modal/_vendor/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
148
148
  modal/_vendor/a2wsgi_wsgi.py,sha256=RVLIYVJA8a8o96QxEXYT5tuog2zev2S24XOuAVTXZAs,21872
@@ -198,7 +198,7 @@ modal/experimental/flash.py,sha256=m803RsPvMgLCSNJfT_11da1sz9l1BIr1MDxAA_qtK2k,1
198
198
  modal/experimental/flash.pyi,sha256=tHS2un-PxndcxV27l7CAWnPhRPmiIs2cCNzKLXBNxJA,8520
199
199
  modal/experimental/ipython.py,sha256=kJ6o9wkAY_MiSb7paewI7f1MJAeqdi8TX5pBdhr_mUI,2925
200
200
  modal/skills/modal/SKILL.md,sha256=ERhA-zss3OoPXU_d3Qo8-PNrK2QI_ctyhGavou3egd0,5276
201
- modal-1.5.6.dev4.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
201
+ modal-1.5.6.dev6.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
202
202
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
203
203
  modal_docs/gen_cli_docs.py,sha256=9-b8vl2EsLWD9zJqpCK8IUi5h44Bjn3i3rSdedZV4bA,6415
204
204
  modal_docs/gen_cli_docs_main.py,sha256=UPGr7O1DYTIRLwfINe2rbdh89J3cPVTU1OWL-SjxnOQ,198
@@ -222,10 +222,10 @@ modal_proto/task_command_router_pb2.py,sha256=rMwEVv6DV1Hpv8_hi7SE0zxCkrv6elWgwK
222
222
  modal_proto/task_command_router_pb2.pyi,sha256=OQ2jBFSduSEPtP3llAhFhW2dZJJxsqBy3Y5s8Yj40nA,47640
223
223
  modal_proto/task_command_router_pb2_grpc.py,sha256=zuFkJgKcswB2ftjnyEgs70dp61VMA7gK0h0uzR3gYFw,42563
224
224
  modal_proto/task_command_router_pb2_grpc.pyi,sha256=UjAJFqz9DpOFXP04fVm5lB2eAyTtjOg3oKjfWei8yLU,14262
225
- modal_version/__init__.py,sha256=44vhXqcK_Swn8S11Mfc4Ss4Kqm7A3Nhi3vo7-C6Pc14,120
225
+ modal_version/__init__.py,sha256=84HgAIM1X72_mUCXkfnyVMdVFz9XeG0k2mySGSL98qA,120
226
226
  modal_version/__main__.py,sha256=nLUbubxKx7GXNzJIkTB7M7_u8080LyLM0IS0rzFkC_c,119
227
- modal-1.5.6.dev4.dist-info/METADATA,sha256=1G6fdSftvRj_xzOuvUZbpwvvsDaHMyt1MTpzsSHi0Lw,2512
228
- modal-1.5.6.dev4.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
229
- modal-1.5.6.dev4.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
230
- modal-1.5.6.dev4.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
231
- modal-1.5.6.dev4.dist-info/RECORD,,
227
+ modal-1.5.6.dev6.dist-info/METADATA,sha256=HTFx36bZnfakJY3u3YzflSeYISJD5vfx4x3eavCtVWQ,2512
228
+ modal-1.5.6.dev6.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
229
+ modal-1.5.6.dev6.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
230
+ modal-1.5.6.dev6.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
231
+ modal-1.5.6.dev6.dist-info/RECORD,,
modal_version/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2026
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.5.6.dev4"
4
+ __version__ = "1.5.6.dev6"