fal 1.11.2__tar.gz → 1.11.4__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 (176) hide show
  1. {fal-1.11.2/fal.egg-info → fal-1.11.4}/PKG-INFO +4 -4
  2. {fal-1.11.2 → fal-1.11.4/fal.egg-info}/PKG-INFO +4 -4
  3. {fal-1.11.2 → fal-1.11.4}/fal.egg-info/requires.txt +3 -3
  4. {fal-1.11.2 → fal-1.11.4}/pyproject.toml +6 -3
  5. {fal-1.11.2 → fal-1.11.4}/src/fal/_fal_version.py +2 -2
  6. {fal-1.11.2 → fal-1.11.4}/src/fal/api.py +4 -1
  7. {fal-1.11.2 → fal-1.11.4}/src/fal/app.py +1 -1
  8. {fal-1.11.2 → fal-1.11.4}/src/fal/auth/__init__.py +2 -3
  9. {fal-1.11.2 → fal-1.11.4}/src/fal/auth/auth0.py +6 -6
  10. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/apps.py +9 -49
  11. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/deploy.py +1 -1
  12. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/main.py +1 -1
  13. fal-1.11.4/src/fal/cli/runners.py +133 -0
  14. {fal-1.11.2 → fal-1.11.4}/src/fal/config.py +1 -1
  15. {fal-1.11.2 → fal-1.11.4}/src/fal/sdk.py +11 -1
  16. {fal-1.11.2 → fal-1.11.4}/tests/test_apps.py +51 -35
  17. {fal-1.11.2 → fal-1.11.4}/tests/test_stability.py +4 -2
  18. fal-1.11.2/src/fal/cli/runners.py +0 -43
  19. {fal-1.11.2 → fal-1.11.4}/.gitignore +0 -0
  20. {fal-1.11.2 → fal-1.11.4}/Makefile +0 -0
  21. {fal-1.11.2 → fal-1.11.4}/README.md +0 -0
  22. {fal-1.11.2 → fal-1.11.4}/docs/conf.py +0 -0
  23. {fal-1.11.2 → fal-1.11.4}/docs/index.rst +0 -0
  24. {fal-1.11.2 → fal-1.11.4}/fal.egg-info/SOURCES.txt +0 -0
  25. {fal-1.11.2 → fal-1.11.4}/fal.egg-info/dependency_links.txt +0 -0
  26. {fal-1.11.2 → fal-1.11.4}/fal.egg-info/entry_points.txt +0 -0
  27. {fal-1.11.2 → fal-1.11.4}/fal.egg-info/top_level.txt +0 -0
  28. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/README.md +0 -0
  29. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  30. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  31. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  32. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  33. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  34. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  35. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  36. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  37. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  38. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  39. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  40. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  41. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  42. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  43. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  44. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  45. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  46. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  47. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  48. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  49. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  50. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  51. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  52. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  53. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  54. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  55. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  56. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  57. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  58. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  59. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  60. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  61. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  62. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  63. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  64. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  65. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  66. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  67. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  68. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  69. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  70. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  71. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  72. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  73. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  74. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  75. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  76. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  77. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  78. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  79. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  80. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  81. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  82. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  83. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  84. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  85. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  86. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  87. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  88. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  89. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  90. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  91. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  92. {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/pyproject.toml +0 -0
  93. {fal-1.11.2 → fal-1.11.4}/openapi_rest.config.yaml +0 -0
  94. {fal-1.11.2 → fal-1.11.4}/setup.cfg +0 -0
  95. {fal-1.11.2 → fal-1.11.4}/src/fal/__init__.py +0 -0
  96. {fal-1.11.2 → fal-1.11.4}/src/fal/__main__.py +0 -0
  97. {fal-1.11.2 → fal-1.11.4}/src/fal/_serialization.py +0 -0
  98. {fal-1.11.2 → fal-1.11.4}/src/fal/_version.py +0 -0
  99. {fal-1.11.2 → fal-1.11.4}/src/fal/apps.py +0 -0
  100. {fal-1.11.2 → fal-1.11.4}/src/fal/auth/local.py +0 -0
  101. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/__init__.py +0 -0
  102. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/_utils.py +0 -0
  103. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/api.py +0 -0
  104. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/auth.py +0 -0
  105. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/cli_nested_json.py +0 -0
  106. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/create.py +0 -0
  107. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/debug.py +0 -0
  108. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/doctor.py +0 -0
  109. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/keys.py +0 -0
  110. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/parser.py +0 -0
  111. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/profile.py +0 -0
  112. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/run.py +0 -0
  113. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/secrets.py +0 -0
  114. {fal-1.11.2 → fal-1.11.4}/src/fal/cli/teams.py +0 -0
  115. {fal-1.11.2 → fal-1.11.4}/src/fal/console/__init__.py +0 -0
  116. {fal-1.11.2 → fal-1.11.4}/src/fal/console/icons.py +0 -0
  117. {fal-1.11.2 → fal-1.11.4}/src/fal/console/ux.py +0 -0
  118. {fal-1.11.2 → fal-1.11.4}/src/fal/container.py +0 -0
  119. {fal-1.11.2 → fal-1.11.4}/src/fal/exceptions/__init__.py +0 -0
  120. {fal-1.11.2 → fal-1.11.4}/src/fal/exceptions/_base.py +0 -0
  121. {fal-1.11.2 → fal-1.11.4}/src/fal/exceptions/_cuda.py +0 -0
  122. {fal-1.11.2 → fal-1.11.4}/src/fal/exceptions/auth.py +0 -0
  123. {fal-1.11.2 → fal-1.11.4}/src/fal/files.py +0 -0
  124. {fal-1.11.2 → fal-1.11.4}/src/fal/flags.py +0 -0
  125. {fal-1.11.2 → fal-1.11.4}/src/fal/logging/__init__.py +0 -0
  126. {fal-1.11.2 → fal-1.11.4}/src/fal/logging/isolate.py +0 -0
  127. {fal-1.11.2 → fal-1.11.4}/src/fal/logging/style.py +0 -0
  128. {fal-1.11.2 → fal-1.11.4}/src/fal/logging/trace.py +0 -0
  129. {fal-1.11.2 → fal-1.11.4}/src/fal/logging/user.py +0 -0
  130. {fal-1.11.2 → fal-1.11.4}/src/fal/py.typed +0 -0
  131. {fal-1.11.2 → fal-1.11.4}/src/fal/rest_client.py +0 -0
  132. {fal-1.11.2 → fal-1.11.4}/src/fal/sync.py +0 -0
  133. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/__init__.py +0 -0
  134. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/exceptions.py +0 -0
  135. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/__init__.py +0 -0
  136. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/file.py +0 -0
  137. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/providers/fal.py +0 -0
  138. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/providers/gcp.py +0 -0
  139. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/providers/r2.py +0 -0
  140. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/providers/s3.py +0 -0
  141. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/types.py +0 -0
  142. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/__init__.py +0 -0
  143. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/image.py +0 -0
  144. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  145. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  146. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  147. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  148. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  149. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/safety_checker.py +0 -0
  150. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/optimize.py +0 -0
  151. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/types.py +0 -0
  152. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/utils/__init__.py +0 -0
  153. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/utils/download_utils.py +0 -0
  154. {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/utils/retry.py +0 -0
  155. {fal-1.11.2 → fal-1.11.4}/src/fal/utils.py +0 -0
  156. {fal-1.11.2 → fal-1.11.4}/src/fal/workflows.py +0 -0
  157. {fal-1.11.2 → fal-1.11.4}/tests/__init__.py +0 -0
  158. {fal-1.11.2 → fal-1.11.4}/tests/assets/cat.png +0 -0
  159. {fal-1.11.2 → fal-1.11.4}/tests/cli/__init__.py +0 -0
  160. {fal-1.11.2 → fal-1.11.4}/tests/cli/test_apps.py +0 -0
  161. {fal-1.11.2 → fal-1.11.4}/tests/cli/test_auth.py +0 -0
  162. {fal-1.11.2 → fal-1.11.4}/tests/cli/test_deploy.py +0 -0
  163. {fal-1.11.2 → fal-1.11.4}/tests/cli/test_keys.py +0 -0
  164. {fal-1.11.2 → fal-1.11.4}/tests/cli/test_run.py +0 -0
  165. {fal-1.11.2 → fal-1.11.4}/tests/cli/test_secrets.py +0 -0
  166. {fal-1.11.2 → fal-1.11.4}/tests/conftest.py +0 -0
  167. {fal-1.11.2 → fal-1.11.4}/tests/integration_test.py +0 -0
  168. {fal-1.11.2 → fal-1.11.4}/tests/mainify_package/__init__.py +0 -0
  169. {fal-1.11.2 → fal-1.11.4}/tests/mainify_package/impl.py +0 -0
  170. {fal-1.11.2 → fal-1.11.4}/tests/mainify_package/utils.py +0 -0
  171. {fal-1.11.2 → fal-1.11.4}/tests/mainify_target.py +0 -0
  172. {fal-1.11.2 → fal-1.11.4}/tests/toolkit/file_test.py +0 -0
  173. {fal-1.11.2 → fal-1.11.4}/tests/toolkit/image_test.py +0 -0
  174. {fal-1.11.2 → fal-1.11.4}/tests/toolkit/test_types.py +0 -0
  175. {fal-1.11.2 → fal-1.11.4}/tests/toolkit/utils/retry.py +0 -0
  176. {fal-1.11.2 → fal-1.11.4}/tools/demo_script.py +0 -0
@@ -1,17 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.11.2
3
+ Version: 1.11.4
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.17.0,>=0.16.1
9
- Requires-Dist: isolate-proto<0.8.0,>=0.7.0
9
+ Requires-Dist: isolate-proto<0.8.0,>=0.7.2
10
10
  Requires-Dist: grpcio==1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
13
13
  Requires-Dist: typing-extensions<5,>=4.7.1
14
- Requires-Dist: click<9,>=8.1.3
15
14
  Requires-Dist: structlog<23,>=22.3.0
16
15
  Requires-Dist: opentelemetry-api<2,>=1.15.0
17
16
  Requires-Dist: opentelemetry-sdk<2,>=1.15.0
@@ -22,7 +21,7 @@ Requires-Dist: rich<14,>=13.3.2
22
21
  Requires-Dist: rich_argparse
23
22
  Requires-Dist: packaging>=21.3
24
23
  Requires-Dist: pathspec<1,>=0.11.1
25
- Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
24
+ Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
26
25
  Requires-Dist: structlog>=22.0
27
26
  Requires-Dist: fastapi<1,>=0.99.1
28
27
  Requires-Dist: starlette-exporter>=0.21.0
@@ -47,6 +46,7 @@ Provides-Extra: test
47
46
  Requires-Dist: pytest<8; extra == "test"
48
47
  Requires-Dist: pytest-asyncio; extra == "test"
49
48
  Requires-Dist: pytest-xdist; extra == "test"
49
+ Requires-Dist: pytest-timeout; extra == "test"
50
50
  Requires-Dist: flaky; extra == "test"
51
51
  Requires-Dist: boto3; extra == "test"
52
52
  Provides-Extra: dev
@@ -1,17 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.11.2
3
+ Version: 1.11.4
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.17.0,>=0.16.1
9
- Requires-Dist: isolate-proto<0.8.0,>=0.7.0
9
+ Requires-Dist: isolate-proto<0.8.0,>=0.7.2
10
10
  Requires-Dist: grpcio==1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
13
13
  Requires-Dist: typing-extensions<5,>=4.7.1
14
- Requires-Dist: click<9,>=8.1.3
15
14
  Requires-Dist: structlog<23,>=22.3.0
16
15
  Requires-Dist: opentelemetry-api<2,>=1.15.0
17
16
  Requires-Dist: opentelemetry-sdk<2,>=1.15.0
@@ -22,7 +21,7 @@ Requires-Dist: rich<14,>=13.3.2
22
21
  Requires-Dist: rich_argparse
23
22
  Requires-Dist: packaging>=21.3
24
23
  Requires-Dist: pathspec<1,>=0.11.1
25
- Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
24
+ Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
26
25
  Requires-Dist: structlog>=22.0
27
26
  Requires-Dist: fastapi<1,>=0.99.1
28
27
  Requires-Dist: starlette-exporter>=0.21.0
@@ -47,6 +46,7 @@ Provides-Extra: test
47
46
  Requires-Dist: pytest<8; extra == "test"
48
47
  Requires-Dist: pytest-asyncio; extra == "test"
49
48
  Requires-Dist: pytest-xdist; extra == "test"
49
+ Requires-Dist: pytest-timeout; extra == "test"
50
50
  Requires-Dist: flaky; extra == "test"
51
51
  Requires-Dist: boto3; extra == "test"
52
52
  Provides-Extra: dev
@@ -1,10 +1,9 @@
1
1
  isolate[build]<0.17.0,>=0.16.1
2
- isolate-proto<0.8.0,>=0.7.0
2
+ isolate-proto<0.8.0,>=0.7.2
3
3
  grpcio==1.64.0
4
4
  dill==0.3.7
5
5
  cloudpickle==3.0.0
6
6
  typing-extensions<5,>=4.7.1
7
- click<9,>=8.1.3
8
7
  structlog<23,>=22.3.0
9
8
  opentelemetry-api<2,>=1.15.0
10
9
  opentelemetry-sdk<2,>=1.15.0
@@ -15,7 +14,7 @@ rich<14,>=13.3.2
15
14
  rich_argparse
16
15
  packaging>=21.3
17
16
  pathspec<1,>=0.11.1
18
- pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
17
+ pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
19
18
  structlog>=22.0
20
19
  fastapi<1,>=0.99.1
21
20
  starlette-exporter>=0.21.0
@@ -48,5 +47,6 @@ sphinx-autodoc-typehints
48
47
  pytest<8
49
48
  pytest-asyncio
50
49
  pytest-xdist
50
+ pytest-timeout
51
51
  flaky
52
52
  boto3
@@ -23,12 +23,11 @@ readme = "README.md"
23
23
  requires-python = ">=3.8"
24
24
  dependencies = [
25
25
  "isolate[build]>=0.16.1,<0.17.0",
26
- "isolate-proto>=0.7.0,<0.8.0",
26
+ "isolate-proto>=0.7.2,<0.8.0",
27
27
  "grpcio==1.64.0",
28
28
  "dill==0.3.7",
29
29
  "cloudpickle==3.0.0",
30
30
  "typing-extensions>=4.7.1,<5",
31
- "click>=8.1.3,<9",
32
31
  "structlog>=22.3.0,<23",
33
32
  "opentelemetry-api>=1.15.0,<2",
34
33
  "opentelemetry-sdk>=1.15.0,<2",
@@ -39,7 +38,7 @@ dependencies = [
39
38
  "rich_argparse",
40
39
  "packaging>=21.3",
41
40
  "pathspec>=0.11.1,<1",
42
- "pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3",
41
+ "pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11",
43
42
  # serve=True dependencies
44
43
  "structlog>=22.0",
45
44
  "fastapi>=0.99.1,<1",
@@ -71,6 +70,7 @@ test = [
71
70
  "pytest<8",
72
71
  "pytest-asyncio",
73
72
  "pytest-xdist",
73
+ "pytest-timeout",
74
74
  "flaky",
75
75
  "boto3",
76
76
  ]
@@ -93,3 +93,6 @@ known-first-party = ["fal"]
93
93
 
94
94
  [tool.ruff.lint.pyupgrade]
95
95
  keep-runtime-typing = true
96
+
97
+ [tool.pytest.ini_options]
98
+ timeout = 300
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.11.2'
21
- __version_tuple__ = version_tuple = (1, 11, 2)
20
+ __version__ = version = '1.11.4'
21
+ __version_tuple__ = version_tuple = (1, 11, 4)
@@ -1088,7 +1088,10 @@ class BaseServable:
1088
1088
  type(exc), exc, exc.__traceback__
1089
1089
  )
1090
1090
 
1091
- print(json.dumps({"traceback": "".join(formatted_exception[::-1])}))
1091
+ print(
1092
+ json.dumps({"traceback": "".join(formatted_exception[::-1])}),
1093
+ flush=True,
1094
+ )
1092
1095
 
1093
1096
  if _is_cuda_oom_exception(exc):
1094
1097
  return await cuda_out_of_memory_exception_handler(
@@ -268,7 +268,7 @@ class App(fal.api.BaseServable):
268
268
  "keep_alive": 60,
269
269
  }
270
270
  app_name: ClassVar[str]
271
- app_auth: ClassVar[Literal["private", "public", "shared"]] = "private"
271
+ app_auth: ClassVar[Literal["private", "public", "shared", None]] = None
272
272
  request_timeout: ClassVar[int | None] = None
273
273
  startup_timeout: ClassVar[int | None] = None
274
274
 
@@ -5,12 +5,11 @@ from dataclasses import dataclass, field
5
5
  from threading import Lock
6
6
  from typing import Optional
7
7
 
8
- import click
9
-
10
8
  from fal.auth import auth0, local
11
9
  from fal.config import Config
12
10
  from fal.console import console
13
11
  from fal.console.icons import CHECK_ICON
12
+ from fal.exceptions import FalServerlessException
14
13
  from fal.exceptions.auth import UnauthenticatedException
15
14
 
16
15
 
@@ -85,7 +84,7 @@ def login():
85
84
  def logout():
86
85
  refresh_token, _ = local.load_token()
87
86
  if refresh_token is None:
88
- raise click.ClickException(message="You're not logged in")
87
+ raise FalServerlessException("You're not logged in")
89
88
  auth0.revoke(refresh_token)
90
89
  with local.lock_token():
91
90
  local.delete_token()
@@ -4,12 +4,12 @@ import functools
4
4
  import time
5
5
  import warnings
6
6
 
7
- import click
8
7
  import httpx
9
8
 
10
9
  from fal.console import console
11
10
  from fal.console.icons import CHECK_ICON
12
11
  from fal.console.ux import maybe_open_browser_tab
12
+ from fal.exceptions import FalServerlessException
13
13
 
14
14
  WEBSITE_URL = "https://fal.ai"
15
15
 
@@ -55,7 +55,7 @@ def login() -> dict:
55
55
  )
56
56
 
57
57
  if device_code_response.status_code != 200:
58
- raise click.ClickException("Error generating the device code")
58
+ raise FalServerlessException("Error generating the device code")
59
59
 
60
60
  device_code_data = device_code_response.json()
61
61
  device_user_code = device_code_data["user_code"]
@@ -92,7 +92,7 @@ def login() -> dict:
92
92
 
93
93
  elif token_data["error"] not in ("authorization_pending", "slow_down"):
94
94
  status.update(spinner=None)
95
- raise click.ClickException(token_data["error_description"])
95
+ raise FalServerlessException(token_data["error_description"])
96
96
 
97
97
  else:
98
98
  time.sleep(device_code_data["interval"])
@@ -115,7 +115,7 @@ def refresh(token: str) -> dict:
115
115
 
116
116
  return token_data
117
117
  else:
118
- raise click.ClickException(token_data["error_description"])
118
+ raise FalServerlessException(token_data["error_description"])
119
119
 
120
120
 
121
121
  def revoke(token: str):
@@ -130,7 +130,7 @@ def revoke(token: str):
130
130
 
131
131
  if token_response.status_code != 200:
132
132
  token_data = token_response.json()
133
- raise click.ClickException(token_data["error_description"])
133
+ raise FalServerlessException(token_data["error_description"])
134
134
 
135
135
  _open_browser(logout_url(WEBSITE_URL), None)
136
136
 
@@ -142,7 +142,7 @@ def get_user_info(bearer_token: str) -> dict:
142
142
  )
143
143
 
144
144
  if userinfo_response.status_code != 200:
145
- raise click.ClickException(userinfo_response.content.decode("utf-8"))
145
+ raise FalServerlessException(userinfo_response.content.decode("utf-8"))
146
146
 
147
147
  return userinfo_response.json()
148
148
 
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ import fal.cli.runners as runners
6
+
5
7
  from ._utils import get_client
6
8
  from .parser import FalClientParser
7
9
 
@@ -268,59 +270,17 @@ def _add_set_rev_parser(subparsers, parents):
268
270
 
269
271
 
270
272
  def _runners(args):
271
- from rich.table import Table
272
-
273
273
  client = get_client(args.host, args.team)
274
274
  with client.connect() as connection:
275
- runners = connection.list_alias_runners(alias=args.app_name)
276
-
277
- table = Table()
278
- table.add_column("Runner ID")
279
- table.add_column("In Flight Requests")
280
- table.add_column("Missing Leases")
281
- table.add_column("Expires In")
282
- table.add_column("Uptime")
283
-
284
- for runner in runners:
285
- num_leases_with_request = len(
286
- [
287
- lease
288
- for lease in runner.external_metadata.get("leases", [])
289
- if lease.get("request_id") is not None
290
- ]
291
- )
292
-
293
- table.add_row(
294
- runner.runner_id,
295
- str(runner.in_flight_requests),
296
- str(runner.in_flight_requests - num_leases_with_request),
297
- (
298
- "N/A (active)"
299
- if runner.expiration_countdown is None
300
- else f"{runner.expiration_countdown}s"
301
- ),
302
- f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
303
- )
304
-
305
- args.console.print(f"Runners: {len(runners)}")
306
- args.console.print(table)
307
-
308
- requests_table = Table()
309
- requests_table.add_column("Runner ID")
310
- requests_table.add_column("Request ID")
311
- requests_table.add_column("Caller ID")
312
-
313
- for runner in runners:
314
- for lease in runner.external_metadata.get("leases", []):
315
- if not (req_id := lease.get("request_id")):
316
- continue
275
+ alias_runners = connection.list_alias_runners(alias=args.app_name)
317
276
 
318
- requests_table.add_row(
319
- runner.runner_id,
320
- req_id,
321
- lease.get("caller_user_id") or "",
322
- )
277
+ runners_table = runners.runners_table(alias_runners)
278
+ args.console.print(f"Runners: {len(alias_runners)}")
279
+ # Drop the alias column, which is the first column
280
+ runners_table.columns.pop(0)
281
+ args.console.print(runners_table)
323
282
 
283
+ requests_table = runners.runners_requests_table(alias_runners)
324
284
  args.console.print(f"Requests: {len(requests_table.rows)}")
325
285
  args.console.print(requests_table)
326
286
 
@@ -97,7 +97,7 @@ def _deploy_from_reference(
97
97
  )
98
98
  isolated_function = loaded.function
99
99
  app_name = app_name or loaded.app_name # type: ignore
100
- app_auth = auth or loaded.app_auth or "private"
100
+ app_auth = auth or loaded.app_auth
101
101
  deployment_strategy = deployment_strategy or "recreate"
102
102
 
103
103
  app_id = host.register(
@@ -2,7 +2,7 @@ import argparse
2
2
 
3
3
  import rich
4
4
 
5
- from fal import __version__
5
+ from fal._version import __version__
6
6
  from fal.console import console
7
7
  from fal.console.icons import CROSS_ICON
8
8
 
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List
4
+
5
+ from fal.sdk import RunnerInfo
6
+
7
+ from ._utils import get_client
8
+ from .parser import FalClientParser
9
+
10
+
11
+ def runners_table(runners: List[RunnerInfo]):
12
+ from rich.table import Table
13
+
14
+ table = Table()
15
+ table.add_column("Alias")
16
+ table.add_column("Runner ID")
17
+ table.add_column("In Flight Requests")
18
+ table.add_column("Missing Leases")
19
+ table.add_column("Expires In")
20
+ table.add_column("Uptime")
21
+ table.add_column("Revision")
22
+
23
+ for runner in runners:
24
+ num_leases_with_request = len(
25
+ [
26
+ lease
27
+ for lease in runner.external_metadata.get("leases", [])
28
+ if lease.get("request_id") is not None
29
+ ]
30
+ )
31
+
32
+ table.add_row(
33
+ runner.alias,
34
+ runner.runner_id,
35
+ str(runner.in_flight_requests),
36
+ str(runner.in_flight_requests - num_leases_with_request),
37
+ (
38
+ "N/A (active)"
39
+ if runner.expiration_countdown is None
40
+ else f"{runner.expiration_countdown}s"
41
+ ),
42
+ f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
43
+ runner.revision,
44
+ )
45
+
46
+ return table
47
+
48
+
49
+ def runners_requests_table(runners: list[RunnerInfo]):
50
+ from rich.table import Table
51
+
52
+ table = Table()
53
+ table.add_column("Runner ID")
54
+ table.add_column("Request ID")
55
+ table.add_column("Caller ID")
56
+
57
+ for runner in runners:
58
+ for lease in runner.external_metadata.get("leases", []):
59
+ if not (req_id := lease.get("request_id")):
60
+ continue
61
+
62
+ table.add_row(
63
+ runner.runner_id,
64
+ req_id,
65
+ lease.get("caller_user_id") or "",
66
+ )
67
+
68
+ return table
69
+
70
+
71
+ def _kill(args):
72
+ client = get_client(args.host, args.team)
73
+ with client.connect() as connection:
74
+ connection.kill_runner(args.id)
75
+
76
+
77
+ def _list(args):
78
+ client = get_client(args.host, args.team)
79
+ with client.connect() as connection:
80
+ runners = connection.list_runners()
81
+ args.console.print(f"Runners: {len(runners)}")
82
+ args.console.print(runners_table(runners))
83
+
84
+ requests_table = runners_requests_table(runners)
85
+ args.console.print(f"Requests: {len(requests_table.rows)}")
86
+ args.console.print(requests_table)
87
+
88
+
89
+ def _add_kill_parser(subparsers, parents):
90
+ kill_help = "Kill a runner."
91
+ parser = subparsers.add_parser(
92
+ "kill",
93
+ description=kill_help,
94
+ help=kill_help,
95
+ parents=parents,
96
+ )
97
+ parser.add_argument(
98
+ "id",
99
+ help="Runner ID.",
100
+ )
101
+ parser.set_defaults(func=_kill)
102
+
103
+
104
+ def _add_list_parser(subparsers, parents):
105
+ list_help = "List runners."
106
+ parser = subparsers.add_parser(
107
+ "list",
108
+ description=list_help,
109
+ help=list_help,
110
+ parents=parents,
111
+ )
112
+ parser.set_defaults(func=_list)
113
+
114
+
115
+ def add_parser(main_subparsers, parents):
116
+ runners_help = "Manage fal runners."
117
+ parser = main_subparsers.add_parser(
118
+ "runners",
119
+ description=runners_help,
120
+ help=runners_help,
121
+ parents=parents,
122
+ aliases=["machine"], # backwards compatibility
123
+ )
124
+
125
+ subparsers = parser.add_subparsers(
126
+ title="Commands",
127
+ metavar="command",
128
+ required=True,
129
+ parser_class=FalClientParser,
130
+ )
131
+
132
+ _add_kill_parser(subparsers, parents)
133
+ _add_list_parser(subparsers, parents)
@@ -72,7 +72,7 @@ class Config:
72
72
  if not self.profile:
73
73
  raise ValueError("No profile set.")
74
74
 
75
- del self._config[self.profile][key]
75
+ self._config.get(self.profile, {}).pop(key, None)
76
76
 
77
77
  def get_internal(self, key: str) -> Optional[str]:
78
78
  if SETTINGS_SECTION not in self._config:
@@ -246,6 +246,8 @@ class RunnerInfo:
246
246
  expiration_countdown: Optional[int]
247
247
  uptime: timedelta
248
248
  external_metadata: dict[str, Any]
249
+ revision: str
250
+ alias: str
249
251
 
250
252
 
251
253
  @dataclass
@@ -392,6 +394,8 @@ def _from_grpc_runner_info(message: isolate_proto.RunnerInfo) -> RunnerInfo:
392
394
  ),
393
395
  uptime=timedelta(seconds=message.uptime),
394
396
  external_metadata=external_metadata,
397
+ revision=message.revision,
398
+ alias=message.alias,
395
399
  )
