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

Files changed (46) hide show
  1. fal/__init__.py +11 -2
  2. fal/api.py +130 -50
  3. fal/app.py +81 -134
  4. fal/apps.py +24 -6
  5. fal/auth/__init__.py +14 -2
  6. fal/auth/auth0.py +34 -25
  7. fal/cli.py +9 -4
  8. fal/env.py +0 -4
  9. fal/flags.py +1 -0
  10. fal/logging/__init__.py +0 -2
  11. fal/logging/trace.py +8 -1
  12. fal/sdk.py +33 -6
  13. fal/toolkit/__init__.py +16 -0
  14. fal/workflows.py +481 -0
  15. {fal-0.12.2.dist-info → fal-0.12.4.dist-info}/METADATA +4 -7
  16. fal-0.12.4.dist-info/RECORD +88 -0
  17. openapi_fal_rest/__init__.py +1 -0
  18. openapi_fal_rest/api/workflows/__init__.py +0 -0
  19. openapi_fal_rest/api/workflows/create_or_update_workflow_workflows_post.py +172 -0
  20. openapi_fal_rest/api/workflows/delete_workflow_workflows_user_id_workflow_name_delete.py +175 -0
  21. openapi_fal_rest/api/workflows/execute_workflow_workflows_user_id_workflow_name_post.py +268 -0
  22. openapi_fal_rest/api/workflows/get_workflow_workflows_user_id_workflow_name_get.py +181 -0
  23. openapi_fal_rest/api/workflows/get_workflows_workflows_get.py +189 -0
  24. openapi_fal_rest/models/__init__.py +34 -0
  25. openapi_fal_rest/models/app_metadata_response_app_metadata.py +1 -0
  26. openapi_fal_rest/models/customer_details.py +15 -14
  27. openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_json_body_type_0.py +44 -0
  28. openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_response_200_type_0.py +44 -0
  29. openapi_fal_rest/models/page_workflow_item.py +107 -0
  30. openapi_fal_rest/models/typed_workflow.py +85 -0
  31. openapi_fal_rest/models/workflow_contents.py +98 -0
  32. openapi_fal_rest/models/workflow_contents_nodes.py +59 -0
  33. openapi_fal_rest/models/workflow_contents_output.py +44 -0
  34. openapi_fal_rest/models/workflow_detail.py +149 -0
  35. openapi_fal_rest/models/workflow_detail_contents_type_0.py +44 -0
  36. openapi_fal_rest/models/workflow_item.py +80 -0
  37. openapi_fal_rest/models/workflow_node.py +74 -0
  38. openapi_fal_rest/models/workflow_node_type.py +9 -0
  39. openapi_fal_rest/models/workflow_schema.py +73 -0
  40. openapi_fal_rest/models/workflow_schema_input.py +44 -0
  41. openapi_fal_rest/models/workflow_schema_output.py +44 -0
  42. openapi_fal_rest/types.py +1 -0
  43. fal/logging/datadog.py +0 -78
  44. fal-0.12.2.dist-info/RECORD +0 -67
  45. {fal-0.12.2.dist-info → fal-0.12.4.dist-info}/WHEEL +0 -0
  46. {fal-0.12.2.dist-info → fal-0.12.4.dist-info}/entry_points.txt +0 -0
