fal 0.12.3__py3-none-any.whl → 0.12.5__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 (35) hide show
  1. fal/auth/__init__.py +13 -1
  2. fal/auth/auth0.py +3 -3
  3. fal/cli.py +9 -1
  4. fal/flags.py +1 -0
  5. fal/workflows.py +481 -0
  6. {fal-0.12.3.dist-info → fal-0.12.5.dist-info}/METADATA +2 -2
  7. {fal-0.12.3.dist-info → fal-0.12.5.dist-info}/RECORD +35 -13
  8. openapi_fal_rest/__init__.py +1 -0
  9. openapi_fal_rest/api/workflows/__init__.py +0 -0
  10. openapi_fal_rest/api/workflows/create_or_update_workflow_workflows_post.py +172 -0
  11. openapi_fal_rest/api/workflows/delete_workflow_workflows_user_id_workflow_name_delete.py +175 -0
  12. openapi_fal_rest/api/workflows/execute_workflow_workflows_user_id_workflow_name_post.py +268 -0
  13. openapi_fal_rest/api/workflows/get_workflow_workflows_user_id_workflow_name_get.py +181 -0
  14. openapi_fal_rest/api/workflows/get_workflows_workflows_get.py +189 -0
  15. openapi_fal_rest/models/__init__.py +34 -0
  16. openapi_fal_rest/models/app_metadata_response_app_metadata.py +1 -0
  17. openapi_fal_rest/models/customer_details.py +15 -14
  18. openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_json_body_type_0.py +44 -0
  19. openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_response_200_type_0.py +44 -0
  20. openapi_fal_rest/models/page_workflow_item.py +107 -0
  21. openapi_fal_rest/models/typed_workflow.py +85 -0
  22. openapi_fal_rest/models/workflow_contents.py +98 -0
  23. openapi_fal_rest/models/workflow_contents_nodes.py +59 -0
  24. openapi_fal_rest/models/workflow_contents_output.py +44 -0
  25. openapi_fal_rest/models/workflow_detail.py +149 -0
  26. openapi_fal_rest/models/workflow_detail_contents_type_0.py +44 -0
  27. openapi_fal_rest/models/workflow_item.py +80 -0
  28. openapi_fal_rest/models/workflow_node.py +74 -0
  29. openapi_fal_rest/models/workflow_node_type.py +9 -0
  30. openapi_fal_rest/models/workflow_schema.py +73 -0
  31. openapi_fal_rest/models/workflow_schema_input.py +44 -0
  32. openapi_fal_rest/models/workflow_schema_output.py +44 -0
  33. openapi_fal_rest/types.py +1 -0
  34. {fal-0.12.3.dist-info → fal-0.12.5.dist-info}/WHEEL +0 -0
  35. {fal-0.12.3.dist-info → fal-0.12.5.dist-info}/entry_points.txt +0 -0
fal/auth/__init__.py CHANGED
@@ -85,10 +85,12 @@ def _fetch_access_token() -> str:
85
85
  class UserAccess:
86
86
  _access_token: str | None = field(repr=False, default=None)
87
87
  _user_info: dict | None = field(repr=False, default=None)
88
+ _exc: Exception | None = field(repr=False, default=None)
88
89
 
89
90
  def invalidate(self) -> None:
90
91
  self._access_token = None
91
92
  self._user_info = None
93
+ self._exc = None
92
94
 
93
95
  @property
94
96
  def info(self) -> dict:
@@ -99,8 +101,18 @@ class UserAccess:
99
101
 
100
102
  @property
101
103
  def access_token(self) -> str:
104
+ if self._exc is not None:
105
+ # We access this several times, so we want to raise the
106
+ # original exception instead of the newer exceptions we
107
+ # would get from the effects of the original exception.
108
+ raise self._exc
109
+
102
110
  if self._access_token is None:
103
- self._access_token = _fetch_access_token()
111
+ try:
112
+ self._access_token = _fetch_access_token()
113
+ except Exception as e:
114
+ self._exc = e
115
+ raise
104
116
 
105
117
  return self._access_token
106
118
 
fal/auth/auth0.py CHANGED
@@ -169,6 +169,7 @@ def validate_id_token(token: str):
169
169
  algorithms=AUTH0_ALGORITHMS,
170
170
  issuer=AUTH0_ISSUER,
171
171
  audience=AUTH0_CLIENT_ID,
172
+ leeway=60, # 1 minute, to account for clock skew
172
173
  options={
173
174
  "verify_signature": True,
174
175
  "verify_exp": True,
@@ -180,12 +181,11 @@ def validate_id_token(token: str):
180
181
 
181
182
 
182
183
  def verify_access_token_expiration(token: str):
183
- from datetime import timedelta
184
-
185
184
  from jwt import decode
186
185
 
186
+ leeway = 60 * 30 * 60 # 30 minutes
187
187
  decode(
188
188
  token,
189
- leeway=timedelta(minutes=-30), # Mark as expired some time before it expires
189
+ leeway=-leeway, # negative to consider expired before actual expiration
190
190
  options={"verify_exp": True, "verify_signature": False},
191
191
  )
fal/cli.py CHANGED
@@ -54,6 +54,8 @@ class MainGroup(click.Group):
54
54
  _tracer = get_tracer(__name__)
55
55
 
56
56
  def invoke(self, ctx):
57
+ from click.exceptions import Abort, ClickException, Exit
58
+
57
59
  execution_info = ExecutionInfo(debug=ctx.params["debug"])
58
60
  qualified_name = " ".join([ctx.info_name] + argv[1:])
59
61
  invocation_id = execution_info.invocation_id
@@ -68,6 +70,9 @@ class MainGroup(click.Group):
68
70
  command=qualified_name,
69
71
  )
70
72
  return super().invoke(ctx)
73
+ except (EOFError, KeyboardInterrupt, ClickException, Exit, Abort):
74
+ # let click's main handle these
75
+ raise
71
76
  except Exception as exception:
72
77
  logger.error(exception)
73
78
  if execution_info.debug:
@@ -571,7 +576,10 @@ def _get_user_id() -> str:
571
576
  raise api.FalServerlessError(content["detail"])
572
577
  try:
573
578
  full_user_id = user_details_response.parsed.user_id
574
- user_id = full_user_id.split("|")[1]
579
+ _provider, _, user_id = full_user_id.partition("|")
580
+ if not user_id:
581
+ user_id = full_user_id
582
+
575
583
  return user_id
576
584
  except Exception as e:
577
585
  raise api.FalServerlessError(f"Could not parse the user data: {e}")
fal/flags.py CHANGED
@@ -30,3 +30,4 @@ FAL_RUN_HOST = (
30
30
  )
31
31
 
32
32
  FORCE_SETUP = bool_envvar("FAL_FORCE_SETUP")
33
+ DONT_OPEN_LINKS = bool_envvar("FAL_DONT_OPEN_LINKS")
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.3
3
+ Version: 0.12.5
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels
6
6
  Author-email: hello@fal.ai
@@ -19,7 +19,7 @@ Requires-Dist: grpc-interceptor (>=0.15.0,<0.16.0)
19
19
  Requires-Dist: grpcio (>=1.50.0,<2.0.0)
20
20
  Requires-Dist: httpx (>=0.15.4)
21
21
  Requires-Dist: importlib-metadata (>=4.4) ; python_version < "3.10"
22
- Requires-Dist: isolate-proto (==0.3.3)
22
+ Requires-Dist: isolate-proto (>=0.3.4,<0.4.0)
23
23
  Requires-Dist: isolate[build] (>=0.12.3,<1.0)
24
24
  Requires-Dist: msgpack (>=1.0.7,<2.0.0)
25
25
  Requires-Dist: opentelemetry-api (>=1.15.0,<2.0.0)
@@ -1,4 +1,4 @@
1
- openapi_fal_rest/__init__.py,sha256=sqsyB55QptrijXTCVFQfIJ6uC__vXez1i5KNvYplk5w,151
1
+ openapi_fal_rest/__init__.py,sha256=ziculmF_i6trw63LzZGFX-6W3Lwq9mCR8_UpkpvpaHI,152
2
2
  openapi_fal_rest/api/__init__.py,sha256=87ApBzKyGb5zsgTMOkQXDqsLZCmaSFoJMwbGzCDQZMw,47
