fal 1.25.0__py3-none-any.whl → 1.26.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/_fal_version.py +2 -2
- fal/api.py +8 -6
- fal/app.py +50 -13
- fal/cli/apps.py +6 -2
- fal/files.py +96 -6
- fal/sdk.py +25 -14
- {fal-1.25.0.dist-info → fal-1.26.1.dist-info}/METADATA +1 -1
- {fal-1.25.0.dist-info → fal-1.26.1.dist-info}/RECORD +11 -11
- {fal-1.25.0.dist-info → fal-1.26.1.dist-info}/WHEEL +0 -0
- {fal-1.25.0.dist-info → fal-1.26.1.dist-info}/entry_points.txt +0 -0
- {fal-1.25.0.dist-info → fal-1.26.1.dist-info}/top_level.txt +0 -0
fal/_fal_version.py
CHANGED
fal/api.py
CHANGED
|
@@ -319,24 +319,26 @@ def _handle_grpc_error():
|
|
|
319
319
|
try:
|
|
320
320
|
return fn(*args, **kwargs)
|
|
321
321
|
except grpc.RpcError as e:
|
|
322
|
+
msg = e.details() or str(e)
|
|
322
323
|
if e.code() == grpc.StatusCode.UNAVAILABLE:
|
|
323
324
|
raise FalServerlessError(
|
|
324
325
|
"Could not reach fal host. "
|
|
325
326
|
"This is most likely a transient problem. "
|
|
326
|
-
"
|
|
327
|
+
"If it persists, please reach out to support@fal.ai with the following details: " # noqa: E501
|
|
328
|
+
f"{msg}"
|
|
327
329
|
)
|
|
328
|
-
elif
|
|
330
|
+
elif msg.endswith("died with <Signals.SIGKILL: 9>.`."):
|
|
329
331
|
raise FalServerlessError(
|
|
330
332
|
"Isolated function crashed. "
|
|
331
333
|
"This is likely due to resource overflow. "
|
|
332
334
|
"You can try again by setting a bigger `machine_type`"
|
|
333
335
|
)
|
|
334
336
|
elif e.code() == grpc.StatusCode.INVALID_ARGUMENT and (
|
|
335
|
-
"The function function could not be deserialized" in
|
|
337
|
+
"The function function could not be deserialized" in msg
|
|
336
338
|
):
|
|
337
|
-
raise FalMissingDependencyError(
|
|
339
|
+
raise FalMissingDependencyError(msg) from None
|
|
338
340
|
else:
|
|
339
|
-
raise FalServerlessError(
|
|
341
|
+
raise FalServerlessError(msg)
|
|
340
342
|
|
|
341
343
|
return handler
|
|
342
344
|
|
|
@@ -498,7 +500,7 @@ class FalServerlessHost(Host):
|
|
|
498
500
|
partial_func,
|
|
499
501
|
environments,
|
|
500
502
|
application_name=application_name,
|
|
501
|
-
|
|
503
|
+
auth_mode=application_auth_mode,
|
|
502
504
|
machine_requirements=machine_requirements,
|
|
503
505
|
metadata=metadata,
|
|
504
506
|
deployment_strategy=deployment_strategy,
|
fal/app.py
CHANGED
|
@@ -197,7 +197,14 @@ class AppClient:
|
|
|
197
197
|
|
|
198
198
|
@classmethod
|
|
199
199
|
@contextmanager
|
|
200
|
-
def connect(
|
|
200
|
+
def connect(
|
|
201
|
+
cls,
|
|
202
|
+
app_cls,
|
|
203
|
+
*,
|
|
204
|
+
health_request_timeout: int = 30,
|
|
205
|
+
startup_timeout: int = 60,
|
|
206
|
+
health_check_interval: float = 0.5,
|
|
207
|
+
):
|
|
201
208
|
app = wrap_app(app_cls)
|
|
202
209
|
info = app.spawn()
|
|
203
210
|
_shutdown_event = threading.Event()
|
|
@@ -214,20 +221,50 @@ class AppClient:
|
|
|
214
221
|
_log_printer.start()
|
|
215
222
|
|
|
216
223
|
try:
|
|
224
|
+
if info.url is None:
|
|
225
|
+
raise AppClientError(
|
|
226
|
+
"App spawn failed: no URL returned",
|
|
227
|
+
status_code=500,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
start_time = time.perf_counter()
|
|
231
|
+
url = info.url + "/health"
|
|
232
|
+
last_error = None
|
|
233
|
+
attempt = 0
|
|
234
|
+
|
|
217
235
|
with httpx.Client() as client:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
raise AppClientError(
|
|
227
|
-
f"Failed to GET {url}: {resp.status_code} {resp.text}",
|
|
228
|
-
status_code=resp.status_code,
|
|
236
|
+
while time.perf_counter() - start_time < startup_timeout:
|
|
237
|
+
attempt += 1
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
resp = client.get(url, timeout=health_request_timeout)
|
|
241
|
+
except httpx.TimeoutException:
|
|
242
|
+
last_error = (
|
|
243
|
+
f"Request timed out after {health_request_timeout} seconds"
|
|
229
244
|
)
|
|
230
|
-
|
|
245
|
+
except httpx.TransportError as e:
|
|
246
|
+
last_error = f"Network error: {e}"
|
|
247
|
+
else:
|
|
248
|
+
if resp.is_success:
|
|
249
|
+
break
|
|
250
|
+
|
|
251
|
+
if resp.status_code in (500, 404):
|
|
252
|
+
last_error = f"Server not ready (HTTP {resp.status_code})"
|
|
253
|
+
else:
|
|
254
|
+
raise AppClientError(
|
|
255
|
+
"Health check failed with non-retryable error: "
|
|
256
|
+
f"{resp.status_code} {resp.text}",
|
|
257
|
+
status_code=resp.status_code,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
time.sleep(health_check_interval)
|
|
261
|
+
else:
|
|
262
|
+
# retry loop completed without success
|
|
263
|
+
raise AppClientError(
|
|
264
|
+
f"Health check failed after {startup_timeout}s "
|
|
265
|
+
f"({attempt} attempts). Last error: {last_error}",
|
|
266
|
+
status_code=500,
|
|
267
|
+
)
|
|
231
268
|
|
|
232
269
|
client = cls(app_cls, info.url)
|
|
233
270
|
yield client
|
fal/cli/apps.py
CHANGED
|
@@ -270,7 +270,7 @@ def _add_set_rev_parser(subparsers, parents):
|
|
|
270
270
|
parser.add_argument(
|
|
271
271
|
"--auth",
|
|
272
272
|
choices=ALIAS_AUTH_MODES,
|
|
273
|
-
default=
|
|
273
|
+
default=None,
|
|
274
274
|
help="Application authentication mode.",
|
|
275
275
|
)
|
|
276
276
|
parser.set_defaults(func=_set_rev)
|
|
@@ -310,7 +310,11 @@ def _add_runners_parser(subparsers, parents):
|
|
|
310
310
|
def _delete(args):
|
|
311
311
|
client = get_client(args.host, args.team)
|
|
312
312
|
with client.connect() as connection:
|
|
313
|
-
connection.delete_alias(args.app_name)
|
|
313
|
+
res = connection.delete_alias(args.app_name)
|
|
314
|
+
if res is None:
|
|
315
|
+
args.console.print(f"Application {args.app_name!r} not found.")
|
|
316
|
+
else:
|
|
317
|
+
args.console.print(f"Application {args.app_name!r} deleted ({res})")
|
|
314
318
|
|
|
315
319
|
|
|
316
320
|
def _add_delete_parser(subparsers, parents):
|
fal/files.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import math
|
|
1
3
|
import os
|
|
2
4
|
import posixpath
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
3
6
|
from functools import cached_property
|
|
4
7
|
from typing import TYPE_CHECKING
|
|
5
8
|
|
|
@@ -9,6 +12,19 @@ if TYPE_CHECKING:
|
|
|
9
12
|
import httpx
|
|
10
13
|
|
|
11
14
|
USER_AGENT = "fal-sdk/1.14.0 (python)"
|
|
15
|
+
MULTIPART_THRESHOLD = 10 * 1024 * 1024 # 10MB
|
|
16
|
+
MULTIPART_CHUNK_SIZE = 10 * 1024 * 1024 # 10MB
|
|
17
|
+
MULTIPART_WORKERS = 2 # only 2 because our REST is currently struggling with more
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _compute_md5(lpath, chunk_size=8192):
|
|
21
|
+
import hashlib
|
|
22
|
+
|
|
23
|
+
hasher = hashlib.md5()
|
|
24
|
+
with open(lpath, "rb") as fobj:
|
|
25
|
+
for chunk in iter(lambda: fobj.read(chunk_size), b""):
|
|
26
|
+
hasher.update(chunk)
|
|
27
|
+
return hasher.hexdigest()
|
|
12
28
|
|
|
13
29
|
|
|
14
30
|
class FalFileSystem(AbstractFileSystem):
|
|
@@ -95,16 +111,90 @@ class FalFileSystem(AbstractFileSystem):
|
|
|
95
111
|
response = self._request("GET", f"/files/file/{rpath}")
|
|
96
112
|
fobj.write(response.content)
|
|
97
113
|
|
|
114
|
+
def _put_file_part(self, rpath, lpath, upload_id, part_number, chunk_size):
|
|
115
|
+
offset = (part_number - 1) * chunk_size
|
|
116
|
+
with open(lpath, "rb") as fobj:
|
|
117
|
+
fobj.seek(offset)
|
|
118
|
+
chunk = fobj.read(chunk_size)
|
|
119
|
+
response = self._request(
|
|
120
|
+
"PUT",
|
|
121
|
+
f"/files/file/multipart/{rpath}/{upload_id}/{part_number}",
|
|
122
|
+
files={"file_upload": (posixpath.basename(lpath), chunk)},
|
|
123
|
+
)
|
|
124
|
+
data = response.json()
|
|
125
|
+
return {
|
|
126
|
+
"part_number": data["part_number"],
|
|
127
|
+
"etag": data["etag"],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
def _put_file_multipart(self, lpath, rpath, size, progress):
|
|
131
|
+
response = self._request(
|
|
132
|
+
"POST",
|
|
133
|
+
f"/files/file/multipart/{rpath}/initiate",
|
|
134
|
+
)
|
|
135
|
+
upload_id = response.json()["upload_id"]
|
|
136
|
+
|
|
137
|
+
parts = []
|
|
138
|
+
num_parts = math.ceil(size / MULTIPART_CHUNK_SIZE)
|
|
139
|
+
md5 = _compute_md5(lpath)
|
|
140
|
+
|
|
141
|
+
task = progress.add_task(f"{os.path.basename(lpath)}", total=num_parts)
|
|
142
|
+
|
|
143
|
+
with ThreadPoolExecutor(max_workers=MULTIPART_WORKERS) as executor:
|
|
144
|
+
futures = []
|
|
145
|
+
|
|
146
|
+
for part_number in range(1, num_parts + 1):
|
|
147
|
+
futures.append(
|
|
148
|
+
executor.submit(
|
|
149
|
+
self._put_file_part,
|
|
150
|
+
rpath,
|
|
151
|
+
lpath,
|
|
152
|
+
upload_id,
|
|
153
|
+
part_number,
|
|
154
|
+
MULTIPART_CHUNK_SIZE,
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
for future in concurrent.futures.as_completed(futures):
|
|
159
|
+
parts.append(future.result())
|
|
160
|
+
progress.advance(task)
|
|
161
|
+
|
|
162
|
+
response = self._request(
|
|
163
|
+
"POST",
|
|
164
|
+
f"/files/file/multipart/{rpath}/{upload_id}/complete",
|
|
165
|
+
json={"parts": parts},
|
|
166
|
+
)
|
|
167
|
+
data = response.json()
|
|
168
|
+
if data["etag"] != md5:
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
f"MD5 mismatch on {rpath}: {data['etag']} != {md5}, "
|
|
171
|
+
"please contact support"
|
|
172
|
+
)
|
|
173
|
+
|
|
98
174
|
def put_file(self, lpath, rpath, mode="overwrite", **kwargs):
|
|
175
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
|
|
176
|
+
|
|
99
177
|
if os.path.isdir(lpath):
|
|
100
178
|
return
|
|
101
179
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
180
|
+
size = os.path.getsize(lpath)
|
|
181
|
+
with Progress(
|
|
182
|
+
SpinnerColumn(),
|
|
183
|
+
TextColumn("[progress.description]{task.description}"),
|
|
184
|
+
BarColumn(),
|
|
185
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
186
|
+
) as progress:
|
|
187
|
+
if size > MULTIPART_THRESHOLD:
|
|
188
|
+
self._put_file_multipart(lpath, rpath, size, progress)
|
|
189
|
+
else:
|
|
190
|
+
task = progress.add_task(f"{os.path.basename(lpath)}", total=1)
|
|
191
|
+
with open(lpath, "rb") as fobj:
|
|
192
|
+
self._request(
|
|
193
|
+
"POST",
|
|
194
|
+
f"/files/file/local/{rpath}",
|
|
195
|
+
files={"file_upload": (posixpath.basename(lpath), fobj)},
|
|
196
|
+
)
|
|
197
|
+
progress.advance(task)
|
|
108
198
|
self.dircache.clear()
|
|
109
199
|
|
|
110
200
|
def put_file_from_url(self, url, rpath, mode="overwrite", **kwargs):
|
fal/sdk.py
CHANGED
|
@@ -45,6 +45,9 @@ logger = get_logger(__name__)
|
|
|
45
45
|
patch_pickle()
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
AuthMode = Optional[Literal["public", "private", "shared"]]
|
|
49
|
+
|
|
50
|
+
|
|
48
51
|
class ServerCredentials:
|
|
49
52
|
def to_grpc(self) -> grpc.ChannelCredentials:
|
|
50
53
|
raise NotImplementedError
|
|
@@ -565,7 +568,7 @@ class FalServerlessConnection:
|
|
|
565
568
|
function: Callable[..., ResultT],
|
|
566
569
|
environments: list[isolate_proto.EnvironmentDefinition],
|
|
567
570
|
application_name: str | None = None,
|
|
568
|
-
|
|
571
|
+
auth_mode: AuthMode = None,
|
|
569
572
|
*,
|
|
570
573
|
serialization_method: str = _DEFAULT_SERIALIZATION_METHOD,
|
|
571
574
|
machine_requirements: MachineRequirements | None = None,
|
|
@@ -598,13 +601,14 @@ class FalServerlessConnection:
|
|
|
598
601
|
else:
|
|
599
602
|
wrapped_requirements = None
|
|
600
603
|
|
|
601
|
-
auth_mode
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
604
|
+
if auth_mode == "public":
|
|
605
|
+
auth = isolate_proto.ApplicationAuthMode.PUBLIC
|
|
606
|
+
elif auth_mode == "shared":
|
|
607
|
+
auth = isolate_proto.ApplicationAuthMode.SHARED
|
|
608
|
+
elif auth_mode == "private":
|
|
609
|
+
auth = isolate_proto.ApplicationAuthMode.PRIVATE
|
|
610
|
+
else:
|
|
611
|
+
auth = None
|
|
608
612
|
|
|
609
613
|
struct_metadata = None
|
|
610
614
|
if metadata:
|
|
@@ -620,7 +624,7 @@ class FalServerlessConnection:
|
|
|
620
624
|
environments=environments,
|
|
621
625
|
machine_requirements=wrapped_requirements,
|
|
622
626
|
application_name=application_name,
|
|
623
|
-
auth_mode=
|
|
627
|
+
auth_mode=auth,
|
|
624
628
|
metadata=struct_metadata,
|
|
625
629
|
deployment_strategy=deployment_strategy_proto,
|
|
626
630
|
scale=scale,
|
|
@@ -730,14 +734,16 @@ class FalServerlessConnection:
|
|
|
730
734
|
self,
|
|
731
735
|
alias: str,
|
|
732
736
|
revision: str,
|
|
733
|
-
auth_mode:
|
|
737
|
+
auth_mode: AuthMode,
|
|
734
738
|
):
|
|
735
739
|
if auth_mode == "public":
|
|
736
740
|
auth = isolate_proto.ApplicationAuthMode.PUBLIC
|
|
737
741
|
elif auth_mode == "shared":
|
|
738
742
|
auth = isolate_proto.ApplicationAuthMode.SHARED
|
|
739
|
-
|
|
743
|
+
elif auth_mode == "private":
|
|
740
744
|
auth = isolate_proto.ApplicationAuthMode.PRIVATE
|
|
745
|
+
else:
|
|
746
|
+
auth = None
|
|
741
747
|
|
|
742
748
|
request = isolate_proto.SetAliasRequest(
|
|
743
749
|
alias=alias,
|
|
@@ -746,10 +752,15 @@ class FalServerlessConnection:
|
|
|
746
752
|
)
|
|
747
753
|
self.stub.SetAlias(request)
|
|
748
754
|
|
|
749
|
-
def delete_alias(self, alias: str) -> str:
|
|
755
|
+
def delete_alias(self, alias: str) -> str | None:
|
|
750
756
|
request = isolate_proto.DeleteAliasRequest(alias=alias)
|
|
751
|
-
|
|
752
|
-
|
|
757
|
+
try:
|
|
758
|
+
res: isolate_proto.DeleteAliasResult = self.stub.DeleteAlias(request)
|
|
759
|
+
return res.revision
|
|
760
|
+
except grpc.RpcError as e:
|
|
761
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
762
|
+
return None
|
|
763
|
+
raise
|
|
753
764
|
|
|
754
765
|
def list_aliases(self) -> list[AliasInfo]:
|
|
755
766
|
request = isolate_proto.ListAliasesRequest()
|
|
@@ -1,19 +1,19 @@
|
|
|
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=
|
|
3
|
+
fal/_fal_version.py,sha256=n2TqZzcgqBKPmTHRD9QCk0lFkU1l5_qG0eOl7E-Tdjs,513
|
|
4
4
|
fal/_serialization.py,sha256=npXNsFJ5G7jzBeBIyVMH01Ww34mGY4XWhHpRbSrTtnQ,7598
|
|
5
5
|
fal/_version.py,sha256=1BbTFnucNC_6ldKJ_ZoC722_UkW4S9aDBSW9L0fkKAw,2315
|
|
6
|
-
fal/api.py,sha256=
|
|
7
|
-
fal/app.py,sha256=
|
|
6
|
+
fal/api.py,sha256=wIEt21P1C7U-dYQEcyHUxxuuTnvzFyTpWDoHoaxq7tg,47385
|
|
7
|
+
fal/app.py,sha256=3RDFV6JUiUk8b3WJanKQYA-kW74t5bqaNy8OYuUH5-Q,25491
|
|
8
8
|
fal/apps.py,sha256=pzCd2mrKl5J_4oVc40_pggvPtFahXBCdrZXWpnaEJVs,12130
|
|
9
9
|
fal/config.py,sha256=19tR4zWr2yqsBRWNhFV1jKMiXAASRz2Et62k68-1r9Y,3290
|
|
10
10
|
fal/container.py,sha256=FTsa5hOW4ars-yV1lUoc0BNeIIvAZcpw7Ftyt3A4m_w,2000
|
|
11
|
-
fal/files.py,sha256=
|
|
11
|
+
fal/files.py,sha256=iwR9jXWmAruP5tSS5-NkY-mZG53Pb80C40SqB0pYRrk,6819
|
|
12
12
|
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=
|
|
16
|
+
fal/sdk.py,sha256=eX_OJVmD6AY5NsLS7VR-LemXTWNEMfsOEfZBgAqytoo,26158
|
|
17
17
|
fal/sync.py,sha256=ZuIJA2-hTPNANG9B_NNJZUsO68EIdTH0dc9MzeVE2VU,4340
|
|
18
18
|
fal/utils.py,sha256=iQTBG3-i6JZgHkkwbY_I4210g0xoW-as51yrke608u0,2208
|
|
19
19
|
fal/workflows.py,sha256=Zl4f6Bs085hY40zmqScxDUyCu7zXkukDbW02iYOLTTI,14805
|
|
@@ -23,7 +23,7 @@ fal/auth/local.py,sha256=sndkM6vKpeVny6NHTacVlTbiIFqaksOmw0Viqs_RN1U,1790
|
|
|
23
23
|
fal/cli/__init__.py,sha256=padK4o0BFqq61kxAA1qQ0jYr2SuhA2mf90B3AaRkmJA,37
|
|
24
24
|
fal/cli/_utils.py,sha256=anFfy6qouB8QzH0Yho41GulGiJu3q1KKIwgyVQCzgRQ,1593
|
|
25
25
|
fal/cli/api.py,sha256=ZuDE_PIC-czzneTAWMwvC7P7WnwIyluNZSuJqzCFhqI,2640
|
|
26
|
-
fal/cli/apps.py,sha256=
|
|
26
|
+
fal/cli/apps.py,sha256=0ex4OWbxcopjEq_BGkwkxCBSTLqUIHmFDz1MWOv06zk,10479
|
|
27
27
|
fal/cli/auth.py,sha256=Qe-Z3ycXJnOzHimz5PjCQYoni8MF4csmdL19yGN7a1o,5171
|
|
28
28
|
fal/cli/cli_nested_json.py,sha256=veSZU8_bYV3Iu1PAoxt-4BMBraNIqgH5nughbs2UKvE,13539
|
|
29
29
|
fal/cli/create.py,sha256=a8WDq-nJLFTeoIXqpb5cr7GR7YR9ZZrQCawNm34KXXE,627
|
|
@@ -142,8 +142,8 @@ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRi
|
|
|
142
142
|
openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
|
|
143
143
|
openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
|
|
144
144
|
openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
|
|
145
|
-
fal-1.
|
|
146
|
-
fal-1.
|
|
147
|
-
fal-1.
|
|
148
|
-
fal-1.
|
|
149
|
-
fal-1.
|
|
145
|
+
fal-1.26.1.dist-info/METADATA,sha256=Dk0fMzu4rMEi6H9l2mWWBqjkcyS-QvzxFrVmt6rbnGY,4089
|
|
146
|
+
fal-1.26.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
147
|
+
fal-1.26.1.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
|
|
148
|
+
fal-1.26.1.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
|
|
149
|
+
fal-1.26.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|