fal 1.3.0__py3-none-any.whl → 1.3.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.

Potentially problematic release.


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

fal/__main__.py CHANGED
@@ -1,4 +1,6 @@
1
+ import sys
2
+
1
3
  from .cli import main
2
4
 
3
5
  if __name__ == "__main__":
4
- main()
6
+ sys.exit(main())
fal/_fal_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.3.0'
16
- __version_tuple__ = version_tuple = (1, 3, 0)
15
+ __version__ = version = '1.3.1'
16
+ __version_tuple__ = version_tuple = (1, 3, 1)
fal/app.py CHANGED
@@ -143,7 +143,7 @@ class AppClient:
143
143
  with httpx.Client() as client:
144
144
  retries = 100
145
145
  for _ in range(retries):
146
- resp = client.get(info.url + "/health")
146
+ resp = client.get(info.url + "/health", timeout=60)
147
147
 
148
148
  if resp.is_success:
149
149
  break
@@ -205,6 +205,14 @@ class App(fal.api.BaseServable):
205
205
  "Running apps through SDK is not implemented yet."
206
206
  )
207
207
 
208
+ @classmethod
209
+ def get_endpoints(cls) -> list[str]:
210
+ return [
211
+ signature.path
212
+ for _, endpoint in inspect.getmembers(cls, inspect.isfunction)
213
+ if (signature := getattr(endpoint, "route_signature", None))
214
+ ]
215
+
208
216
  def collect_routes(self) -> dict[RouteSignature, Callable[..., Any]]:
209
217
  return {
210
218
  signature: endpoint
fal/cli/deploy.py CHANGED
@@ -81,13 +81,14 @@ def _deploy(args):
81
81
 
82
82
  user = _get_user()
83
83
  host = FalServerlessHost(args.host)
84
- isolated_function, app_name, app_auth = load_function_from(
84
+ loaded = load_function_from(
85
85
  host,
86
86
  file_path,
87
87
  func_name,
88
88
  )
89
- app_name = args.app_name or app_name
90
- app_auth = args.auth or app_auth or "private"
89
+ isolated_function = loaded.function
90
+ app_name = args.app_name or loaded.app_name
91
+ app_auth = args.auth or loaded.app_auth or "private"
91
92
  app_id = host.register(
92
93
  func=isolated_function.func,
93
94
  options=isolated_function.options,
@@ -106,12 +107,16 @@ def _deploy(args):
106
107
  "Registered a new revision for function "
107
108
  f"'{app_name}' (revision='{app_id}')."
108
109
  )
109
- args.console.print(
110
- f"Playground: https://fal.ai/models/{user.username}/{app_name}"
111
- )
112
- args.console.print(
113
- f"Endpoint: https://{gateway_host}/{user.username}/{app_name}"
114
- )
110
+ args.console.print("Playground:")
111
+ for endpoint in loaded.endpoints:
112
+ args.console.print(
113
+ f"\thttps://fal.ai/models/{user.username}/{app_name}{endpoint}"
114
+ )
115
+ args.console.print("Endpoints:")
116
+ for endpoint in loaded.endpoints:
117
+ args.console.print(
118
+ f"\thttps://{gateway_host}/{user.username}/{app_name}{endpoint}"
119
+ )
115
120
 
116
121
 
117
122
  def add_parser(main_subparsers, parents):
fal/cli/run.py CHANGED
@@ -6,7 +6,8 @@ def _run(args):
6
6
  from fal.utils import load_function_from
7
7
 
8
8
  host = FalServerlessHost(args.host)
9
- isolated_function, _, _ = load_function_from(host, *args.func_ref)
9
+ loaded = load_function_from(host, *args.func_ref)
10
+ isolated_function = loaded.function
10
11
  # let our exc handlers handle UserFunctionException
11
12
  isolated_function.reraise = False
12
13
  isolated_function()
fal/exceptions/_base.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Sequence
5
4
 
6
5
 
7
6
  class FalServerlessException(Exception):
@@ -40,11 +39,13 @@ class FieldException(FalServerlessException):
40
39
  status_code: int = 422
41
40
  type: str = "value_error"
42
41
 
43
- def to_pydantic_format(self) -> Sequence[dict]:
44
- return [
45
- {
46
- "loc": ["body", self.field],
47
- "msg": self.message,
48
- "type": self.type,
49
- }
50
- ]
42
+ def to_pydantic_format(self) -> dict[str, list[dict]]:
43
+ return dict(
44
+ detail=[
45
+ {
46
+ "loc": ["body", self.field],
47
+ "msg": self.message,
48
+ "type": self.type,
49
+ }
50
+ ]
51
+ )
fal/toolkit/file/file.py CHANGED
@@ -149,14 +149,31 @@ class File(BaseModel):
149
149
  path: str | Path,
150
150
  content_type: Optional[str] = None,
151
151
  repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
152
+ multipart: bool | None = None,
152
153
  ) -> File:
153
154
  file_path = Path(path)
154
155
  if not file_path.exists():
155
156
  raise FileNotFoundError(f"File {file_path} does not exist")
156
- with open(file_path, "rb") as f:
157
- data = f.read()
158
- return File.from_bytes(
159
- data, content_type, file_name=file_path.name, repository=repository
157
+
158
+ repo = (
159
+ repository
160
+ if isinstance(repository, FileRepository)
161
+ else get_builtin_repository(repository)
162
+ )
163
+
164
+ content_type = content_type or "application/octet-stream"
165
+
166
+ url, data = repo.save_file(
167
+ file_path,
168
+ content_type=content_type,
169
+ multipart=multipart,
170
+ )
171
+ return cls(
172
+ url=url,
173
+ file_data=data.data if data else None,
174
+ content_type=content_type,
175
+ file_name=file_path.name,
176
+ file_size=file_path.stat().st_size,
160
177
  )
161
178
 
162
179
  def as_bytes(self) -> bytes:
@@ -2,9 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
4
  import json
5
+ import math
5
6
  import os
6
7
  from base64 import b64encode
7
8
  from dataclasses import dataclass
9
+ from pathlib import Path
8
10
  from urllib.error import HTTPError
9
11
  from urllib.request import Request, urlopen
10
12
 
@@ -87,11 +89,185 @@ class FalFileRepository(FalFileRepositoryBase):
87
89
  return self._save(file, "gcs")
88
90
 
89
91
 
92
+ class MultipartUpload:
93
+ MULTIPART_THRESHOLD = 100 * 1024 * 1024
94
+ MULTIPART_CHUNK_SIZE = 100 * 1024 * 1024
95
+ MULTIPART_MAX_CONCURRENCY = 10
96
+
97
+ def __init__(
98
+ self,
99
+ file_path: str | Path,
100
+ chunk_size: int | None = None,
101
+ content_type: str | None = None,
102
+ max_concurrency: int | None = None,
103
+ ) -> None:
104
+ self.file_path = file_path
105
+ self.chunk_size = chunk_size or self.MULTIPART_CHUNK_SIZE
106
+ self.content_type = content_type or "application/octet-stream"
107
+ self.max_concurrency = max_concurrency or self.MULTIPART_MAX_CONCURRENCY
108
+
109
+ self._parts: list[dict] = []
110
+
111
+ key_creds = key_credentials()
112
+ if not key_creds:
113
+ raise FileUploadException("FAL_KEY must be set")
114
+
115
+ key_id, key_secret = key_creds
116
+
117
+ self._auth_headers = {
118
+ "Authorization": f"Key {key_id}:{key_secret}",
119
+ }
120
+ grpc_host = os.environ.get("FAL_HOST", "api.alpha.fal.ai")
121
+ rest_host = grpc_host.replace("api", "rest", 1)
122
+ self._storage_upload_url = f"https://{rest_host}/storage/upload"
123
+
124
+ def create(self):
125
+ try:
126
+ req = Request(
127
+ f"{self._storage_upload_url}/initiate-multipart",
128
+ method="POST",
129
+ headers={
130
+ **self._auth_headers,
131
+ "Accept": "application/json",
132
+ "Content-Type": "application/json",
133
+ },
134
+ data=json.dumps(
135
+ {
136
+ "file_name": os.path.basename(self.file_path),
137
+ "content_type": self.content_type,
138
+ }
139
+ ).encode(),
140
+ )
141
+ with urlopen(req) as response:
142
+ result = json.load(response)
143
+ self._upload_id = result["upload_id"]
144
+ self._file_url = result["file_url"]
145
+ except HTTPError as exc:
146
+ raise FileUploadException(
147
+ f"Error initiating upload. Status {exc.status}: {exc.reason}"
148
+ )
149
+
150
+ def _upload_part(self, url: str, part_number: int) -> dict:
151
+ with open(self.file_path, "rb") as f:
152
+ start = (part_number - 1) * self.chunk_size
153
+ f.seek(start)
154
+ data = f.read(self.chunk_size)
155
+ req = Request(
156
+ url,
157
+ method="PUT",
158
+ headers={"Content-Type": self.content_type},
159
+ data=data,
160
+ )
161
+
162
+ try:
163
+ with urlopen(req) as resp:
164
+ return {
165
+ "part_number": part_number,
166
+ "etag": resp.headers["ETag"],
167
+ }
168
+ except HTTPError as exc:
169
+ raise FileUploadException(
170
+ f"Error uploading part {part_number} to {url}. "
171
+ f"Status {exc.status}: {exc.reason}"
172
+ )
173
+
174
+ def upload(self) -> None:
175
+ import concurrent.futures
176
+
177
+ parts = math.ceil(os.path.getsize(self.file_path) / self.chunk_size)
178
+ with concurrent.futures.ThreadPoolExecutor(
179
+ max_workers=self.max_concurrency
180
+ ) as executor:
181
+ futures = []
182
+ for part_number in range(1, parts + 1):
183
+ upload_url = (
184
+ f"{self._file_url}?upload_id={self._upload_id}"
185
+ f"&part_number={part_number}"
186
+ )
187
+ futures.append(
188
+ executor.submit(self._upload_part, upload_url, part_number)
189
+ )
190
+
191
+ for future in concurrent.futures.as_completed(futures):
192
+ entry = future.result()
193
+ self._parts.append(entry)
194
+
195
+ def complete(self):
196
+ url = f"{self._file_url}?upload_id={self._upload_id}"
197
+ try:
198
+ req = Request(
199
+ url,
200
+ method="POST",
201
+ headers={
202
+ "Accept": "application/json",
203
+ "Content-Type": "application/json",
204
+ },
205
+ data=json.dumps({"parts": self._parts}).encode(),
206
+ )
207
+ with urlopen(req):
208
+ pass
209
+ except HTTPError as e:
210
+ raise FileUploadException(
211
+ f"Error completing upload {url}. Status {e.status}: {e.reason}"
212
+ )
213
+
214
+ return self._file_url
215
+
216
+
90
217
  @dataclass
91
218
  class FalFileRepositoryV2(FalFileRepositoryBase):
92
219
  def save(self, file: FileData) -> str:
93
220
  return self._save(file, "fal-cdn")
94
221
 
222
+ def _save_multipart(
223
+ self,
224
+ file_path: str | Path,
225
+ chunk_size: int | None = None,
226
+ content_type: str | None = None,
227
+ max_concurrency: int | None = None,
228
+ ) -> str:
229
+ multipart = MultipartUpload(
230
+ file_path,
231
+ chunk_size=chunk_size,
232
+ content_type=content_type,
233
+ max_concurrency=max_concurrency,
234
+ )
235
+ multipart.create()
236
+ multipart.upload()
237
+ return multipart.complete()
238
+
239
+ def save_file(
240
+ self,
241
+ file_path: str | Path,
242
+ content_type: str,
243
+ multipart: bool | None = None,
244
+ multipart_threshold: int | None = None,
245
+ multipart_chunk_size: int | None = None,
246
+ multipart_max_concurrency: int | None = None,
247
+ ) -> tuple[str, FileData | None]:
248
+ if multipart is None:
249
+ threshold = multipart_threshold or MultipartUpload.MULTIPART_THRESHOLD
250
+ multipart = os.path.getsize(file_path) > threshold
251
+
252
+ if multipart:
253
+ url = self._save_multipart(
254
+ file_path,
255
+ chunk_size=multipart_chunk_size,
256
+ content_type=content_type,
257
+ max_concurrency=multipart_max_concurrency,
258
+ )
259
+ data = None
260
+ else:
261
+ with open(file_path, "rb") as f:
262
+ data = FileData(
263
+ f.read(),
264
+ content_type=content_type,
265
+ file_name=os.path.basename(file_path),
266
+ )
267
+ url = self.save(data)
268
+
269
+ return url, data
270
+
95
271
 
96
272
  @dataclass
97
273
  class InMemoryRepository(FileRepository):
fal/toolkit/file/types.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from mimetypes import guess_extension, guess_type
5
+ from pathlib import Path
5
6
  from typing import Literal
6
7
  from uuid import uuid4
7
8
 
@@ -35,3 +36,20 @@ RepositoryId = Literal["fal", "fal_v2", "in_memory", "gcp_storage", "r2", "cdn"]
35
36
  class FileRepository:
36
37
  def save(self, data: FileData) -> str:
37
38
  raise NotImplementedError()
39
+
40
+ def save_file(
41
+ self,
42
+ file_path: str | Path,
43
+ content_type: str,
44
+ multipart: bool | None = None,
45
+ multipart_threshold: int | None = None,
46
+ multipart_chunk_size: int | None = None,
47
+ multipart_max_concurrency: int | None = None,
48
+ ) -> tuple[str, FileData | None]:
49
+ if multipart:
50
+ raise NotImplementedError()
51
+
52
+ with open(file_path, "rb") as fobj:
53
+ data = FileData(fobj.read(), content_type, Path(file_path).name)
54
+
55
+ return self.save(data), data
fal/utils.py CHANGED
@@ -1,16 +1,26 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
4
+
3
5
  import fal._serialization
4
6
  from fal import App, wrap_app
5
7
 
6
8
  from .api import FalServerlessError, FalServerlessHost, IsolatedFunction
7
9
 
8
10
 
11
+ @dataclass
12
+ class LoadedFunction:
13
+ function: IsolatedFunction
14
+ endpoints: list[str]
15
+ app_name: str | None
16
+ app_auth: str | None
17
+
18
+
9
19
  def load_function_from(
10
20
  host: FalServerlessHost,
11
21
  file_path: str,
12
22
  function_name: str | None = None,
13
- ) -> tuple[IsolatedFunction, str | None, str | None]:
23
+ ) -> LoadedFunction:
14
24
  import runpy
15
25
 
16
26
  module = runpy.run_path(file_path)
@@ -45,6 +55,7 @@ def load_function_from(
45
55
  fal._serialization.include_package_from_path(file_path)
46
56
 
47
57
  target = module[function_name]
58
+ endpoints = target.get_endpoints() or ["/"]
48
59
  if isinstance(target, type) and issubclass(target, App):
49
60
  app_name = target.app_name
50
61
  app_auth = target.app_auth
@@ -54,4 +65,4 @@ def load_function_from(
54
65
  raise FalServerlessError(
55
66
  f"Function '{function_name}' is not a fal.function or a fal.App"
56
67
  )
57
- return target, app_name, app_auth
68
+ return LoadedFunction(target, endpoints, app_name=app_name, app_auth=app_auth)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 1.3.0
3
+ Version: 1.3.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
@@ -1,10 +1,10 @@
1
1
  fal/__init__.py,sha256=wXs1G0gSc7ZK60-bHe-B2m0l_sA6TrFk4BxY0tMoLe8,784
2
- fal/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
3
- fal/_fal_version.py,sha256=HGwtpza1HCPtlyqElUvIyH97K44TO13CYiYVZNezQ1M,411
2
+ fal/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83
3
+ fal/_fal_version.py,sha256=cOVPCvD2h2G_2KB6G3ddreYkIQfAnS6WqgAkF_qgGOQ,411
4
4
  fal/_serialization.py,sha256=rD2YiSa8iuzCaZohZwN_MPEB-PpSKbWRDeaIDpTEjyY,7653
5
5
  fal/_version.py,sha256=EBGqrknaf1WygENX-H4fBefLvHryvJBBGtVJetaB0NY,266
6
6
  fal/api.py,sha256=bOCxmOQSbdcR6h6VLPEuvsD4i0j_Mod6E2UNP07cAQo,41893
7
- fal/app.py,sha256=PGx-6Zr4evqe4Fzs4g4-MxKnaOp_7hW5G7vU1PpPvkI,16800
7
+ fal/app.py,sha256=Cw0opdt7V6W6XXoo1QVufPiGITysh7QTngeQ0grL6V4,17073
8
8
  fal/apps.py,sha256=FrKmaAUo8U9vE_fcva0GQvk4sCrzaTEr62lGtu3Ld5M,6825
9
9
  fal/container.py,sha256=V7riyyq8AZGwEX9QaqRQDZyDN_bUKeRKV1OOZArXjL0,622
10
10
  fal/flags.py,sha256=oWN_eidSUOcE9wdPK_77si3A1fpgOC0UEERPsvNLIMc,842
@@ -12,7 +12,7 @@ fal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  fal/rest_client.py,sha256=kGBGmuyHfX1lR910EoKCYPjsyU8MdXawT_cW2q8Sajc,568
13
13
  fal/sdk.py,sha256=wA58DYnSK1vdsBi8Or9Z8kvMMEyBNfeZYk_xulSfTWE,20078
14
14
  fal/sync.py,sha256=ZuIJA2-hTPNANG9B_NNJZUsO68EIdTH0dc9MzeVE2VU,4340
15
- fal/utils.py,sha256=4-V6iGSRd3kG_-UP6OdZp_-EhAkl3zectFlFKkCsS0Q,1884
15
+ fal/utils.py,sha256=WfS-Cx7GQFtzpZEdJYTOeglEGT_omMIrDiQ_l0PghGE,2120
16
16
  fal/workflows.py,sha256=jx3tGy2R7cN6lLvOzT6lhhlcjmiq64iZls2smVrmQj0,14657
17
17
  fal/auth/__init__.py,sha256=r8iA2-5ih7-Fik3gEC4HEWNFbGoxpYnXpZu1icPIoS0,3561
18
18
  fal/auth/auth0.py,sha256=rSG1mgH-QGyKfzd7XyAaj1AYsWt-ho8Y_LZ-FUVWzh4,5421
@@ -22,18 +22,18 @@ fal/cli/apps.py,sha256=-DDp-Gvxz5kHho5YjAhbri8vOny_9cftAI_wP2KR5nU,8175
22
22
  fal/cli/auth.py,sha256=--MhfHGwxmtHbRkGioyn1prKn_U-pBzbz0G_QeZou-U,1352
23
23
  fal/cli/create.py,sha256=a8WDq-nJLFTeoIXqpb5cr7GR7YR9ZZrQCawNm34KXXE,627
24
24
  fal/cli/debug.py,sha256=u_urnyFzSlNnrq93zz_GXE9FX4VyVxDoamJJyrZpFI0,1312
25
- fal/cli/deploy.py,sha256=1e4OERVGtfwgM0VEFjlCLpNyuOl1BiLI-dx8u-71PVs,4817
25
+ fal/cli/deploy.py,sha256=asSa8UpX7BzepcvmH_Heitwwpv-5xKromZkpjfi-hwI,5033
26
26
  fal/cli/doctor.py,sha256=U4ne9LX5gQwNblsYQ27XdO8AYDgbYjTO39EtxhwexRM,983
27
27
  fal/cli/keys.py,sha256=trDpA3LJu9S27qE_K8Hr6fKLK4vwVzbxUHq8TFrV4pw,3157
28
28
  fal/cli/main.py,sha256=_Wh_DQc02qwh-ZN7v41lZm0lDR1WseViXVOcqUlyWLg,2009
29
29
  fal/cli/parser.py,sha256=r1hd5e8Jq6yzDZw8-S0On1EjJbjRtHMuVuHC6MlvUj4,2835
30
- fal/cli/run.py,sha256=8wHNDruIr8i21JwbfFzS389C-y0jktM5zN5iDnJHsvA,873
30
+ fal/cli/run.py,sha256=YOD1PBzqFTpjeL55SpIe5SuBCnYTOzD_wvBBuPIhqSY,896
31
31
  fal/cli/secrets.py,sha256=740msFm7d41HruudlcfqUXlFl53N-WmChsQP9B9M9Po,2572
32
32
  fal/console/__init__.py,sha256=ernZ4bzvvliQh5SmrEqQ7lA5eVcbw6Ra2jalKtA7dxg,132
33
33
  fal/console/icons.py,sha256=De9MfFaSkO2Lqfne13n3PrYfTXJVIzYZVqYn5BWsdrA,108
34
34
  fal/console/ux.py,sha256=KMQs3UHQvVHDxDQQqlot-WskVKoMQXOE3jiVkkfmIMY,356
35
35
  fal/exceptions/__init__.py,sha256=5b2rFnvLf2Q4fPGedcYN_JVLv-j6iOy5DNG7jB6FNUk,180
36
- fal/exceptions/_base.py,sha256=zFiiRceThgdnR8DEYtzpkq-IDO_D0EHGLd0oZjX5Gqk,1256
36
+ fal/exceptions/_base.py,sha256=Kuq9UeGrWAa0RHsnkl5STD2GUbCxWz9dsL0TEqvpNdc,1294
37
37
  fal/exceptions/_cuda.py,sha256=q5EPFYEb7Iyw03cHrQlRHnH5xOvjwTwQdM6a9N3GB8k,1494
38
38
  fal/exceptions/auth.py,sha256=gxRago5coI__vSIcdcsqhhq1lRPkvCnwPAueIaXTAdw,329
39
39
  fal/logging/__init__.py,sha256=snqprf7-sKw6oAATS_Yxklf-a3XhLg0vIHICPwLp6TM,1583
@@ -45,9 +45,9 @@ fal/toolkit/__init__.py,sha256=sV95wiUzKoiDqF9vDgq4q-BLa2sD6IpuKSqp5kdTQNE,658
45
45
  fal/toolkit/exceptions.py,sha256=elHZ7dHCJG5zlHGSBbz-ilkZe9QUvQMomJFi8Pt91LA,198
46
46
  fal/toolkit/optimize.py,sha256=p75sovF0SmRP6zxzpIaaOmqlxvXB_xEz3XPNf59EF7w,1339
47
47
  fal/toolkit/file/__init__.py,sha256=FbNl6wD-P0aSSTUwzHt4HujBXrbC3ABmaigPQA4hRfg,70
48
- fal/toolkit/file/file.py,sha256=_OCtg0Po3wlDT41dMThJJ1Z9XLtnajyjYaupO9DhfeQ,6137
49
- fal/toolkit/file/types.py,sha256=bJCeV5NPcpJYJoglailiRgFsuNAfcextYA8Et5-XUag,1060
50
- fal/toolkit/file/providers/fal.py,sha256=65-BkK9jhGBwYI_OjhHJsL2DthyKxBBRrqXPI_ZN4-k,4115
48
+ fal/toolkit/file/file.py,sha256=qk_hj7U3cfvuWO-qF_eC_R8lzzVhudfnt1erWEa8eDQ,6578
49
+ fal/toolkit/file/types.py,sha256=GymH0CJesJvsZ6wph7GqTGTuNjzvyMgLxQmBBxoKzS0,1627
50
+ fal/toolkit/file/providers/fal.py,sha256=ClCWM4GI11hOjEIVv2IJZj2SdzBNO8iS1r1WaXFcF6I,10090
51
51
  fal/toolkit/file/providers/gcp.py,sha256=pUVH2qNcnO_VrDQQU8MmfYOQZMGaKQIqE4yGnYdQhAc,2003
52
52
  fal/toolkit/file/providers/r2.py,sha256=WxmOHF5WxHt6tKMcFjWj7ZWO8a1EXysO9lfYv_tB3MI,2627
53
53
  fal/toolkit/image/__init__.py,sha256=aLcU8HzD7HyOxx-C-Bbx9kYCMHdBhy9tR98FSVJ6gSA,1830
@@ -123,8 +123,8 @@ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRi
123
123
  openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
124
124
  openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
125
125
  openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
126
- fal-1.3.0.dist-info/METADATA,sha256=sP1Mdzh8ciPGfXFrY_GzmRm8IH0Ew6gv9IUjBsHWCe4,3766
127
- fal-1.3.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
128
- fal-1.3.0.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
129
- fal-1.3.0.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
130
- fal-1.3.0.dist-info/RECORD,,
126
+ fal-1.3.1.dist-info/METADATA,sha256=Xy6_mCqUDjtkFtJRGtxQSEsHMNt7srJgvoorONf4LYs,3766
127
+ fal-1.3.1.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
128
+ fal-1.3.1.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
129
+ fal-1.3.1.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
130
+ fal-1.3.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5