fal/workflows.py ADDED
@@ -0,0 +1,481 @@
1
+ from __future__ import annotations
2
+
3
+ import graphlib
4
+ import json
5
+ import webbrowser
6
+ from argparse import ArgumentParser
7
+ from collections import Counter
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Iterator, Union, cast
10
+
11
+ import rich
12
+ from openapi_fal_rest.api.workflows import (
13
+ create_or_update_workflow_workflows_post as publish_workflow,
14
+ )
15
+ from pydantic import BaseModel
16
+ from rich.syntax import Syntax
17
+
18
+ import fal
19
+ from fal import flags
20
+ from fal.rest_client import REST_CLIENT
21
+
22
+ JSONType = Union[dict[str, Any], list[Any], str, int, float, bool, None, "Leaf"]
23
+ SchemaType = dict[str, Any]
24
+
25
+ VARIABLE_PREFIX = "$"
26
+ INPUT_VARIABLE_NAME = "input"
27
+
28
+ # Will be 1.0 once the server is finalized and anything <1.0
29
+ # is going to be rejected.
30
+ WORKFLOW_EXPORT_VERSION = "0.1"
31
+
32
+
33
+ class WorkflowSyntaxError(Exception):
34
+ pass
35
+
36
+
37
+ class MisconfiguredGraphError(WorkflowSyntaxError):
38
+ pass
39
+
40
+
41
+ def parse_leaf(raw_leaf: str) -> Leaf:
42
+ """Parses a leaf (which is in the form of $variable.field.field_2[index] etc.)
43
+ into a tree of Leaf objects."""
44
+ raw_parts = raw_leaf.split(".")
45
+ reference, *raw_parts = raw_parts
46
+ if not reference.startswith(VARIABLE_PREFIX):
47
+ raise WorkflowSyntaxError(
48
+ f"Invalid leaf: {raw_leaf} (must start with a reference)"
49
+ )
50
+
51
+ leaf: Leaf = ReferenceLeaf(reference.removeprefix(VARIABLE_PREFIX))
52
+ for raw_part in raw_parts:
53
+ if raw_part.isdigit():
54
+ leaf = IndexLeaf(leaf, int(raw_part))
55
+ elif raw_part.isidentifier():
56
+ leaf = AttributeLeaf(leaf, raw_part)
57
+ else:
58
+ raise WorkflowSyntaxError(
59
+ f"Invalid leaf: {raw_leaf} (unexpected {raw_part})"
60
+ )
61
+
62
+ return leaf
63
+
64
+
65
+ def export_workflow_json(data: JSONType) -> JSONType:
66
+ if isinstance(data, dict):
67
+ return {k: export_workflow_json(v) for k, v in data.items()}
68
+ elif isinstance(data, list):
69
+ return [export_workflow_json(v) for v in data]
70
+ elif isinstance(data, Leaf):
71
+ return repr(data)
72
+ else:
73
+ return data
74
+
75
+
76
+ def import_workflow_json(data: JSONType) -> JSONType:
77
+ if isinstance(data, dict):
78
+ return {k: import_workflow_json(v) for k, v in data.items()}
79
+ elif isinstance(data, list):
80
+ return [import_workflow_json(v) for v in data]
81
+ elif isinstance(data, str) and data.startswith(VARIABLE_PREFIX):
82
+ return parse_leaf(data)
83
+ else:
84
+ return data
85
+
86
+
87
+ def iter_leaves(data: JSONType) -> Iterator[JSONType]:
88
+ if isinstance(data, dict):
89
+ for value in data.values():
90
+ yield from iter_leaves(value)
91
+ elif isinstance(data, list):
92
+ for item in data:
93
+ yield from iter_leaves(item)
94
+ else:
95
+ yield data
96
+
97
+
98
+ def depends(data: JSONType) -> set[str]:
99
+ return {
100
+ leaf.referee.id # type: ignore
101
+ for leaf in iter_leaves(data)
102
+ if isinstance(leaf, Leaf)
103
+ }
104
+
105
+
106
+ @dataclass
107
+ class Context:
108
+ vars: dict[str, JSONType]
109
+
110
+ def hydrate(self, input: JSONType) -> JSONType:
111
+ if isinstance(input, dict):
112
+ return {k: self.hydrate(v) for k, v in input.items()}
113
+ elif isinstance(input, list):
114
+ return [self.hydrate(v) for v in input]
115
+ elif isinstance(input, Leaf):
116
+ return input.execute(self)
117
+ else:
118
+ return input
119
+
120
+
121
+ @dataclass
122
+ class Leaf:
123
+ def execute(self, context: Context) -> JSONType:
124
+ raise NotImplementedError
125
+
126
+ def __getattr__(self, name: str) -> AttributeLeaf:
127
+ return AttributeLeaf(self, name)
128
+
129
+ def __getitem__(self, index: int) -> IndexLeaf:
130
+ return IndexLeaf(self, index)
131
+
132
+ @property
133
+ def referee(self) -> ReferenceLeaf:
134
+ raise NotImplementedError
135
+
136
+
137
+ @dataclass
138
+ class AttributeLeaf(Leaf):
139
+ leaf: Leaf
140
+ attribute: str
141
+
142
+ def execute(self, context: Context) -> JSONType:
143
+ output = self.leaf.execute(context)
144
+ assert isinstance(output, dict), f"{self.leaf!r} is not a dict"
145
+ return output[self.attribute]
146
+
147
+ def __repr__(self) -> str:
148
+ return f"{self.leaf!r}.{self.attribute}"
149
+
150
+ @property
151
+ def referee(self) -> ReferenceLeaf:
152
+ return self.leaf.referee
153
+
154
+
155
+ @dataclass
156
+ class IndexLeaf(Leaf):
157
+ leaf: Leaf
158
+ index: int
159
+
160
+ def execute(self, context: Context) -> JSONType:
161
+ output = self.leaf.execute(context)
162
+ assert isinstance(output, list), f"{self.leaf!r} is not an array"
163
+ return output[self.index]
164
+
165
+ def __repr__(self) -> str:
166
+ return f"{self.leaf!r}.{self.index}"
167
+
168
+ @property
169
+ def referee(self) -> ReferenceLeaf:
170
+ return self.leaf.referee
171
+
172
+
173
+ @dataclass
174
+ class ReferenceLeaf(Leaf):
175
+ id: str
176
+
177
+ def execute(self, context: Context) -> JSONType:
178
+ try:
179
+ return context.vars[self.id]
180
+ except KeyError:
181
+ raise MisconfiguredGraphError(f"Variable {self.id!r} is not defined")
182
+
183
+ def __repr__(self) -> str:
184
+ return VARIABLE_PREFIX + self.id
185
+
186
+ @property
187
+ def referee(self) -> ReferenceLeaf:
188
+ return self
189
+
190
+
191
+ @dataclass
192
+ class Node:
193
+ id: str
194
+ depends: set[str]
195
+
196
+ @classmethod
197
+ def from_json(cls, data: dict[str, Any]) -> Node:
198
+ type = data.pop("type")
199
+ if type == "display":
200
+ return Display.from_json(data)
201
+ elif type == "run":
202
+ return Run.from_json(data)
203
+ else:
204
+ raise WorkflowSyntaxError(f"Invalid node type: {type}")
205
+
206
+ def to_json(self) -> dict[str, Any]:
207
+ raise NotImplementedError
208
+
209
+ def execute(self, context: Context) -> JSONType:
210
+ raise NotImplementedError
211
+
212
+
213
+ @dataclass
214
+ class Display(Node):
215
+ fields: list[Leaf]
216
+
217
+ @classmethod
218
+ def from_json(cls, data: dict[str, Any]) -> Display:
219
+ return cls(
220
+ id=data["id"],
221
+ depends=set(data["depends"]),
222
+ fields=import_workflow_json(data["fields"]), # type: ignore
223
+ )
224
+
225
+ def to_json(self) -> dict[str, Any]:
226
+ return {
227
+ "type": "display",
228
+ "id": self.id,
229
+ "depends": list(self.depends),
230
+ "fields": export_workflow_json(self.fields),
231
+ }
232
+
233
+ def execute(self, context: Context) -> JSONType:
234
+ for url in context.hydrate(self.fields): # type: ignore
235
+ if flags.DONT_OPEN_LINKS:
236
+ print("Link:", url)
237
+ else:
238
+ webbrowser.open(url)
239
+
240
+
241
+ @dataclass
242
+ class Run(Node):
243
+ app: str
244
+ input: JSONType
245
+
246
+ @classmethod
247
+ def from_json(cls, data: dict[str, Any]) -> Run:
248
+ return cls(
249
+ id=data["id"],
250
+ depends=set(data["depends"]),
251
+ app=data["app"],
252
+ input=import_workflow_json(data["input"]),
253
+ )
254
+
255
+ def execute(self, context: Context) -> JSONType:
256
+ input = context.hydrate(self.input)
257
+ assert isinstance(input, dict)
258
+ return cast(JSONType, fal.apps.run(self.app, input))
259
+
260
+ def to_json(self) -> dict[str, Any]:
261
+ return {
262
+ "type": "run",
263
+ "id": self.id,
264
+ "app": self.app,
265
+ "depends": list(self.depends),
266
+ "input": export_workflow_json(self.input), # type: ignore
267
+ }
268
+
269
+
270
+ @dataclass
271
+ class Workflow:
272
+ name: str
273
+ input_schema: SchemaType
274
+ output_schema: SchemaType
275
+ nodes: dict[str, Node] = field(default_factory=dict)
276
+ output: dict[str, Any] | None = None
277
+ _app_counter: Counter = field(default_factory=Counter)
278
+
279
+ @classmethod
280
+ def from_json(cls, data: dict[str, Any]) -> Workflow:
281
+ data = import_workflow_json(data) # type: ignore
282
+ return cls(
283
+ name=data["name"],
284
+ input_schema=data["schema"]["input"],
285
+ output_schema=data["schema"]["output"],
286
+ nodes={
287
+ node_id: Node.from_json(node_data)
288
+ for node_id, node_data in data["nodes"].items()
289
+ },
290
+ output=data["output"],
291
+ )
292
+
293
+ def __post_init__(self) -> None:
294
+ for node in self.nodes.values():
295
+ if isinstance(node, Run):
296
+ self._app_counter[node.app] += 1
297
+
298
+ def _generate_node_id(self, app: str) -> str:
299
+ self._app_counter[app] += 1
300
+ return f"{app.replace('/', '_').replace('-', '_')}_{self._app_counter[app]}"
301
+
302
+ def run(self, app: str, input: JSONType) -> ReferenceLeaf:
303
+ node_id = self._generate_node_id(app)
304
+ node = self.nodes[node_id] = Run(
305
+ id=node_id,
306
+ depends=depends(input),
307
+ app=app,
308
+ input=input,
309
+ )
310
+ return ReferenceLeaf(node.id)
311
+
312
+ def display(self, *fields: Leaf) -> None:
313
+ node_id = self._generate_node_id("display")
314
+ self.nodes[node_id] = Display(
315
+ node_id,
316
+ depends=depends(list(fields)),
317
+ fields=list(fields),
318
+ )
319
+
320
+ def set_output(self, output: JSONType) -> None:
321
+ self.output = output # type: ignore
322
+
323
+ def execute(self, input: JSONType) -> JSONType:
324
+ if not self.output:
325
+ raise WorkflowSyntaxError(
326
+ "Can't execute the workflow before the output is set."
327
+ )
328
+
329
+ context = Context({INPUT_VARIABLE_NAME: input})
330
+
331
+ sorter = graphlib.TopologicalSorter(
332
+ graph={
333
+ node.id: node.depends - {INPUT_VARIABLE_NAME}
334
+ for node in self.nodes.values()
335
+ }
336
+ )
337
+ for node_id in sorter.static_order():
338
+ node = self.nodes[node_id]
339
+ context.vars[node_id] = node.execute(context)
340
+
341
+ return context.hydrate(self.output)
342
+
343
+ @property
344
+ def input(self) -> ReferenceLeaf:
345
+ return ReferenceLeaf(INPUT_VARIABLE_NAME)
346
+
347
+ def to_json(self) -> dict[str, JSONType]:
348
+ if not self.output:
349
+ raise WorkflowSyntaxError(
350
+ "Can't serialize the workflow before the output is set."
351
+ )
352
+
353
+ return {
354
+ "name": self.name,
355
+ "schema": {
356
+ "input": self.input_schema,
357
+ "output": self.output_schema,
358
+ },
359
+ "nodes": {node.id: node.to_json() for node in self.nodes.values()},
360
+ "output": export_workflow_json(self.output),
361
+ "version": WORKFLOW_EXPORT_VERSION,
362
+ }
363
+
364
+ to_dict = to_json
365
+
366
+ def publish(self, title: str, *, is_public: bool = True) -> None:
367
+ workflow_contents = publish_workflow.TypedWorkflow(
368
+ name=self.name,
369
+ title=title,
370
+ contents=self, # type: ignore
371
+ is_public=is_public,
372
+ )
373
+ published_workflow = publish_workflow.sync(
374
+ client=REST_CLIENT,
375
+ json_body=workflow_contents,
376
+ )
377
+ if isinstance(published_workflow, Exception):
378
+ raise published_workflow
379
+
380
+ return (
381
+ REST_CLIENT.base_url
382
+ + "/workflows/"
383
+ + published_workflow.user_id
384
+ + "/"
385
+ + published_workflow.name
386
+ )
387
+
388
+
389
+ def create_workflow(
390
+ name: str,
391
+ input: type[BaseModel],
392
+ output: type[BaseModel],
393
+ ) -> Workflow:
394
+ return Workflow(
395
+ name=name,
396
+ input_schema=input.schema(),
397
+ output_schema=output.schema(),
398
+ )
399
+
400
+
401
+ def main() -> None:
402
+ import cli_nested_json
403
+
404
+ parser = ArgumentParser()
405
+ parser.add_argument("workflow_file", type=str)
406
+ args, input_params = parser.parse_known_args()
407
+
408
+ with open(args.workflow_file) as stream:
409
+ workflow = Workflow.from_json(json.load(stream))
410
+
411
+ payload = cli_nested_json.interpret_nested_json(
412
+ [part.split("=") for part in input_params]
413
+ )
414
+ console = rich.get_console()
415
+ console.print(
416
+ f"🤧 Loaded {workflow.name!r} with {len(workflow.nodes)} nodes!",
417
+ style="bold magenta",
418
+ )
419
+
420
+ context = Context({INPUT_VARIABLE_NAME: payload})
421
+
422
+ sorter = graphlib.TopologicalSorter(
423
+ graph={
424
+ node.id: node.depends - {INPUT_VARIABLE_NAME}
425
+ for node in workflow.nodes.values()
426
+ }
427
+ )
428
+ with console.status("Starting the execution", spinner="bouncingBall") as status:
429
+ for n, node_id in enumerate(sorter.static_order()):
430
+ node = workflow.nodes[node_id]
431
+ status.update(
432
+ status=f"Executing {node_id!r} ({n}/{len(workflow.nodes)})",
433
+ spinner="runner",
434
+ )
435
+ if isinstance(node, Run):
436
+ input = context.hydrate(node.input)
437
+ assert isinstance(input, dict)
438
+
439
+ owner, _, app = node.app.partition("/")
440
+ app, sep, path = app.partition("/")
441
+
442
+ handle = fal.apps.submit(
443
+ f"{owner}/{app}",
444
+ path=f"{sep}{path}",
445
+ arguments=input,
446
+ )
447
+ log_count = 0
448
+ for event in handle.iter_events(logs=True):
449
+ if isinstance(event, fal.apps.Queued):
450
+ status.update(
451
+ status=f"Queued for {node_id!r} (position={event.position}) ({n}/{len(workflow.nodes)})",
452
+ spinner="dots",
453
+ )
454
+ elif isinstance(event, fal.apps.InProgress):
455
+ status.update(
456
+ status=f"Executing {node_id!r} ({n}/{len(workflow.nodes)})",
457
+ spinner="runner",
458
+ )
459
+ for log in event.logs[log_count:]: # type: ignore
460
+ console.log(log["message"], style="dim")
461
+ log_count += 1
462
+
463
+ handle_status = handle.status(logs=True)
464
+ assert isinstance(handle_status, fal.apps.Completed)
465
+ for log in handle_status.logs[log_count:]: # type: ignore
466
+ console.log(log["message"], style="dim")
467
+
468
+ context.vars[node_id] = handle.get()
469
+ else:
470
+ context.vars[node_id] = node.execute(context)
471
+
472
+ console.print(
473
+ f"🎉 Execution complete!",
474
+ style="bold green",
475
+ )
476
+ output = context.hydrate(workflow.output)
477
+ console.print(Syntax(json.dumps(output, indent=2), "json"))
478
+
479
+
480
+ if __name__ == "__main__":
481
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 0.12.2
3
+ Version: 0.12.4
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels
6
6
  Author-email: hello@fal.ai
