modal 1.1.5.dev2__py3-none-any.whl → 1.1.5.dev3__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/client.pyi CHANGED
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.1.5.dev2",
36
+ version: str = "1.1.5.dev3",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.1.5.dev2",
167
+ version: str = "1.1.5.dev3",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -21,7 +21,7 @@ from ..client import _Client
21
21
  from ..config import logger
22
22
  from ..exception import InvalidError
23
23
 
24
- MAX_FAILURES = 3
24
+ _MAX_FAILURES = 3
25
25
 
26
26
 
27
27
  class _FlashManager:
@@ -42,7 +42,9 @@ class _FlashManager:
42
42
  self.num_failures = 0
43
43
  self.task_id = os.environ["MODAL_TASK_ID"]
44
44
 
45
- async def check_port_connection(self, process: Optional[subprocess.Popen], timeout: int = 10):
45
+ async def is_port_connection_healthy(
46
+ self, process: Optional[subprocess.Popen], timeout: int = 5
47
+ ) -> tuple[bool, Optional[Exception]]:
46
48
  import socket
47
49
 
48
50
  start_time = time.monotonic()
@@ -50,13 +52,13 @@ class _FlashManager:
50
52
  while time.monotonic() - start_time < timeout:
51
53
  try:
52
54
  if process is not None and process.poll() is not None:
53
- return Exception(f"Process {process.pid} exited with code {process.returncode}")
55
+ return False, Exception(f"Process {process.pid} exited with code {process.returncode}")
54
56
  with socket.create_connection(("localhost", self.port), timeout=1):
55
- return
57
+ return True, None
56
58
  except (ConnectionRefusedError, OSError):
57
59
  await asyncio.sleep(0.1)
58
60
 
59
- return Exception(f"Waited too long for port {self.port} to start accepting connections")
61
+ return False, Exception(f"Waited too long for port {self.port} to start accepting connections")
60
62
 
61
63
  async def _start(self):
62
64
  self.tunnel = await self.tunnel_manager.__aenter__()
@@ -74,7 +76,7 @@ class _FlashManager:
74
76
  while True:
75
77
  try:
76
78
  # Check if the container should be drained (e.g., too many failures)
77
- if self.num_failures > MAX_FAILURES:
79
+ if self.num_failures > _MAX_FAILURES:
78
80
  logger.warning(
79
81
  f"[Modal Flash] Draining task {self.task_id} on {self.tunnel.url} due to too many failures."
80
82
  )
@@ -101,35 +103,38 @@ class _FlashManager:
101
103
  first_registration = True
102
104
  while True:
103
105
  try:
104
- await self.check_port_connection(process=self.process)
105
- resp = await self.client.stub.FlashContainerRegister(
106
- api_pb2.FlashContainerRegisterRequest(
107
- priority=10,
108
- weight=5,
109
- host=host,
110
- port=port,
111
- ),
112
- timeout=10,
113
- )
114
- self.num_failures = 0
115
- if first_registration:
116
- logger.warning(
117
- f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
106
+ port_check_resp, port_check_error = await self.is_port_connection_healthy(process=self.process)
107
+ if port_check_resp:
108
+ resp = await self.client.stub.FlashContainerRegister(
109
+ api_pb2.FlashContainerRegisterRequest(
110
+ priority=10,
111
+ weight=5,
112
+ host=host,
113
+ port=port,
114
+ ),
115
+ timeout=10,
116
+ )
117
+ self.num_failures = 0
118
+ if first_registration:
119
+ logger.warning(
120
+ f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
121
+ )
122
+ first_registration = False
123
+ else:
124
+ logger.error(
125
+ f"[Modal Flash] Deregistering container {self.task_id} on {self.tunnel.url} "
126
+ f"due to error: {port_check_error}, num_failures: {self.num_failures}"
127
+ )
128
+ self.num_failures += 1
129
+ await retry_transient_errors(
130
+ self.client.stub.FlashContainerDeregister,
131
+ api_pb2.FlashContainerDeregisterRequest(),
118
132
  )
119
- first_registration = False
120
133
  except asyncio.CancelledError:
121
134
  logger.warning("[Modal Flash] Shutting down...")
122
135
  break
123
136
  except Exception as e:
124
137
  logger.error(f"[Modal Flash] Heartbeat failed: {e}")
125
- self.num_failures += 1
126
- logger.error(
127
- f"[Modal Flash] Deregistering container {self.tunnel.url}, num_failures: {self.num_failures}"
128
- )
129
- await retry_transient_errors(
130
- self.client.stub.FlashContainerDeregister,
131
- api_pb2.FlashContainerDeregisterRequest(),
132
- )
133
138
 
134
139
  try:
135
140
  await asyncio.sleep(1)
@@ -167,7 +172,9 @@ FlashManager = synchronize_api(_FlashManager)
167
172
 
168
173
  @synchronizer.create_blocking
169
174
  async def flash_forward(
170
- port: int, process: Optional[subprocess.Popen] = None, health_check_url: Optional[str] = None
175
+ port: int,
176
+ process: Optional[subprocess.Popen] = None,
177
+ health_check_url: Optional[str] = None,
171
178
  ) -> _FlashManager:
172
179
  """
173
180
  Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
@@ -15,7 +15,9 @@ class _FlashManager:
15
15
  """Initialize self. See help(type(self)) for accurate signature."""
16
16
  ...
17
17
 
18
- async def check_port_connection(self, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
18
+ async def is_port_connection_healthy(
19
+ self, process: typing.Optional[subprocess.Popen], timeout: int = 5
20
+ ) -> tuple[bool, typing.Optional[Exception]]: ...
19
21
  async def _start(self): ...
20
22
  async def _drain_container(self):
21
23
  """Background task that checks if we've encountered too many failures and drains the container if so."""
@@ -37,11 +39,15 @@ class FlashManager:
37
39
  health_check_url: typing.Optional[str] = None,
38
40
  ): ...
39
41
 
40
- class __check_port_connection_spec(typing_extensions.Protocol[SUPERSELF]):
41
- def __call__(self, /, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
42
- async def aio(self, /, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
42
+ class __is_port_connection_healthy_spec(typing_extensions.Protocol[SUPERSELF]):
43
+ def __call__(
44
+ self, /, process: typing.Optional[subprocess.Popen], timeout: int = 5
45
+ ) -> tuple[bool, typing.Optional[Exception]]: ...
46
+ async def aio(
47
+ self, /, process: typing.Optional[subprocess.Popen], timeout: int = 5
48
+ ) -> tuple[bool, typing.Optional[Exception]]: ...
43
49
 
44
- check_port_connection: __check_port_connection_spec[typing_extensions.Self]
50
+ is_port_connection_healthy: __is_port_connection_healthy_spec[typing_extensions.Self]
45
51
 
46
52
  class ___start_spec(typing_extensions.Protocol[SUPERSELF]):
47
53
  def __call__(self, /): ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.5.dev2
3
+ Version: 1.1.5.dev3
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,7 +22,7 @@ modal/app.py,sha256=F4baVULljFq0CwC_7U-EKNRNx7CYeWBKudjjYUuWc4U,48416
22
22
  modal/app.pyi,sha256=AbXJCBkyt2rI_-M3VbTBYb32at0P6iRZuoC87xY_JrQ,43591
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
24
  modal/client.py,sha256=kyAIVB3Ay-XKJizQ_1ufUFB__EagV0MLmHJpyYyJ7J0,18636
25
- modal/client.pyi,sha256=hJfIJH60sX3D2zzwpVGIdUjFeh0YYNyJZCIagvmVLc8,15829
25
+ modal/client.pyi,sha256=c9p8XWAlM_rGXLwTQmPudc2C_CW7Melsbw20mhAj8jE,15829
26
26
  modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
27
27
  modal/cloud_bucket_mount.pyi,sha256=-qSfYAQvIoO_l2wsCCGTG5ZUwQieNKXdAO00yP1-LYU,7394
28
28
  modal/cls.py,sha256=pTEO7pHjlO7taMbIqs4oI9ZZgKDJpVKyGkO5ZT0w6tQ,40934
@@ -150,10 +150,10 @@ modal/cli/programs/run_jupyter.py,sha256=44Lpvqk2l3hH-uOkmAOzw60NEsfB5uaRDWDKVsh
150
150
  modal/cli/programs/run_marimo.py,sha256=HyZ2za0NYqg31-mGxFQxUIAJ3Q-jRaMocEwWwI5-cdw,2887
151
151
  modal/cli/programs/vscode.py,sha256=KbTAaIXyQBVCDXxXjmBHmKpgXkUw0q4R4KkJvUjCYgk,3380
152
152
  modal/experimental/__init__.py,sha256=fCqzo_f3vcY750vHtd7CtLs5dvdM_C0ZLLGb3zXuK9w,14913
153
- modal/experimental/flash.py,sha256=dPTB1k2H72dsq3TuEFs2WAxrU8y5n-SoJzZ4AXVMdZs,28287
154
- modal/experimental/flash.pyi,sha256=32bvUlolZHthplDJNXokmbjwb0RSOuXGCBpU6qfFPOk,13732
153
+ modal/experimental/flash.py,sha256=amsEPtzD2OX5w4YcTPKj9MAUhANEgQni1VHnYjLshrc,28647
154
+ modal/experimental/flash.pyi,sha256=Tu9n25ZnW4dO1YjNRHIQpZb4VWSfNW5IENrY0HJW-ME,13936
155
155
  modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
156
- modal-1.1.5.dev2.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
156
+ modal-1.1.5.dev3.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
157
157
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
158
158
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
159
159
  modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
@@ -176,10 +176,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
176
176
  modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
177
177
  modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
178
178
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
- modal_version/__init__.py,sha256=7xr5x1fR3u8HehALlYaPVtqFIs1N4jX9fmYOu7qKObo,120
179
+ modal_version/__init__.py,sha256=0Y1tDsgw11T_vf_1T67OFGKn1Gtt4JMwDn3IKJeIETY,120
180
180
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
181
- modal-1.1.5.dev2.dist-info/METADATA,sha256=w5lDiGuDQkQMzeJaXCB77cmJTdB5sznIbfgyqDSJG_0,2459
182
- modal-1.1.5.dev2.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
183
- modal-1.1.5.dev2.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
184
- modal-1.1.5.dev2.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
185
- modal-1.1.5.dev2.dist-info/RECORD,,
181
+ modal-1.1.5.dev3.dist-info/METADATA,sha256=mactKwIxNbCbeYMuYXywsEKRAnJgV6GEnURnLtmTI5o,2459
182
+ modal-1.1.5.dev3.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
183
+ modal-1.1.5.dev3.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
184
+ modal-1.1.5.dev3.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
185
+ modal-1.1.5.dev3.dist-info/RECORD,,
modal_version/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.1.5.dev2"
4
+ __version__ = "1.1.5.dev3"