fal 0.12.5__tar.gz → 0.12.6__tar.gz

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 (90) hide show
  1. {fal-0.12.5 → fal-0.12.6}/PKG-INFO +6 -2
  2. {fal-0.12.5 → fal-0.12.6}/README.md +3 -0
  3. {fal-0.12.5 → fal-0.12.6}/pyproject.toml +3 -2
  4. {fal-0.12.5 → fal-0.12.6}/setup.py +4 -3
  5. fal-0.12.6/src/fal/__main__.py +4 -0
  6. {fal-0.12.5 → fal-0.12.6}/src/fal/apps.py +6 -2
  7. {fal-0.12.5 → fal-0.12.6}/src/fal/auth/auth0.py +12 -14
  8. {fal-0.12.5 → fal-0.12.6}/src/fal/cli.py +58 -29
  9. fal-0.12.6/src/fal/console/ux.py +14 -0
  10. {fal-0.12.5 → fal-0.12.6}/src/fal/workflows.py +10 -9
  11. fal-0.12.5/src/fal/console/ux.py +0 -17
  12. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  13. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  14. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  15. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  16. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  17. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  18. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  19. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  20. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  21. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  22. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_or_update_workflow_workflows_post.py +0 -0
  23. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow_workflows_user_id_workflow_name_delete.py +0 -0
  24. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/execute_workflow_workflows_user_id_workflow_name_post.py +0 -0
  25. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow_workflows_user_id_workflow_name_get.py +0 -0
  26. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflows_workflows_get.py +0 -0
  27. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  28. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  29. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  30. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  31. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  32. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  33. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_json_body_type_0.py +0 -0
  34. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_response_200_type_0.py +0 -0
  35. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  36. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  37. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  38. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  39. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  40. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  41. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  42. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  43. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  44. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  45. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents_type_0.py +0 -0
  46. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  47. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  48. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  49. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  50. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  51. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  52. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  53. {fal-0.12.5 → fal-0.12.6}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  54. {fal-0.12.5 → fal-0.12.6}/src/fal/__init__.py +0 -0
  55. {fal-0.12.5 → fal-0.12.6}/src/fal/_serialization.py +0 -0
  56. {fal-0.12.5 → fal-0.12.6}/src/fal/api.py +0 -0
  57. {fal-0.12.5 → fal-0.12.6}/src/fal/app.py +0 -0
  58. {fal-0.12.5 → fal-0.12.6}/src/fal/auth/__init__.py +0 -0
  59. {fal-0.12.5 → fal-0.12.6}/src/fal/auth/local.py +0 -0
  60. {fal-0.12.5 → fal-0.12.6}/src/fal/console/__init__.py +0 -0
  61. {fal-0.12.5 → fal-0.12.6}/src/fal/console/icons.py +0 -0
  62. {fal-0.12.5 → fal-0.12.6}/src/fal/env.py +0 -0
  63. {fal-0.12.5 → fal-0.12.6}/src/fal/exceptions/__init__.py +0 -0
  64. {fal-0.12.5 → fal-0.12.6}/src/fal/exceptions/_base.py +0 -0
  65. {fal-0.12.5 → fal-0.12.6}/src/fal/exceptions/auth.py +0 -0
  66. {fal-0.12.5 → fal-0.12.6}/src/fal/exceptions/handlers.py +0 -0
  67. {fal-0.12.5 → fal-0.12.6}/src/fal/flags.py +0 -0
  68. {fal-0.12.5 → fal-0.12.6}/src/fal/logging/__init__.py +0 -0
  69. {fal-0.12.5 → fal-0.12.6}/src/fal/logging/isolate.py +0 -0
  70. {fal-0.12.5 → fal-0.12.6}/src/fal/logging/style.py +0 -0
  71. {fal-0.12.5 → fal-0.12.6}/src/fal/logging/trace.py +0 -0
  72. {fal-0.12.5 → fal-0.12.6}/src/fal/logging/user.py +0 -0
  73. {fal-0.12.5 → fal-0.12.6}/src/fal/py.typed +0 -0
  74. {fal-0.12.5 → fal-0.12.6}/src/fal/rest_client.py +0 -0
  75. {fal-0.12.5 → fal-0.12.6}/src/fal/sdk.py +0 -0
  76. {fal-0.12.5 → fal-0.12.6}/src/fal/sync.py +0 -0
  77. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/__init__.py +0 -0
  78. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/exceptions.py +0 -0
  79. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/file/__init__.py +0 -0
  80. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/file/file.py +0 -0
  81. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/file/providers/fal.py +0 -0
  82. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/file/providers/gcp.py +0 -0
  83. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/file/providers/r2.py +0 -0
  84. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/file/types.py +0 -0
  85. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/image/__init__.py +0 -0
  86. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/image/image.py +0 -0
  87. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/mainify.py +0 -0
  88. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/optimize.py +0 -0
  89. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/utils/__init__.py +0 -0
  90. {fal-0.12.5 → fal-0.12.6}/src/fal/toolkit/utils/download_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 0.12.5
