fal 0.15.2__tar.gz → 1.0.0__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 (132) hide show
  1. fal-1.0.0/.gitignore +1 -0
  2. {fal-0.15.2 → fal-1.0.0}/PKG-INFO +2 -2
  3. {fal-0.15.2 → fal-1.0.0}/fal.egg-info/PKG-INFO +2 -2
  4. {fal-0.15.2 → fal-1.0.0}/fal.egg-info/SOURCES.txt +21 -2
  5. fal-1.0.0/fal.egg-info/entry_points.txt +2 -0
  6. {fal-0.15.2 → fal-1.0.0}/fal.egg-info/requires.txt +1 -1
  7. {fal-0.15.2 → fal-1.0.0}/pyproject.toml +3 -2
  8. {fal-0.15.2 → fal-1.0.0}/src/fal/__init__.py +4 -0
  9. fal-1.0.0/src/fal/__main__.py +4 -0
  10. fal-1.0.0/src/fal/_fal_version.py +16 -0
  11. fal-1.0.0/src/fal/_version.py +6 -0
  12. {fal-0.15.2 → fal-1.0.0}/src/fal/api.py +10 -1
  13. {fal-0.15.2 → fal-1.0.0}/src/fal/app.py +29 -1
  14. fal-1.0.0/src/fal/cli/__init__.py +1 -0
  15. fal-1.0.0/src/fal/cli/apps.py +313 -0
  16. fal-1.0.0/src/fal/cli/auth.py +59 -0
  17. fal-1.0.0/src/fal/cli/debug.py +65 -0
  18. fal-1.0.0/src/fal/cli/deploy.py +146 -0
  19. fal-1.0.0/src/fal/cli/keys.py +118 -0
  20. fal-1.0.0/src/fal/cli/main.py +82 -0
  21. fal-1.0.0/src/fal/cli/parser.py +74 -0
  22. fal-1.0.0/src/fal/cli/run.py +33 -0
  23. fal-1.0.0/src/fal/cli/secrets.py +107 -0
  24. fal-1.0.0/src/fal/exceptions/__init__.py +3 -0
  25. {fal-0.15.2 → fal-1.0.0}/src/fal/flags.py +0 -1
  26. {fal-0.15.2 → fal-1.0.0}/src/fal/sdk.py +1 -0
  27. fal-1.0.0/src/fal/utils.py +55 -0
  28. fal-1.0.0/tests/cli/__init__.py +0 -0
  29. fal-1.0.0/tests/cli/test_apps.py +63 -0
  30. fal-1.0.0/tests/cli/test_auth.py +17 -0
  31. fal-1.0.0/tests/cli/test_deploy.py +8 -0
  32. fal-1.0.0/tests/cli/test_keys.py +27 -0
  33. fal-1.0.0/tests/cli/test_run.py +8 -0
  34. fal-1.0.0/tests/cli/test_secrets.py +19 -0
  35. {fal-0.15.2 → fal-1.0.0}/tests/test_apps.py +6 -6
  36. fal-0.15.2/fal.egg-info/entry_points.txt +0 -2
  37. fal-0.15.2/src/fal/__main__.py +0 -4
  38. fal-0.15.2/src/fal/cli.py +0 -622
  39. fal-0.15.2/src/fal/exceptions/__init__.py +0 -32
  40. fal-0.15.2/src/fal/exceptions/handlers.py +0 -58
  41. {fal-0.15.2 → fal-1.0.0}/README.md +0 -0
  42. {fal-0.15.2 → fal-1.0.0}/fal.egg-info/dependency_links.txt +0 -0
  43. {fal-0.15.2 → fal-1.0.0}/fal.egg-info/top_level.txt +0 -0
  44. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/README.md +0 -0
  45. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  46. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  47. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  48. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  49. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  50. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  51. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  52. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  53. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  54. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  55. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_or_update_workflow_workflows_post.py +0 -0
  56. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow_workflows_user_id_workflow_name_delete.py +0 -0
  57. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/execute_workflow_workflows_user_id_workflow_name_post.py +0 -0
  58. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow_workflows_user_id_workflow_name_get.py +0 -0
  59. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflows_workflows_get.py +0 -0
  60. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  61. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  62. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  63. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  64. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  65. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  66. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_json_body_type_0.py +0 -0
  67. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_response_200_type_0.py +0 -0
  68. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  69. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  70. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  71. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  72. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  73. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  74. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  75. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  76. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  77. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  78. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents_type_0.py +0 -0
  79. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  80. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  81. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  82. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  83. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  84. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  85. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  86. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  87. {fal-0.15.2 → fal-1.0.0}/openapi-fal-rest/pyproject.toml +0 -0
  88. {fal-0.15.2 → fal-1.0.0}/openapi_rest.config.yaml +0 -0
  89. {fal-0.15.2 → fal-1.0.0}/setup.cfg +0 -0
  90. {fal-0.15.2 → fal-1.0.0}/src/fal/_serialization.py +0 -0
  91. {fal-0.15.2 → fal-1.0.0}/src/fal/apps.py +0 -0
  92. {fal-0.15.2 → fal-1.0.0}/src/fal/auth/__init__.py +0 -0
  93. {fal-0.15.2 → fal-1.0.0}/src/fal/auth/auth0.py +0 -0
  94. {fal-0.15.2 → fal-1.0.0}/src/fal/auth/local.py +0 -0
  95. {fal-0.15.2 → fal-1.0.0}/src/fal/console/__init__.py +0 -0
  96. {fal-0.15.2 → fal-1.0.0}/src/fal/console/icons.py +0 -0
  97. {fal-0.15.2 → fal-1.0.0}/src/fal/console/ux.py +0 -0
  98. {fal-0.15.2 → fal-1.0.0}/src/fal/exceptions/_base.py +0 -0
  99. {fal-0.15.2 → fal-1.0.0}/src/fal/exceptions/auth.py +0 -0
  100. {fal-0.15.2 → fal-1.0.0}/src/fal/logging/__init__.py +0 -0
  101. {fal-0.15.2 → fal-1.0.0}/src/fal/logging/isolate.py +0 -0
  102. {fal-0.15.2 → fal-1.0.0}/src/fal/logging/style.py +0 -0
  103. {fal-0.15.2 → fal-1.0.0}/src/fal/logging/trace.py +0 -0
  104. {fal-0.15.2 → fal-1.0.0}/src/fal/logging/user.py +0 -0
  105. {fal-0.15.2 → fal-1.0.0}/src/fal/py.typed +0 -0
  106. {fal-0.15.2 → fal-1.0.0}/src/fal/rest_client.py +0 -0
  107. {fal-0.15.2 → fal-1.0.0}/src/fal/sync.py +0 -0
  108. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/__init__.py +0 -0
  109. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/exceptions.py +0 -0
  110. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/file/__init__.py +0 -0
  111. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/file/file.py +0 -0
  112. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/file/providers/fal.py +0 -0
  113. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/file/providers/gcp.py +0 -0
  114. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/file/providers/r2.py +0 -0
  115. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/file/types.py +0 -0
  116. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/image/__init__.py +0 -0
  117. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/image/image.py +0 -0
  118. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/optimize.py +0 -0
  119. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/utils/__init__.py +0 -0
  120. {fal-0.15.2 → fal-1.0.0}/src/fal/toolkit/utils/download_utils.py +0 -0
  121. {fal-0.15.2 → fal-1.0.0}/src/fal/workflows.py +0 -0
  122. {fal-0.15.2 → fal-1.0.0}/tests/__init__.py +0 -0
  123. {fal-0.15.2 → fal-1.0.0}/tests/conftest.py +0 -0
  124. {fal-0.15.2 → fal-1.0.0}/tests/integration_test.py +0 -0
  125. {fal-0.15.2 → fal-1.0.0}/tests/mainify_package/__init__.py +0 -0
  126. {fal-0.15.2 → fal-1.0.0}/tests/mainify_package/impl.py +0 -0
  127. {fal-0.15.2 → fal-1.0.0}/tests/mainify_package/utils.py +0 -0
  128. {fal-0.15.2 → fal-1.0.0}/tests/mainify_target.py +0 -0
  129. {fal-0.15.2 → fal-1.0.0}/tests/test_stability.py +0 -0
  130. {fal-0.15.2 → fal-1.0.0}/tests/toolkit/file_test.py +0 -0
  131. {fal-0.15.2 → fal-1.0.0}/tests/toolkit/image_test.py +0 -0
  132. {fal-0.15.2 → fal-1.0.0}/tools/demo_script.py +0 -0