@@ -11,18 +11,15 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Requires-Dist: attrs (>=21.3.0)
14
- Requires-Dist: auth0-python (>=4.1.0,<5.0.0)
15
- Requires-Dist: boto3 (>=1.33.8,<2.0.0)
16
14
  Requires-Dist: click (>=8.1.3,<9.0.0)
17
15
  Requires-Dist: colorama (>=0.4.6,<0.5.0)
18
- Requires-Dist: datadog-api-client (==2.12.0)
19
16
  Requires-Dist: dill (==0.3.7)
20
17
  Requires-Dist: fastapi (==0.99.1)
21
18
  Requires-Dist: grpc-interceptor (>=0.15.0,<0.16.0)
22
19
  Requires-Dist: grpcio (>=1.50.0,<2.0.0)
23
- Requires-Dist: httpx (>=0.15.4,<0.25.0)
20
+ Requires-Dist: httpx (>=0.15.4)
24
21
  Requires-Dist: importlib-metadata (>=4.4) ; python_version < "3.10"
25
- Requires-Dist: isolate-proto (>=0.3.1,<0.4.0)
22
+ Requires-Dist: isolate-proto (>=0.3.4,<0.4.0)
26
23
  Requires-Dist: isolate[build] (>=0.12.3,<1.0)
27
24
  Requires-Dist: msgpack (>=1.0.7,<2.0.0)
