fal 1.26.4__tar.gz → 1.26.6__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 (187) hide show
  1. {fal-1.26.4/fal.egg-info → fal-1.26.6}/PKG-INFO +2 -2
  2. {fal-1.26.4 → fal-1.26.6/fal.egg-info}/PKG-INFO +2 -2
  3. {fal-1.26.4 → fal-1.26.6}/fal.egg-info/SOURCES.txt +1 -0
  4. {fal-1.26.4 → fal-1.26.6}/fal.egg-info/requires.txt +1 -1
  5. {fal-1.26.4 → fal-1.26.6}/pyproject.toml +1 -1
  6. {fal-1.26.4 → fal-1.26.6}/src/fal/_fal_version.py +2 -2
  7. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/apps.py +4 -1
  8. {fal-1.26.4 → fal-1.26.6}/src/fal/files.py +33 -10
  9. {fal-1.26.4 → fal-1.26.6}/src/fal/sdk.py +3 -2
  10. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/file/file.py +56 -43
  11. fal-1.26.6/tests/test_files.py +59 -0
  12. {fal-1.26.4 → fal-1.26.6}/tests/test_stability.py +40 -1
  13. fal-1.26.6/tests/toolkit/file_test.py +352 -0
  14. fal-1.26.4/tests/toolkit/file_test.py +0 -92
  15. {fal-1.26.4 → fal-1.26.6}/.gitignore +0 -0
  16. {fal-1.26.4 → fal-1.26.6}/Makefile +0 -0
  17. {fal-1.26.4 → fal-1.26.6}/README.md +0 -0
  18. {fal-1.26.4 → fal-1.26.6}/docs/conf.py +0 -0
  19. {fal-1.26.4 → fal-1.26.6}/docs/index.rst +0 -0
  20. {fal-1.26.4 → fal-1.26.6}/fal.egg-info/dependency_links.txt +0 -0
  21. {fal-1.26.4 → fal-1.26.6}/fal.egg-info/entry_points.txt +0 -0
  22. {fal-1.26.4 → fal-1.26.6}/fal.egg-info/top_level.txt +0 -0
  23. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/README.md +0 -0
  24. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  25. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  26. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  27. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  28. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  29. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  30. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  31. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  32. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  33. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  34. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  35. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  36. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  37. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  38. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  39. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  40. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  41. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  42. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  43. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  44. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  45. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  46. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  47. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  48. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  49. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  50. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  51. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  52. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  53. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  54. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  55. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  56. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  57. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  58. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  59. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  60. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  61. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  62. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  63. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  64. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  65. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  66. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  67. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  68. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  69. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  70. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  71. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  72. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  73. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  74. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  75. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  76. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  77. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  78. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  79. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  80. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  81. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  82. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  83. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  84. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  85. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  86. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  87. {fal-1.26.4 → fal-1.26.6}/openapi-fal-rest/pyproject.toml +0 -0
  88. {fal-1.26.4 → fal-1.26.6}/openapi_rest.config.yaml +0 -0
  89. {fal-1.26.4 → fal-1.26.6}/setup.cfg +0 -0
  90. {fal-1.26.4 → fal-1.26.6}/src/fal/__init__.py +0 -0
  91. {fal-1.26.4 → fal-1.26.6}/src/fal/__main__.py +0 -0
  92. {fal-1.26.4 → fal-1.26.6}/src/fal/_serialization.py +0 -0
  93. {fal-1.26.4 → fal-1.26.6}/src/fal/_version.py +0 -0
  94. {fal-1.26.4 → fal-1.26.6}/src/fal/api.py +0 -0
  95. {fal-1.26.4 → fal-1.26.6}/src/fal/app.py +0 -0
  96. {fal-1.26.4 → fal-1.26.6}/src/fal/apps.py +0 -0
  97. {fal-1.26.4 → fal-1.26.6}/src/fal/auth/__init__.py +0 -0
  98. {fal-1.26.4 → fal-1.26.6}/src/fal/auth/auth0.py +0 -0
  99. {fal-1.26.4 → fal-1.26.6}/src/fal/auth/local.py +0 -0
  100. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/__init__.py +0 -0
  101. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/_utils.py +0 -0
  102. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/api.py +0 -0
  103. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/auth.py +0 -0
  104. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/cli_nested_json.py +0 -0
  105. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/create.py +0 -0
  106. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/debug.py +0 -0
  107. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/deploy.py +0 -0
  108. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/doctor.py +0 -0
  109. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/files.py +0 -0
  110. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/keys.py +0 -0
  111. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/main.py +0 -0
  112. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/parser.py +0 -0
  113. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/profile.py +0 -0
  114. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/run.py +0 -0
  115. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/runners.py +0 -0
  116. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/secrets.py +0 -0
  117. {fal-1.26.4 → fal-1.26.6}/src/fal/cli/teams.py +0 -0
  118. {fal-1.26.4 → fal-1.26.6}/src/fal/config.py +0 -0
  119. {fal-1.26.4 → fal-1.26.6}/src/fal/console/__init__.py +0 -0
  120. {fal-1.26.4 → fal-1.26.6}/src/fal/console/icons.py +0 -0
  121. {fal-1.26.4 → fal-1.26.6}/src/fal/console/ux.py +0 -0
  122. {fal-1.26.4 → fal-1.26.6}/src/fal/container.py +0 -0
  123. {fal-1.26.4 → fal-1.26.6}/src/fal/exceptions/__init__.py +0 -0
  124. {fal-1.26.4 → fal-1.26.6}/src/fal/exceptions/_base.py +0 -0
  125. {fal-1.26.4 → fal-1.26.6}/src/fal/exceptions/_cuda.py +0 -0
  126. {fal-1.26.4 → fal-1.26.6}/src/fal/exceptions/auth.py +0 -0
  127. {fal-1.26.4 → fal-1.26.6}/src/fal/flags.py +0 -0
  128. {fal-1.26.4 → fal-1.26.6}/src/fal/logging/__init__.py +0 -0
  129. {fal-1.26.4 → fal-1.26.6}/src/fal/logging/isolate.py +0 -0
  130. {fal-1.26.4 → fal-1.26.6}/src/fal/logging/style.py +0 -0
  131. {fal-1.26.4 → fal-1.26.6}/src/fal/logging/trace.py +0 -0
  132. {fal-1.26.4 → fal-1.26.6}/src/fal/logging/user.py +0 -0
  133. {fal-1.26.4 → fal-1.26.6}/src/fal/project.py +0 -0
  134. {fal-1.26.4 → fal-1.26.6}/src/fal/py.typed +0 -0
  135. {fal-1.26.4 → fal-1.26.6}/src/fal/rest_client.py +0 -0
  136. {fal-1.26.4 → fal-1.26.6}/src/fal/sync.py +0 -0
  137. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/__init__.py +0 -0
  138. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/audio/__init__.py +0 -0
  139. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/audio/audio.py +0 -0
  140. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/exceptions.py +0 -0
  141. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/file/__init__.py +0 -0
  142. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/file/providers/fal.py +0 -0
  143. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/file/providers/gcp.py +0 -0
  144. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/file/providers/r2.py +0 -0
  145. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/file/providers/s3.py +0 -0
  146. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/file/types.py +0 -0
  147. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/image/__init__.py +0 -0
  148. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/image/image.py +0 -0
  149. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  150. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  151. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  152. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  153. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  154. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/image/safety_checker.py +0 -0
  155. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/kv.py +0 -0
  156. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/optimize.py +0 -0
  157. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/types.py +0 -0
  158. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/utils/__init__.py +0 -0
  159. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/utils/download_utils.py +0 -0
  160. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/utils/endpoint.py +0 -0
  161. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/utils/retry.py +0 -0
  162. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/video/__init__.py +0 -0
  163. {fal-1.26.4 → fal-1.26.6}/src/fal/toolkit/video/video.py +0 -0
  164. {fal-1.26.4 → fal-1.26.6}/src/fal/utils.py +0 -0
  165. {fal-1.26.4 → fal-1.26.6}/src/fal/workflows.py +0 -0
  166. {fal-1.26.4 → fal-1.26.6}/tests/__init__.py +0 -0
  167. {fal-1.26.4 → fal-1.26.6}/tests/assets/cat.png +0 -0
  168. {fal-1.26.4 → fal-1.26.6}/tests/cli/__init__.py +0 -0
  169. {fal-1.26.4 → fal-1.26.6}/tests/cli/test_apps.py +0 -0
  170. {fal-1.26.4 → fal-1.26.6}/tests/cli/test_auth.py +0 -0
  171. {fal-1.26.4 → fal-1.26.6}/tests/cli/test_deploy.py +0 -0
  172. {fal-1.26.4 → fal-1.26.6}/tests/cli/test_keys.py +0 -0
  173. {fal-1.26.4 → fal-1.26.6}/tests/cli/test_run.py +0 -0
  174. {fal-1.26.4 → fal-1.26.6}/tests/cli/test_secrets.py +0 -0
  175. {fal-1.26.4 → fal-1.26.6}/tests/conftest.py +0 -0
  176. {fal-1.26.4 → fal-1.26.6}/tests/integration_test.py +0 -0
  177. {fal-1.26.4 → fal-1.26.6}/tests/mainify_package/__init__.py +0 -0
  178. {fal-1.26.4 → fal-1.26.6}/tests/mainify_package/impl.py +0 -0
  179. {fal-1.26.4 → fal-1.26.6}/tests/mainify_package/utils.py +0 -0
  180. {fal-1.26.4 → fal-1.26.6}/tests/mainify_target.py +0 -0
  181. {fal-1.26.4 → fal-1.26.6}/tests/test_apps.py +0 -0
  182. {fal-1.26.4 → fal-1.26.6}/tests/test_kv.py +0 -0
  183. {fal-1.26.4 → fal-1.26.6}/tests/toolkit/file/providers/test_fal_retry.py +0 -0
  184. {fal-1.26.4 → fal-1.26.6}/tests/toolkit/image_test.py +0 -0
  185. {fal-1.26.4 → fal-1.26.6}/tests/toolkit/test_types.py +0 -0
  186. {fal-1.26.4 → fal-1.26.6}/tests/toolkit/utils/retry.py +0 -0
  187. {fal-1.26.4 → fal-1.26.6}/tools/demo_script.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.26.4