396
400
 
397
401
 
@@ -571,11 +575,12 @@ class FalServerlessConnection:
571
575
  else:
572
576
  wrapped_requirements = None
573
577
 
578
+ auth_mode = None
574
579
  if application_auth_mode == "public":
575
580
  auth_mode = isolate_proto.ApplicationAuthMode.PUBLIC
576
581
  elif application_auth_mode == "shared":
577
582
  auth_mode = isolate_proto.ApplicationAuthMode.SHARED
578
- else:
583
+ elif application_auth_mode == "private":
579
584
  auth_mode = isolate_proto.ApplicationAuthMode.PRIVATE
580
585
 
581
586
  struct_metadata = None
@@ -750,3 +755,8 @@ class FalServerlessConnection:
750
755
  def kill_runner(self, runner_id: str) -> None:
751
756
  request = isolate_proto.KillRunnerRequest(runner_id=runner_id)
752
757
  self.stub.KillRunner(request)
758
+
759
+ def list_runners(self) -> list[RunnerInfo]:
760
+ request = isolate_proto.ListRunnersRequest()
761
+ response = self.stub.ListRunners(request)
762
+ return [from_grpc(runner) for runner in response.runners]
@@ -576,6 +576,41 @@ def test_app_client_async():
576
576
  assert result == {"slept": True}