3
3
  openapi_fal_rest/api/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  openapi_fal_rest/api/applications/app_metadata.py,sha256=GqG6Q7jt8Jcyhb3ms_6i0M1B3cy205y3_A8W-AGEapY,5120
@@ -7,27 +7,48 @@ openapi_fal_rest/api/billing/get_user_details.py,sha256=2HQHRUQj8QwqSKgiV_USBdXC
7
7
  openapi_fal_rest/api/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  openapi_fal_rest/api/files/check_dir_hash.py,sha256=zPNlOwG4YVvnhgfrleQtYLhI1lG0t8YQ1CU3TyvXvfk,4747
9
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
10
16
  openapi_fal_rest/client.py,sha256=G6BpJg9j7-JsrAUGddYwkzeWRYickBjPdcVgXoPzxuE,2817
11
17
  openapi_fal_rest/errors.py,sha256=8mXSxdfSGzxT82srdhYbR0fHfgenxJXaUtMkaGgb6iU,470
12
- openapi_fal_rest/models/__init__.py,sha256=u2YVZnZwu9YqDPasBnh9GLVIkEJj4PFCThf97Pblx4o,601
13
- openapi_fal_rest/models/app_metadata_response_app_metadata.py,sha256=swJMfWvbjlMF8dmv-KEqcR9If0UjsRogwj9UqBBlkpc,1251
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
14
20
  openapi_fal_rest/models/body_upload_local_file.py,sha256=rOTEbYBXfwZk8TsywZWSPPQQEfJgvsLIufT6A40RJZs,1980
15
- openapi_fal_rest/models/customer_details.py,sha256=XQBaO-A5DI54nCau5ZIR0jhCAmsBJKrtDgApuu6PFrU,3912
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
16
24
  openapi_fal_rest/models/hash_check.py,sha256=T9R7n4EdadCxbFUZvresZZFPYwDfyJMZVNxY6wIJEE8,1352
17
25
  openapi_fal_rest/models/http_validation_error.py,sha256=2nhqlv8RX2qp6VR7hb8-SKtzJWXSZ0J95ThW9J4agJo,2131
18
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
19
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
20
41
  openapi_fal_rest/py.typed,sha256=8ZJUsxZiuOy1oJeVhsTWQhTG_6pTVHVXk5hJL79ebTk,25
21
- openapi_fal_rest/types.py,sha256=4xaUIOliefW-5jz_p-JT2LO7-V0wKWaniHGtjPBQfvQ,993
42
+ openapi_fal_rest/types.py,sha256=GLwJwOotUOdfqryo_r0naw55-dh6Ilm4IvxePekSACk,994
22
43
  fal/__init__.py,sha256=6SvCuotCb0tuqSWDZSFDjtySktJ5m1QpVIlefumJpvM,1199
23
44
  fal/_serialization.py,sha256=l_dZuSX5BT7SogXw1CalYLfT2H3zy3tfq4y6jHuxZqQ,4201
24
45
  fal/api.py,sha256=Qack_oYNkvF4qown3P_oKvyvRfTJkhOG7PL1xpa8FUQ,32872
25
46
  fal/app.py,sha256=KAIgvBBpvzp6oY8BpH5hFOLDUpG4bjtwlV5jPGj2IE0,12487
26
47
  fal/apps.py,sha256=T387WJDtKpKEytu27b2AVqqo0uijKrRT9ymk6FcRiEw,6705
27
- fal/auth/__init__.py,sha256=4W_9svpsmohRPhBi4yjx9rAPaUeBTHaJvSRpdRzXA5s,3133
28
- fal/auth/auth0.py,sha256=hQ3ZTqqsgpL62GsNB9KvjE8k_2hxXMIJb5TNpRmaiYs,5485
48
+ fal/auth/__init__.py,sha256=ZnR1fxonzDk0UhS3-i33Kq2xOrN-leYXvJ-Ddnj94xc,3594
49
+ fal/auth/auth0.py,sha256=Lb63u99fjZVIpbkAicVL1B5V6iPb2mE04bwHmorXbu4,5542
29
50
  fal/auth/local.py,sha256=lZqp4j32l2xFpY8zYvLoIHHyJrNAJDcm5MxgsLpY_pw,1786