3
+ Version: 1.26.6
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.19.0,>=0.18.0
9
- Requires-Dist: isolate-proto<0.11.0,>=0.10.0
9
+ Requires-Dist: isolate-proto<0.11.0,>=0.10.2
10
10
  Requires-Dist: grpcio<2,>=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.4
2
2
  Name: fal
3
- Version: 1.26.4
3
+ Version: 1.26.6
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.19.0,>=0.18.0
9
- Requires-Dist: isolate-proto<0.11.0,>=0.10.0
9
+ Requires-Dist: isolate-proto<0.11.0,>=0.10.2
10
10
  Requires-Dist: grpcio<2,>=1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -162,6 +162,7 @@ tests/conftest.py
162
162
  tests/integration_test.py
163
163
  tests/mainify_target.py
164
164
  tests/test_apps.py
165
+ tests/test_files.py
165
166
  tests/test_kv.py
166
167
  tests/test_stability.py
167
168
  tests/assets/cat.png
@@ -1,5 +1,5 @@
1
1
  isolate[build]<0.19.0,>=0.18.0
2
- isolate-proto<0.11.0,>=0.10.0
2
+ isolate-proto<0.11.0,>=0.10.2
3
3
  grpcio<2,>=1.64.0
4
4
  dill==0.3.7
5
5
  cloudpickle==3.0.0
@@ -23,7 +23,7 @@ readme = "README.md"
23
23
  requires-python = ">=3.8"
24
24
  dependencies = [
25
25
  "isolate[build]>=0.18.0,<0.19.0",
26
- "isolate-proto>=0.10.0,<0.11.0",
26
+ "isolate-proto>=0.10.2,<0.11.0",
27
27
  "grpcio>=1.64.0,<2",
28
28
  "dill==0.3.7",
29
29
  "cloudpickle==3.0.0",
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.26.4'
21
- __version_tuple__ = version_tuple = (1, 26, 4)
20
+ __version__ = version = '1.26.6'
21
+ __version_tuple__ = version_tuple = (1, 26, 6)
@@ -246,7 +246,10 @@ def _add_scale_parser(subparsers, parents):
246
246
  def _set_rev(args):
247
247
  client = get_client(args.host, args.team)
248
248
  with client.connect() as connection:
249
- connection.create_alias(args.app_name, args.app_rev, args.auth)
249
+ alias_info = connection.create_alias(args.app_name, args.app_rev, args.auth)
250
+ table = _apps_table([alias_info])
251
+
252
+ args.console.print(table)
250
253
 
251
254
 
252
255
  def _add_set_rev_parser(subparsers, parents):
@@ -56,6 +56,16 @@ class FalFileSystem(AbstractFileSystem):
56
56
  raise FalServerlessException(detail)
57
57
  return response
58
58
 
59
+ def _abspath(self, rpath):
60
+ if rpath.startswith("/"):
61
+ return rpath
62
+
63
+ cwd = "/data"
64
+ if rpath in [".", ""]:
65
+ return cwd
66
+
67
+ return posixpath.join(cwd, rpath)
68
+
59
69
  def _ls(self, path):
60
70
  response = self._request("GET", f"/files/list/{path}")
61
71
  files = response.json()
@@ -73,7 +83,7 @@ class FalFileSystem(AbstractFileSystem):
73
83
  )
74
84
 
75
85
  def ls(self, path, detail=True, **kwargs):
76
- abs_path = "/" + path.lstrip("/")
86
+ abs_path = self._abspath(path)
77
87
  if abs_path in self.dircache:
78
88
  entries = self.dircache[abs_path]
79
89
  elif abs_path in ["/", "", "."]:
@@ -95,20 +105,29 @@ class FalFileSystem(AbstractFileSystem):
95
105
  return [entry["name"] for entry in entries]
96
106
 
97
107
  def info(self, path, **kwargs):
98
- parent = posixpath.dirname(path)
108
+ abs_path = self._abspath(path)
109
+ if abs_path == "/":
110
+ return {
111
+ "name": "/",
112
+ "size": 0,
113
+ "type": "directory",
114
+ "mtime": 0,
115
+ }
116
+ parent = posixpath.dirname(abs_path)
99
117
  entries = self.ls(parent, detail=True)
100
118
  for entry in entries:
101
- if entry["name"] == path:
119
+ if entry["name"] == abs_path:
102
120
  return entry
103
- raise FileNotFoundError(f"File not found: {path}")
121
+ raise FileNotFoundError(f"File not found: {abs_path}")
104
122
 
105
123
  def get_file(self, rpath, lpath, **kwargs):
106
- if self.isdir(rpath):
124
+ abs_rpath = self._abspath(rpath)
125
+ if self.isdir(abs_rpath):
107
126
  os.makedirs(lpath, exist_ok=True)
108
127
  return
109
128
 
110
129
  with open(lpath, "wb") as fobj:
111
- response = self._request("GET", f"/files/file/{rpath}")
130
+ response = self._request("GET", f"/files/file/{abs_rpath}")
112
131
  fobj.write(response.content)
113
132
 
114
133
  def _put_file_part(self, rpath, lpath, upload_id, part_number, chunk_size):
@@ -177,6 +196,8 @@ class FalFileSystem(AbstractFileSystem):
177
196
  if os.path.isdir(lpath):
178
197
  return
179
198
 
199
+ abs_rpath = self._abspath(rpath)
200
+
180
201
  size = os.path.getsize(lpath)
181
202
  with Progress(
182
203
  SpinnerColumn(),
@@ -185,29 +206,31 @@ class FalFileSystem(AbstractFileSystem):
185
206
  TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
186
207
  ) as progress:
187
208
  if size > MULTIPART_THRESHOLD:
188
- self._put_file_multipart(lpath, rpath, size, progress)
209
+ self._put_file_multipart(lpath, abs_rpath, size, progress)
189
210
  else:
190
211
  task = progress.add_task(f"{os.path.basename(lpath)}", total=1)
191
212
  with open(lpath, "rb") as fobj:
192
213
  self._request(
193
214
  "POST",
194
- f"/files/file/local/{rpath}",
215
+ f"/files/file/local/{abs_rpath}",
195
216
  files={"file_upload": (posixpath.basename(lpath), fobj)},
196
217
  )
