fal 1.12.1__tar.gz → 1.13.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 (176) hide show
  1. {fal-1.12.1/fal.egg-info → fal-1.13.0}/PKG-INFO +1 -1
  2. {fal-1.12.1 → fal-1.13.0/fal.egg-info}/PKG-INFO +1 -1
  3. {fal-1.12.1 → fal-1.13.0}/src/fal/_fal_version.py +2 -2
  4. {fal-1.12.1 → fal-1.13.0}/src/fal/api.py +44 -23
  5. {fal-1.12.1 → fal-1.13.0}/src/fal/app.py +15 -7
  6. {fal-1.12.1 → fal-1.13.0}/src/fal/apps.py +42 -5
  7. {fal-1.12.1 → fal-1.13.0}/src/fal/auth/__init__.py +15 -31
  8. {fal-1.12.1 → fal-1.13.0}/src/fal/auth/auth0.py +5 -8
  9. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/api.py +27 -8
  10. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/auth.py +36 -15
  11. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/profile.py +1 -1
  12. {fal-1.12.1 → fal-1.13.0}/src/fal/config.py +3 -0
  13. {fal-1.12.1 → fal-1.13.0}/src/fal/logging/__init__.py +2 -2
  14. {fal-1.12.1 → fal-1.13.0}/src/fal/logging/isolate.py +8 -1
  15. fal-1.13.0/src/fal/logging/user.py +25 -0
  16. {fal-1.12.1 → fal-1.13.0}/src/fal/sdk.py +28 -16
  17. fal-1.12.1/src/fal/logging/user.py +0 -21
  18. {fal-1.12.1 → fal-1.13.0}/.gitignore +0 -0
  19. {fal-1.12.1 → fal-1.13.0}/Makefile +0 -0
  20. {fal-1.12.1 → fal-1.13.0}/README.md +0 -0
  21. {fal-1.12.1 → fal-1.13.0}/docs/conf.py +0 -0
  22. {fal-1.12.1 → fal-1.13.0}/docs/index.rst +0 -0
  23. {fal-1.12.1 → fal-1.13.0}/fal.egg-info/SOURCES.txt +0 -0
  24. {fal-1.12.1 → fal-1.13.0}/fal.egg-info/dependency_links.txt +0 -0
  25. {fal-1.12.1 → fal-1.13.0}/fal.egg-info/entry_points.txt +0 -0
  26. {fal-1.12.1 → fal-1.13.0}/fal.egg-info/requires.txt +0 -0
  27. {fal-1.12.1 → fal-1.13.0}/fal.egg-info/top_level.txt +0 -0
  28. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/README.md +0 -0
  29. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  30. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  31. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  32. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  33. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  34. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  35. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  36. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  37. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  38. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  39. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  40. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  41. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  42. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  43. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  44. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  45. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  46. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  47. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  48. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  49. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  50. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  51. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  52. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  53. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  54. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  55. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  56. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  57. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  58. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  59. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  60. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  61. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  62. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  63. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  64. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  65. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  66. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  67. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  68. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  69. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  70. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  71. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  72. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  73. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  74. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  75. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  76. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  77. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  78. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  79. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  80. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  81. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  82. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  83. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  84. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  85. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  86. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  87. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  88. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  89. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  90. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  91. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  92. {fal-1.12.1 → fal-1.13.0}/openapi-fal-rest/pyproject.toml +0 -0
  93. {fal-1.12.1 → fal-1.13.0}/openapi_rest.config.yaml +0 -0
  94. {fal-1.12.1 → fal-1.13.0}/pyproject.toml +0 -0
  95. {fal-1.12.1 → fal-1.13.0}/setup.cfg +0 -0
  96. {fal-1.12.1 → fal-1.13.0}/src/fal/__init__.py +0 -0
  97. {fal-1.12.1 → fal-1.13.0}/src/fal/__main__.py +0 -0
  98. {fal-1.12.1 → fal-1.13.0}/src/fal/_serialization.py +0 -0
  99. {fal-1.12.1 → fal-1.13.0}/src/fal/_version.py +0 -0
  100. {fal-1.12.1 → fal-1.13.0}/src/fal/auth/local.py +0 -0
  101. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/__init__.py +0 -0
  102. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/_utils.py +0 -0
  103. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/apps.py +0 -0
  104. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/cli_nested_json.py +0 -0
  105. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/create.py +0 -0
  106. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/debug.py +0 -0
  107. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/deploy.py +0 -0
  108. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/doctor.py +0 -0
  109. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/keys.py +0 -0
  110. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/main.py +0 -0
  111. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/parser.py +0 -0
  112. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/run.py +0 -0
  113. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/runners.py +0 -0
  114. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/secrets.py +0 -0
  115. {fal-1.12.1 → fal-1.13.0}/src/fal/cli/teams.py +0 -0
  116. {fal-1.12.1 → fal-1.13.0}/src/fal/console/__init__.py +0 -0
  117. {fal-1.12.1 → fal-1.13.0}/src/fal/console/icons.py +0 -0
  118. {fal-1.12.1 → fal-1.13.0}/src/fal/console/ux.py +0 -0
  119. {fal-1.12.1 → fal-1.13.0}/src/fal/container.py +0 -0
  120. {fal-1.12.1 → fal-1.13.0}/src/fal/exceptions/__init__.py +0 -0
  121. {fal-1.12.1 → fal-1.13.0}/src/fal/exceptions/_base.py +0 -0
  122. {fal-1.12.1 → fal-1.13.0}/src/fal/exceptions/_cuda.py +0 -0
  123. {fal-1.12.1 → fal-1.13.0}/src/fal/exceptions/auth.py +0 -0
  124. {fal-1.12.1 → fal-1.13.0}/src/fal/files.py +0 -0
  125. {fal-1.12.1 → fal-1.13.0}/src/fal/flags.py +0 -0
  126. {fal-1.12.1 → fal-1.13.0}/src/fal/logging/style.py +0 -0
  127. {fal-1.12.1 → fal-1.13.0}/src/fal/logging/trace.py +0 -0
  128. {fal-1.12.1 → fal-1.13.0}/src/fal/py.typed +0 -0
  129. {fal-1.12.1 → fal-1.13.0}/src/fal/rest_client.py +0 -0
  130. {fal-1.12.1 → fal-1.13.0}/src/fal/sync.py +0 -0
  131. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/__init__.py +0 -0
  132. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/exceptions.py +0 -0
  133. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/file/__init__.py +0 -0
  134. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/file/file.py +0 -0
  135. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/file/providers/fal.py +0 -0
  136. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/file/providers/gcp.py +0 -0
  137. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/file/providers/r2.py +0 -0
  138. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/file/providers/s3.py +0 -0
  139. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/file/types.py +0 -0
  140. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/image/__init__.py +0 -0
  141. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/image/image.py +0 -0
  142. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  143. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  144. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  145. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  146. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  147. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/image/safety_checker.py +0 -0
  148. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/optimize.py +0 -0
  149. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/types.py +0 -0
  150. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/utils/__init__.py +0 -0
  151. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/utils/download_utils.py +0 -0
  152. {fal-1.12.1 → fal-1.13.0}/src/fal/toolkit/utils/retry.py +0 -0
  153. {fal-1.12.1 → fal-1.13.0}/src/fal/utils.py +0 -0
  154. {fal-1.12.1 → fal-1.13.0}/src/fal/workflows.py +0 -0
  155. {fal-1.12.1 → fal-1.13.0}/tests/__init__.py +0 -0
  156. {fal-1.12.1 → fal-1.13.0}/tests/assets/cat.png +0 -0
  157. {fal-1.12.1 → fal-1.13.0}/tests/cli/__init__.py +0 -0
  158. {fal-1.12.1 → fal-1.13.0}/tests/cli/test_apps.py +0 -0
  159. {fal-1.12.1 → fal-1.13.0}/tests/cli/test_auth.py +0 -0
  160. {fal-1.12.1 → fal-1.13.0}/tests/cli/test_deploy.py +0 -0
  161. {fal-1.12.1 → fal-1.13.0}/tests/cli/test_keys.py +0 -0
  162. {fal-1.12.1 → fal-1.13.0}/tests/cli/test_run.py +0 -0
  163. {fal-1.12.1 → fal-1.13.0}/tests/cli/test_secrets.py +0 -0
  164. {fal-1.12.1 → fal-1.13.0}/tests/conftest.py +0 -0
  165. {fal-1.12.1 → fal-1.13.0}/tests/integration_test.py +0 -0
  166. {fal-1.12.1 → fal-1.13.0}/tests/mainify_package/__init__.py +0 -0
  167. {fal-1.12.1 → fal-1.13.0}/tests/mainify_package/impl.py +0 -0
  168. {fal-1.12.1 → fal-1.13.0}/tests/mainify_package/utils.py +0 -0
  169. {fal-1.12.1 → fal-1.13.0}/tests/mainify_target.py +0 -0
  170. {fal-1.12.1 → fal-1.13.0}/tests/test_apps.py +0 -0
  171. {fal-1.12.1 → fal-1.13.0}/tests/test_stability.py +0 -0
  172. {fal-1.12.1 → fal-1.13.0}/tests/toolkit/file_test.py +0 -0
  173. {fal-1.12.1 → fal-1.13.0}/tests/toolkit/image_test.py +0 -0
  174. {fal-1.12.1 → fal-1.13.0}/tests/toolkit/test_types.py +0 -0
  175. {fal-1.12.1 → fal-1.13.0}/tests/toolkit/utils/retry.py +0 -0
  176. {fal-1.12.1 → fal-1.13.0}/tools/demo_script.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.12.1
3
+ Version: 1.13.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.12.1
3
+ Version: 1.13.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.12.1'
21
- __version_tuple__ = version_tuple = (1, 12, 1)
20
+ __version__ = version = '1.13.0'
21
+ __version_tuple__ = version_tuple = (1, 13, 0)
@@ -12,6 +12,7 @@ from functools import wraps
12
12
  from os import PathLike
13
13
  from queue import Queue
14
14
  from typing import (
15
+ TYPE_CHECKING,
15
16
  Any,
16
17
  Callable,
17
18
  ClassVar,
@@ -27,15 +28,11 @@ from typing import (
27
28
 
28
29
  import cloudpickle
29
30
  import grpc
30
- import isolate
31
31
  import tblib
32
32
  import uvicorn
33
33
  import yaml
34
34
  from fastapi import FastAPI
35
35
  from fastapi import __version__ as fastapi_version
36
- from isolate.backends.common import active_python
37
- from isolate.backends.settings import DEFAULT_SETTINGS
38
- from isolate.connections import PythonIPC
39
36
  from packaging.requirements import Requirement
40
37
  from packaging.utils import canonicalize_name
41
38
  from pydantic import __version__ as pydantic_version
@@ -66,6 +63,9 @@ from fal.sdk import (
66
63
  get_default_credentials,
67
64
  )
68
65
 
66
+ if TYPE_CHECKING:
67
+ from isolate.backends import BaseEnvironment
68
+
69
69
  ArgsT = ParamSpec("ArgsT")
70
70
  ReturnT = TypeVar("ReturnT", covariant=True) # noqa: PLC0105
71
71
 
@@ -83,9 +83,6 @@ SERVE_REQUIREMENTS = [
83
83
  ]
84
84
 
85
85
 
86
- THREAD_POOL = ThreadPoolExecutor()
87
-
88
-
89
86
  @dataclass
90
87
  class FalServerlessError(FalServerlessException):
91
88
  message: str
@@ -222,9 +219,10 @@ def cached(func: Callable[ArgsT, ReturnT]) -> Callable[ArgsT, ReturnT]:
222
219
  ) -> ReturnT:
223
220
  from functools import lru_cache
224
221
 
225
- # HACK: Using the isolate module as a global cache.
226
222
  import isolate
227
223
 
224
+ # HACK: Using the isolate module as a global cache.
225
+
228
226
  if not hasattr(isolate, "__cached_functions__"):
229
227
  isolate.__cached_functions__ = {}
230
228
 
@@ -269,17 +267,23 @@ def _prepare_partial_func(
269
267
  return wrapper
270
268
 
271
269
 
272
- @dataclass
273
- class LocalHost(Host):
274
- # The environment which provides the default set of
275
- # packages for isolate agent to run.
276
- _AGENT_ENVIRONMENT = isolate.prepare_environment(
270
+ def _prepare_environment() -> BaseEnvironment:
271
+ import isolate
272
+
273
+ return isolate.prepare_environment(
277
274
  "virtualenv",
278
275
  requirements=[
279
276
  f"cloudpickle=={cloudpickle.__version__}",
280
277
  f"tblib=={tblib.__version__}",
281
278
  ],
282
279
  )
280
+
281
+
282
+ @dataclass
283
+ class LocalHost(Host):
284
+ # The environment which provides the default set of
285
+ # packages for isolate agent to run.
286
+ _AGENT_ENVIRONMENT: BaseEnvironment = field(default_factory=_prepare_environment)
283
287
  _log_printer = IsolateLogPrinter(debug=flags.DEBUG)
284
288
 
285
289
  def run(
@@ -289,6 +293,10 @@ class LocalHost(Host):
289
293
  args: tuple[Any, ...],
290
294
  kwargs: dict[str, Any],
291
295
  ) -> ReturnT:
296
+ import isolate
297
+ from isolate.backends.settings import DEFAULT_SETTINGS
298
+ from isolate.connections import PythonIPC
299
+
292
300
  settings = replace(
293
301
  DEFAULT_SETTINGS,
294
302
  serialization_method="cloudpickle",
@@ -419,8 +427,16 @@ class FalServerlessHost(Host):
419
427
 
420
428
  _log_printer = IsolateLogPrinter(debug=flags.DEBUG)
421
429
 
430
+ _thread_pool: ThreadPoolExecutor = field(default_factory=ThreadPoolExecutor)
431
+
432
+ def __getstate__(self) -> dict[str, Any]:
433
+ state = self.__dict__.copy()
434
+ state["_thread_pool"] = None
435
+ return state
436
+
422
437
  def __setstate__(self, state: dict[str, Any]) -> None:
423
438
  self.__dict__.update(state)
439
+ self._thread_pool = ThreadPoolExecutor()
424
440
  self.credentials = get_agent_credentials(self.credentials)
425
441
 
426
442
  @property
@@ -440,6 +456,8 @@ class FalServerlessHost(Host):
440
456
  deployment_strategy: Literal["recreate", "rolling"] = "recreate",
441
457
  scale: bool = True,
442
458
  ) -> str | None:
459
+ from isolate.backends.common import active_python
460
+
443
461
  environment_options = options.environment.copy()
444
462
  environment_options.setdefault("python_version", active_python())
445
463
  environments = [self._connection.define_environment(**environment_options)]
@@ -517,6 +535,8 @@ class FalServerlessHost(Host):
517
535
  kwargs: dict[str, Any],
518
536
  result_handler: Callable[..., None],
519
537
  ) -> ReturnT:
538
+ from isolate.backends.common import active_python
539
+
520
540
  environment_options = options.environment.copy()
521
541
  environment_options.setdefault("python_version", active_python())
522
542
  environments = [self._connection.define_environment(**environment_options)]
@@ -611,7 +631,7 @@ class FalServerlessHost(Host):
611
631
  ret.url = log.message.rsplit()[-1]
612
632
  ret.logs.put(log)
613
633
 
614
- THREAD_POOL.submit(
634
+ self._thread_pool.submit(
615
635
  self._run,
616
636
  func,
617
637
  options,
@@ -654,7 +674,6 @@ class Options:
654
674
  return self.gateway.get("exposed_port")
655
675
 
656
676
 
657
- _DEFAULT_HOST = FalServerlessHost()
658
677
  _SERVE_PORT = 8080
659
678
 
660
679
  # Overload @function to help users identify the correct signature.
@@ -704,7 +723,7 @@ def function(
704
723
  python_version: str | None = None,
705
724
  requirements: list[str] | None = None,
706
725
  # Common options
707
- host: FalServerlessHost = _DEFAULT_HOST,
726
+ host: FalServerlessHost | None = None,
708
727
  serve: Literal[False] = False,
709
728
  exposed_port: int | None = None,
710
729
  max_concurrency: int | None = None,
@@ -733,7 +752,7 @@ def function(
733
752
  python_version: str | None = None,
734
753
  requirements: list[str] | None = None,
735
754
  # Common options
736
- host: FalServerlessHost = _DEFAULT_HOST,
755
+ host: FalServerlessHost | None = None,
737
756
  serve: Literal[True],
738
757
  exposed_port: int | None = None,
739
758
  max_concurrency: int | None = None,
@@ -812,7 +831,7 @@ def function(
812
831
  pip: list[str] | None = None,
813
832
  channels: list[str] | None = None,
814
833
  # Common options
815
- host: FalServerlessHost = _DEFAULT_HOST,
834
+ host: FalServerlessHost | None = None,
816
835
  serve: Literal[False] = False,
817
836
  exposed_port: int | None = None,
818
837
  max_concurrency: int | None = None,
@@ -846,7 +865,7 @@ def function(
846
865
  pip: list[str] | None = None,
847
866
  channels: list[str] | None = None,
848
867
  # Common options
849
- host: FalServerlessHost = _DEFAULT_HOST,
868
+ host: FalServerlessHost | None = None,
850
869
  serve: Literal[True],
851
870
  exposed_port: int | None = None,
852
871
  max_concurrency: int | None = None,
@@ -874,7 +893,7 @@ def function(
874
893
  *,
875
894
  image: ContainerImage | None = None,
876
895
  # Common options
877
- host: FalServerlessHost = _DEFAULT_HOST,
896
+ host: FalServerlessHost | None = None,
878
897
  serve: Literal[False] = False,
879
898
  exposed_port: int | None = None,
880
899
  max_concurrency: int | None = None,
@@ -902,7 +921,7 @@ def function(
902
921
  *,
903
922
  image: ContainerImage | None = None,
904
923
  # Common options
905
- host: FalServerlessHost = _DEFAULT_HOST,
924
+ host: FalServerlessHost | None = None,
906
925
  serve: Literal[True],
907
926
  exposed_port: int | None = None,
908
927
  max_concurrency: int | None = None,
@@ -928,15 +947,17 @@ def function(
928
947
  def function( # type: ignore
929
948
  kind: str = "virtualenv",
930
949
  *,
931
- host: Host = _DEFAULT_HOST,
950
+ host: Host | None = None,
932
951
  **config: Any,
933
952
  ):
953
+ if host is None:
954
+ host = FalServerlessHost()
934
955
  options = host.parse_options(kind=kind, **config)
935
956
 
936
957
  def wrapper(func: Callable[ArgsT, ReturnT]):
937
958
  include_modules_from(func)
938
959
  proxy = IsolatedFunction(
939
- host=host,
960
+ host=host, # type: ignore
940
961
  raw_func=func, # type: ignore
941
962
  options=options,
942
963
  )
@@ -16,11 +16,17 @@ from typing import Any, Callable, ClassVar, Literal, TypeVar
16
16
  import fastapi
17
17
  import grpc.aio as async_grpc
18
18
  import httpx
19
- from isolate.server import definitions
20
19
 
21
- import fal.api
22
20
  from fal._serialization import include_modules_from
23
- from fal.api import RouteSignature
21
+ from fal.api import (
22
+ SERVE_REQUIREMENTS,
23
+ BaseServable,
24
+ IsolatedFunction,
25
+ RouteSignature,
26
+ )
27
+ from fal.api import (
28
+ function as fal_function,
29
+ )
24
30
  from fal.exceptions import FalServerlessException, RequestCancelledException
25
31
  from fal.logging import get_logger
26
32
  from fal.toolkit.file import request_lifecycle_preference
@@ -70,6 +76,8 @@ async def _set_logger_labels(
70
76
  try:
71
77
  import sys
72
78
 
79
+ from isolate.server import definitions
80
+
73
81
  # Flush any prints that were buffered before setting the logger labels
74
82
  sys.stderr.flush()
75
83
  sys.stdout.flush()
@@ -89,7 +97,7 @@ async def _set_logger_labels(
89
97
  pass
90
98
 
91
99
 
92
- def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
100
+ def wrap_app(cls: type[App], **kwargs) -> IsolatedFunction:
93
101
  include_modules_from(cls)
94
102
 
95
103
  def initialize_and_serve():
@@ -111,7 +119,7 @@ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
111
119
  if kind == "container":
112
120
  cls.host_kwargs.pop("resolver", None)
113
121
 
114
- wrapper = fal.api.function(
122
+ wrapper = fal_function(
115
123
  kind,
116
124
  requirements=cls.requirements,
117
125
  machine_type=cls.machine_type,
@@ -123,7 +131,7 @@ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
123
131
  serve=False,
124
132
  )
125
133
  fn = wrapper(initialize_and_serve)
126
- fn.options.add_requirements(fal.api.SERVE_REQUIREMENTS)
134
+ fn.options.add_requirements(SERVE_REQUIREMENTS)
127
135
  if realtime_app:
128
136
  fn.options.add_requirements(REALTIME_APP_REQUIREMENTS)
129
137
 
@@ -255,7 +263,7 @@ def _print_python_packages() -> None:
255
263
  print("[debug] Python packages installed:", ", ".join(packages))
256
264
 
257
265
 
258
- class App(fal.api.BaseServable):
266
+ class App(BaseServable):
259
267
  requirements: ClassVar[list[str]] = []
260
268
  machine_type: ClassVar[str] = "S"
261
269
  num_gpus: ClassVar[int | None] = None
@@ -4,6 +4,7 @@ import json
4
4
  import time
5
5
  from contextlib import contextmanager
6
6
  from dataclasses import dataclass, field
7
+ from functools import lru_cache
7
8
  from typing import TYPE_CHECKING, Any, Iterator
8
9
 
9
10
  import httpx
@@ -14,6 +15,7 @@ from fal.sdk import Credentials, get_default_credentials
14
15
  if TYPE_CHECKING:
15
16
  from websockets.sync.connection import Connection
16
17
 
18
+ _STREAM_URL_FORMAT = f"https://{flags.FAL_RUN_HOST}/{{app_id}}"
17
19
  _QUEUE_URL_FORMAT = f"https://queue.{flags.FAL_RUN_HOST}/{{app_id}}"
18
20
  _REALTIME_URL_FORMAT = f"wss://{flags.FAL_RUN_HOST}/{{app_id}}"
19
21
  _WS_URL_FORMAT = f"wss://ws.{flags.FAL_RUN_HOST}/{{app_id}}"
@@ -55,6 +57,11 @@ class Completed(_Status):
55
57
  logs: list[dict[str, Any]] | None = field()
56
58
 
57
59
 
60
+ @lru_cache(maxsize=1)
61
+ def _get_http_client() -> httpx.Client:
62
+ return httpx.Client(headers={"User-Agent": "Fal/Python"})
63
+
64
+
58
65
  @dataclass
59
66
  class RequestHandle:
60
67
  """A handle to an async inference request."""
@@ -62,6 +69,8 @@ class RequestHandle:
62
69
  app_id: str
63
70
  request_id: str
64
71
 
72
+ _client: httpx.Client = field(default_factory=_get_http_client)
73
+
65
74
  # Use the credentials that were used to submit the request by default.
66
75
  _creds: Credentials = field(default_factory=get_default_credentials, repr=False)
67
76
 
@@ -82,7 +91,7 @@ class RequestHandle:
82
91
  _QUEUE_URL_FORMAT.format(app_id=self.app_id)
83
92
  + f"/requests/{self.request_id}/status/"
84
93
  )
85
- response = _HTTP_CLIENT.get(
94
+ response = self._client.get(
86
95
  url,
87
96
  headers=self._creds.to_headers(),
88
97
  params={"logs": int(logs)},
@@ -107,7 +116,7 @@ class RequestHandle:
107
116
  _QUEUE_URL_FORMAT.format(app_id=self.app_id)
108
117
  + f"/requests/{self.request_id}/cancel"
109
118
  )
110
- response = _HTTP_CLIENT.put(url, headers=self._creds.to_headers())
119
+ response = self._client.put(url, headers=self._creds.to_headers())
111
120
  response.raise_for_status()
112
121
 
113
122
  def iter_events(
@@ -134,7 +143,7 @@ class RequestHandle:
134
143
  _QUEUE_URL_FORMAT.format(app_id=self.app_id)
135
144
  + f"/requests/{self.request_id}/"
136
145
  )
137
- response = _HTTP_CLIENT.get(url, headers=self._creds.to_headers())
146
+ response = self._client.get(url, headers=self._creds.to_headers())
138
147
  try:
139
148
  response.raise_for_status()
140
149
  except httpx.HTTPStatusError as e:
@@ -159,7 +168,33 @@ class RequestHandle:
159
168
  return self.fetch_result()
160
169
 
161
170
 
162
- _HTTP_CLIENT = httpx.Client(headers={"User-Agent": "Fal/Python"})
171
+ def stream(
172
+ app_id: str, arguments: dict[str, Any], *, path: str = ""
173
+ ) -> Iterator[str | bytes]:
174
+ """Stream an inference task on a Fal app."""
175
+
176
+ app_id = _backwards_compatible_app_id(app_id)
177
+ url = _STREAM_URL_FORMAT.format(app_id=app_id)
178
+ if path:
179
+ _path = path[len("/") :] if path.startswith("/") else path
180
+ url += "/" + _path
181
+
182
+ creds = get_default_credentials()
183
+ client = _get_http_client()
184
+
185
+ response = client.post(
186
+ url,
187
+ json=arguments,
188
+ headers=creds.to_headers(),
189
+ )
190
+ response.raise_for_status()
191
+
192
+ if response.headers["Content-Type"].startswith("text/event-stream"):
193
+ for line in response.iter_lines():
194
+ if line:
195
+ yield line
196
+ else:
197
+ yield from response.iter_bytes()
163
198
 
164
199
 
165
200
  def run(app_id: str, arguments: dict[str, Any], *, path: str = "") -> dict[str, Any]:
@@ -181,8 +216,9 @@ def submit(app_id: str, arguments: dict[str, Any], *, path: str = "") -> Request
181
216
  url += "/" + _path
182
217
 
183
218
  creds = get_default_credentials()
219
+ client = _get_http_client()
184
220
 
185
- response = _HTTP_CLIENT.post(
221
+ response = client.post(
186
222
  url,
187
223
  json=arguments,
188
224
  headers=creds.to_headers(),
@@ -194,6 +230,7 @@ def submit(app_id: str, arguments: dict[str, Any], *, path: str = "") -> Request
194
230
  app_id=app_id,
195
231
  request_id=data["request_id"],
196
232
  _creds=creds,
233
+ _client=client,
197
234
  )
198
235
 
199
236
 
@@ -7,8 +7,6 @@ from typing import Optional
7
7
 
8
8
  from fal.auth import auth0, local
9
9
  from fal.config import Config
10
- from fal.console import console
11
- from fal.console.icons import CHECK_ICON
12
10
  from fal.exceptions import FalServerlessException
13
11
  from fal.exceptions.auth import UnauthenticatedException
14
12
 
@@ -73,26 +71,6 @@ def key_credentials() -> tuple[str, str] | None:
73
71
  return None
74
72
 
75
73
 
76
- def login():
77
- token_data = auth0.login()
78
- with local.lock_token():
79
- local.save_token(token_data["refresh_token"])
80
-
81
- USER.invalidate()
82
-
83
-
84
- def logout():
85
- refresh_token, _ = local.load_token()
86
- if refresh_token is None:
87
- raise FalServerlessException("You're not logged in")
88
- auth0.revoke(refresh_token)
89
- with local.lock_token():
90
- local.delete_token()
91
-
92
- USER.invalidate()
93
- console.print(f"{CHECK_ICON} Logged out of [cyan bold]fal[/]. Bye!")
94
-
95
-
96
74
  def _fetch_access_token() -> str:
97
75
  """
98
76
  Load the refresh token, request a new access_token (refreshing the refresh token)
@@ -147,6 +125,21 @@ def _fetch_teams(bearer_token: str) -> list[dict]:
147
125
  raise FalServerlessException("Failed to fetch teams") from exc
148
126
 
149
127
 
128
+ def login(console):
129
+ token_data = auth0.login(console)
130
+ with local.lock_token():
131
+ local.save_token(token_data["refresh_token"])
132
+
133
+
134
+ def logout(console):
135
+ refresh_token, _ = local.load_token()
136
+ if refresh_token is None:
137
+ raise FalServerlessException("You're not logged in")
138
+ auth0.revoke(refresh_token, console)
139
+ with local.lock_token():
140
+ local.delete_token()
141
+
142
+
150
143
  @dataclass
151
144
  class UserAccess:
152
145
  _access_token: str | None = field(repr=False, default=None)
@@ -154,12 +147,6 @@ class UserAccess:
154
147
  _exc: Exception | None = field(repr=False, default=None)
155
148
  _accounts: list[dict] | None = field(repr=False, default=None)
156
149
 
157
- def invalidate(self) -> None:
158
- self._access_token = None
159
- self._user_info = None
160
- self._exc = None
161
- self._accounts = None
162
-
163
150
  @property
164
151
  def info(self) -> dict:
165
152
  if self._user_info is None:
@@ -203,6 +190,3 @@ class UserAccess:
203
190
  if t["nickname"].lower() == team.lower():
204
191
  return t
205
192
  raise ValueError(f"Team {team} not found")
206
-
207
-
208
- USER = UserAccess()
@@ -6,8 +6,6 @@ import warnings
6
6
 
7
7
  import httpx
8
8
 
9
- from fal.console import console
10
- from fal.console.icons import CHECK_ICON
11
9
  from fal.console.ux import maybe_open_browser_tab
12
10
  from fal.exceptions import FalServerlessException
13
11
 
@@ -26,7 +24,7 @@ def logout_url(return_url: str):
26
24
  return f"https://{AUTH0_DOMAIN}/v2/logout?client_id={AUTH0_CLIENT_ID}&returnTo={return_url}"
27
25
 
28
26
 
29
- def _open_browser(url: str, code: str | None) -> None:
27
+ def _open_browser(url: str, code: str | None, console) -> None:
30
28
  maybe_open_browser_tab(url)
31
29
 
32
30
  console.print(
@@ -41,7 +39,7 @@ def _open_browser(url: str, code: str | None) -> None:
41
39
  )
42
40
 
43
41
 
44
- def login() -> dict:
42
+ def login(console) -> dict:
45
43
  """
46
44
  Runs the device authorization flow and stores the user object in memory
47
45
  """
@@ -63,7 +61,7 @@ def login() -> dict:
63
61
 
64
62
  url = logout_url(device_confirmation_url)
65
63
 
66
- _open_browser(url, device_user_code)
64
+ _open_browser(url, device_user_code, console)
67
65
 
68
66
  # This is needed to suppress the ResourceWarning emitted
69
67
  # when the process is waiting for user confirmation
@@ -84,7 +82,6 @@ def login() -> dict:
84
82
  token_data = token_response.json()
85
83
  if token_response.status_code == 200:
86
84
  status.update(spinner=None)
87
- console.print(f"{CHECK_ICON} Authenticated successfully, welcome!")
88
85
 
89
86
  validate_id_token(token_data["id_token"])
90
87
 
@@ -118,7 +115,7 @@ def refresh(token: str) -> dict:
118
115
  raise FalServerlessException(token_data["error_description"])
119
116
 
120
117
 
121
- def revoke(token: str):
118
+ def revoke(token: str, console):
122
119
  token_payload = {
123
120
  "client_id": AUTH0_CLIENT_ID,
124
121
  "token": token,
@@ -132,7 +129,7 @@ def revoke(token: str):
132
129
  token_data = token_response.json()
133
130
  raise FalServerlessException(token_data["error_description"])
134
131
 
135
- _open_browser(logout_url(WEBSITE_URL), None)
132
+ _open_browser(logout_url(WEBSITE_URL), None, console)
136
133
 
137
134
 
138
135
  def get_user_info(bearer_token: str) -> dict:
@@ -10,19 +10,38 @@ KV_SPLIT_RE = re.compile(r"(=|:=)")
10
10
 
11
11
  def _api(args):
12
12
  """Handle the api command execution."""
13
- from rich.console import Group
14
- from rich.live import Live
15
- from rich.panel import Panel
16
- from rich.text import Text
17
-
18
13
  from . import cli_nested_json
19
14
 
20
- params = [KV_SPLIT_RE.split(param) for param in args.params]
15
+ params_split = [KV_SPLIT_RE.split(param) for param in args.params]
21
16
  params = cli_nested_json.interpret_nested_json( # type: ignore
22
- [(key, value) for key, _, value in params]
17
+ [(key, value) for key, _, value in params_split]
23
18
  )
24
19
 
25
- handle = fal.apps.submit(args.model_id, params) # type: ignore
20
+ if args.model_id.endswith("/stream"):
21
+ stream_run(args.model_id, params)
22
+ else:
23
+ queue_run(args.model_id, params)
24
+
25
+
26
+ def stream_run(model_id: str, params: dict):
27
+ res = fal.apps.stream(model_id, params) # type: ignore
28
+ for line in res:
29
+ if isinstance(line, str):
30
+ rich.print(line)
31
+ else:
32
+ if isinstance(line, memoryview):
33
+ rich.print(line.tobytes().decode())
34
+ else:
35
+ rich.print(line.decode())
36
+
37
+
38
+ def queue_run(model_id: str, params: dict):
39
+ from rich.console import Group
40
+ from rich.live import Live
41
+ from rich.panel import Panel
42
+ from rich.text import Text
43
+
44
+ handle = fal.apps.submit(model_id, params) # type: ignore
26
45
  logs = [] # type: ignore
27
46
 
28
47
  with Live(auto_refresh=False) as live: