fal 1.49.4__py3-none-any.whl → 1.50.1__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.
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.4'
32
- __version_tuple__ = version_tuple = (1, 49, 4)
31
+ __version__ = version = '1.50.1'
32
+ __version_tuple__ = version_tuple = (1, 50, 1)
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/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)
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.49.4
3
+ Version: 1.50.1
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=nkz7P0FDHfiQq2YgDDq4yvKyQgh2ZcsmheqPLQ9sLFI,706
3
+ fal/_fal_version.py,sha256=LL1Yx-cpQ3cud6IHC9ya0kDMPSCBaY6e8nDAG_QIwYY,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
@@ -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.4.dist-info/METADATA,sha256=73ocOntc1h0qoRIXFA94xo5rXm87nRx6zQSEX_hV5iM,4250
156
- fal-1.49.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
157
- fal-1.49.4.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
158
- fal-1.49.4.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
159
- fal-1.49.4.dist-info/RECORD,,
155
+ fal-1.50.1.dist-info/METADATA,sha256=xpBAJQgk3Bc9GMkOGbqAEa7KCmh0p94ABjpr_MS_DwU,4250
156
+ fal-1.50.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
157
+ fal-1.50.1.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
158
+ fal-1.50.1.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
159
+ fal-1.50.1.dist-info/RECORD,,
File without changes