197
218
  progress.advance(task)
198
219
  self.dircache.clear()
199
220
 
200
221
  def put_file_from_url(self, url, rpath, mode="overwrite", **kwargs):
222
+ abs_rpath = self._abspath(rpath)
201
223
  self._request(
202
224
  "POST",
203
- f"/files/file/url/{rpath}",
225
+ f"/files/file/url/{abs_rpath}",
204
226
  json={"url": url},
205
227
  )
206
228
  self.dircache.clear()
207
229
 
208
230
  def rm(self, path, **kwargs):
231
+ abs_path = self._abspath(path)
209
232
  self._request(
210
233
  "DELETE",
211
- f"/files/file/{path}",
234
+ f"/files/file/{abs_path}",
212
235
  )
213
236
  self.dircache.clear()
@@ -735,7 +735,7 @@ class FalServerlessConnection:
735
735
  alias: str,
736
736
  revision: str,
737
737
  auth_mode: AuthMode,
738
- ):
738
+ ) -> AliasInfo:
739
739
  if auth_mode == "public":
740
740
  auth = isolate_proto.ApplicationAuthMode.PUBLIC
741
741
  elif auth_mode == "shared":
@@ -750,7 +750,8 @@ class FalServerlessConnection:
750
750
  revision=revision,
751
751
  auth_mode=auth,
752
752
  )
753
- self.stub.SetAlias(request)
753
+ res = self.stub.SetAlias(request)
754
+ return from_grpc(res.alias_info)
754
755
 
755
756
  def delete_alias(self, alias: str) -> str | None:
756
757
  request = isolate_proto.DeleteAliasRequest(alias=alias)
@@ -73,6 +73,42 @@ def FileField(*args, **kwargs):
73
73
  return Field(*args, **kwargs)
74
74
 
75
75
 
76
+ def _try_with_fallback(
77
+ func: str,
78
+ args: list[Any],
79
+ repository: FileRepository | RepositoryId,
80
+ fallback_repository: Optional[
81
+ FileRepository | RepositoryId | list[FileRepository | RepositoryId]
82
+ ],
83
+ save_kwargs: dict,
84
+ fallback_save_kwargs: dict,
85
+ ) -> Any:
86
+ if fallback_repository is None:
87
+ fallback_repositories = []
88
+ elif isinstance(fallback_repository, list):
89
+ fallback_repositories = fallback_repository
90
+ else:
91
+ fallback_repositories = [fallback_repository]
92
+
93
+ attempts = [
94
+ (repository, save_kwargs),
95
+ *((fallback, fallback_save_kwargs) for fallback in fallback_repositories),
96
+ ]
97
+ for idx, (repo, kwargs) in enumerate(attempts):
98
+ repo_obj = get_builtin_repository(repo)
99
+ try:
100
+ return getattr(repo_obj, func)(*args, **kwargs)
101
+ except Exception as exc:
102
+ if idx >= len(attempts) - 1:
103
+ raise
104
+
105
+ traceback.print_exc()
106
+ print(
107
+ f"Failed to {func} to repository {repo}: {exc}, "
108
+ f"falling back to {attempts[idx + 1][0]}"
109
+ )
110
+
111
+
76
112
  class File(BaseModel):
77
113
  # public properties
78
114
  url: str = Field(
@@ -154,14 +190,12 @@ class File(BaseModel):
154
190
  file_name: Optional[str] = None,
155
191
  repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
156
192
  fallback_repository: Optional[
157
- FileRepository | RepositoryId
193
+ FileRepository | RepositoryId | list[FileRepository | RepositoryId]
158
194
  ] = FALLBACK_REPOSITORY,
159
195
  request: Optional[Request] = None,
160
196
  save_kwargs: Optional[dict] = None,
161
197
  fallback_save_kwargs: Optional[dict] = None,
162
198
  ) -> File:
163
- repo = get_builtin_repository(repository)
164
-
165
199
  save_kwargs = save_kwargs or {}
166
200
  fallback_save_kwargs = fallback_save_kwargs or {}
167
201
 