30
- fal/cli.py,sha256=nLk4LJsGvLicA_iW0T1ldYb_igMwYOdC2fQxUsdWCRQ,17236
51
+ fal/cli.py,sha256=mvYl6-wlrofY9wFzdjAO31ut2RVvwnXf6aIqFkxNWmQ,17526
31
52
  fal/console/__init__.py,sha256=ernZ4bzvvliQh5SmrEqQ7lA5eVcbw6Ra2jalKtA7dxg,132
32
53
  fal/console/icons.py,sha256=De9MfFaSkO2Lqfne13n3PrYfTXJVIzYZVqYn5BWsdrA,108
33
54
  fal/console/ux.py,sha256=4vj1aGA3grRn-ebeMuDLR6u3YjMwUGpqtNgdTG9su5s,485
@@ -36,7 +57,7 @@ fal/exceptions/__init__.py,sha256=Q4LCSqIrJ8GFQZWH5BvWL5mDPR0HwYQuIhNvsdiOkEU,93
36
57
  fal/exceptions/_base.py,sha256=LeQmx-soL_-s1742WKN18VwTVjUuYP0L0BdQHPJBpM4,460
37
58
  fal/exceptions/auth.py,sha256=01Ro7SyGJpwchubdHe14Cl6-Al1jUj16Sy4BvakNWf4,384
38
59
  fal/exceptions/handlers.py,sha256=b21a8S13euECArjpgm2N69HsShqLYVqAboIeMoWlWA4,1414
39
- fal/flags.py,sha256=8OaKkJg_-UvtyRbZf-rW5ZTW3B1xQpzzXnLRNFB7grA,889
60
+ fal/flags.py,sha256=AATQO65M4C87dGp0j7o6cSQWcr62xE-8DnJYsUjFFbw,942
40
61
  fal/logging/__init__.py,sha256=snqprf7-sKw6oAATS_Yxklf-a3XhLg0vIHICPwLp6TM,1583
41
62
  fal/logging/isolate.py,sha256=yDW_P4aR-t53IRmvD2Iprufv1Wn-xQXoBbMB2Ufr59s,2122
42
63
  fal/logging/style.py,sha256=ckIgHzvF4DShM5kQh8F133X53z_vF46snuDHVmo_h9g,386
@@ -60,7 +81,8 @@ fal/toolkit/mainify.py,sha256=E7gE45nZQZoaJdSlIq0mqajcH-IjcuPBWFmKm5hvhAU,406
60
81
  fal/toolkit/optimize.py,sha256=OIhX0T-efRMgUJDpvL0bujdun5SovZgTdKxNOv01b_Y,1394
61
82
  fal/toolkit/utils/__init__.py,sha256=b3zVpm50Upx1saXU7RiV9r9in6-Chs4OU9KRjBv7MYI,83
62
83
  fal/toolkit/utils/download_utils.py,sha256=bigcLJjLK1OBAGxpYisJ0-5vcQCh0HAPuCykPrcCNd0,15596
63
- fal-0.12.3.dist-info/METADATA,sha256=0eR9dtKw9ZU7y2Dxjx9NtXp--hmw7XG24LuTylD5BlE,2930
64
- fal-0.12.3.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
65
- fal-0.12.3.dist-info/entry_points.txt,sha256=nE9GBVV3PdBosudFwbIzZQUe_9lfPR6EH8K_FdDASnM,62
66
- fal-0.12.3.dist-info/RECORD,,
84
+ fal/workflows.py,sha256=2xMC4dybiAv05eEua_YpKRAs395YR2UVKvhGS0HZdm8,14155
85
+ fal-0.12.5.dist-info/entry_points.txt,sha256=nE9GBVV3PdBosudFwbIzZQUe_9lfPR6EH8K_FdDASnM,62
86
+ fal-0.12.5.dist-info/METADATA,sha256=CG9k8-tOXQ9rvTFC4mLc44pj7FDNLpTpyYwEeBp5n4w,2937
87
+ fal-0.12.5.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
88
+ fal-0.12.5.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