3
+ Version: 0.12.6
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels
6
6
  Author-email: hello@fal.ai
@@ -29,15 +29,19 @@ Requires-Dist: pathspec (>=0.11.1,<0.12.0)
29
29
  Requires-Dist: pillow (>=10.2.0,<11.0.0)
30
30
  Requires-Dist: portalocker (>=2.7.0,<3.0.0)
31
31
  Requires-Dist: pydantic (<2.0)
32
- Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
32
+ Requires-Dist: pyjwt[crypto] (>=2.8.0,<3.0.0)
33
33
  Requires-Dist: python-dateutil (>=2.8.0,<3.0.0)
34
34
  Requires-Dist: rich (>=13.3.2,<14.0.0)
35
+ Requires-Dist: rich_click
35
36
  Requires-Dist: structlog (>=22.3.0,<23.0.0)
36
37
  Requires-Dist: types-python-dateutil (>=2.8.0,<3.0.0)
37
38
  Requires-Dist: typing-extensions (>=4.7.1,<5.0.0)
38
39
  Requires-Dist: websockets (>=12.0,<13.0)
39
40
  Description-Content-Type: text/markdown
40
41
 
42
+ [![PyPI](https://img.shields.io/pypi/v/fal.svg?logo=PyPI)](https://pypi.org/project/fal)
43
+ [![Tests](https://img.shields.io/github/actions/workflow/status/fal-ai/fal/integration_tests.yaml?label=Tests)](https://github.com/fal-ai/fal/actions)
44
+
41
45
  # fal
42
46
 
43
47
  fal is a serverless Python runtime that lets you run and scale code in the cloud with no infra management.
@@ -1,3 +1,6 @@
1
+ [![PyPI](https://img.shields.io/pypi/v/fal.svg?logo=PyPI)](https://pypi.org/project/fal)
2
+ [![Tests](https://img.shields.io/github/actions/workflow/status/fal-ai/fal/integration_tests.yaml?label=Tests)](https://github.com/fal-ai/fal/actions)
3
+
1
4
  # fal
2
5
 
3
6
  fal is a serverless Python runtime that lets you run and scale code in the cloud with no infra management.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "fal"
3
- version = "0.12.5"
3
+ version = "0.12.6"
4
4
  description = "fal is an easy-to-use Serverless Python Framework"
5
5
  authors = ["Features & Labels <hello@fal.ai>"]
6
6
  readme = "README.md"
@@ -24,6 +24,7 @@ grpc-interceptor = "^0.15.0"
24
24
  colorama = "^0.4.6"
25
25
  portalocker = "^2.7.0"
26
26
  rich = "^13.3.2"
27
+ rich_click = "*"
27
28
  packaging = ">=21.3"
28
29
  pathspec = "^0.11.1"
29
30
  pydantic = "<2.0"
@@ -42,7 +43,7 @@ importlib-metadata = { version = ">=4.4", python = "<3.10" }
42
43
  msgpack = "^1.0.7"
43
44
  websockets = "^12.0"
44
45
  pillow = "^10.2.0"
45
- pyjwt = "^2.8.0"
46
+ pyjwt = { version = "^2.8.0", extras = ["crypto"] }
46
47
 
47
48
  [tool.poetry.group.dev.dependencies]
48
49
  openapi-python-client = "^0.14.1"
@@ -52,9 +52,10 @@ install_requires = \
52
52
  'pillow>=10.2.0,<11.0.0',
53
53
  'portalocker>=2.7.0,<3.0.0',
54
54
  'pydantic<2.0',
55
- 'pyjwt>=2.8.0,<3.0.0',
55
+ 'pyjwt[crypto]>=2.8.0,<3.0.0',
56
56
  'python-dateutil>=2.8.0,<3.0.0',
57
57
  'rich>=13.3.2,<14.0.0',
58
+ 'rich_click',
58
59
  'structlog>=22.3.0,<23.0.0',
59
60
  'types-python-dateutil>=2.8.0,<3.0.0',
60
61
  'typing-extensions>=4.7.1,<5.0.0',
@@ -68,9 +69,9 @@ entry_points = \
68
69
 
69
70
  setup_kwargs = {
70
71
  'name': 'fal',
71
- 'version': '0.12.5',
72
+ 'version': '0.12.6',
72
73
  'description': 'fal is an easy-to-use Serverless Python Framework',
73
- 'long_description': '# fal\n\nfal is a serverless Python runtime that lets you run and scale code in the cloud with no infra management.\n\nWith fal, you can build pipelines, serve ML models and scale them up to many users. You scale down to 0 when you don\'t use any resources.\n\n## Quickstart\n\nFirst, you need to install the `fal` package. You can do so using pip:\n```shell\npip install fal\n```\n\nThen you need to authenticate:\n```shell\nfal auth login\n```\n\nYou can also use fal keys that you can get from [our dashboard](https://fal.ai/dashboard/keys).\n\nNow can use the fal package in your Python scripts as follows:\n\n```py\nimport fal\n\n@fal.function(\n "virtualenv",\n requirements=["pyjokes"],\n)\ndef tell_joke() -> str:\n import pyjokes\n\n joke = pyjokes.get_joke()\n return joke\n\nprint("Joke from the clouds: ", tell_joke())\n```\n\nA new virtual environment will be created by fal in the cloud and the set of requirements that we passed will be installed as soon as this function is called. From that point on, our code will be executed as if it were running locally, and the joke prepared by the pyjokes library will be returned.\n\n## Next steps\n\nIf you would like to find out more about the capabilities of fal, check out to the [docs](https://fal.ai/docs). You can learn more about persistent storage, function caches and deploying your functions as API endpoints.\n',
74
+ 'long_description': '[![PyPI](https://img.shields.io/pypi/v/fal.svg?logo=PyPI)](https://pypi.org/project/fal)\n[![Tests](https://img.shields.io/github/actions/workflow/status/fal-ai/fal/integration_tests.yaml?label=Tests)](https://github.com/fal-ai/fal/actions)\n\n# fal\n\nfal is a serverless Python runtime that lets you run and scale code in the cloud with no infra management.\n\nWith fal, you can build pipelines, serve ML models and scale them up to many users. You scale down to 0 when you don\'t use any resources.\n\n## Quickstart\n\nFirst, you need to install the `fal` package. You can do so using pip:\n```shell\npip install fal\n```\n\nThen you need to authenticate:\n```shell\nfal auth login\n```\n\nYou can also use fal keys that you can get from [our dashboard](https://fal.ai/dashboard/keys).\n\nNow can use the fal package in your Python scripts as follows:\n\n```py\nimport fal\n\n@fal.function(\n "virtualenv",\n requirements=["pyjokes"],\n)\ndef tell_joke() -> str:\n import pyjokes\n\n joke = pyjokes.get_joke()\n return joke\n\nprint("Joke from the clouds: ", tell_joke())\n```\n\nA new virtual environment will be created by fal in the cloud and the set of requirements that we passed will be installed as soon as this function is called. From that point on, our code will be executed as if it were running locally, and the joke prepared by the pyjokes library will be returned.\n\n## Next steps\n\nIf you would like to find out more about the capabilities of fal, check out to the [docs](https://fal.ai/docs). You can learn more about persistent storage, function caches and deploying your functions as API endpoints.\n',
74
75
  'author': 'Features & Labels',
75
76
  'author_email': 'hello@fal.ai',
76
77
  'maintainer': 'None',
@@ -0,0 +1,4 @@
1
+ from .cli import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
@@ -65,8 +65,12 @@ class RequestHandle:
65
65
  def __post_init__(self):
66
66
  app_id = _backwards_compatible_app_id(self.app_id)
67
67
  # drop any extra path components
68
- user_id, app_name = app_id.split("/")[:2]
69
- self.app_id = f"{user_id}/{app_name}"
68
+ parts = app_id.split("/")[:3]
69
+ if parts[0] != "workflows":
70
+ # if the app_id is not a workflow, only keep the first two parts
71
+ parts = parts[:2]
72
+
73
+ self.app_id = "/".join(parts)
70
74
 
71
75
  def status(self, *, logs: bool = False) -> _Status:
72
76
  """Check the status of an async inference request."""
@@ -9,7 +9,7 @@ import httpx
9
9
 
10
10
  from fal.console import console
11
11
  from fal.console.icons import CHECK_ICON
12
- from fal.console.ux import get_browser
12
+ from fal.console.ux import maybe_open_browser_tab
13
13
 
14
14
  WEBSITE_URL = "https://fal.ai"
15
15
 
@@ -27,19 +27,17 @@ def logout_url(return_url: str):
27
27
 
28
28
 
29
29
  def _open_browser(url: str, code: str | None) -> None:
30
- browser = get_browser()
31
- console.print()
30
+ maybe_open_browser_tab(url)
32
31
 
33
- if browser is not None and click.confirm(
34
- "Open browser automatically ('no' to show URL)?", default=True, err=True
35
- ):
36
- browser.open_new_tab(url)
37
- else:
38
- console.print(f"On your computer or mobile device navigate to: {url}")
39
- if code:
40
- console.print(
41
- f"Confirm it shows the following code: [markdown.code]{code}[/]"
42
- )
32
+ console.print(
33
+ "If browser didn't open automatically, on your computer or mobile device navigate to"
34
+ )
35
+ console.print(url)
36
+
37
+ if code:
38
+ console.print(
39
+ f"\nConfirm it shows the following code: [markdown.code]{code}[/]\n"
40
+ )
43
41
 
44
42
 
45
43
  def login() -> dict:
@@ -183,7 +181,7 @@ def validate_id_token(token: str):
183
181
  def verify_access_token_expiration(token: str):
184
182
  from jwt import decode
185
183
 
186
- leeway = 60 * 30 * 60 # 30 minutes
184
+ leeway = 30 * 60 # 30 minutes
187
185
  decode(
188
186
  token,
189
187
  leeway=-leeway, # negative to consider expired before actual expiration
@@ -1,14 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ from dataclasses import dataclass, field
4
5
  from http import HTTPStatus
5
6
  from sys import argv
6
- from typing import Literal
7
+ from typing import Any, Callable, Literal
7
8
  from uuid import uuid4
8
9
 
9
10
  import click
10
11
  import openapi_fal_rest.api.billing.get_user_details as get_user_details
11
12
  from rich.table import Table
13
+ from rich_click import RichCommand, RichGroup
12
14
 
13
15
  import fal
14
16
  import fal.auth as auth
@@ -32,16 +34,29 @@ DEBUG_ENABLED = False
32
34
  logger = get_logger(__name__)
33
35
 
34
36
 
35
- class ExecutionInfo:
36
- debug: bool
37
- invocation_id: str
37
+ @dataclass
38
+ class State:
39
+ debug: bool = False
40
+ invocation_id: str = field(default_factory=lambda: str(uuid4()))
38
41
 
39
- def __init__(self, debug=False):
40
- self.debug = debug
41
- self.invocation_id = str(uuid4())
42
42
 
43
+ def debug_option(*param_decls: str, **kwargs: Any) -> Callable[[click.FC], click.FC]:
44
+ def callback(ctx: click.Context, param: click.Parameter, value: bool) -> None:
45
+ state = ctx.ensure_object(State)
46
+ state.debug = value
47
+ set_debug_logging(value)
43
48
 
44
- class MainGroup(click.Group):
49
+ if not param_decls:
50
+ param_decls = ("--debug",)
51
+
52
+ kwargs.setdefault("is_flag", True)
53
+ kwargs.setdefault("expose_value", False)
54
+ kwargs.setdefault("callback", callback)
55
+ kwargs.setdefault("help", "Enable detailed errors and verbose logging.")
56
+ return click.option(*param_decls, **kwargs)
57
+
58
+
59
+ class MainGroup(RichGroup):
45
60
  """A custom implementation of the top-level group
46
61
  (i.e. called on all commands and subcommands).
47
62
 
@@ -56,13 +71,11 @@ class MainGroup(click.Group):
56
71
  def invoke(self, ctx):
57
72
  from click.exceptions import Abort, ClickException, Exit
58
73
 
59
- execution_info = ExecutionInfo(debug=ctx.params["debug"])
74
+ state = ctx.ensure_object(State)
60
75
  qualified_name = " ".join([ctx.info_name] + argv[1:])
61
- invocation_id = execution_info.invocation_id
62
- set_debug_logging(execution_info.debug)
63
76
 
64
77
  with self._tracer.start_as_current_span(
65
- qualified_name, attributes={"invocation_id": invocation_id}
78
+ qualified_name, attributes={"invocation_id": state.invocation_id}
66
79
  ):
67
80
  try:
68
81
  logger.debug(
@@ -75,20 +88,20 @@ class MainGroup(click.Group):
75
88
  raise
76
89
  except Exception as exception:
77
90
  logger.error(exception)
78
- if execution_info.debug:
91
+ if state.debug:
79
92
  # Here we supress detailed errors on click lines because
80
93
  # they're mostly decorator calls, irrelevant to the dev's error tracing
81
94
  console.print_exception(suppress=[click])
82
95
  console.print()
83
96
  console.print(
84
- f"The [markdown.code]invocation_id[/] for this operation is: [white]{invocation_id}[/]"
97
+ f"The [markdown.code]invocation_id[/] for this operation is: [white]{state.invocation_id}[/]"
85
98
  )
86
99
  else:
87
100
  self._exception_handler.handle(exception)
88
101
 
89
102
  def add_command(
90
103
  self,
91
- cmd: click.Command,
104
+ cmd: RichCommand,
92
105
  name: str | None = None,
93
106
  aliases: list[str] | None = None,
94
107
  ) -> None:
@@ -113,7 +126,7 @@ class MainGroup(click.Group):
113
126
  self.add_command(alias_cmd, alias)
114
127
 
115
128
 
116
- class AliasCommand(click.Command):
129
+ class AliasCommand(RichCommand):
117
130
  def __init__(self, wrapped):
118
131
  self._wrapped = wrapped
119
132
 
@@ -129,31 +142,33 @@ class AliasCommand(click.Command):
129
142
 
130
143
 
131
144
  @click.group(cls=MainGroup)
132
- @click.option(
133
- "--debug", is_flag=True, help="Enable detailed errors and verbose logging."
134
- )
135
145
  @click.version_option()
136
- def cli(debug):
146
+ @debug_option()
147
+ def cli():
137
148
  pass
138
149
 
139
150
 
140
151
  ###### Auth group ######
141
- @click.group
152
+ @click.group(cls=RichGroup)
153
+ @debug_option()
142
154
  def auth_cli():
143
155
  pass
144
156
 
145
157
 
146
158
  @auth_cli.command(name="login")
159
+ @debug_option()
147
160
  def auth_login():
148
161
  auth.login()
149
162
 
150
163
 
151
164
  @auth_cli.command(name="logout")
165
+ @debug_option()
152
166
  def auth_logout():
153
167
  auth.logout()
154
168
 
155
169
 
156
170
  @auth_cli.command(name="hello", hidden=True)
171
+ @debug_option()
157
172
  def auth_test():
158
173
  """
159
174
  To test auth.
@@ -162,9 +177,10 @@ def auth_test():
162
177
 
163
178
 
164
179
  ###### Key group ######
165
- @click.group
180
+ @click.group(cls=RichGroup)
166
181
  @click.option("--host", default=DEFAULT_HOST, envvar=HOST_ENVVAR)
167
182
  @click.option("--port", default=DEFAULT_PORT, envvar=PORT_ENVVAR, hidden=True)
183
+ @debug_option()
168
184
  @click.pass_context
169
185
  def key_cli(ctx, host: str, port: str):
170
186
  ctx.obj = sdk.FalServerlessClient(f"{host}:{port}")
@@ -183,6 +199,7 @@ def key_cli(ctx, host: str, port: str):
183
199
  default=None,
184
200
  help="An alias for the key.",
185
201
  )
202
+ @debug_option()
186
203
  @click.pass_obj
187
204
  def key_generate(client: sdk.FalServerlessClient, scope: str, alias: str | None):
188
205
  with client.connect() as connection:
@@ -197,6 +214,7 @@ def key_generate(client: sdk.FalServerlessClient, scope: str, alias: str | None)
197
214
 
198
215
 
199
216
  @key_cli.command(name="list")
217
+ @debug_option()
200
218
  @click.pass_obj
201
219
  def key_list(client: sdk.FalServerlessClient):
202
220
  table = Table(title="Keys")
@@ -217,6 +235,7 @@ def key_list(client: sdk.FalServerlessClient):
217
235
 
218
236
  @key_cli.command(name="revoke")
219
237
  @click.argument("key_id", required=True)
238
+ @debug_option()
220
239
  @click.pass_obj
221
240
  def key_revoke(client: sdk.FalServerlessClient, key_id: str):
222
241
  with client.connect() as connection:
@@ -228,9 +247,10 @@ ALIAS_AUTH_OPTIONS = ["public", "private", "shared"]
228
247
  ALIAS_AUTH_TYPE = Literal["public", "private", "shared"]
229
248
 
230
249
 
231
- @click.group
250
+ @click.group(cls=RichGroup)
232
251
  @click.option("--host", default=DEFAULT_HOST, envvar=HOST_ENVVAR)
233
252
  @click.option("--port", default=DEFAULT_PORT, envvar=PORT_ENVVAR, hidden=True)
253
+ @debug_option()
234
254
  @click.pass_context
235
255
  def function_cli(ctx, host: str, port: str):
236
256
  ctx.obj = api.FalServerlessHost(f"{host}:{port}")
@@ -272,6 +292,7 @@ def load_function_from(
272
292
  )
273
293
  @click.argument("file_path", required=True)
274
294
  @click.argument("function_name", required=True)
295
+ @debug_option()
275
296
  @click.pass_obj
276
297
  def register_application(
277
298
  host: api.FalServerlessHost,
@@ -323,6 +344,7 @@ def register_application(
323
344
  @function_cli.command("run")
324
345
  @click.argument("file_path", required=True)
325
346
  @click.argument("function_name", required=True)
347
+ @debug_option()
326
348
  @click.pass_obj
327
349
  def run(host: api.FalServerlessHost, file_path: str, function_name: str):
328
350
  isolated_function = load_function_from(host, file_path, function_name)
@@ -332,6 +354,7 @@ def run(host: api.FalServerlessHost, file_path: str, function_name: str):
332
354
  @function_cli.command("logs")
333
355
  @click.option("--lines", default=100)
334
356
  @click.option("--url", default=None)
357
+ @debug_option()
335
358
  @click.pass_obj
336
359
  def get_logs(
337
360
  host: api.FalServerlessHost, lines: int | None = 100, url: str | None = None
@@ -342,9 +365,10 @@ def get_logs(
342
365
 
343
366
 
344
367
  ##### Alias group #####
345
- @click.group
368
+ @click.group(cls=RichGroup)
346
369
  @click.option("--host", default=DEFAULT_HOST, envvar=HOST_ENVVAR)
347
370
  @click.option("--port", default=DEFAULT_PORT, envvar=PORT_ENVVAR, hidden=True)
371
+ @debug_option()
348
372
  @click.pass_context
349
373
  def alias_cli(ctx, host: str, port: str):
350
374
  ctx.obj = api.FalServerlessClient(f"{host}:{port}")
@@ -385,6 +409,7 @@ def _alias_table(aliases: list[AliasInfo]):
385
409
  type=click.Choice(ALIAS_AUTH_OPTIONS),
386
410
  default="private",
387
411
  )
412
+ @debug_option()
388
413
  @click.pass_obj
389
414
  def alias_set(
390
415
  client: api.FalServerlessClient,
@@ -398,6 +423,7 @@ def alias_set(
398
423
 
399
424
  @alias_cli.command("delete")
400
425
  @click.argument("alias", required=True)
426
+ @debug_option()
401
427
  @click.pass_obj
402
428
  def alias_delete(client: api.FalServerlessClient, alias: str):
403
429
  with client.connect() as connection:
@@ -407,6 +433,7 @@ def alias_delete(client: api.FalServerlessClient, alias: str):
407
433
 
408
434
 
409
435
  @alias_cli.command("list")
436
+ @debug_option()
410
437
  @click.pass_obj
411
438
  def alias_list(client: api.FalServerlessClient):
412
439
  with client.connect() as connection:
@@ -428,6 +455,7 @@ def alias_list(client: api.FalServerlessClient):
428
455
  # "auth_mode",
429
456
  # type=click.Choice(ALIAS_AUTH_OPTIONS),
430
457
  # )
458
+ @debug_option()
431
459
  @click.pass_obj
432
460
  def alias_update(
433
461
  client: api.FalServerlessClient,
@@ -461,6 +489,7 @@ def alias_update(
461
489
 
462
490
  @alias_cli.command("runners")
463
491
  @click.argument("alias", required=True)
492
+ @debug_option()
464
493
  @click.pass_obj
465
494
  def alias_list_runners(
466
495
  client: api.FalServerlessClient,
@@ -491,15 +520,17 @@ def alias_list_runners(
491
520
 
492
521
 
493
522
  ##### Secrets group #####
494
- @click.group
523
+ @click.group(cls=RichGroup)
495
524
  @click.option("--host", default=DEFAULT_HOST, envvar=HOST_ENVVAR)
496
525
  @click.option("--port", default=DEFAULT_PORT, envvar=PORT_ENVVAR, hidden=True)
526
+ @debug_option()
497
527
  @click.pass_context
498
528
  def secrets_cli(ctx, host: str, port: str):
499
529
  ctx.obj = sdk.FalServerlessClient(f"{host}:{port}")
500
530
 
501
531
 
502
532
  @secrets_cli.command("list")
533
+ @debug_option()
503
534
  @click.pass_obj
504
535
  def list_secrets(client: api.FalServerlessClient):
505
536
  table = Table(title="Secrets")
@@ -516,6 +547,7 @@ def list_secrets(client: api.FalServerlessClient):
516
547
  @secrets_cli.command("set")
517
548
  @click.argument("secret_name", required=True)
518
549
  @click.argument("secret_value", required=True)
550
+ @debug_option()
519
551
  @click.pass_obj
520
552
  def set_secret(client: api.FalServerlessClient, secret_name: str, secret_value: str):
521
553
  with client.connect() as connection:
@@ -525,6 +557,7 @@ def set_secret(client: api.FalServerlessClient, secret_name: str, secret_value:
525
557
 
526
558
  @secrets_cli.command("delete")
527
559
  @click.argument("secret_name", required=True)
560
+ @debug_option()
528
561
  @click.pass_obj
529
562
  def delete_secret(client: api.FalServerlessClient, secret_name: str):
530
563
  with client.connect() as connection:
@@ -583,7 +616,3 @@ def _get_user_id() -> str:
583
616
  return user_id
584
617
  except Exception as e:
585
618
  raise api.FalServerlessError(f"Could not parse the user data: {e}")
586
-
587
-
588
- if __name__ == "__main__":
589
- cli()
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ import webbrowser
4
+
5
+
6
+ def maybe_open_browser_tab(url) -> None:
7
+ try:
8
+ # Avoids unwanted output in the console from the standard `webbrowser.open()`.
9
+ # See https://stackoverflow.com/a/19199794
10
+ browser = webbrowser.get()
11
+
12
+ browser.open_new_tab(url)
13
+ except webbrowser.Error:
14
+ pass
@@ -12,6 +12,7 @@ import rich
12
12
  from openapi_fal_rest.api.workflows import (
13
13
  create_or_update_workflow_workflows_post as publish_workflow,
14
14
  )
15
+ from openapi_fal_rest.models.http_validation_error import HTTPValidationError
15
16
  from pydantic import BaseModel
16
17
  from rich.syntax import Syntax
17
18
 
@@ -363,7 +364,7 @@ class Workflow:
363
364
 
364
365
  to_dict = to_json
365
366
 
366
- def publish(self, title: str, *, is_public: bool = True) -> None:
367
+ def publish(self, title: str, *, is_public: bool = True):
367
368
  workflow_contents = publish_workflow.TypedWorkflow(
368
369
  name=self.name,
369
370
  title=title,
@@ -376,14 +377,14 @@ class Workflow:
376
377
  )
377
378
  if isinstance(published_workflow, Exception):
378
379
  raise published_workflow
379
-
380
- return (
381
- REST_CLIENT.base_url
382
- + "/workflows/"
383
- + published_workflow.user_id
384
- + "/"
385
- + published_workflow.name
386
- )
380
+ if isinstance(published_workflow, HTTPValidationError):
381
+ raise RuntimeError(published_workflow.detail)
382
+ if not published_workflow:
383
+ raise RuntimeError("Failed to publish the workflow")
384
+
385
+ # NOTE: dropping the provider prefix from the user_id
386
+ user_id_part = published_workflow.user_id.split("|")[-1]
387
+ return f"{user_id_part}/{published_workflow.name}"
387
388
 
388
389
 
389
390
  def create_workflow(
@@ -1,17 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import webbrowser
4
-
5
-
6
- def get_browser() -> webbrowser.BaseBrowser | None:
7
- """Gets a reference to the default browser, if available.
8
-
9
- This allows us to decide on a flow before showing the actual browser window.
10
- It also avoids unwanted output in the console from the standard `webbrowser.open()`.
11
-
12
- See https://stackoverflow.com/a/19199794
13
- """
14
- try:
15
- return webbrowser.get()
16
- except webbrowser.Error:
17
- return None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes