fal 1.21.1__tar.gz → 1.23.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 (183) hide show
  1. {fal-1.21.1/fal.egg-info → fal-1.23.0}/PKG-INFO +1 -1
  2. {fal-1.21.1 → fal-1.23.0/fal.egg-info}/PKG-INFO +1 -1
  3. {fal-1.21.1 → fal-1.23.0}/src/fal/_fal_version.py +2 -2
  4. {fal-1.21.1 → fal-1.23.0}/src/fal/api.py +27 -3
  5. {fal-1.21.1 → fal-1.23.0}/src/fal/container.py +2 -0
  6. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/utils/download_utils.py +13 -4
  7. {fal-1.21.1 → fal-1.23.0}/tests/integration_test.py +3 -3
  8. {fal-1.21.1 → fal-1.23.0}/tests/test_apps.py +35 -0
  9. {fal-1.21.1 → fal-1.23.0}/.gitignore +0 -0
  10. {fal-1.21.1 → fal-1.23.0}/Makefile +0 -0
  11. {fal-1.21.1 → fal-1.23.0}/README.md +0 -0
  12. {fal-1.21.1 → fal-1.23.0}/docs/conf.py +0 -0
  13. {fal-1.21.1 → fal-1.23.0}/docs/index.rst +0 -0
  14. {fal-1.21.1 → fal-1.23.0}/fal.egg-info/SOURCES.txt +0 -0
  15. {fal-1.21.1 → fal-1.23.0}/fal.egg-info/dependency_links.txt +0 -0
  16. {fal-1.21.1 → fal-1.23.0}/fal.egg-info/entry_points.txt +0 -0
  17. {fal-1.21.1 → fal-1.23.0}/fal.egg-info/requires.txt +0 -0
  18. {fal-1.21.1 → fal-1.23.0}/fal.egg-info/top_level.txt +0 -0
  19. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/README.md +0 -0
  20. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  21. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  22. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  23. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  24. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  25. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  26. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  27. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  28. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  29. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  30. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  31. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  32. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  33. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  34. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  35. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  36. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  37. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  38. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  39. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  40. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  41. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  42. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  43. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  44. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  45. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  46. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  47. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  48. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  49. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  50. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  51. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  52. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  53. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  54. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  55. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  56. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  57. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  58. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  59. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  60. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  61. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  62. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  63. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  64. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  65. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  66. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  67. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  68. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  69. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  70. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  71. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  72. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  73. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  74. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  75. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  76. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  77. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  78. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  79. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  80. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  81. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  82. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  83. {fal-1.21.1 → fal-1.23.0}/openapi-fal-rest/pyproject.toml +0 -0
  84. {fal-1.21.1 → fal-1.23.0}/openapi_rest.config.yaml +0 -0
  85. {fal-1.21.1 → fal-1.23.0}/pyproject.toml +0 -0
  86. {fal-1.21.1 → fal-1.23.0}/setup.cfg +0 -0
  87. {fal-1.21.1 → fal-1.23.0}/src/fal/__init__.py +0 -0
  88. {fal-1.21.1 → fal-1.23.0}/src/fal/__main__.py +0 -0
  89. {fal-1.21.1 → fal-1.23.0}/src/fal/_serialization.py +0 -0
  90. {fal-1.21.1 → fal-1.23.0}/src/fal/_version.py +0 -0
  91. {fal-1.21.1 → fal-1.23.0}/src/fal/app.py +0 -0
  92. {fal-1.21.1 → fal-1.23.0}/src/fal/apps.py +0 -0
  93. {fal-1.21.1 → fal-1.23.0}/src/fal/auth/__init__.py +0 -0
  94. {fal-1.21.1 → fal-1.23.0}/src/fal/auth/auth0.py +0 -0
  95. {fal-1.21.1 → fal-1.23.0}/src/fal/auth/local.py +0 -0
  96. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/__init__.py +0 -0
  97. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/_utils.py +0 -0
  98. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/api.py +0 -0
  99. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/apps.py +0 -0
  100. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/auth.py +0 -0
  101. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/cli_nested_json.py +0 -0
  102. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/create.py +0 -0
  103. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/debug.py +0 -0
  104. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/deploy.py +0 -0
  105. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/doctor.py +0 -0
  106. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/files.py +0 -0
  107. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/keys.py +0 -0
  108. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/main.py +0 -0
  109. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/parser.py +0 -0
  110. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/profile.py +0 -0
  111. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/run.py +0 -0
  112. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/runners.py +0 -0
  113. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/secrets.py +0 -0
  114. {fal-1.21.1 → fal-1.23.0}/src/fal/cli/teams.py +0 -0
  115. {fal-1.21.1 → fal-1.23.0}/src/fal/config.py +0 -0
  116. {fal-1.21.1 → fal-1.23.0}/src/fal/console/__init__.py +0 -0
  117. {fal-1.21.1 → fal-1.23.0}/src/fal/console/icons.py +0 -0
  118. {fal-1.21.1 → fal-1.23.0}/src/fal/console/ux.py +0 -0
  119. {fal-1.21.1 → fal-1.23.0}/src/fal/exceptions/__init__.py +0 -0
  120. {fal-1.21.1 → fal-1.23.0}/src/fal/exceptions/_base.py +0 -0
  121. {fal-1.21.1 → fal-1.23.0}/src/fal/exceptions/_cuda.py +0 -0
  122. {fal-1.21.1 → fal-1.23.0}/src/fal/exceptions/auth.py +0 -0
  123. {fal-1.21.1 → fal-1.23.0}/src/fal/files.py +0 -0
  124. {fal-1.21.1 → fal-1.23.0}/src/fal/flags.py +0 -0
  125. {fal-1.21.1 → fal-1.23.0}/src/fal/logging/__init__.py +0 -0
  126. {fal-1.21.1 → fal-1.23.0}/src/fal/logging/isolate.py +0 -0
  127. {fal-1.21.1 → fal-1.23.0}/src/fal/logging/style.py +0 -0
  128. {fal-1.21.1 → fal-1.23.0}/src/fal/logging/trace.py +0 -0
  129. {fal-1.21.1 → fal-1.23.0}/src/fal/logging/user.py +0 -0
  130. {fal-1.21.1 → fal-1.23.0}/src/fal/project.py +0 -0
  131. {fal-1.21.1 → fal-1.23.0}/src/fal/py.typed +0 -0
  132. {fal-1.21.1 → fal-1.23.0}/src/fal/rest_client.py +0 -0
  133. {fal-1.21.1 → fal-1.23.0}/src/fal/sdk.py +0 -0
  134. {fal-1.21.1 → fal-1.23.0}/src/fal/sync.py +0 -0
  135. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/__init__.py +0 -0
  136. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/audio/__init__.py +0 -0
  137. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/audio/audio.py +0 -0
  138. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/exceptions.py +0 -0
  139. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/file/__init__.py +0 -0
  140. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/file/file.py +0 -0
  141. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/file/providers/fal.py +0 -0
  142. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/file/providers/gcp.py +0 -0
  143. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/file/providers/r2.py +0 -0
  144. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/file/providers/s3.py +0 -0
  145. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/file/types.py +0 -0
  146. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/image/__init__.py +0 -0
  147. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/image/image.py +0 -0
  148. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  149. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  150. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  151. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  152. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  153. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/image/safety_checker.py +0 -0
  154. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/optimize.py +0 -0
  155. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/types.py +0 -0
  156. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/utils/__init__.py +0 -0
  157. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/utils/endpoint.py +0 -0
  158. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/utils/retry.py +0 -0
  159. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/video/__init__.py +0 -0
  160. {fal-1.21.1 → fal-1.23.0}/src/fal/toolkit/video/video.py +0 -0
  161. {fal-1.21.1 → fal-1.23.0}/src/fal/utils.py +0 -0
  162. {fal-1.21.1 → fal-1.23.0}/src/fal/workflows.py +0 -0
  163. {fal-1.21.1 → fal-1.23.0}/tests/__init__.py +0 -0
  164. {fal-1.21.1 → fal-1.23.0}/tests/assets/cat.png +0 -0
  165. {fal-1.21.1 → fal-1.23.0}/tests/cli/__init__.py +0 -0
  166. {fal-1.21.1 → fal-1.23.0}/tests/cli/test_apps.py +0 -0
  167. {fal-1.21.1 → fal-1.23.0}/tests/cli/test_auth.py +0 -0
  168. {fal-1.21.1 → fal-1.23.0}/tests/cli/test_deploy.py +0 -0
  169. {fal-1.21.1 → fal-1.23.0}/tests/cli/test_keys.py +0 -0
  170. {fal-1.21.1 → fal-1.23.0}/tests/cli/test_run.py +0 -0
  171. {fal-1.21.1 → fal-1.23.0}/tests/cli/test_secrets.py +0 -0
  172. {fal-1.21.1 → fal-1.23.0}/tests/conftest.py +0 -0
  173. {fal-1.21.1 → fal-1.23.0}/tests/mainify_package/__init__.py +0 -0
  174. {fal-1.21.1 → fal-1.23.0}/tests/mainify_package/impl.py +0 -0
  175. {fal-1.21.1 → fal-1.23.0}/tests/mainify_package/utils.py +0 -0
  176. {fal-1.21.1 → fal-1.23.0}/tests/mainify_target.py +0 -0
  177. {fal-1.21.1 → fal-1.23.0}/tests/test_stability.py +0 -0
  178. {fal-1.21.1 → fal-1.23.0}/tests/toolkit/file/providers/test_fal_retry.py +0 -0
  179. {fal-1.21.1 → fal-1.23.0}/tests/toolkit/file_test.py +0 -0
  180. {fal-1.21.1 → fal-1.23.0}/tests/toolkit/image_test.py +0 -0
  181. {fal-1.21.1 → fal-1.23.0}/tests/toolkit/test_types.py +0 -0
  182. {fal-1.21.1 → fal-1.23.0}/tests/toolkit/utils/retry.py +0 -0
  183. {fal-1.21.1 → fal-1.23.0}/tools/demo_script.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.21.1
3
+ Version: 1.23.0
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.21.1
3
+ Version: 1.23.0
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.21.1'
21
- __version_tuple__ = version_tuple = (1, 21, 1)
20
+ __version__ = version = '1.23.0'
21
+ __version_tuple__ = version_tuple = (1, 23, 0)
@@ -33,6 +33,8 @@ import uvicorn
33
33
  import yaml
34
34
  from fastapi import FastAPI
35
35
  from fastapi import __version__ as fastapi_version
36
+ from fastapi.encoders import jsonable_encoder
37
+ from fastapi.exceptions import RequestValidationError
36
38
  from packaging.requirements import Requirement
37
39
  from packaging.utils import canonicalize_name
38
40
  from pydantic import __version__ as pydantic_version
@@ -613,8 +615,8 @@ class FalServerlessHost(Host):
613
615
  def result_handler(partial_result):
614
616
  ret.stream = partial_result.stream
615
617
  for log in partial_result.logs:
616
- if "Access your exposed service at" in log.message:
617
- ret.url = log.message.rsplit()[-1]
618
+ if "And API access through" in log.message:
619
+ ret.url = log.message.rsplit()[-1].replace("queue.", "")
618
620
  ret.logs.put(log)
619
621
 
620
622
  self._thread_pool.submit(
@@ -954,7 +956,18 @@ def function( # type: ignore
954
956
  def wrapper(func: Callable[ArgsT, ReturnT]):
955
957
  include_modules_from(func)
956
958
 
957
- for module_name in local_python_modules or []:
959
+ if local_python_modules and not isinstance(local_python_modules, list):
960
+ raise ValueError(
961
+ "local_python_modules must be a list of module names as strings, got "
962
+ f"{repr(local_python_modules)}"
963
+ )
964
+
965
+ for idx, module_name in enumerate(local_python_modules or []):
966
+ if not isinstance(module_name, str):
967
+ raise ValueError(
968
+ "local_python_modules must be a list of module names as strings, "
969
+ f"got {repr(module_name)} at index {idx}"
970
+ )
958
971
  include_module(module_name)
959
972
 
960
973
  proxy = IsolatedFunction(
@@ -1092,6 +1105,17 @@ class BaseServable:
1092
1105
  async def field_exception_handler(request: Request, exc: FieldException):
1093
1106
  return JSONResponse(exc.to_pydantic_format(), exc.status_code)
1094
1107
 
1108
+ # ref: https://github.com/fastapi/fastapi/blob/37c8e7d76b4b47eb2c4cced6b4de59eb3d5f08eb/fastapi/exception_handlers.py#L20
1109
+ @_app.exception_handler(RequestValidationError)
1110
+ async def request_val_exception_handler(
1111
+ request: Request, exc: RequestValidationError
1112
+ ):
1113
+ return JSONResponse(
1114
+ {"detail": jsonable_encoder(exc.errors())},
1115
+ 422,
1116
+ headers={"x-fal-billable-units": "0"},
1117
+ )
1118
+
1095
1119
  @_app.exception_handler(CUDAOutOfMemoryException)
1096
1120
  async def cuda_out_of_memory_exception_handler(
1097
1121
  request: Request, exc: CUDAOutOfMemoryException
@@ -19,6 +19,7 @@ class ContainerImage:
19
19
  builder: Optional[Builder] = field(default=None)
20
20
  compression: str = DEFAULT_COMPRESSION
21
21
  force_compression: bool = DEFAULT_FORCE_COMPRESSION
22
+ secrets: Dict[str, str] = field(default_factory=dict)
22
23
 
23
24
  def __post_init__(self) -> None:
24
25
  if self.registries:
@@ -51,4 +52,5 @@ class ContainerImage:
51
52
  "builder": self.builder,
52
53
  "compression": self.compression,
53
54
  "force_compression": self.force_compression,
55
+ "secrets": self.secrets,
54
56
  }
@@ -427,13 +427,22 @@ def clone_repository(
427
427
  if repo_name is None:
428
428
  repo_name = Path(https_url).stem
429
429
  if commit_hash:
430
+ if len(commit_hash) < 8:
431
+ raise ValueError(f"Commit hash '{commit_hash}' is too short.")
430
432
  repo_name += f"-{commit_hash[:8]}"
431
433
 
432
434
  local_repo_path = Path(target_dir) / repo_name # type: ignore[arg-type]
433
435
 
434
436
  if local_repo_path.exists():
435
- local_repo_commit_hash = _get_git_revision_hash(local_repo_path)
436
- if local_repo_commit_hash == commit_hash and not force:
437
+ local_repo_commit_hash = _git_rev_parse(local_repo_path, "HEAD")
438
+ full_commit_hash = (
439
+ _git_rev_parse(local_repo_path, commit_hash) if commit_hash else None
440
+ )
441
+ if (
442
+ full_commit_hash
443
+ and local_repo_commit_hash == full_commit_hash
444
+ and not force
445
+ ):
437
446
  if include_to_path:
438
447
  __add_local_path_to_sys_path(local_repo_path)
439
448
  return local_repo_path
@@ -516,12 +525,12 @@ def __add_local_path_to_sys_path(local_path: Path | str):
516
525
  sys.path.insert(0, local_path_str)
517
526
 
518
527
 
519
- def _get_git_revision_hash(repo_path: Path) -> str:
528
+ def _git_rev_parse(repo_path: Path, ref: str) -> str:
520
529
  import subprocess
521
530
 
522
531
  try:
523
532
  return subprocess.check_output(
524
- ["git", "rev-parse", "HEAD"],
533
+ ["git", "rev-parse", ref],
525
534
  cwd=repo_path,
526
535
  text=True,
527
536
  stderr=subprocess.STDOUT,
@@ -23,7 +23,7 @@ from fal.toolkit import (
23
23
  Image as FalImage,
24
24
  )
25
25
  from fal.toolkit.file.file import CompressedFile
26
- from fal.toolkit.utils.download_utils import _get_git_revision_hash, _hash_url
26
+ from fal.toolkit.utils.download_utils import _git_rev_parse, _hash_url
27
27
 
28
28
  EXAMPLE_FILE_URL = "https://raw.githubusercontent.com/fal-ai/fal/main/projects/fal/tests/assets/cat.png"
29
29
 
@@ -376,13 +376,13 @@ def test_clone_repository(isolated_client, mock_fal_persistent_dirs):
376
376
  first_path = clone_repository(
377
377
  EXAMPLE_REPO_URL, commit_hash=EXAMPLE_REPO_FIRST_COMMIT
378
378
  )
379
- first_repo_hash = _get_git_revision_hash(first_path)
379
+ first_repo_hash = _git_rev_parse(first_path, "HEAD")
380
380
 
381
381
  second_path = clone_repository(
382
382
  EXAMPLE_REPO_URL, commit_hash=EXAMPLE_REPO_SECOND_COMMIT
383
383
  )
384
384
 
385
- second_repo_hash = _get_git_revision_hash(second_path)
385
+ second_repo_hash = _git_rev_parse(second_path, "HEAD")
386
386
 
387
387
  return first_path, first_repo_hash, second_path, second_repo_hash
388
388
 
@@ -212,6 +212,7 @@ class ExceptionApp(fal.App, keep_alive=300, max_concurrency=1):
212
212
  def app_exception(self) -> Output:
213
213
  raise AppException(message="this app is designed to fail", status_code=401)
214
214
 
215
+ # While making the request provide payload as {"lhs": 1, "rhs": 2}
215
216
  @fal.endpoint("/field-exception")
216
217
  def field_exception(self, input: Input) -> Output:
217
218
  raise FieldException(
@@ -402,6 +403,12 @@ def test_stateful_app(host: api.FalServerlessHost, user: User):
402
403
  yield f"{user.username}/{app_alias}"
403
404
 
404
405
 
406
+ @pytest.fixture(scope="module")
407
+ def test_pydantic_validation_error():
408
+ with AppClient.connect(StatefulAdditionApp) as client:
409
+ yield client
410
+
411
+
405
412
  @pytest.fixture(scope="module")
406
413
  def test_cancellable_app(host: api.FalServerlessHost, user: User):
407
414
  cancellable_app = fal.wrap_app(CancellableApp)
@@ -958,6 +965,34 @@ def test_app_exceptions(test_exception_app: AppClient):
958
965
  assert _CUDA_OOM_MESSAGE in cuda_exc.value.message
959
966
 
960
967
 
968
+ def test_pydantic_validation_billing(test_pydantic_validation_error: AppClient):
969
+ with httpx.Client() as httpx_client:
970
+ url = test_pydantic_validation_error.url + "/increment"
971
+ response = httpx_client.post(
972
+ url,
973
+ json={"value": "this-is-not-an-integer"},
974
+ timeout=30,
975
+ )
976
+
977
+ assert response.status_code == 422
978
+ assert response.headers.get("x-fal-billable-units") == "0"
979
+
980
+
981
+ def test_field_exception_billing(test_exception_app: AppClient):
982
+ with httpx.Client() as httpx_client:
983
+ url = test_exception_app.url + "/field-exception"
984
+ response = httpx_client.post(
985
+ url,
986
+ json={"lhs": 1, "rhs": 2},
987
+ timeout=30,
988
+ )
989
+
990
+ assert response.status_code == 422
991
+ # For errors raised on runtime, developers should be handling the billing.
992
+ # Therefore not adding billing units.
993
+ assert not hasattr(response.headers, "x-fal-billable-units")
994
+
995
+
961
996
  def test_kill_runner(host: api.FalServerlessHost, test_sleep_app: str):
962
997
  handle = apps.submit(test_sleep_app, arguments={"wait_time": 10})
963
998
 
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
File without changes