fal 1.13.3__tar.gz → 1.13.5__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 (175) hide show
  1. {fal-1.13.3/fal.egg-info → fal-1.13.5}/PKG-INFO +1 -1
  2. {fal-1.13.3 → fal-1.13.5/fal.egg-info}/PKG-INFO +1 -1
  3. {fal-1.13.3 → fal-1.13.5}/src/fal/_fal_version.py +2 -2
  4. {fal-1.13.3 → fal-1.13.5}/src/fal/api.py +5 -0
  5. {fal-1.13.3 → fal-1.13.5}/src/fal/app.py +19 -1
  6. {fal-1.13.3 → fal-1.13.5}/tests/test_apps.py +109 -126
  7. {fal-1.13.3 → fal-1.13.5}/.gitignore +0 -0
  8. {fal-1.13.3 → fal-1.13.5}/Makefile +0 -0
  9. {fal-1.13.3 → fal-1.13.5}/README.md +0 -0
  10. {fal-1.13.3 → fal-1.13.5}/docs/conf.py +0 -0
  11. {fal-1.13.3 → fal-1.13.5}/docs/index.rst +0 -0
  12. {fal-1.13.3 → fal-1.13.5}/fal.egg-info/SOURCES.txt +0 -0
  13. {fal-1.13.3 → fal-1.13.5}/fal.egg-info/dependency_links.txt +0 -0
  14. {fal-1.13.3 → fal-1.13.5}/fal.egg-info/entry_points.txt +0 -0
  15. {fal-1.13.3 → fal-1.13.5}/fal.egg-info/requires.txt +0 -0
  16. {fal-1.13.3 → fal-1.13.5}/fal.egg-info/top_level.txt +0 -0
  17. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/README.md +0 -0
  18. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  19. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  20. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  21. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  22. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  23. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  24. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  25. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  26. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  27. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  28. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  29. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  30. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  31. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  32. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  33. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  34. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  35. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  36. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  37. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  38. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  39. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  40. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  41. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  42. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  43. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  44. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  45. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  46. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  47. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  48. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  49. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  50. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  51. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  52. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  53. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  54. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  55. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  56. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  57. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  58. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  59. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  60. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  61. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  62. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  63. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  64. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  65. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  66. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  67. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  68. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  69. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  70. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  71. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  72. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  73. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  74. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  75. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  76. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  77. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  78. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  79. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  80. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  81. {fal-1.13.3 → fal-1.13.5}/openapi-fal-rest/pyproject.toml +0 -0
  82. {fal-1.13.3 → fal-1.13.5}/openapi_rest.config.yaml +0 -0
  83. {fal-1.13.3 → fal-1.13.5}/pyproject.toml +0 -0
  84. {fal-1.13.3 → fal-1.13.5}/setup.cfg +0 -0
  85. {fal-1.13.3 → fal-1.13.5}/src/fal/__init__.py +0 -0
  86. {fal-1.13.3 → fal-1.13.5}/src/fal/__main__.py +0 -0
  87. {fal-1.13.3 → fal-1.13.5}/src/fal/_serialization.py +0 -0
  88. {fal-1.13.3 → fal-1.13.5}/src/fal/_version.py +0 -0
  89. {fal-1.13.3 → fal-1.13.5}/src/fal/apps.py +0 -0
  90. {fal-1.13.3 → fal-1.13.5}/src/fal/auth/__init__.py +0 -0
  91. {fal-1.13.3 → fal-1.13.5}/src/fal/auth/auth0.py +0 -0
  92. {fal-1.13.3 → fal-1.13.5}/src/fal/auth/local.py +0 -0
  93. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/__init__.py +0 -0
  94. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/_utils.py +0 -0
  95. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/api.py +0 -0
  96. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/apps.py +0 -0
  97. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/auth.py +0 -0
  98. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/cli_nested_json.py +0 -0
  99. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/create.py +0 -0
  100. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/debug.py +0 -0
  101. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/deploy.py +0 -0
  102. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/doctor.py +0 -0
  103. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/keys.py +0 -0
  104. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/main.py +0 -0
  105. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/parser.py +0 -0
  106. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/profile.py +0 -0
  107. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/run.py +0 -0
  108. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/runners.py +0 -0
  109. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/secrets.py +0 -0
  110. {fal-1.13.3 → fal-1.13.5}/src/fal/cli/teams.py +0 -0
  111. {fal-1.13.3 → fal-1.13.5}/src/fal/config.py +0 -0
  112. {fal-1.13.3 → fal-1.13.5}/src/fal/console/__init__.py +0 -0
  113. {fal-1.13.3 → fal-1.13.5}/src/fal/console/icons.py +0 -0
  114. {fal-1.13.3 → fal-1.13.5}/src/fal/console/ux.py +0 -0
  115. {fal-1.13.3 → fal-1.13.5}/src/fal/container.py +0 -0
  116. {fal-1.13.3 → fal-1.13.5}/src/fal/exceptions/__init__.py +0 -0
  117. {fal-1.13.3 → fal-1.13.5}/src/fal/exceptions/_base.py +0 -0
  118. {fal-1.13.3 → fal-1.13.5}/src/fal/exceptions/_cuda.py +0 -0
  119. {fal-1.13.3 → fal-1.13.5}/src/fal/exceptions/auth.py +0 -0
  120. {fal-1.13.3 → fal-1.13.5}/src/fal/files.py +0 -0
  121. {fal-1.13.3 → fal-1.13.5}/src/fal/flags.py +0 -0
  122. {fal-1.13.3 → fal-1.13.5}/src/fal/logging/__init__.py +0 -0
  123. {fal-1.13.3 → fal-1.13.5}/src/fal/logging/isolate.py +0 -0
  124. {fal-1.13.3 → fal-1.13.5}/src/fal/logging/style.py +0 -0
  125. {fal-1.13.3 → fal-1.13.5}/src/fal/logging/trace.py +0 -0
  126. {fal-1.13.3 → fal-1.13.5}/src/fal/logging/user.py +0 -0
  127. {fal-1.13.3 → fal-1.13.5}/src/fal/py.typed +0 -0
  128. {fal-1.13.3 → fal-1.13.5}/src/fal/rest_client.py +0 -0
  129. {fal-1.13.3 → fal-1.13.5}/src/fal/sdk.py +0 -0
  130. {fal-1.13.3 → fal-1.13.5}/src/fal/sync.py +0 -0
  131. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/__init__.py +0 -0
  132. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/exceptions.py +0 -0
  133. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/file/__init__.py +0 -0
  134. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/file/file.py +0 -0
  135. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/file/providers/fal.py +0 -0
  136. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/file/providers/gcp.py +0 -0
  137. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/file/providers/r2.py +0 -0
  138. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/file/providers/s3.py +0 -0
  139. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/file/types.py +0 -0
  140. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/image/__init__.py +0 -0
  141. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/image/image.py +0 -0
  142. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  143. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  144. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  145. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  146. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  147. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/image/safety_checker.py +0 -0
  148. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/optimize.py +0 -0
  149. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/types.py +0 -0
  150. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/utils/__init__.py +0 -0
  151. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/utils/download_utils.py +0 -0
  152. {fal-1.13.3 → fal-1.13.5}/src/fal/toolkit/utils/retry.py +0 -0
  153. {fal-1.13.3 → fal-1.13.5}/src/fal/utils.py +0 -0
  154. {fal-1.13.3 → fal-1.13.5}/src/fal/workflows.py +0 -0
  155. {fal-1.13.3 → fal-1.13.5}/tests/__init__.py +0 -0
  156. {fal-1.13.3 → fal-1.13.5}/tests/assets/cat.png +0 -0
  157. {fal-1.13.3 → fal-1.13.5}/tests/cli/__init__.py +0 -0
  158. {fal-1.13.3 → fal-1.13.5}/tests/cli/test_apps.py +0 -0
  159. {fal-1.13.3 → fal-1.13.5}/tests/cli/test_auth.py +0 -0
  160. {fal-1.13.3 → fal-1.13.5}/tests/cli/test_deploy.py +0 -0
  161. {fal-1.13.3 → fal-1.13.5}/tests/cli/test_keys.py +0 -0
  162. {fal-1.13.3 → fal-1.13.5}/tests/cli/test_run.py +0 -0
  163. {fal-1.13.3 → fal-1.13.5}/tests/cli/test_secrets.py +0 -0
  164. {fal-1.13.3 → fal-1.13.5}/tests/conftest.py +0 -0
  165. {fal-1.13.3 → fal-1.13.5}/tests/integration_test.py +0 -0
  166. {fal-1.13.3 → fal-1.13.5}/tests/mainify_package/__init__.py +0 -0
  167. {fal-1.13.3 → fal-1.13.5}/tests/mainify_package/impl.py +0 -0
  168. {fal-1.13.3 → fal-1.13.5}/tests/mainify_package/utils.py +0 -0
  169. {fal-1.13.3 → fal-1.13.5}/tests/mainify_target.py +0 -0
  170. {fal-1.13.3 → fal-1.13.5}/tests/test_stability.py +0 -0
  171. {fal-1.13.3 → fal-1.13.5}/tests/toolkit/file_test.py +0 -0
  172. {fal-1.13.3 → fal-1.13.5}/tests/toolkit/image_test.py +0 -0
  173. {fal-1.13.3 → fal-1.13.5}/tests/toolkit/test_types.py +0 -0
  174. {fal-1.13.3 → fal-1.13.5}/tests/toolkit/utils/retry.py +0 -0
  175. {fal-1.13.3 → fal-1.13.5}/tools/demo_script.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.13.3
3
+ Version: 1.13.5
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.13.3
3
+ Version: 1.13.5
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
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.13.3'
21
- __version_tuple__ = version_tuple = (1, 13, 3)
20
+ __version__ = version = '1.13.5'
21
+ __version_tuple__ = version_tuple = (1, 13, 5)
@@ -1119,6 +1119,11 @@ class BaseServable:
1119
1119
  request, CUDAOutOfMemoryException()
1120
1120
  )
1121
1121
 
1122
+ # last line of defense against misc GPU errors that could indicate a bad
1123
+ # worker
1124
+ if any(marker in str(exc).lower() for marker in ["cuda", "cudnn", "nvml"]):
1125
+ return JSONResponse({"detail": "GPU error"}, 503)
1126
+
1122
1127
  return JSONResponse({"detail": "Internal Server Error"}, 500)
1123
1128
 
1124
1129
  routes = self.collect_routes()
@@ -345,7 +345,25 @@ class App(BaseServable):
345
345
  async def provide_hints_headers(request, call_next):
346
346
  response = await call_next(request)
347
347
  try:
348
- response.headers["X-Fal-Runner-Hints"] = ",".join(self.provide_hints())
348
+ # make sure the hints can be encoded in latin-1, so we don't crash
349
+ # when serving.
350
+ # https://github.com/encode/starlette/blob/a766a58d14007f07c0b5782fa78cdc370b892796/starlette/datastructures.py#L568
351
+ hints = []
352
+ for hint in self.provide_hints():
353
+ try:
354
+ _ = hint.encode("latin-1")
355
+ hints.append(hint)
356
+ except UnicodeEncodeError:
357
+ from fastapi.logger import logger
358
+
359
+ logger.warning(
360
+ "Ignoring hint %s for %s because it can't be encoded in "
361
+ "latin-1",
362
+ hint,
363
+ self.__class__.__name__,
364
+ )
365
+
366
+ response.headers["X-Fal-Runner-Hints"] = ",".join(hints)
349
367
  except NotImplementedError:
350
368
  # This lets us differentiate between apps that don't provide hints
351
369
  # and apps that provide empty hints.
@@ -21,7 +21,7 @@ import fal
21
21
  import fal.api as api
22
22
  from fal import apps
23
23
  from fal.app import AppClient, AppClientError
24
- from fal.cli.deploy import _get_user
24
+ from fal.cli.deploy import User, _get_user
25
25
  from fal.container import ContainerImage
26
26
  from fal.exceptions import AppException, FieldException, RequestCancelledException
27
27
  from fal.exceptions._cuda import _CUDA_OOM_MESSAGE, _CUDA_OOM_STATUS_CODE
@@ -302,126 +302,106 @@ class RealtimeApp(fal.App, keep_alive=300, max_concurrency=1):
302
302
 
303
303
 
304
304
  @pytest.fixture(scope="module")
305
- def aliased_app() -> Generator[Tuple[str, str], None, None]:
306
- # Create a temporary app, register it, and return the ID of it.
305
+ def host() -> Generator[api.FalServerlessHost, None, None]:
306
+ yield addition_app.host
307
307
 
308
- app_alias = str(uuid.uuid4()) + "-alias"
309
- app_revision = addition_app.host.register(
310
- func=addition_app.func,
311
- options=addition_app.options,
312
- # random enough
308
+
309
+ @pytest.fixture(scope="module")
310
+ def user() -> Generator[User, None, None]:
311
+ user = _get_user()
312
+ yield user
313
+
314
+
315
+ @contextmanager
316
+ def register_app(host: api.FalServerlessHost, app: fal.App, suffix: str = ""):
317
+ app_alias = str(uuid.uuid4()) + "-test-alias" + ("-" + suffix if suffix else "")
318
+ app_revision = host.register(
319
+ func=app.func,
320
+ options=app.options,
313
321
  application_name=app_alias,
314
322
  application_auth_mode="private",
315
323
  )
316
- yield app_revision, app_alias # type: ignore
324
+ try:
325
+ yield app_alias, app_revision
326
+ finally:
327
+ with host._connection as client:
328
+ client.delete_alias(app_alias)
317
329
 
318
330
 
319
331
  @pytest.fixture(scope="module")
320
- def test_app():
321
- # Create a temporary app, register it, and return the ID of it.
322
-
323
- app_revision = addition_app.host.register(
324
- func=addition_app.func,
325
- options=addition_app.options,
326
- )
327
- user = _get_user()
328
- yield f"{user.user_id}/{app_revision}"
332
+ def base_app(host: api.FalServerlessHost):
333
+ # running apps without aliases is no longer supported
334
+ # so we need to create an alias for the app
335
+ with register_app(host, addition_app) as (app_alias, app_revision):
336
+ yield app_alias, app_revision
329
337
 
330
338
 
331
339
  @pytest.fixture(scope="module")
332
- def test_nomad_app():
333
- # Create a temporary app, register it, and return the ID of it.
334
-
335
- app_revision = nomad_addition_app.host.register(
336
- func=nomad_addition_app.func,
337
- options=nomad_addition_app.options,
338
- )
339
- user = _get_user()
340
- yield f"{user.user_id}/{app_revision}"
340
+ def aliased_app(base_app: Tuple[str, str, str]):
341
+ yield base_app[1], base_app[0]
341
342
 
342
343
 
343
344
  @pytest.fixture(scope="module")
344
- def test_container_app():
345
- # Create a temporary app, register it, and return the ID of it.
346
-
347
- app_revision = container_addition_app.host.register(
348
- func=container_addition_app.func,
349
- options=container_addition_app.options,
350
- )
351
- user = _get_user()
352
- yield f"{user.user_id}/{app_revision}"
345
+ def test_app(base_app: Tuple[str, str, str], user: User):
346
+ yield f"{user.username}/{base_app[1]}"
353
347
 
354
348
 
355
349
  @pytest.fixture(scope="module")
356
- def test_container_build_args_app():
357
- # Create a temporary app, register it, and return the ID of it.
358
-
359
- app_revision = container_build_args_app.host.register(
360
- func=container_build_args_app.func,
361
- options=container_build_args_app.options,
362
- )
363
- user = _get_user()
364
- yield f"{user.user_id}/{app_revision}"
350
+ def test_nomad_app(host: api.FalServerlessHost, user: User):
351
+ with register_app(host, nomad_addition_app) as (app_alias, _app_revision):
352
+ yield f"{user.username}/{app_alias}"
365
353
 
366
354
 
367
355
  @pytest.fixture(scope="module")
368
- def test_fastapi_app():
369
- # Create a temporary app, register it, and return the ID of it.
370
-
371
- app_revision = calculator_app.host.register(
372
- func=calculator_app.func,
373
- options=calculator_app.options,
374
- )
375
- user = _get_user()
376
- yield f"{user.user_id}/{app_revision}"
356
+ def test_container_app(host: api.FalServerlessHost, user: User):
357
+ with register_app(host, container_addition_app) as (app_alias, _app_revision):
358
+ yield f"{user.username}/{app_alias}"
377
359
 
378
360
 
379
361
  @pytest.fixture(scope="module")
380
- def test_stateful_app():
381
- # Create a temporary app, register it, and return the ID of it.
362
+ def test_container_build_args_app(host: api.FalServerlessHost, user: User):
363
+ with register_app(host, container_build_args_app) as (app_alias, _app_revision):
364
+ yield f"{user.username}/{app_alias}"
382
365
 
383
- app = fal.wrap_app(StatefulAdditionApp)
384
- app_revision = app.host.register(
385
- func=app.func,
386
- options=app.options,
387
- )
388
- user = _get_user()
389
- yield f"{user.user_id}/{app_revision}"
366
+
367
+ @pytest.fixture(scope="module")
368
+ def test_fastapi_app(host: api.FalServerlessHost, user: User):
369
+ with register_app(host, calculator_app) as (app_alias, _app_revision):
370
+ yield f"{user.username}/{app_alias}"
390
371
 
391
372
 
392
373
  @pytest.fixture(scope="module")
393
- def test_cancellable_app():
394
- # Create a temporary app, register it, and return the ID of it.
374
+ def test_stateful_app(host: api.FalServerlessHost, user: User):
375
+ stateful_app = fal.wrap_app(StatefulAdditionApp)
376
+ with register_app(host, stateful_app) as (app_alias, _app_revision):
377
+ yield f"{user.username}/{app_alias}"
395
378
 
396
- app = fal.wrap_app(CancellableApp)
397
- app_revision = app.host.register(
398
- func=app.func,
399
- options=app.options,
400
- application_auth_mode="public",
401
- )
402
- user = _get_user()
403
- yield f"{user.user_id}/{app_revision}"
379
+
380
+ @pytest.fixture(scope="module")
381
+ def test_cancellable_app(host: api.FalServerlessHost, user: User):
382
+ cancellable_app = fal.wrap_app(CancellableApp)
383
+ with register_app(host, cancellable_app) as (app_alias, _app_revision):
384
+ yield f"{user.username}/{app_alias}"
404
385
 
405
386
 
406
387
  @pytest.fixture(scope="module")
407
388
  def test_exception_app():
408
- # Create a temporary app, register it, and return the ID of it.
409
389
  with AppClient.connect(ExceptionApp) as client:
410
390
  yield client
411
391
 
412
392
 
413
393
  @pytest.fixture(scope="module")
414
- def test_realtime_app():
415
- # Create a temporary app, register it, and return the ID of it.
394
+ def test_sleep_app(host: api.FalServerlessHost, user: User):
395
+ sleep_app = fal.wrap_app(SleepApp)
396
+ with register_app(host, sleep_app) as (app_alias, _app_revision):
397
+ yield f"{user.username}/{app_alias}"
416
398
 
417
- app = fal.wrap_app(RealtimeApp)
418
- app_revision = app.host.register(
419
- func=app.func,
420
- options=app.options,
421
- application_auth_mode="public",
422
- )
423
- user = _get_user()
424
- yield f"{user.user_id}/{app_revision}"
399
+
400
+ @pytest.fixture(scope="module")
401
+ def test_realtime_app(host: api.FalServerlessHost, user: User):
402
+ realtime_app = fal.wrap_app(RealtimeApp)
403
+ with register_app(host, realtime_app) as (app_alias, _app_revision):
404
+ yield f"{user.username}/{app_alias}"
425
405
 
426
406
 
427
407
  def test_app_client(test_app: str, test_nomad_app: str):
@@ -453,13 +433,9 @@ def test_ws_client(test_app: str):
453
433
  assert response["result"] == 2 + i
454
434
 
455
435
 
456
- def test_app_client_old_format(test_app: str):
457
- assert test_app.count("/") == 1, "Test app should be in new format"
458
- old_format = test_app.replace("/", "-")
459
- assert test_app.count("-") + 1 == old_format.count(
460
- "-"
461
- ), "Old format should have one more hyphen"
462
-
436
+ def test_app_client_old_format(base_app: Tuple[str, str], user: User):
437
+ app_alias, _ = base_app
438
+ old_format = f"{user.user_id}-{app_alias}"
463
439
  response = apps.run(old_format, arguments={"lhs": 1, "rhs": 2})
464
440
  assert response["result"] == 3
465
441
 
@@ -533,30 +509,19 @@ def test_app_cancellation(test_app: str, test_cancellable_app: str):
533
509
 
534
510
 
535
511
  @pytest.mark.flaky(max_runs=3)
536
- def test_app_client_async():
537
- app_alias = str(uuid.uuid4()) + "-client-async-alias"
538
-
539
- app = fal.wrap_app(SleepApp)
540
- app_revision = app.host.register(
541
- func=app.func,
542
- options=app.options,
543
- application_name=app_alias,
544
- application_auth_mode="private",
545
- )
546
-
547
- user = _get_user()
548
-
549
- handle = apps.submit(f"{user.user_id}/{app_revision}", arguments={"wait_time": 1})
512
+ def test_app_client_async(test_sleep_app: str):
513
+ handle = apps.submit(test_sleep_app, arguments={"wait_time": 1})
550
514
  with pytest.raises(HTTPStatusError) as e:
551
515
  # Not yet completed
552
516
  handle.fetch_result()
517
+
553
518
  assert e.value.response.status_code == 400
554
519
 
555
520
  # Wait until the app is completed
556
521
  assert handle.get() == {"slept": True}
557
522
 
558
523
  # New request
559
- handle = apps.submit(f"{user.user_id}/{app_revision}", arguments={"wait_time": 5})
524
+ handle = apps.submit(test_sleep_app, arguments={"wait_time": 5})
560
525
 
561
526
  for event in handle.iter_events(logs=True):
562
527
  assert isinstance(event, (apps.Queued, apps.InProgress))
@@ -673,7 +638,7 @@ def test_app_no_auth():
673
638
  )
