fal 1.49.3__py3-none-any.whl → 1.50.0__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.

Potentially problematic release.


This version of fal might be problematic. Click here for more details.

fal/_fal_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.49.3'
32
- __version_tuple__ = version_tuple = (1, 49, 3)
31
+ __version__ = version = '1.50.0'
32
+ __version_tuple__ = version_tuple = (1, 50, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
fal/api/client.py CHANGED
@@ -66,6 +66,12 @@ class _RunnersNamespace:
66
66
  def list(self, *, since=None) -> List[RunnerInfo]:
67
67
  return runners_api.list_runners(self.client, since=since)
68
68
 
69
+ def stop(self, runner_id: str) -> None:
70
+ return runners_api.stop_runner(self.client, runner_id)
71
+
72
+ def kill(self, runner_id: str) -> None:
73
+ return runners_api.kill_runner(self.client, runner_id)
74
+
69
75
 
70
76
  @dataclass
71
77
  class SyncServerlessClient:
fal/api/runners.py CHANGED
@@ -14,3 +14,13 @@ def list_runners(
14
14
  ) -> List[RunnerInfo]:
15
15
  with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
16
16
  return conn.list_runners(start_time=since)
17
+
18
+
19
+ def stop_runner(client: SyncServerlessClient, runner_id: str) -> None:
20
+ with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
21
+ conn.stop_runner(runner_id)
22
+
23
+
24
+ def kill_runner(client: SyncServerlessClient, runner_id: str) -> None:
25
+ with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
26
+ conn.kill_runner(runner_id)
fal/cli/runners.py CHANGED
@@ -1,12 +1,23 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import argparse
4
+ import fcntl
3
5
  import json
6
+ import os
7
+ import signal
8
+ import struct
9
+ import sys
10
+ import termios
11
+ import tty
4
12
  from collections import deque
5
13
  from dataclasses import dataclass
6
14
  from datetime import datetime, timedelta, timezone
7
15
  from http import HTTPStatus
16
+ from queue import Empty, Queue
17
+ from threading import Thread
8
18
  from typing import Iterator, List
9
19
 
20
+ import grpc
10
21
  import httpx
11
22
  from httpx_sse import connect_sse
12
23
  from rich.console import Console
@@ -14,7 +25,7 @@ from structlog.typing import EventDict
14
25
 
15
26
  from fal.api.client import SyncServerlessClient
16
27
  from fal.rest_client import REST_CLIENT
17
- from fal.sdk import FalServerlessClient, RunnerInfo, RunnerState
28
+ from fal.sdk import RunnerInfo, RunnerState
18
29
 
19
30
  from .parser import FalClientParser, SinceAction, get_output_parser
20
31
 
@@ -95,12 +106,108 @@ def runners_requests_table(runners: list[RunnerInfo]):
95
106
  return table
96
107
 
97
108
 
109
+ def _get_tty_size():
110
+ """Get current terminal dimensions."""
111
+ try:
112
+ h, w = struct.unpack("HH", fcntl.ioctl(0, termios.TIOCGWINSZ, b"\0" * 4))[:2]
113
+ return h, w
114
+ except (OSError, ValueError):
115
+ return 24, 80 # Fallback to standard size
116
+
117
+
118
+ def _shell(args):
119
+ """Execute interactive shell in runner."""
120
+ import isolate_proto
121
+
122
+ client = SyncServerlessClient(host=args.host, team=args.team)
123
+ stub = client._create_host()._connection.stub
124
+ runner_id = args.id
125
+
126
+ # Setup terminal for raw mode
127
+ fd = sys.stdin.fileno()
128
+ old_settings = termios.tcgetattr(fd)
129
+ tty.setraw(fd)
130
+
131
+ # Message queue for stdin data and resize events
132
+ messages = Queue() # type: ignore
133
+ stop_flag = False
134
+
135
+ def handle_resize(*_):
136
+ messages.put(("resize", None))
137
+
138
+ signal.signal(signal.SIGWINCH, handle_resize)
139
+
140
+ def read_stdin():
141
+ """Read stdin in a background thread."""
142
+ nonlocal stop_flag
143
+ while not stop_flag:
144
+ try:
145
+ data = os.read(fd, 4096)
146
+ if not data:
147
+ break
148
+ messages.put(("data", data))
149
+ except OSError:
150
+ break
151
+
152
+ reader = Thread(target=read_stdin, daemon=True)
153
+ reader.start()
154
+
155
+ def stream_inputs():
156
+ """Generate input stream for gRPC."""
157
+ # Send initial message with runner_id and terminal size
158
+ msg = isolate_proto.ShellRunnerInput(runner_id=runner_id)
159
+ h, w = _get_tty_size()
160
+ msg.tty_size.height = h
161
+ msg.tty_size.width = w
162
+ yield msg
163
+
164
+ # Stream stdin data and resize events
165
+ while True:
166
+ try:
167
+ msg_type, data = messages.get(timeout=0.1)
168
+ except Empty:
169
+ continue
170
+
171
+ if msg_type == "data":
172
+ yield isolate_proto.ShellRunnerInput(data=data)
173
+ elif msg_type == "resize":
174
+ msg = isolate_proto.ShellRunnerInput()
175
+ h, w = _get_tty_size()
176
+ msg.tty_size.height = h
177
+ msg.tty_size.width = w
178
+ yield msg
179
+
180
+ exit_code = 1
181
+ try:
182
+ for output in stub.ShellRunner(stream_inputs()):
183
+ if output.HasField("exit_code"):
184
+ exit_code = output.exit_code
185
+ break
186
+ if output.data:
187
+ sys.stdout.buffer.write(output.data)
188
+ sys.stdout.buffer.flush()
189
+ if output.close:
190
+ break
191
+ exit_code = exit_code or 0
192
+ except grpc.RpcError as exc:
193
+ args.console.print(f"\n[red]Connection error:[/] {exc.details()}")
194
+ except Exception as exc:
195
+ args.console.print(f"\n[red]Error:[/] {exc}")
196
+ finally:
197
+ stop_flag = True
198
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
199
+
200
+ return exit_code
201
+
202
+
203
+ def _stop(args):
204
+ client = SyncServerlessClient(host=args.host, team=args.team)
205
+ client.runners.stop(args.id)
206
+
207
+
98
208
  def _kill(args):
99
209
  client = SyncServerlessClient(host=args.host, team=args.team)
100
- with FalServerlessClient(
101
- client._grpc_host, client._credentials
102
- ).connect() as connection:
103
- connection.kill_runner(args.id)
210
+ client.runners.kill(args.id)
104
211
 
105
212
 
106
213
  def _list_json(args, runners: list[RunnerInfo]):
@@ -161,6 +268,21 @@ def _list(args):
161
268
  raise AssertionError(f"Invalid output format: {args.output}")
162
269
 
163
270
 
271
+ def _add_stop_parser(subparsers, parents):
272
+ stop_help = "Stop a runner gracefully."
273
+ parser = subparsers.add_parser(
274
+ "stop",
275
+ description=stop_help,
276
+ help=stop_help,
277
+ parents=parents,
278
+ )
279
+ parser.add_argument(
280
+ "id",
281
+ help="Runner ID.",
282
+ )
283
+ parser.set_defaults(func=_stop)
284
+
285
+
164
286
  def _add_kill_parser(subparsers, parents):
165
287
  kill_help = "Kill a runner."
166
288
  parser = subparsers.add_parser(
@@ -546,6 +668,17 @@ def _add_logs_parser(subparsers, parents):
546
668
  parser.set_defaults(func=_logs)
547
669
 
548
670
 
671
+ def _add_shell_parser(subparsers, parents):
672
+ """Add hidden shell command parser."""
673
+ parser = subparsers.add_parser(
674
+ "shell",
675
+ help=argparse.SUPPRESS,
676
+ parents=parents,
677
+ )
678
+ parser.add_argument("id", help="Runner ID.")
679
+ parser.set_defaults(func=_shell)
680
+
681
+
549
682
  def add_parser(main_subparsers, parents):
550
683
  runners_help = "Manage fal runners."
551
684
  parser = main_subparsers.add_parser(
@@ -563,6 +696,8 @@ def add_parser(main_subparsers, parents):
563
696
  parser_class=FalClientParser,
564
697
  )
565
698
 
699
+ _add_stop_parser(subparsers, parents)
566
700
  _add_kill_parser(subparsers, parents)
567
701
  _add_list_parser(subparsers, parents)
568
702
  _add_logs_parser(subparsers, parents)
703
+ _add_shell_parser(subparsers, parents)
fal/logging/__init__.py CHANGED
@@ -7,11 +7,6 @@ from structlog.typing import EventDict, WrappedLogger
7
7
 
8
8
  from .style import LEVEL_STYLES
9
9
 
10
- # Unfortunately structlog console processor does not support
11
- # more general theming as a public API. Consider a PR on the
12
- # structlog repo to add better support for it.
13
- structlog.dev._ColorfulStyles.bright = ""
14
-
15
10
 
16
11
  class DebugConsoleLogProcessor:
17
12
  """
fal/sdk.py CHANGED
@@ -884,6 +884,10 @@ class FalServerlessConnection:
884
884
  for secret in response.secrets
885
885
  ]
886
886
 
887
+ def stop_runner(self, runner_id: str) -> None:
888
+ request = isolate_proto.StopRunnerRequest(runner_id=runner_id)
889
+ self.stub.StopRunner(request)
890
+
887
891
  def kill_runner(self, runner_id: str) -> None:
888
892
  request = isolate_proto.KillRunnerRequest(runner_id=runner_id)
889
893
  self.stub.KillRunner(request)
@@ -56,21 +56,44 @@ def _should_retry(exc: Exception) -> bool:
56
56
  return False
57
57
 
58
58
 
59
+ class _RetryingRequestContext:
60
+ def __init__(self, request: Request, kwargs: dict[str, Any]):
61
+ self.request = request
62
+ self.kwargs = kwargs
63
+ self._cm: Any | None = None
64
+ self._response: addinfourl | None = None
65
+
66
+ def __enter__(self) -> addinfourl:
67
+ def _enter_once() -> addinfourl:
68
+ # Obtain the original context manager and explicitly enter it
69
+ self._cm = _urlopen(self.request, **self.kwargs)
70
+ return self._cm.__enter__()
71
+
72
+ _enter_with_retry = retry(
73
+ max_retries=MAX_ATTEMPTS,
74
+ base_delay=BASE_DELAY,
75
+ max_delay=MAX_DELAY,
76
+ backoff_type="exponential",
77
+ jitter=True,
78
+ should_retry=_should_retry,
79
+ )(_enter_once)
80
+
81
+ self._response = _enter_with_retry()
82
+ assert self._response is not None
83
+ return self._response
84
+
85
+ def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
86
+ if self._cm is not None:
87
+ return self._cm.__exit__(exc_type, exc_val, exc_tb)
88
+ return False
89
+
90
+
59
91
  @contextmanager
60
92
  def _maybe_retry_request(
61
93
  request: Request,
62
94
  **kwargs: Any,
63
95
  ) -> Generator[addinfourl, None, None]:
64
- _urlopen_with_retry = retry(
65
- max_retries=MAX_ATTEMPTS,
66
- base_delay=BASE_DELAY,
67
- max_delay=MAX_DELAY,
68
- backoff_type="exponential",
69
- jitter=True,
70
- should_retry=_should_retry,
71
- )(_urlopen)
72
-
73
- with _urlopen_with_retry(request, **kwargs) as response:
96
+ with _RetryingRequestContext(request, kwargs) as response:
74
97
  yield response
75
98
 
76
99
 
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.49.3
3
+ Version: 1.50.0
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.21.0,>=0.18.0
9
- Requires-Dist: isolate-proto<0.20.0,>=0.19.0
9
+ Requires-Dist: isolate-proto<0.23.0,>=0.22.0
10
10
  Requires-Dist: grpcio<2,>=1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -1,6 +1,6 @@
1
1
  fal/__init__.py,sha256=wXs1G0gSc7ZK60-bHe-B2m0l_sA6TrFk4BxY0tMoLe8,784
2
2
  fal/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83
3
- fal/_fal_version.py,sha256=Fm2Lp5OuT8uPpdd9Zw-cA1A-4engOTbwXFkIEgRRG9s,706
3
+ fal/_fal_version.py,sha256=_Pf-vbXKGGS2hP-Yx1JBi6Y-zBGNCXuwL2wRrnt_z_s,706
4
4
  fal/_serialization.py,sha256=2hPQhinTWinTTs2gDjPG6SxVCwkL_i6S8TfOSoCqLUs,7626
5
5
  fal/_version.py,sha256=1BbTFnucNC_6ldKJ_ZoC722_UkW4S9aDBSW9L0fkKAw,2315
6
6
  fal/app.py,sha256=mW7H2k41y4-BTEyvVmwTMEfVy2a5_ctAHTZEtQCn2ig,31061
@@ -13,16 +13,16 @@ fal/flags.py,sha256=QonyDM7R2GqfAB1bJr46oriu-fHJCkpUwXuSdanePWg,987
13
13
  fal/project.py,sha256=QgfYfMKmNobMPufrAP_ga1FKcIAlSbw18Iar1-0qepo,2650
14
14
  fal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  fal/rest_client.py,sha256=kGBGmuyHfX1lR910EoKCYPjsyU8MdXawT_cW2q8Sajc,568
16
- fal/sdk.py,sha256=e4ZzTn4qjUeRc0puqdITtI0omDrClenZNwmuUea4Z8I,29508
16
+ fal/sdk.py,sha256=ksxXaSKzsK5KehDsmPzuhh-RX6kJazttuAY41NN-kqQ,29669
17
17
  fal/sync.py,sha256=ZuIJA2-hTPNANG9B_NNJZUsO68EIdTH0dc9MzeVE2VU,4340
18
18
  fal/utils.py,sha256=GSDJFdGBM7MtjkWIZW-VHcA6T99nZVYcRClLcoKNHuk,2309
19
19
  fal/workflows.py,sha256=Zl4f6Bs085hY40zmqScxDUyCu7zXkukDbW02iYOLTTI,14805
20
20
  fal/api/__init__.py,sha256=dcPgWqbf3xA0BSv1rKsZMOmIyZeHjiIwTrGLYQHnhjI,88
21
21
  fal/api/api.py,sha256=m67EqjDcCiCHFdBDkVpCqFEgiW2panqKXay-JZxpXzo,51940
22
22
  fal/api/apps.py,sha256=YSy2RUvFGBmXp8OrtWf49FMLvzwS79e0LD426naaMx0,2219
23
- fal/api/client.py,sha256=DGKL-FAc_JNTacZ7MW6zDWkozTbECQSeo3lQQoaFIRY,3925
23
+ fal/api/client.py,sha256=KCHZ6_Eom5j4n0hAujsGBjjXaOySg1KBh3dk0V8Qaw8,4141
24
24
  fal/api/deploy.py,sha256=1HpvVbdKxJHXjq6Y7KzSHTKfe_MYDs_a2vR5tKMAykA,6447
25
- fal/api/runners.py,sha256=4P1llGDhcl0xM5UayK8XvxGMU-o07gzZaAchkUUKREY,484
25
+ fal/api/runners.py,sha256=QE7SnczoIC5ZEmeTFShkhaSFOMMkHBHrt-3y_IuFruE,878
26
26
  fal/auth/__init__.py,sha256=mtyQou8DGHC-COjW9WbtRyyzjyt7fMlhVmsB4U-CBh4,6509
27
27
  fal/auth/auth0.py,sha256=g5OgEKe4rsbkLQp6l7EauOAVL6WsmKjuA1wmzmyvvhc,5354
28
28
  fal/auth/local.py,sha256=sndkM6vKpeVny6NHTacVlTbiIFqaksOmw0Viqs_RN1U,1790
@@ -43,7 +43,7 @@ fal/cli/parser.py,sha256=siSY1kxqczZIs3l_jLwug_BpVzY_ZqHpewON3am83Ow,6658
43
43
  fal/cli/profile.py,sha256=PAY_ffifCT71VJ8VxfDVaXPT0U1oN8drvWZDFRXwvek,6678
44
44
  fal/cli/queue.py,sha256=YhcD0URGeqaJXqTl1hPEV9-UqdWsQ-4beG5xE9AGLJI,2727
45
45
  fal/cli/run.py,sha256=l3wq-Fa3bCznKrdsC-4ouAU_74rAjrb-SqRCWEuGJQM,1432
46
- fal/cli/runners.py,sha256=29SQqBQQidABiDYaW-SA68HjxVw4tbxfXwimevX_xkw,17282
46
+ fal/cli/runners.py,sha256=JaqSQWQXRjM3vatllLUp8KnfElhUpih9kyPCuaHUedY,20980
47
47
  fal/cli/secrets.py,sha256=HfIeO2IZpCEiBC6Cs5Kpi3zckfDnc7GsLwLdgj3NnPU,3085
48
48
  fal/cli/teams.py,sha256=_JcNcf659ZoLBFOxKnVP5A6Pyk1jY1vh4_xzMweYIDo,1285
49
49
  fal/console/__init__.py,sha256=lGPUuTqIM9IKTa1cyyA-MA2iZJKVHp2YydsITZVlb6g,148
@@ -56,7 +56,7 @@ fal/exceptions/__init__.py,sha256=4hq-sy3dMZs6YxvbO_p6R-bK4Tzf7ubvA8AyUR0GVPo,34
56
56
  fal/exceptions/_base.py,sha256=PLSOHQs7lftDaRYDHKz9xkB6orQvynmUTi4DrdPnYMs,1797
57
57
  fal/exceptions/_cuda.py,sha256=L3qvDNaPTthp95IFSBI6pMt3YbRfn1H0inQkj_7NKF8,1719
58
58
  fal/exceptions/auth.py,sha256=fHea3SIeguInJVB5M33IuP4I5e_pVEifck1C_XJTYvc,351
59
- fal/logging/__init__.py,sha256=U7DhMpnNqmVdC2XCT5xZkNmYhpL0Q85iDYPeSo_56LU,1532
59
+ fal/logging/__init__.py,sha256=SRuG6TpTmxFmPtAKH0ZBqhpvahfBccFbaKNvKRZPPd0,1320
60
60
  fal/logging/isolate.py,sha256=jIryi46ZVlJ1mfan4HLNQQ3jwMi8z-WwfqqLlttQVkc,2449
61
61
  fal/logging/style.py,sha256=ckIgHzvF4DShM5kQh8F133X53z_vF46snuDHVmo_h9g,386
62
62
  fal/logging/trace.py,sha256=OhzB6d4rQZimBc18WFLqH_9BGfqFFumKKTAGSsmWRMg,1904
@@ -70,7 +70,7 @@ fal/toolkit/audio/audio.py,sha256=gt458h989iQ-EhQSH-mCuJuPBY4RneLJE05f_QWU1E0,57
70
70
  fal/toolkit/file/__init__.py,sha256=FbNl6wD-P0aSSTUwzHt4HujBXrbC3ABmaigPQA4hRfg,70
71
71
  fal/toolkit/file/file.py,sha256=_KCKmtmBkBIKD_gFOZALV10dCtOFZTC9MQw2qmdeevw,11013
72
72
  fal/toolkit/file/types.py,sha256=MMAH_AyLOhowQPesOv1V25wB4qgbJ3vYNlnTPbdSv1M,2304
73
- fal/toolkit/file/providers/fal.py,sha256=P9hm11uKVe6ilmL7CjFztBHswZEHOm4k-K4B36UZe6M,47543
73
+ fal/toolkit/file/providers/fal.py,sha256=S1DjzKtM69IvHLhhID2Lf4atKiYBWLB4EVEiC52-x9g,48374
74
74
  fal/toolkit/file/providers/gcp.py,sha256=DKeZpm1MjwbvEsYvkdXUtuLIJDr_UNbqXj_Mfv3NTeo,2437
75
75
  fal/toolkit/file/providers/r2.py,sha256=YqnYkkAo_ZKIa-xoSuDnnidUFwJWHdziAR34PE6irdI,3061
76
76
  fal/toolkit/file/providers/s3.py,sha256=EI45T54Mox7lHZKROss_O8o0DIn3CHP9k1iaNYVrxvg,2714
@@ -152,8 +152,8 @@ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRi
152
152
  openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
153
153
  openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
154
154
  openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
155
- fal-1.49.3.dist-info/METADATA,sha256=JTZT-DHUU8hNp1OqHcXcQOHMxLV6yAf6hjGKw-tVjXs,4250
156
- fal-1.49.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
157
- fal-1.49.3.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
158
- fal-1.49.3.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
159
- fal-1.49.3.dist-info/RECORD,,
155
+ fal-1.50.0.dist-info/METADATA,sha256=_lrQ7svffHYjyo3QCatmTJJuzm1FGuHrddF7IQw9_F8,4250
156
+ fal-1.50.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
157
+ fal-1.50.0.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
158
+ fal-1.50.0.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
159
+ fal-1.50.0.dist-info/RECORD,,
File without changes