@@ -177,21 +211,14 @@ class File(BaseModel):
177
211
  "object_lifecycle_preference", object_lifecycle_preference
178
212
  )
179
213
 
180
- try:
181
- url = repo.save(fdata, **save_kwargs)
182
- except Exception as exc:
183
- if not fallback_repository:
184
- raise
185
-
186
- traceback.print_exc()
187
- print(
188
- f"Failed to save bytes to repository {repository}: {exc}, "
189
- f"falling back to {fallback_repository}"
190
- )
191
-
192
- fallback_repo = get_builtin_repository(fallback_repository)
193
-
194
- url = fallback_repo.save(fdata, **fallback_save_kwargs)
214
+ url = _try_with_fallback(
215
+ "save",
216
+ [fdata],
217
+ repository=repository,
218
+ fallback_repository=fallback_repository,
219
+ save_kwargs=save_kwargs,
220
+ fallback_save_kwargs=fallback_save_kwargs,
221
+ )
195
222
 
196
223
  return cls(
197
224
  url=url,
@@ -209,7 +236,7 @@ class File(BaseModel):
209
236
  repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
210
237
  multipart: bool | None = None,
211
238
  fallback_repository: Optional[
212
- FileRepository | RepositoryId
239
+ FileRepository | RepositoryId | list[FileRepository | RepositoryId]
213
240
  ] = FALLBACK_REPOSITORY,
214
241
  request: Optional[Request] = None,
215
242
  save_kwargs: Optional[dict] = None,
@@ -219,8 +246,6 @@ class File(BaseModel):
219
246
  if not file_path.exists():
220
247
  raise FileNotFoundError(f"File {file_path} does not exist")
221
248
 
222
- repo = get_builtin_repository(repository)
223
-
224
249
  save_kwargs = save_kwargs or {}
225
250
  fallback_save_kwargs = fallback_save_kwargs or {}
226
251
 
@@ -238,29 +263,17 @@ class File(BaseModel):
238
263
  save_kwargs.setdefault("multipart", multipart)
239
264
  fallback_save_kwargs.setdefault("multipart", multipart)
240
265
 
241
- try:
242
- url, data = repo.save_file(
243
- file_path,
244
- content_type=content_type,
245
- **save_kwargs,
246
- )
247
- except Exception as exc:
248
- if not fallback_repository:
249
- raise
250
-
251
- traceback.print_exc()
252
- print(
253
- f"Failed to save file to repository {repository}: {exc}, "
254
- f"falling back to {fallback_repository}"
255
- )
256
-
257
- fallback_repo = get_builtin_repository(fallback_repository)
266
+ save_kwargs.setdefault("content_type", content_type)
267
+ fallback_save_kwargs.setdefault("content_type", content_type)
258
268
 
259
- url, data = fallback_repo.save_file(
260
- file_path,
261
- content_type=content_type,
262
- **fallback_save_kwargs,
263
- )
269
+ url, data = _try_with_fallback(
270
+ "save_file",
271
+ [file_path],
272
+ repository=repository,
273
+ fallback_repository=fallback_repository,
274
+ save_kwargs=save_kwargs,
275
+ fallback_save_kwargs=fallback_save_kwargs,
276
+ )
264
277
 
265
278
  return cls(
266
279
  url=url,
@@ -0,0 +1,59 @@
1
+ import posixpath
2
+ import uuid
3
+
4
+ from fal.files import FalFileSystem
5
+
6
+
7
+ def test_fal_fs(tmp_path):
8
+ (tmp_path / "myfile").write_text("myfile")
9
+ (tmp_path / "mydir").mkdir()
10
+ (tmp_path / "mydir" / "myfile1").write_text("myfile1")
11
+ (tmp_path / "mydir" / "myfile2").write_text("myfile2")
12
+
13
+ (tmp_path / "downloaded").mkdir()
14
+
15
+ remote_temp_dir = f"/data/tmp/{uuid.uuid4()}"
16
+
17
+ fs = FalFileSystem()
18
+
19
+ assert fs.exists("/")
20
+ assert fs.isdir("/")
21
+ assert fs.exists("/data")
22
+ assert fs.isdir("/data")
23
+ assert fs.exists(".")
24
+ assert fs.isdir(".")
25
+
26
+ fs.put(str(tmp_path / "myfile"), remote_temp_dir + "/myfile")
27
+ assert fs.exists(remote_temp_dir + "/myfile")
28
+ assert fs.isdir(remote_temp_dir)
29
+ assert fs.isfile(remote_temp_dir + "/myfile")
30
+ assert not fs.isdir(remote_temp_dir + "/myfile")
31
+ fs.get(remote_temp_dir + "/myfile", str(tmp_path / "downloaded" / "myfile"))
32
+ assert (tmp_path / "downloaded" / "myfile").read_text() == "myfile"
33
+
34
+ fs.put(str(tmp_path / "mydir"), remote_temp_dir + "/mydir", recursive=True)
35
+ assert fs.exists(remote_temp_dir + "/mydir")
36
+ assert fs.isdir(remote_temp_dir + "/mydir")
37
+ assert fs.isfile(remote_temp_dir + "/mydir/myfile1")
38
+ assert fs.isfile(remote_temp_dir + "/mydir/myfile2")
39
+ fs.get(
40
+ remote_temp_dir + "/mydir",
41
+ str(tmp_path / "downloaded" / "mydir"),
42
+ recursive=True,
43
+ )
44
+ assert (tmp_path / "downloaded" / "mydir" / "myfile1").read_text() == "myfile1"
45
+ assert (tmp_path / "downloaded" / "mydir" / "myfile2").read_text() == "myfile2"
46
+
47
+ assert fs.ls(remote_temp_dir, detail=False) == [
48
+ remote_temp_dir + "/mydir",
49
+ remote_temp_dir + "/myfile",
50
+ ]
51
+ assert fs.ls(posixpath.relpath(remote_temp_dir, "/data"), detail=False) == [
52
+ remote_temp_dir + "/mydir",
53
+ remote_temp_dir + "/myfile",
54
+ ]
55
+
56
+ fs.rm(remote_temp_dir + "/myfile")
57
+ assert not fs.exists(remote_temp_dir + "/myfile")
58
+ fs.rm(remote_temp_dir + "/mydir", recursive=True)
59
+ assert not fs.exists(remote_temp_dir + "/mydir")
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import subprocess
4
+ import uuid
4
5
  from contextlib import suppress
5
6
 
6
7
  import pytest
@@ -82,6 +83,44 @@ def test_regular_function_in_a_container(isolated_client):
82
83
  assert mult(5, 2) == 10
83
84
 
84
85
 
86
+ def test_container_no_venv(isolated_client):
87
+ actual_python = active_python()
88
+
89
+ @isolated_client(
90
+ "container",
91
+ image=ContainerImage.from_dockerfile_str(
92
+ f"""
93
+ FROM python:{actual_python}
94
+ # {uuid.uuid4()}
95
+ """
96
+ ),
97
+ )
98
+ def myfunc():
99
+ return 42
100
+
101
+ assert myfunc() == 42
102
+
103
+
104
+ def test_container_venv(isolated_client):
105
+ actual_python = active_python()
106
+
107
+ @isolated_client(
108
+ "container",
109
+ image=ContainerImage.from_dockerfile_str(
110
+ f"""
111
+ FROM python:{actual_python}
112
+ # {uuid.uuid4()}
113
+ RUN python -m venv .venv
114
+ ENV PATH="/root/.venv/bin:$PATH"
115
+ """
116
+ ),
117
+ )
118
+ def myfunc():
119
+ return 42
120
+
121
+ assert myfunc() == 42
122
+
123
+
85
124
  def test_regular_function_in_a_container_with_custom_image(isolated_client):
86
125
  actual_python = active_python()
87
126
 
@@ -584,7 +623,7 @@ def test_worker_env_vars(isolated_client):
584
623
  [
585
624
  (
586
625
  "fal",
587
- "https://storage.googleapis.com/isolate-dev-smiling-shark_toolkit_bucket/",
626
+ "https://storage.googleapis.com/isolate-dev-smiling-shark_toolkit_public_bucket/",
588
627
  ),
589
628
  ("fal_v2", "https://v2.fal.media/files"),
590
629
  ("fal_v3", "https://v3.fal.media/files"),