674
639
 
675
640
 
676
- def test_app_deploy_scale(aliased_app: Tuple[str, str]):
641
+ def test_app_deploy_scale(host: api.FalServerlessHost):
677
642
  from dataclasses import replace
678
643
 
679
644
  app_alias = str(uuid.uuid4()) + "-alias"
@@ -684,7 +649,6 @@ def test_app_deploy_scale(aliased_app: Tuple[str, str]):
684
649
  application_auth_mode="private",
685
650
  )
686
651
 
687
- host: api.FalServerlessHost = addition_app.host # type: ignore
688
652
  options = replace(
689
653
  addition_app.options, host={**addition_app.options.host, "max_multiplexing": 30}
690
654
  )
@@ -921,21 +885,8 @@ def test_app_exceptions(test_exception_app: AppClient):
921
885
  assert _CUDA_OOM_MESSAGE in cuda_exc.value.message
922
886
 
923
887
 
924
- def test_kill_runner():
925
- app_alias = str(uuid.uuid4()) + "-sleep-alias"
926
- app = fal.wrap_app(SleepApp)
927
- app_revision = app.host.register(
928
- func=app.func,
929
- options=app.options,
930
- application_name=app_alias,
931
- application_auth_mode="private",
932
- )
933
-
934
- host: api.FalServerlessHost = app.host # type: ignore
935
-
936
- user = _get_user()
937
-
938
- handle = apps.submit(f"{user.user_id}/{app_revision}", arguments={"wait_time": 10})
888
+ def test_kill_runner(host: api.FalServerlessHost, test_sleep_app: str):
889
+ handle = apps.submit(test_sleep_app, arguments={"wait_time": 10})
939
890
 
940
891
  while True:
941
892
  status = handle.status()
@@ -947,16 +898,20 @@ def test_kill_runner():
947
898
  raise Exception(f"Failed to start the app: {status}")
948
899
 
949
900
  with host._connection as client:
950
- try:
901
+ with pytest.raises(Exception) as e:
951
902
  client.kill_runner("1234567890")
952
- except Exception as e:
953
- assert "not found" in str(e).lower()
954
903
 
904
+ assert "not found" in str(e).lower()
905
+
906
+ _, _, app_alias = test_sleep_app.partition("/")
955
907
  runners = client.list_alias_runners(app_alias)
956
908
  assert len(runners) == 1
957
909
 
958
910
  client.kill_runner(runners[0].runner_id)
959
911
 
912
+ runners = client.list_alias_runners(app_alias)
913
+ assert len(runners) == 0
914
+
960
915
 
961
916
  def test_container_app_client(test_container_app: str):
962
917
  response = apps.run(test_container_app, arguments={"lhs": 1, "rhs": 2})
@@ -966,3 +921,31 @@ def test_container_app_client(test_container_app: str):
966
921
  def test_container_build_args_app_client(test_container_build_args_app: str):
967
922
  response = apps.run(test_container_build_args_app, {})
968
923
  assert response == "built with build args"
924
+
925
+
926
+ class HintsApp(fal.App, keep_alive=300, max_concurrency=1):
927
+ machine_type = "S"
928
+
929
+ def provide_hints(self) -> List[str]:
930
+ return ["é", "😀"]
931
+
932
+ @fal.endpoint("/add")
933
+ def add(self, input: Input) -> Output:
934
+ return Output(result=input.lhs + input.rhs)
935
+
936
+
937
+ def test_hints_encoding():
938
+ """
939
+ Make sure that hints that can't be encoded in latin-1 don't crash the app
940
+ https://github.com/encode/starlette/blob/a766a58d14007f07c0b5782fa78cdc370b892796/starlette/datastructures.py#L568
941
+ """
942
+ with AppClient.connect(HintsApp) as client:
943
+ with httpx.Client() as httpx_client:
944
+ url = client.url + "/add"
945
+ resp = httpx_client.post(
946
+ url,
947
+ json={"lhs": 1, "rhs": 2},
948
+ timeout=30,
949
+ )
950
+ assert resp.is_success
951
+ assert resp.json()["result"] == 3
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
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
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
File without changes
File without changes
File without changes