fal-1.0.0/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /src/fal/_fal_version.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 0.15.2
3
+ Version: 1.0.0
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <hello@fal.ai>
6
6
  Requires-Python: >=3.8
@@ -19,7 +19,7 @@ Requires-Dist: grpc-interceptor<1,>=0.15.0
19
19
  Requires-Dist: colorama<1,>=0.4.6
20
20
  Requires-Dist: portalocker<3,>=2.7.0
21
21
  Requires-Dist: rich<14,>=13.3.2
22
- Requires-Dist: rich_click
22
+ Requires-Dist: rich_argparse
23
23
  Requires-Dist: packaging<22,>=21.3
24
24
  Requires-Dist: pathspec<1,>=0.11.1
25
25
  Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 0.15.2
3
+ Version: 1.0.0
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <hello@fal.ai>
6
6
  Requires-Python: >=3.8
@@ -19,7 +19,7 @@ Requires-Dist: grpc-interceptor<1,>=0.15.0
19
19
  Requires-Dist: colorama<1,>=0.4.6
20
20
  Requires-Dist: portalocker<3,>=2.7.0
21
21
  Requires-Dist: rich<14,>=13.3.2
22
- Requires-Dist: rich_click
22
+ Requires-Dist: rich_argparse
23
23
  Requires-Dist: packaging<22,>=21.3