28
25
  Requires-Dist: opentelemetry-api (>=1.15.0,<2.0.0)
@@ -32,8 +29,8 @@ Requires-Dist: pathspec (>=0.11.1,<0.12.0)
32
29
  Requires-Dist: pillow (>=10.2.0,<11.0.0)
33
30
  Requires-Dist: portalocker (>=2.7.0,<3.0.0)
34
31
  Requires-Dist: pydantic (<2.0)
32
+ Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
35
33
  Requires-Dist: python-dateutil (>=2.8.0,<3.0.0)
36
- Requires-Dist: requests (>=2.28.1,<3.0.0)
37
34
  Requires-Dist: rich (>=13.3.2,<14.0.0)
38
35
  Requires-Dist: structlog (>=22.3.0,<23.0.0)
39
36
  Requires-Dist: types-python-dateutil (>=2.8.0,<3.0.0)
@@ -0,0 +1,88 @@
1
+ openapi_fal_rest/__init__.py,sha256=ziculmF_i6trw63LzZGFX-6W3Lwq9mCR8_UpkpvpaHI,152
2
+ openapi_fal_rest/api/__init__.py,sha256=87ApBzKyGb5zsgTMOkQXDqsLZCmaSFoJMwbGzCDQZMw,47
3
+ openapi_fal_rest/api/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ openapi_fal_rest/api/applications/app_metadata.py,sha256=GqG6Q7jt8Jcyhb3ms_6i0M1B3cy205y3_A8W-AGEapY,5120
5
+ openapi_fal_rest/api/billing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ openapi_fal_rest/api/billing/get_user_details.py,sha256=2HQHRUQj8QwqSKgiV_USBdXCxGlfaVTBbLiPaDsMBUM,4013
7
+ openapi_fal_rest/api/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ openapi_fal_rest/api/files/check_dir_hash.py,sha256=zPNlOwG4YVvnhgfrleQtYLhI1lG0t8YQ1CU3TyvXvfk,4747
9
+ openapi_fal_rest/api/files/upload_local_file.py,sha256=p2lM7hswGbs8KNLg1Pp6vwV7x-1PKtWX-aYmaHUHSDU,5649
10
+ openapi_fal_rest/api/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ openapi_fal_rest/api/workflows/create_or_update_workflow_workflows_post.py,sha256=bS-CUo3CMsBxkXBEVOoa_GqL-EDgVMW9FHUMyYBneug,4611
12
+ openapi_fal_rest/api/workflows/delete_workflow_workflows_user_id_workflow_name_delete.py,sha256=svJcV5q2e8caxuLJKIld3M7-raQFqSIxPNq58e3AgaA,4590
13
+ openapi_fal_rest/api/workflows/execute_workflow_workflows_user_id_workflow_name_post.py,sha256=kofDDU3TEVQ8Fmua_K2-lk5xh16g6_FNFHQ0KzoFMcM,8743
14
+ openapi_fal_rest/api/workflows/get_workflow_workflows_user_id_workflow_name_get.py,sha256=-E-TELi9Q_-yqobdHAcTHVm-8HithJRUGk7wc1mLA18,4763
15
+ openapi_fal_rest/api/workflows/get_workflows_workflows_get.py,sha256=iJF3lNYp22p8-JbbBMDoHO9iXQB8779lSnH2fNimYP4,5242
16
+ openapi_fal_rest/client.py,sha256=G6BpJg9j7-JsrAUGddYwkzeWRYickBjPdcVgXoPzxuE,2817
17
+ openapi_fal_rest/errors.py,sha256=8mXSxdfSGzxT82srdhYbR0fHfgenxJXaUtMkaGgb6iU,470
18
+ openapi_fal_rest/models/__init__.py,sha256=pGB91qXG3VDxob4DPAh3OMa2fyJFYBxc-jQ-9A4y3Gs,2037
19
+ openapi_fal_rest/models/app_metadata_response_app_metadata.py,sha256=1vx_5cp8V0jyE8iBRIe8TfngaeXMojfEpMCpT6i3qvs,1252
20
+ openapi_fal_rest/models/body_upload_local_file.py,sha256=rOTEbYBXfwZk8TsywZWSPPQQEfJgvsLIufT6A40RJZs,1980
21
+ openapi_fal_rest/models/customer_details.py,sha256=_zvJ_Y2uLowpXqhUnkcpoZpyIB0s8kc5pWSdlBA2_7o,3913
22
+ openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_json_body_type_0.py,sha256=84MkDJeOgxwPozZZuP8Fb2dqlF57fmXuRGlrvMKxUew,1418
23
+ openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_response_200_type_0.py,sha256=SAGhH0grz3tYISHqsTr-MeiKWqTQsi8gFwSK2ksBoqY,1433
24
+ openapi_fal_rest/models/hash_check.py,sha256=T9R7n4EdadCxbFUZvresZZFPYwDfyJMZVNxY6wIJEE8,1352
25
+ openapi_fal_rest/models/http_validation_error.py,sha256=2nhqlv8RX2qp6VR7hb8-SKtzJWXSZ0J95ThW9J4agJo,2131
26
+ openapi_fal_rest/models/lock_reason.py,sha256=3b_foCV6bZKvsbyic3hM1_qzvJk_9ZD_5mS1GzSawdw,703
27
+ openapi_fal_rest/models/page_workflow_item.py,sha256=5DCUMQ2b8LAH8So6nQJW8mhlxZbedM4ixAJqbf-KZNE,2812
28
+ openapi_fal_rest/models/typed_workflow.py,sha256=haE4Sa16s4iea_VNYtVR7cP3A8Z2ja2KNywYJhc6GmQ,2119
29
+ openapi_fal_rest/models/validation_error.py,sha256=I6tB-HbEOmE0ua27erDX5PX5YUynENv_dgPN3SrwTrQ,2091
30
+ openapi_fal_rest/models/workflow_contents.py,sha256=TIockMwkpjeUjbWtZmtzcC327akwD4XgnTskgdsHQFQ,2703
31
+ openapi_fal_rest/models/workflow_contents_nodes.py,sha256=mMeQO_DlQZQPSwyGKTNC9eBovFVCVQNFbrB60k7tTtU,1695
32
+ openapi_fal_rest/models/workflow_contents_output.py,sha256=2m4ITxXcQxTt8iiY5bos0QQW_uMYOGAR1xAcm1rzrcI,1206
33
+ openapi_fal_rest/models/workflow_detail.py,sha256=lxlPkiOgc9_Dz6c0v3mDJNFXa4CWDkMWw9on-9McojE,4597
34
+ openapi_fal_rest/models/workflow_detail_contents_type_0.py,sha256=DUQg0mDQKczL5g76rJMYvFK_RFcQaQPC1BoTr43eheA,1237
35
+ openapi_fal_rest/models/workflow_item.py,sha256=M_8ojGsBpnKUe3l9yDnciPPgEjmfq1v-Bn9kVHPxT-0,1988
36
+ openapi_fal_rest/models/workflow_node.py,sha256=DZ3i-auxvm2cWFTBE52YSoLOEIVFvLPW9MyzyR91e78,1797
37
+ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRiPyAthdBLlioLH8Zw,161
38
+ openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
39
+ openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
40
+ openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
41
+ openapi_fal_rest/py.typed,sha256=8ZJUsxZiuOy1oJeVhsTWQhTG_6pTVHVXk5hJL79ebTk,25
42
+ openapi_fal_rest/types.py,sha256=GLwJwOotUOdfqryo_r0naw55-dh6Ilm4IvxePekSACk,994
43
+ fal/__init__.py,sha256=6SvCuotCb0tuqSWDZSFDjtySktJ5m1QpVIlefumJpvM,1199
44
+ fal/_serialization.py,sha256=l_dZuSX5BT7SogXw1CalYLfT2H3zy3tfq4y6jHuxZqQ,4201
45
+ fal/api.py,sha256=Qack_oYNkvF4qown3P_oKvyvRfTJkhOG7PL1xpa8FUQ,32872
46
+ fal/app.py,sha256=KAIgvBBpvzp6oY8BpH5hFOLDUpG4bjtwlV5jPGj2IE0,12487
47
+ fal/apps.py,sha256=T387WJDtKpKEytu27b2AVqqo0uijKrRT9ymk6FcRiEw,6705
48
+ fal/auth/__init__.py,sha256=ZnR1fxonzDk0UhS3-i33Kq2xOrN-leYXvJ-Ddnj94xc,3594
49
+ fal/auth/auth0.py,sha256=Lb63u99fjZVIpbkAicVL1B5V6iPb2mE04bwHmorXbu4,5542
50
+ fal/auth/local.py,sha256=lZqp4j32l2xFpY8zYvLoIHHyJrNAJDcm5MxgsLpY_pw,1786
51
+ fal/cli.py,sha256=-3P9O3QORA0NolZR9juR8fv59JdJo37_puwUcinzDlY,17299
52
+ fal/console/__init__.py,sha256=ernZ4bzvvliQh5SmrEqQ7lA5eVcbw6Ra2jalKtA7dxg,132
53
+ fal/console/icons.py,sha256=De9MfFaSkO2Lqfne13n3PrYfTXJVIzYZVqYn5BWsdrA,108
54
+ fal/console/ux.py,sha256=4vj1aGA3grRn-ebeMuDLR6u3YjMwUGpqtNgdTG9su5s,485
55
+ fal/env.py,sha256=-fA8x62BbOX3MOuO0maupa-_QJ9PNwr8ogfeG11QUyQ,53
56
+ fal/exceptions/__init__.py,sha256=Q4LCSqIrJ8GFQZWH5BvWL5mDPR0HwYQuIhNvsdiOkEU,938
57
+ fal/exceptions/_base.py,sha256=LeQmx-soL_-s1742WKN18VwTVjUuYP0L0BdQHPJBpM4,460
58
+ fal/exceptions/auth.py,sha256=01Ro7SyGJpwchubdHe14Cl6-Al1jUj16Sy4BvakNWf4,384
59
+ fal/exceptions/handlers.py,sha256=b21a8S13euECArjpgm2N69HsShqLYVqAboIeMoWlWA4,1414
60
+ fal/flags.py,sha256=AATQO65M4C87dGp0j7o6cSQWcr62xE-8DnJYsUjFFbw,942
61
+ fal/logging/__init__.py,sha256=snqprf7-sKw6oAATS_Yxklf-a3XhLg0vIHICPwLp6TM,1583
62
+ fal/logging/isolate.py,sha256=yDW_P4aR-t53IRmvD2Iprufv1Wn-xQXoBbMB2Ufr59s,2122
63
+ fal/logging/style.py,sha256=ckIgHzvF4DShM5kQh8F133X53z_vF46snuDHVmo_h9g,386
64
+ fal/logging/trace.py,sha256=OhzB6d4rQZimBc18WFLqH_9BGfqFFumKKTAGSsmWRMg,1904
65
+ fal/logging/user.py,sha256=A8vbZX9z13TPZEDzvlbvCDDdD0EL1KrCP3qHdrT58-A,632
66
+ fal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
+ fal/rest_client.py,sha256=kGBGmuyHfX1lR910EoKCYPjsyU8MdXawT_cW2q8Sajc,568
68
+ fal/sdk.py,sha256=Z3MQsD8MMQZq_GEC2VjaYChdNafFJtsgdk77-VK6N44,18782
69
+ fal/sync.py,sha256=Ljet584PVFz4r888-0bwV1Kio-tTneF_85TnHvBPvJw,4277
70
+ fal/toolkit/__init__.py,sha256=JDNBT_duflp93geeAzw2kFmGzG5odWnPJEXFLXE2nF4,713
71
+ fal/toolkit/exceptions.py,sha256=--WKKYxUop6WFy_vqAPXK6uH8C-JR98gnNXwhHNCb7E,258
72
+ fal/toolkit/file/__init__.py,sha256=YpUU6YziZV1AMuq12L0EDWToS0sgpHSGWsARbiOEHWk,56
73
+ fal/toolkit/file/file.py,sha256=ku4agJiGXU2gdfZmFrU5mDlVsag834zoeskbo-6ErEU,5926
74
+ fal/toolkit/file/providers/fal.py,sha256=hO59loXzGP4Vg-Q1FFR56nWbbI6BccJRnFsEI6z6EQE,3404
75
+ fal/toolkit/file/providers/gcp.py,sha256=Bq5SJSghXF8YfFnbZ83_mPdrWs2dFhi8ytODp92USgk,1962
76
+ fal/toolkit/file/providers/r2.py,sha256=xJtZfX3cfzJgLXS3F8mHArbrHi0_QBpIMy5M4-tS8H8,2586
77
+ fal/toolkit/file/types.py,sha256=MTIj6Y_ioL4CiMZXMiqx74vlmUifc3SNvcrWAXQfULE,1109
78
+ fal/toolkit/image/__init__.py,sha256=liEq0CqkRqUQ1udnnyGVFBwCXUhR2f6o5ffbtbAlP8o,57
79
+ fal/toolkit/image/image.py,sha256=bF1PzO4cJoFGJFpQYeG0sNaGuw3cC1zmobmbZrxbPFY,4339
80
+ fal/toolkit/mainify.py,sha256=E7gE45nZQZoaJdSlIq0mqajcH-IjcuPBWFmKm5hvhAU,406
81
+ fal/toolkit/optimize.py,sha256=OIhX0T-efRMgUJDpvL0bujdun5SovZgTdKxNOv01b_Y,1394
82
+ fal/toolkit/utils/__init__.py,sha256=b3zVpm50Upx1saXU7RiV9r9in6-Chs4OU9KRjBv7MYI,83
83
+ fal/toolkit/utils/download_utils.py,sha256=bigcLJjLK1OBAGxpYisJ0-5vcQCh0HAPuCykPrcCNd0,15596
84
+ fal/workflows.py,sha256=2xMC4dybiAv05eEua_YpKRAs395YR2UVKvhGS0HZdm8,14155
85
+ fal-0.12.4.dist-info/entry_points.txt,sha256=nE9GBVV3PdBosudFwbIzZQUe_9lfPR6EH8K_FdDASnM,62
86
+ fal-0.12.4.dist-info/METADATA,sha256=lcJ7E_Q3-0l1rtEIb-5tc2MLY4rpzIVSE7UXFh1D780,2937
87
+ fal-0.12.4.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
88
+ fal-0.12.4.dist-info/RECORD,,
@@ -1,4 +1,5 @@
1
1
  """ A client library for accessing FastAPI """
2
+
2
3
  from .client import AuthenticatedClient, Client
3
4
 
4
5
  __all__ = (
File without changes