fal 1.5.7__tar.gz → 1.5.9__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 (167) hide show
  1. {fal-1.5.7/fal.egg-info → fal-1.5.9}/PKG-INFO +3 -3
  2. {fal-1.5.7 → fal-1.5.9/fal.egg-info}/PKG-INFO +3 -3
  3. {fal-1.5.7 → fal-1.5.9}/fal.egg-info/SOURCES.txt +1 -0
  4. {fal-1.5.7 → fal-1.5.9}/fal.egg-info/requires.txt +2 -2
  5. {fal-1.5.7 → fal-1.5.9}/pyproject.toml +2 -2
  6. {fal-1.5.7 → fal-1.5.9}/src/fal/_fal_version.py +2 -2
  7. {fal-1.5.7 → fal-1.5.9}/src/fal/api.py +6 -4
  8. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/apps.py +1 -1
  9. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/deploy.py +10 -0
  10. {fal-1.5.7 → fal-1.5.9}/src/fal/sdk.py +7 -3
  11. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/file/file.py +16 -2
  12. fal-1.5.9/src/fal/toolkit/file/providers/s3.py +80 -0
  13. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/image/__init__.py +3 -3
  14. {fal-1.5.7 → fal-1.5.9}/tests/test_apps.py +42 -0
  15. {fal-1.5.7 → fal-1.5.9}/.gitignore +0 -0
  16. {fal-1.5.7 → fal-1.5.9}/Makefile +0 -0
  17. {fal-1.5.7 → fal-1.5.9}/README.md +0 -0
  18. {fal-1.5.7 → fal-1.5.9}/docs/conf.py +0 -0
  19. {fal-1.5.7 → fal-1.5.9}/docs/index.rst +0 -0
  20. {fal-1.5.7 → fal-1.5.9}/fal.egg-info/dependency_links.txt +0 -0
  21. {fal-1.5.7 → fal-1.5.9}/fal.egg-info/entry_points.txt +0 -0
  22. {fal-1.5.7 → fal-1.5.9}/fal.egg-info/top_level.txt +0 -0
  23. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/README.md +0 -0
  24. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  25. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  26. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  27. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  28. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  29. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  30. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  31. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  32. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  33. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  34. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  35. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  36. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  37. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  38. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  39. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  40. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  41. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  42. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  43. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  44. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  45. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  46. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  47. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  48. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  49. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  50. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  51. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  52. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  53. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  54. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  55. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  56. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  57. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  58. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  59. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  60. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  61. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  62. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  63. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  64. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  65. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  66. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  67. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  68. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  69. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  70. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  71. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  72. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  73. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  74. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  75. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  76. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  77. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  78. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  79. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  80. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  81. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  82. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  83. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  84. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  85. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  86. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  87. {fal-1.5.7 → fal-1.5.9}/openapi-fal-rest/pyproject.toml +0 -0
  88. {fal-1.5.7 → fal-1.5.9}/openapi_rest.config.yaml +0 -0
  89. {fal-1.5.7 → fal-1.5.9}/setup.cfg +0 -0
  90. {fal-1.5.7 → fal-1.5.9}/src/fal/__init__.py +0 -0
  91. {fal-1.5.7 → fal-1.5.9}/src/fal/__main__.py +0 -0
  92. {fal-1.5.7 → fal-1.5.9}/src/fal/_serialization.py +0 -0
  93. {fal-1.5.7 → fal-1.5.9}/src/fal/_version.py +0 -0
  94. {fal-1.5.7 → fal-1.5.9}/src/fal/app.py +0 -0
  95. {fal-1.5.7 → fal-1.5.9}/src/fal/apps.py +0 -0
  96. {fal-1.5.7 → fal-1.5.9}/src/fal/auth/__init__.py +0 -0
  97. {fal-1.5.7 → fal-1.5.9}/src/fal/auth/auth0.py +0 -0
  98. {fal-1.5.7 → fal-1.5.9}/src/fal/auth/local.py +0 -0
  99. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/__init__.py +0 -0
  100. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/_utils.py +0 -0
  101. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/auth.py +0 -0
  102. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/create.py +0 -0
  103. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/debug.py +0 -0
  104. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/doctor.py +0 -0
  105. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/keys.py +0 -0
  106. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/main.py +0 -0
  107. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/parser.py +0 -0
  108. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/run.py +0 -0
  109. {fal-1.5.7 → fal-1.5.9}/src/fal/cli/secrets.py +0 -0
  110. {fal-1.5.7 → fal-1.5.9}/src/fal/console/__init__.py +0 -0
  111. {fal-1.5.7 → fal-1.5.9}/src/fal/console/icons.py +0 -0
  112. {fal-1.5.7 → fal-1.5.9}/src/fal/console/ux.py +0 -0
  113. {fal-1.5.7 → fal-1.5.9}/src/fal/container.py +0 -0
  114. {fal-1.5.7 → fal-1.5.9}/src/fal/exceptions/__init__.py +0 -0
  115. {fal-1.5.7 → fal-1.5.9}/src/fal/exceptions/_base.py +0 -0
  116. {fal-1.5.7 → fal-1.5.9}/src/fal/exceptions/_cuda.py +0 -0
  117. {fal-1.5.7 → fal-1.5.9}/src/fal/exceptions/auth.py +0 -0
  118. {fal-1.5.7 → fal-1.5.9}/src/fal/files.py +0 -0
  119. {fal-1.5.7 → fal-1.5.9}/src/fal/flags.py +0 -0
  120. {fal-1.5.7 → fal-1.5.9}/src/fal/logging/__init__.py +0 -0
  121. {fal-1.5.7 → fal-1.5.9}/src/fal/logging/isolate.py +0 -0
  122. {fal-1.5.7 → fal-1.5.9}/src/fal/logging/style.py +0 -0
  123. {fal-1.5.7 → fal-1.5.9}/src/fal/logging/trace.py +0 -0
  124. {fal-1.5.7 → fal-1.5.9}/src/fal/logging/user.py +0 -0
  125. {fal-1.5.7 → fal-1.5.9}/src/fal/py.typed +0 -0
  126. {fal-1.5.7 → fal-1.5.9}/src/fal/rest_client.py +0 -0
  127. {fal-1.5.7 → fal-1.5.9}/src/fal/sync.py +0 -0
  128. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/__init__.py +0 -0
  129. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/exceptions.py +0 -0
  130. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/file/__init__.py +0 -0
  131. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/file/providers/fal.py +0 -0
  132. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/file/providers/gcp.py +0 -0
  133. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/file/providers/r2.py +0 -0
  134. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/file/types.py +0 -0
  135. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/image/image.py +0 -0
  136. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  137. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  138. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  139. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  140. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  141. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/image/safety_checker.py +0 -0
  142. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/optimize.py +0 -0
  143. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/utils/__init__.py +0 -0
  144. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/utils/download_utils.py +0 -0
  145. {fal-1.5.7 → fal-1.5.9}/src/fal/toolkit/utils/retry.py +0 -0
  146. {fal-1.5.7 → fal-1.5.9}/src/fal/utils.py +0 -0
  147. {fal-1.5.7 → fal-1.5.9}/src/fal/workflows.py +0 -0
  148. {fal-1.5.7 → fal-1.5.9}/tests/__init__.py +0 -0
  149. {fal-1.5.7 → fal-1.5.9}/tests/assets/cat.png +0 -0
  150. {fal-1.5.7 → fal-1.5.9}/tests/cli/__init__.py +0 -0
  151. {fal-1.5.7 → fal-1.5.9}/tests/cli/test_apps.py +0 -0
  152. {fal-1.5.7 → fal-1.5.9}/tests/cli/test_auth.py +0 -0
  153. {fal-1.5.7 → fal-1.5.9}/tests/cli/test_deploy.py +0 -0
  154. {fal-1.5.7 → fal-1.5.9}/tests/cli/test_keys.py +0 -0
  155. {fal-1.5.7 → fal-1.5.9}/tests/cli/test_run.py +0 -0
  156. {fal-1.5.7 → fal-1.5.9}/tests/cli/test_secrets.py +0 -0
  157. {fal-1.5.7 → fal-1.5.9}/tests/conftest.py +0 -0
  158. {fal-1.5.7 → fal-1.5.9}/tests/integration_test.py +0 -0
  159. {fal-1.5.7 → fal-1.5.9}/tests/mainify_package/__init__.py +0 -0
  160. {fal-1.5.7 → fal-1.5.9}/tests/mainify_package/impl.py +0 -0
  161. {fal-1.5.7 → fal-1.5.9}/tests/mainify_package/utils.py +0 -0
  162. {fal-1.5.7 → fal-1.5.9}/tests/mainify_target.py +0 -0
  163. {fal-1.5.7 → fal-1.5.9}/tests/test_stability.py +0 -0
  164. {fal-1.5.7 → fal-1.5.9}/tests/toolkit/file_test.py +0 -0
  165. {fal-1.5.7 → fal-1.5.9}/tests/toolkit/image_test.py +0 -0
  166. {fal-1.5.7 → fal-1.5.9}/tests/toolkit/utils/retry.py +0 -0
  167. {fal-1.5.7 → fal-1.5.9}/tools/demo_script.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 1.5.7
3
+ Version: 1.5.9
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
- Requires-Dist: isolate[build]<1.14.0,>=0.13.0
9
- Requires-Dist: isolate-proto==0.5.4
8
+ Requires-Dist: isolate[build]<0.15.0,>=0.14.2
9
+ Requires-Dist: isolate-proto==0.5.5
10
10
  Requires-Dist: grpcio==1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 1.5.7
3
+ Version: 1.5.9
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
- Requires-Dist: isolate[build]<1.14.0,>=0.13.0
9
- Requires-Dist: isolate-proto==0.5.4
8
+ Requires-Dist: isolate[build]<0.15.0,>=0.14.2
9
+ Requires-Dist: isolate-proto==0.5.5
10
10
  Requires-Dist: grpcio==1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -130,6 +130,7 @@ src/fal/toolkit/file/types.py
130
130
  src/fal/toolkit/file/providers/fal.py
131
131
  src/fal/toolkit/file/providers/gcp.py
132
132
  src/fal/toolkit/file/providers/r2.py
133
+ src/fal/toolkit/file/providers/s3.py
133
134
  src/fal/toolkit/image/__init__.py
134
135
  src/fal/toolkit/image/image.py
135
136
  src/fal/toolkit/image/safety_checker.py
@@ -1,5 +1,5 @@
1
- isolate[build]<1.14.0,>=0.13.0
2
- isolate-proto==0.5.4
1
+ isolate[build]<0.15.0,>=0.14.2
2
+ isolate-proto==0.5.5
3
3
  grpcio==1.64.0
4
4
  dill==0.3.7
5
5
  cloudpickle==3.0.0
@@ -22,8 +22,8 @@ authors = [{ name = "Features & Labels <support@fal.ai>"}]
22
22
  readme = "README.md"
23
23
  requires-python = ">=3.8"
24
24
  dependencies = [
25
- "isolate[build]>=0.13.0,<1.14.0",
26
- "isolate-proto==0.5.4",
25
+ "isolate[build]>=0.14.2,<0.15.0",
26
+ "isolate-proto==0.5.5",
27
27
  "grpcio==1.64.0",
28
28
  "dill==0.3.7",
29
29
  "cloudpickle==3.0.0",
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.5.7'
16
- __version_tuple__ = version_tuple = (1, 5, 7)
15
+ __version__ = version = '1.5.9'
16
+ __version_tuple__ = version_tuple = (1, 5, 9)
@@ -171,6 +171,7 @@ class Host(Generic[ArgsT, ReturnT]):
171
171
  application_name: str | None = None,
172
172
  application_auth_mode: Literal["public", "shared", "private"] | None = None,
173
173
  metadata: dict[str, Any] | None = None,
174
+ scale: bool = True,
174
175
  ) -> str | None:
175
176
  """Register the given function on the host for API call execution."""
176
177
  raise NotImplementedError
@@ -430,6 +431,7 @@ class FalServerlessHost(Host):
430
431
  application_auth_mode: Literal["public", "shared", "private"] | None = None,
431
432
  metadata: dict[str, Any] | None = None,
432
433
  deployment_strategy: Literal["recreate", "rolling"] = "recreate",
434
+ scale: bool = True,
433
435
  ) -> str | None:
434
436
  environment_options = options.environment.copy()
435
437
  environment_options.setdefault("python_version", active_python())
@@ -439,15 +441,14 @@ class FalServerlessHost(Host):
439
441
  "machine_type", FAL_SERVERLESS_DEFAULT_MACHINE_TYPE
440
442
  )
441
443
  keep_alive = options.host.get("keep_alive", FAL_SERVERLESS_DEFAULT_KEEP_ALIVE)
442
- max_concurrency = options.host.get("max_concurrency")
443
- min_concurrency = options.host.get("min_concurrency")
444
- max_multiplexing = options.host.get("max_multiplexing")
445
444
  base_image = options.host.get("_base_image", None)
446
445
  scheduler = options.host.get("_scheduler", None)
447
446
  scheduler_options = options.host.get("_scheduler_options", None)
447
+ max_concurrency = options.host.get("max_concurrency")
448
+ min_concurrency = options.host.get("min_concurrency")
449
+ max_multiplexing = options.host.get("max_multiplexing")
448
450
  exposed_port = options.get_exposed_port()
449
451
  request_timeout = options.host.get("request_timeout")
450
-
451
452
  machine_requirements = MachineRequirements(
452
453
  machine_types=machine_type, # type: ignore
453
454
  num_gpus=options.host.get("num_gpus"),
@@ -486,6 +487,7 @@ class FalServerlessHost(Host):
486
487
  machine_requirements=machine_requirements,
487
488
  metadata=metadata,
488
489
  deployment_strategy=deployment_strategy,
490
+ scale=scale,
489
491
  ):
490
492
  for log in partial_result.logs:
491
493
  self._log_printer.print(log)
@@ -221,7 +221,7 @@ def _runners(args):
221
221
  str(runner.in_flight_requests),
222
222
  (
223
223
  "N/A (active)"
224
- if not runner.expiration_countdown
224
+ if runner.expiration_countdown is None
225
225
  else f"{runner.expiration_countdown}s"
226
226
  ),
227
227
  f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
@@ -106,6 +106,7 @@ def _deploy_from_reference(
106
106
  application_auth_mode=app_auth,
107
107
  metadata=isolated_function.options.host.get("metadata", {}),
108
108
  deployment_strategy=deployment_strategy,
109
+ scale=not args.no_scale,
109
110
  )
110
111
 
111
112
  if app_id:
@@ -219,5 +220,14 @@ def add_parser(main_subparsers, parents):
219
220
  help="Deployment strategy.",
220
221
  default="recreate",
221
222
  )
223
+ parser.add_argument(
224
+ "--no-scale",
225
+ action="store_true",
226
+ help=(
227
+ "Use min_concurrency/max_concurrency/max_multiplexing from previous "
228
+ "deployment of application with this name, if exists. Otherwise will "
229
+ "use the values from the application code."
230
+ ),
231
+ )
222
232
 
223
233
  parser.set_defaults(func=_deploy)
@@ -5,7 +5,7 @@ from contextlib import ExitStack
5
5
  from dataclasses import dataclass, field
6
6
  from datetime import datetime, timedelta
7
7
  from enum import Enum
8
- from typing import Any, Callable, Generic, Iterator, Literal, TypeVar
8
+ from typing import Any, Callable, Generic, Iterator, Literal, Optional, TypeVar
9
9
 
10
10
  import grpc
11
11
  import isolate_proto
@@ -214,7 +214,7 @@ class AliasInfo:
214
214
  class RunnerInfo:
215
215
  runner_id: str
216
216
  in_flight_requests: int
217
- expiration_countdown: int
217
+ expiration_countdown: Optional[int]
218
218
  uptime: timedelta
219
219
 
220
220
 
@@ -344,7 +344,9 @@ def _from_grpc_runner_info(message: isolate_proto.RunnerInfo) -> RunnerInfo:
344
344
  return RunnerInfo(
345
345
  runner_id=message.runner_id,
346
346
  in_flight_requests=message.in_flight_requests,
347
- expiration_countdown=message.expiration_countdown,
347
+ expiration_countdown=message.expiration_countdown
348
+ if message.HasField("expiration_countdown")
349
+ else None,
348
350
  uptime=timedelta(seconds=message.uptime),
349
351
  )
350
352
 
@@ -497,6 +499,7 @@ class FalServerlessConnection:
497
499
  machine_requirements: MachineRequirements | None = None,
498
500
  metadata: dict[str, Any] | None = None,
499
501
  deployment_strategy: Literal["recreate", "rolling"] = "recreate",
502
+ scale: bool = True,
500
503
  ) -> Iterator[isolate_proto.RegisterApplicationResult]:
501
504
  wrapped_function = to_serialized_object(function, serialization_method)
502
505
  if machine_requirements:
@@ -544,6 +547,7 @@ class FalServerlessConnection:
544
547
  auth_mode=auth_mode,
545
548
  metadata=struct_metadata,
546
549
  deployment_strategy=deployment_strategy_proto,
550
+ scale=scale,
547
551
  )
548
552
  for partial_result in self.stub.RegisterApplication(request):
549
553
  yield from_grpc(partial_result)
@@ -135,6 +135,8 @@ class File(BaseModel):
135
135
  FileRepository | RepositoryId
136
136
  ] = FALLBACK_REPOSITORY,
137
137
  request: Optional[Request] = None,
138
+ save_kwargs: Optional[dict] = None,
139
+ fallback_save_kwargs: Optional[dict] = None,
138
140
  ) -> File:
139
141
  repo = (
140
142
  repository
@@ -142,12 +144,15 @@ class File(BaseModel):
142
144
  else get_builtin_repository(repository)
143
145
  )
144
146
 
147
+ save_kwargs = save_kwargs or {}
148
+ fallback_save_kwargs = fallback_save_kwargs or {}
149
+
145
150
  fdata = FileData(data, content_type, file_name)
146
151
 
147
152
  object_lifecycle_preference = get_lifecycle_preference(request)
148
153
 
149
154
  try:
150
- url = repo.save(fdata, object_lifecycle_preference)
155
+ url = repo.save(fdata, object_lifecycle_preference, **save_kwargs)
151
156
  except Exception:
152
157
  if not fallback_repository:
153
158
  raise
@@ -158,7 +163,9 @@ class File(BaseModel):
158
163
  else get_builtin_repository(fallback_repository)
159
164
  )
160
165
 
161
- url = fallback_repo.save(fdata, object_lifecycle_preference)
166
+ url = fallback_repo.save(
167
+ fdata, object_lifecycle_preference, **fallback_save_kwargs
168
+ )
162
169
 
163
170
  return cls(
164
171
  url=url,
@@ -179,6 +186,8 @@ class File(BaseModel):
179
186
  FileRepository | RepositoryId
180
187
  ] = FALLBACK_REPOSITORY,
181
188
  request: Optional[Request] = None,
189
+ save_kwargs: Optional[dict] = None,
190
+ fallback_save_kwargs: Optional[dict] = None,
182
191
  ) -> File:
183
192
  file_path = Path(path)
184
193
  if not file_path.exists():
@@ -190,6 +199,9 @@ class File(BaseModel):
190
199
  else get_builtin_repository(repository)
191
200
  )
192
201
 
202
+ save_kwargs = save_kwargs or {}
203
+ fallback_save_kwargs = fallback_save_kwargs or {}
204
+
193
205
  content_type = content_type or "application/octet-stream"
194
206
  object_lifecycle_preference = get_lifecycle_preference(request)
195
207
 
@@ -199,6 +211,7 @@ class File(BaseModel):
199
211
  content_type=content_type,
200
212
  multipart=multipart,
201
213
  object_lifecycle_preference=object_lifecycle_preference,
214
+ **save_kwargs,
202
215
  )
203
216
  except Exception:
204
217
  if not fallback_repository:
@@ -215,6 +228,7 @@ class File(BaseModel):
215
228
  content_type=content_type,
216
229
  multipart=multipart,
217
230
  object_lifecycle_preference=object_lifecycle_preference,
231
+ **fallback_save_kwargs,
218
232
  )
219
233
 