24
24
  Requires-Dist: pathspec<1,>=0.11.1
25
25
  Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
@@ -1,3 +1,4 @@
1
+ .gitignore
1
2
  README.md
2
3
  openapi_rest.config.yaml
3
4
  pyproject.toml
@@ -53,27 +54,38 @@ openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py
53
54
  openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py
54
55
  src/fal/__init__.py
55
56
  src/fal/__main__.py
57
+ src/fal/_fal_version.py
56
58
  src/fal/_serialization.py
59
+ src/fal/_version.py
57
60
  src/fal/api.py
58
61
  src/fal/app.py
59
62
  src/fal/apps.py
60
- src/fal/cli.py
61
63
  src/fal/flags.py
62
64
  src/fal/py.typed
63
65
  src/fal/rest_client.py
64
66
  src/fal/sdk.py
65
67
  src/fal/sync.py
68
+ src/fal/utils.py
66
69
  src/fal/workflows.py
67
70
  src/fal/auth/__init__.py
68
71
  src/fal/auth/auth0.py
69
72
  src/fal/auth/local.py
73
+ src/fal/cli/__init__.py
74
+ src/fal/cli/apps.py
75
+ src/fal/cli/auth.py
76
+ src/fal/cli/debug.py
77
+ src/fal/cli/deploy.py
78
+ src/fal/cli/keys.py
79
+ src/fal/cli/main.py
80
+ src/fal/cli/parser.py
81
+ src/fal/cli/run.py
82
+ src/fal/cli/secrets.py
70
83
  src/fal/console/__init__.py
71
84
  src/fal/console/icons.py
72
85
  src/fal/console/ux.py
73
86
  src/fal/exceptions/__init__.py
74
87
  src/fal/exceptions/_base.py
75
88
  src/fal/exceptions/auth.py
76
- src/fal/exceptions/handlers.py
77
89
  src/fal/logging/__init__.py
78
90
  src/fal/logging/isolate.py
79
91
  src/fal/logging/style.py
@@ -98,6 +110,13 @@ tests/integration_test.py
98
110
  tests/mainify_target.py
99
111
  tests/test_apps.py
100
112
  tests/test_stability.py
113
+ tests/cli/__init__.py
114
+ tests/cli/test_apps.py
115
+ tests/cli/test_auth.py
116
+ tests/cli/test_deploy.py
117
+ tests/cli/test_keys.py
118
+ tests/cli/test_run.py
119
+ tests/cli/test_secrets.py
101
120
  tests/mainify_package/__init__.py
102
121
  tests/mainify_package/impl.py
103
122
  tests/mainify_package/utils.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fal = fal.cli:main
@@ -12,7 +12,7 @@ grpc-interceptor<1,>=0.15.0
12
12
  colorama<1,>=0.4.6
13
13
  portalocker<3,>=2.7.0
14
14
  rich<14,>=13.3.2
15
- rich_click
15
+ rich_argparse
16
16
  packaging<22,>=21.3
17
17
  pathspec<1,>=0.11.1
18
18
  pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
@@ -7,6 +7,7 @@ root = "../../"
7
7
  git_describe_command= 'git describe --tags --abbrev=0 --dirty --match "fal_v*"'
8
8
  tag_regex = "^fal_v(?P<version>.*)$"
9
9
  fallback_version = "0.0.0"
10
+ version_file = "src/fal/_fal_version.py"
10
11
 
11
12
  [tool.setuptools.packages.find]
12
13
  where = ["src", "openapi-fal-rest"]
@@ -35,7 +36,7 @@ dependencies = [
35
36
  "colorama>=0.4.6,<1",
36
37
  "portalocker>=2.7.0,<3",
37
38
  "rich>=13.3.2,<14",
38
- "rich_click",
39
+ "rich_argparse",
39
40
  "packaging>=21.3,<22",
40
41
  "pathspec>=0.11.1,<1",
41
42
  "pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3",
@@ -69,7 +70,7 @@ dev = [
69
70
  ]
70
71
 
71
72
  [project.scripts]
72
- fal = "fal.cli:cli"
73
+ fal = "fal.cli:main"
73
74
 
74
75
  [tool.ruff]
75
76
  target-version = "py38"
@@ -7,6 +7,8 @@ from fal.app import App, endpoint, realtime, wrap_app # noqa: F401
7
7
  from fal.sdk import FalServerlessKeyCredentials
8
8
  from fal.sync import sync_dir
9
9
 
10
+ from ._version import __version__, version_tuple # noqa: F401
11
+
10
12
  local = LocalHost()
11
13
  serverless = FalServerlessHost()
12
14
 
@@ -22,4 +24,6 @@ __all__ = [
22
24
  # "wrap_app",
23
25
  "FalServerlessKeyCredentials",
24
26
  "sync_dir",
27
+ "__version__",
28
+ "version_tuple",
25
29
  ]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '1.0.0'
16
+ __version_tuple__ = version_tuple = (1, 0, 0)
@@ -0,0 +1,6 @@
1
+ try:
2
+ from ._fal_version import version as __version__ # type: ignore[import]
3
+ from ._fal_version import version_tuple # type: ignore[import]
4
+ except ImportError:
5
+ __version__ = "UNKNOWN"
6
+ version_tuple = (0, 0, __version__) # type: ignore[assignment]
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import inspect
4
+ import os
4
5
  import sys
5
6
  import threading
6
7
  from collections import defaultdict
@@ -832,6 +833,9 @@ class BaseServable:
832
833
  """
833
834
  pass
834
835
 
836
+ def _add_extra_routes(self, app: FastAPI):
837
+ pass
838
+
835
839
  @asynccontextmanager
836
840
  async def lifespan(self, app: FastAPI):
837
841
  yield
@@ -842,7 +846,10 @@ class BaseServable:
842
846
  from fastapi.responses import JSONResponse
843
847
  from starlette_exporter import PrometheusMiddleware
844
848
 
845
- _app = FalFastAPI(lifespan=self.lifespan)
849
+ _app = FalFastAPI(
850
+ lifespan=self.lifespan,
851
+ root_path=os.getenv("FAL_APP_ROOT_PATH") or "",
852
+ )
846
853
 
847
854
  _app.add_middleware(
848
855
  CORSMiddleware,
@@ -893,6 +900,8 @@ class BaseServable:
893
900
  methods=["POST"],
894
901
  )
895
902
 
903
+ self._add_extra_routes(_app)
904
+
896
905
  return _app
897
906
 
898
907
  def openapi(self) -> dict[str, Any]:
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import inspect
4
4
  import json
5
5
  import os
6
+ import re
6
7
  import typing
7
8
  from contextlib import asynccontextmanager
8
9
  from typing import Any, Callable, ClassVar, TypeVar
@@ -64,14 +65,33 @@ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
64
65
  return fn
65
66
 
66
67
 
68
+
69
+ PART_FINDER_RE = re.compile(r"[A-Z][a-z]*")
70
+
71
+
72
+ def _to_fal_app_name(name: str) -> str:
73
+ # Convert MyGoodApp into my-good-app
74
+ return "-".join(part.lower() for part in PART_FINDER_RE.findall(name))
75
+
76
+
67
77
  class App(fal.api.BaseServable):
68
78
  requirements: ClassVar[list[str]] = []
69
79
  machine_type: ClassVar[str] = "S"
70
- host_kwargs: ClassVar[dict[str, Any]] = {}
80
+ host_kwargs: ClassVar[dict[str, Any]] = {
81
+ "_scheduler": "nomad",
82
+ "_scheduler_options": {
83
+ "storage_region": "us-east",
84
+ },
85
+ "resolver": "uv",
86
+ "keep_alive": 60,
87
+ }
88
+ app_name: ClassVar[str]
71
89
 
72
90
  def __init_subclass__(cls, **kwargs):
91
+ app_name = kwargs.pop("name", None) or _to_fal_app_name(cls.__name__)
73
92
  parent_settings = getattr(cls, "host_kwargs", {})
74
93
  cls.host_kwargs = {**parent_settings, **kwargs}
94
+ cls.app_name = app_name
75
95
 
76
96
  if cls.__init__ is not App.__init__:
77
97
  raise ValueError(
@@ -100,6 +120,9 @@ class App(fal.api.BaseServable):
100
120
  finally:
101
121
  await _call_any_fn(self.teardown)
102
122
 
123
+ def health(self):
124
+ return {}
125
+
103
126
  def setup(self):
104
127
  """Setup the application before serving."""
105
128
 
@@ -141,6 +164,11 @@ class App(fal.api.BaseServable):
141
164
  )
142
165
  return response
143
166
 
167
+ def _add_extra_routes(self, app: FastAPI):
168
+ @app.get("/health")
169
+ def health():
170
+ return self.health()
171
+
144
172
  def provide_hints(self) -> list[str]:
145
173
  """Provide hints for routing the application."""
146
174
  raise NotImplementedError
@@ -0,0 +1 @@
1
+ from .main import main # noqa: F401
@@ -0,0 +1,313 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from .parser import FalClientParser
4
+
5
+ if TYPE_CHECKING:
6
+ from fal.sdk import AliasInfo, ApplicationInfo
7
+
8
+
9
+ def _apps_table(apps: list["AliasInfo"]):
10
+ from rich.table import Table
11
+
12
+ table = Table()
13
+ table.add_column("Name", no_wrap=True)
14
+ table.add_column("Revision")
15
+ table.add_column("Auth")
16
+ table.add_column("Min Concurrency")
17
+ table.add_column("Max Concurrency")
18
+ table.add_column("Max Multiplexing")
19
+ table.add_column("Keep Alive")
20
+ table.add_column("Active Workers")
21
+
22
+ for app in apps:
23
+ table.add_row(
24
+ app.alias,
25
+ app.revision,
26
+ app.auth_mode,
27
+ str(app.min_concurrency),
28
+ str(app.max_concurrency),
29
+ str(app.max_multiplexing),
30
+ str(app.keep_alive),
31
+ str(app.active_runners),
32
+ )
33
+
34
+ return table
35
+
36
+
37
+ def _list(args):
38
+ from fal.sdk import FalServerlessClient
39
+
40
+ client = FalServerlessClient(args.host)
41
+ with client.connect() as connection:
42
+ apps = connection.list_aliases()
43
+ table = _apps_table(apps)
44
+
45
+ args.console.print(table)
46
+
47
+
48
+ def _add_list_parser(subparsers, parents):
49
+ list_help = "List applications."
50
+ parser = subparsers.add_parser(
51
+ "list",
52
+ description=list_help,
53
+ help=list_help,
54
+ parents=parents,
55
+ )
56
+ parser.set_defaults(func=_list)
57
+
58
+
59
+ def _app_rev_table(revs: list["ApplicationInfo"]):
60
+ from rich.table import Table
61
+
62
+ table = Table()
63
+ table.add_column("Revision", no_wrap=True)
64
+ table.add_column("Min Concurrency")
65
+ table.add_column("Max Concurrency")
66
+ table.add_column("Max Multiplexing")
67
+ table.add_column("Keep Alive")
68
+ table.add_column("Active Workers")
69
+
70
+ for rev in revs:
71
+ table.add_row(
72
+ rev.application_id,
73
+ str(rev.min_concurrency),
74
+ str(rev.max_concurrency),
75
+ str(rev.max_multiplexing),
76
+ str(rev.keep_alive),
77
+ str(rev.active_runners),
78
+ )
79
+
80
+ return table
81
+
82
+
83
+ def _list_rev(args):
84
+ from fal.sdk import FalServerlessClient
85
+
86
+ client = FalServerlessClient(args.host)
87
+ with client.connect() as connection:
88
+ revs = connection.list_applications()
89
+ table = _app_rev_table(revs)
90
+
91
+ args.console.print(table)
92
+
93
+
94
+ def _add_list_rev_parser(subparsers, parents):
95
+ list_help = "List application revisions."
96
+ parser = subparsers.add_parser(
97
+ "list-rev",
98
+ description=list_help,
99
+ help=list_help,
100
+ parents=parents,
101
+ )
102
+ parser.set_defaults(func=_list_rev)
103
+
104
+
105
+ def _scale(args):
106
+ from fal.sdk import FalServerlessClient
107
+
108
+ client = FalServerlessClient(args.host)
109
+ with client.connect() as connection:
110
+ if (
111
+ args.keep_alive is None
112
+ and args.max_multiplexing is None
113
+ and args.max_concurrency is None
114
+ and args.min_concurrency is None
115
+ ):
116
+ args.console.log("No parameters for update were provided, ignoring.")
117
+ return
118
+
119
+ alias_info = connection.update_application(
120
+ application_name=args.app_alias,
121
+ keep_alive=args.keep_alive,
122
+ max_multiplexing=args.max_multiplexing,
123
+ max_concurrency=args.max_concurrency,
124
+ min_concurrency=args.min_concurrency,
125
+ )
126
+ table = _apps_table([alias_info])
127
+
128
+ args.console.print(table)
129
+
130
+
131
+ def _add_scale_parser(subparsers, parents):
132
+ scale_help = "Scale application."
133
+ parser = subparsers.add_parser(
134
+ "scale",
135
+ description=scale_help,
136
+ help=scale_help,
137
+ parents=parents,
138
+ )
139
+ parser.add_argument(
140
+ "--keep-alive",
141
+ type=int,
142
+ help="Keep alive (seconds).",
143
+ )
144
+ parser.add_argument(
145
+ "--max-multiplexing",
146
+ type=int,
147
+ help="Maximum multiplexing",
148
+ )
149
+ parser.add_argument(
150
+ "--max-concurrency",
151
+ type=int,
152
+ help="Maximum concurrency.",
153
+ )
154
+ parser.add_argument(
155
+ "--min-concurrency",
156
+ type=int,
157
+ help="Minimum concurrency",
158
+ )
159
+ parser.set_defaults(func=_scale)
160
+
161
+
162
+ def _set_rev(args):
163
+ from fal.sdk import FalServerlessClient
164
+
165
+ client = FalServerlessClient(args.host)
166
+ with client.connect() as connection:
167
+ connection.create_alias(args.app_name, args.app_rev, args.auth)
168
+
169
+
170
+ def _add_set_rev_parser(subparsers, parents):
171
+ from fal.sdk import ALIAS_AUTH_MODES
172
+
173
+ set_help = "Set application to a particular revision."
174
+ parser = subparsers.add_parser(
175
+ "set-rev",
176
+ description=set_help,
177
+ help=set_help,
178
+ parents=parents,
179
+ )
180
+ parser.add_argument(
181
+ "app_name",
182
+ help="Application name.",
183
+ )
184
+ parser.add_argument(
185
+ "app_rev",
186
+ help="Application revision.",
187
+ )
188
+ parser.add_argument(
189
+ "--auth",
190
+ choices=ALIAS_AUTH_MODES,
191
+ default="private",
192
+ help="Application authentication mode.",
193
+ )
194
+ parser.set_defaults(func=_set_rev)
195
+
196
+
197
+ def _runners(args):
198
+ from rich.table import Table
199
+
200
+ from fal.sdk import FalServerlessClient
201
+
202
+
203
+ client = FalServerlessClient(args.host)
204
+ with client.connect() as connection:
205
+ runners = connection.list_alias_runners(alias=args.app_name)
206
+
207
+ table = Table()
208
+ table.add_column("Runner ID")
209
+ table.add_column("In Flight Requests")
210
+ table.add_column("Expires in")
211
+ table.add_column("Uptime")
212
+
213
+ for runner in runners:
214
+ table.add_row(
215
+ runner.runner_id,
216
+ str(runner.in_flight_requests),
217
+ (
218
+ "N/A (active)"
219
+ if not runner.expiration_countdown
220
+ else f"{runner.expiration_countdown}s"
221
+ ),
222
+ f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
223
+ )
224
+
225
+ args.console.print(table)
226
+
227
+
228
+ def _add_runners_parser(subparsers, parents):
229
+ runners_help = "List application runners."
230
+ parser = subparsers.add_parser(
231
+ "runners",
232
+ description=runners_help,
233
+ help=runners_help,
234
+ parents=parents,
235
+ )
236
+ parser.add_argument(
237
+ "app_name",
238
+ help="Application name.",
239
+ )
240
+ parser.set_defaults(func=_runners)
241
+
242
+
243
+ def _delete(args):
244
+ from fal.sdk import FalServerlessClient
245
+
246
+ client = FalServerlessClient(args.host)
247
+ with client.connect() as connection:
248
+ connection.delete_alias(args.app_name)
249
+
250
+
251
+ def _add_delete_parser(subparsers, parents):
252
+ delete_help = "Delete application."
253
+ parser = subparsers.add_parser(
254
+ "delete",
255
+ description=delete_help,
256
+ help=delete_help,
257
+ parents=parents,
258
+ )
259
+ parser.add_argument(
260
+ "app_name",
261
+ help="Application name.",
262
+ )
263
+ parser.set_defaults(func=_delete)
264
+
265
+
266
+ def _delete_rev(args):
267
+ from fal.sdk import FalServerlessClient
268
+
269
+ client = FalServerlessClient(args.host)
270
+ with client.connect() as connection:
271
+ connection.delete_application(args.app_rev)
272
+
273
+
274
+ def _add_delete_rev_parser(subparsers, parents):
275
+ delete_help = "Delete application revision."
276
+ parser = subparsers.add_parser(
277
+ "delete-rev",
278
+ description=delete_help,
279
+ help=delete_help,
280
+ parents=parents,
281
+ )
282
+ parser.add_argument(
283
+ "app_rev",
284
+ help="Application revision.",
285
+ )
286
+ parser.set_defaults(func=_delete_rev)
287
+
288
+
289
+ def add_parser(main_subparsers, parents):
290
+ apps_help = "Manage fal applications."
291
+ parser = main_subparsers.add_parser(
292
+ "apps",
293
+ aliases=["app"],
294
+ description=apps_help,
295
+ help=apps_help,
296
+ parents=parents,
297
+ )
298
+
299
+ subparsers = parser.add_subparsers(
300
+ title="Commands",
301
+ metavar="command",
302
+ required=True,
303
+ )
304
+
305
+ parents = [*parents, FalClientParser(add_help=False)]
306
+
307
+ _add_list_parser(subparsers, parents)
308
+ _add_list_rev_parser(subparsers, parents)
309
+ _add_set_rev_parser(subparsers, parents)
310
+ _add_scale_parser(subparsers, parents)
311
+ _add_runners_parser(subparsers, parents)
312
+ _add_delete_parser(subparsers, parents)
313
+ _add_delete_rev_parser(subparsers, parents)
@@ -0,0 +1,59 @@
1
+ from fal.auth import USER, login, logout
2
+
3
+
4
+ def _login(args):
5
+ login()
6
+
7
+
8
+ def _logout(args):
9
+ logout()
10
+
11
+
12
+ def _whoami(args):
13
+ user_name = USER.info["name"]
14
+ sub = USER.info["sub"]
15
+ args.console.print(f"Hello, {user_name} - '{sub}'")
16
+
17
+
18
+ def add_parser(main_subparsers, parents):
19
+ auth_help = "Authenticate with fal."
20
+ parser = main_subparsers.add_parser(
21
+ "auth",
22
+ description=auth_help,
23
+ help=auth_help,
24
+ parents=parents,
25
+ )
26
+
27
+ subparsers = parser.add_subparsers(
28
+ title="Commands",
29
+ metavar="command",
30
+ dest="cmd",
31
+ required=True,
32
+ )
33
+
34
+ login_help = "Log in a user."
35
+ login_parser = subparsers.add_parser(
36
+ "login",
37
+ description=login_help,
38
+ help=login_help,
39
+ parents=parents,
40
+ )
41
+ login_parser.set_defaults(func=_login)
42
+
43
+ logout_help = "Log out the currently logged-in user."
44
+ logout_parser = subparsers.add_parser(
45
+ "logout",
46
+ description=logout_help,
47
+ help=logout_help,
48
+ parents=parents,
49
+ )
50
+ logout_parser.set_defaults(func=_logout)
51
+
52
+ whoami_help = "Show the currently authenticated user."
53
+ whoami_parser = subparsers.add_parser(
54
+ "whoami",
55
+ description=whoami_help,
56
+ help=whoami_help,
57
+ parents=parents,
58
+ )
59
+ whoami_parser.set_defaults(func=_whoami)