577
577
 
578
578
 
579
+ # If the logging subsystem is not working for some nodes, this test will flake
580
+ @pytest.mark.flaky(max_runs=10)
581
+ def test_traceback_logs(test_exception_app: AppClient):
582
+ date = (datetime.utcnow() - timedelta(seconds=1)).isoformat()
583
+
584
+ with pytest.raises(AppClientError):
585
+ test_exception_app.fail({})
586
+
587
+ with httpx.Client(
588
+ base_url=REST_CLIENT.base_url,
589
+ headers=REST_CLIENT.get_headers(),
590
+ timeout=300,
591
+ ) as client:
592
+ # Give some time for logs to propagate through the logging subsystem.
593
+ for _ in range(10):
594
+ time.sleep(2)
595
+ response = client.get(
596
+ REST_CLIENT.base_url + f"/logs/?traceback=true&since={date}"
597
+ )
598
+
599
+ logs = response.json()
600
+ if len(logs) > 0:
601
+ break
602
+
603
+ assert len(logs) > 0
604
+ for log in logs:
605
+ assert log["message"].count("\n") > 1, "Logs should be multi-line"
606
+ assert (
607
+ '{"traceback":' not in log["message"]
608
+ ), "Logs should not be JSON-wrapped"
609
+ assert (
610
+ "this app is designed to fail" in log["message"]
611
+ ), "Logs should contain the traceback message"
612
+
613
+
579
614
  def test_app_openapi_spec_metadata(test_app: str, request: pytest.FixtureRequest):
580
615
  user_id, _, app_id = test_app.partition("/")
581
616
  res = app_metadata.sync_detailed(
@@ -617,6 +652,18 @@ def test_404_response(test_app: str, request: pytest.FixtureRequest):
617
652
  apps.run(test_app, path="/other", arguments={"lhs": 1, "rhs": 2})
618
653
 
619
654
 
655
+ def test_app_no_auth():
656
+ # This will just pass for users with shared apps access
657
+ app_alias = str(uuid.uuid4()) + "-alias"
658
+ with pytest.raises(api.FalServerlessError, match="Must specify auth_mode"):
659
+ addition_app.host.register(
660
+ func=addition_app.func,
661
+ options=addition_app.options,
662
+ # random enough
663
+ application_name=app_alias,
664
+ )
665
+
666
+
620
667
  def test_app_deploy_scale(aliased_app: Tuple[str, str]):
621
668
  from dataclasses import replace
622
669
 
@@ -658,6 +705,8 @@ def test_app_deploy_scale(aliased_app: Tuple[str, str]):
658
705
  assert found.max_multiplexing == 30
659
706
 
660
707
 
708
+ # List aliases is taking long
709
+ @pytest.mark.timeout(600)
661
710
  def test_app_update_app(aliased_app: Tuple[str, str]):
662
711
  app_revision, app_alias = aliased_app
663
712
 
@@ -708,6 +757,8 @@ def test_app_update_app(aliased_app: Tuple[str, str]):
708
757
  assert res.max_multiplexing == new_max_multiplexing
709
758
 
710
759
 
760
+ # List aliases is taking long
761
+ @pytest.mark.timeout(600)
711
762
  def test_app_set_delete_alias(aliased_app: Tuple[str, str]):
712
763
  app_revision, app_alias = aliased_app
713
764
 
@@ -831,41 +882,6 @@ def test_workflows(test_app: str):
831
882
  assert data["result"] == 10
832
883
 
833
884
 
834
- # If the logging subsystem is not working for some nodes, this test will flake
835
- @pytest.mark.flaky(max_runs=5)
836
- def test_traceback_logs(test_exception_app: AppClient):
837
- date = (datetime.utcnow() - timedelta(seconds=1)).isoformat()
838
-
839
- with pytest.raises(AppClientError):
840
- test_exception_app.fail({})
841
-
842
- with httpx.Client(
843
- base_url=REST_CLIENT.base_url,
844
- headers=REST_CLIENT.get_headers(),
845
- timeout=300,
846
- ) as client:
847
- # Give some time for logs to propagate through the logging subsystem.
848
- for _ in range(10):
849
- time.sleep(2)
850
- response = client.get(
851
- REST_CLIENT.base_url + f"/logs/?traceback=true&since={date}"
852
- )
853
-
854
- logs = response.json()
855
- if len(logs) > 0:
856
- break
857
-
858
- assert len(logs) > 0
859
- for log in logs:
860
- assert log["message"].count("\n") > 1, "Logs should be multi-line"
861
- assert (
862
- '{"traceback":' not in log["message"]
863
- ), "Logs should not be JSON-wrapped"
864
- assert (
865
- "this app is designed to fail" in log["message"]
866
- ), "Logs should contain the traceback message"
867
-
868
-
869
885
  def test_app_exceptions(test_exception_app: AppClient):
870
886
  with pytest.raises(AppClientError) as app_exc:
871
887
  test_exception_app.app_exception({})
@@ -559,6 +559,8 @@ def test_serve_on_off(isolated_client):
559
559
 
560
560
 
561
561
  def test_worker_env_vars(isolated_client):
562
+ from fal.flags import GRPC_HOST
563
+
562
564
  @isolated_client("virtualenv", keep_alive=5)
563
565
  def get_env_var(name: str) -> str | None:
564
566
  import os
@@ -567,8 +569,8 @@ def test_worker_env_vars(isolated_client):
567
569
 
568
570
  fal_host = get_env_var("FAL_HOST")
569
571
  assert fal_host, "FAL_HOST is not set"
570
- assert fal_host.startswith("api.")
571
- assert fal_host.endswith(".shark.fal.ai")
572
+ assert fal_host == GRPC_HOST, "FAL_HOST is not set to the correct value"
573
+ print(f"FAL_HOST: {fal_host}, GRPC_HOST: {GRPC_HOST}")
572
574
 
573
575
  fal_key_id = get_env_var("FAL_KEY_ID")
574
576
  assert fal_key_id, "FAL_KEY_ID is not set"