220
234
  return cls(
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import posixpath
5
+ import uuid
6
+ from dataclasses import dataclass
7
+ from io import BytesIO
8
+ from typing import Optional
9
+
10
+ from fal.toolkit.file.types import FileData, FileRepository
11
+ from fal.toolkit.utils.retry import retry
12
+
13
+ DEFAULT_URL_TIMEOUT = 60 * 15 # 15 minutes
14
+
15
+
16
+ @dataclass
17
+ class S3Repository(FileRepository):
18
+ bucket_name: str = "fal_file_storage"
19
+ url_expiration: int = DEFAULT_URL_TIMEOUT
20
+ aws_access_key_id: str | None = None
21
+ aws_secret_access_key: str | None = None
22
+
23
+ _s3_client = None
24
+
25
+ def __post_init__(self):
26
+ try:
27
+ import boto3
28
+ from botocore.client import Config
29
+ except ImportError:
30
+ raise Exception("boto3 is not installed")
31
+
32
+ if self.aws_access_key_id is None:
33
+ self.aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID")
34
+ if self.aws_access_key_id is None:
35
+ raise Exception("AWS_ACCESS_KEY_ID environment variable is not set")
36
+
37
+ if self.aws_secret_access_key is None:
38
+ self.aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
39
+ if self.aws_secret_access_key is None:
40
+ raise Exception("AWS_SECRET_ACCESS_KEY environment variable is not set")
41
+
42
+ self._s3_client = boto3.client(
43
+ "s3",
44
+ aws_access_key_id=self.aws_access_key_id,
45
+ aws_secret_access_key=self.aws_secret_access_key,
46
+ config=Config(signature_version="s3v4"),
47
+ )
48
+
49
+ @property
50
+ def storage_client(self):
51
+ if self._s3_client is None:
52
+ raise Exception("S3 client is not initialized")
53
+
54
+ return self._s3_client
55
+
56
+ @retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
57
+ def save(
58
+ self,
59
+ data: FileData,
60
+ object_lifecycle_preference: Optional[dict[str, str]] = None,
61
+ key: Optional[str] = None,
62
+ ) -> str:
63
+ destination_path = posixpath.join(
64
+ key or "",
65
+ f"{uuid.uuid4().hex}_{data.file_name}",
66
+ )
67
+
68
+ self.storage_client.upload_fileobj(
69
+ BytesIO(data.data),
70
+ self.bucket_name,
71
+ destination_path,
72
+ ExtraArgs={"ContentType": data.content_type},
73
+ )
74
+
75
+ public_url = self.storage_client.generate_presigned_url(
76
+ ClientMethod="get_object",
77
+ Params={"Bucket": self.bucket_name, "Key": destination_path},
78
+ ExpiresIn=self.url_expiration,
79
+ )
80
+ return public_url
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import urllib.request
3
4
  from functools import lru_cache
4
5
  from typing import TYPE_CHECKING
5
- from urllib.request import Request, urlopen
6
6
 
7
7
  from .image import * # noqa: F403
8
8
 
@@ -62,8 +62,8 @@ def read_image_from_url(
62
62
  }
63
63
 
64
64
  try:
65
- request = Request(url, headers=TEMP_HEADERS)
66
- response = urlopen(request)
65
+ request = urllib.request.Request(url, headers=TEMP_HEADERS)
66
+ response = urllib.request.urlopen(request)
67
67
  image_pil = Image.open(response)
68
68
  except Exception:
69
69
  import traceback
@@ -516,6 +516,48 @@ def test_404_response(test_app: str, request: pytest.FixtureRequest):
516
516
  apps.run(test_app, path="/other", arguments={"lhs": 1, "rhs": 2})
517
517
 
518
518
 
519
+ def test_app_deploy_scale(aliased_app: tuple[str, str]):
520
+ import uuid
521
+ from dataclasses import replace
522
+
523
+ app_alias = str(uuid.uuid4()) + "-alias"
524
+ app_revision = addition_app.host.register(
525
+ func=addition_app.func,
526
+ options=addition_app.options,
527
+ application_name=app_alias,
528
+ application_auth_mode="private",
529
+ )
530
+
531
+ host: api.FalServerlessHost = addition_app.host # type: ignore
532
+ options = replace(
533
+ addition_app.options, host={**addition_app.options.host, "max_multiplexing": 30}
534
+ )
535
+ kwargs = dict(
536
+ func=addition_app.func,
537
+ options=options,
538
+ application_name=app_alias,
539
+ application_auth_mode="private",
540
+ )
541
+
542
+ app_revision = addition_app.host.register(**kwargs, scale=False)
543
+
544
+ with host._connection as client:
545
+ res = client.list_aliases()
546
+ found = next(filter(lambda alias: alias.alias == app_alias, res), None)
547
+ assert found, f"Could not find app {app_alias} in {res}"
548
+ assert found.revision == app_revision
549
+ assert found.max_multiplexing == 1
550
+
551
+ app_revision = addition_app.host.register(**kwargs, scale=True)
552
+
553
+ with host._connection as client:
554
+ res = client.list_aliases()
555
+ found = next(filter(lambda alias: alias.alias == app_alias, res), None)
556
+ assert found, f"Could not find app {app_alias} in {res}"
557
+ assert found.revision == app_revision
558
+ assert found.max_multiplexing == 30
559
+
560
+
519
561
  def test_app_update_app(aliased_app: tuple[str, str]):
520
562
  app_revision, app_alias = aliased_app
521
563
 
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