fal 1.13.5__tar.gz → 1.15.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 (177) hide show
  1. {fal-1.13.5/fal.egg-info → fal-1.15.0}/PKG-INFO +3 -2
  2. {fal-1.13.5 → fal-1.15.0/fal.egg-info}/PKG-INFO +3 -2
  3. {fal-1.13.5 → fal-1.15.0}/fal.egg-info/SOURCES.txt +2 -0
  4. {fal-1.13.5 → fal-1.15.0}/fal.egg-info/requires.txt +2 -1
  5. {fal-1.13.5 → fal-1.15.0}/pyproject.toml +2 -1
  6. {fal-1.13.5 → fal-1.15.0}/src/fal/_fal_version.py +2 -2
  7. {fal-1.13.5 → fal-1.15.0}/src/fal/auth/__init__.py +20 -0
  8. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/_utils.py +1 -1
  9. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/apps.py +6 -1
  10. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/auth.py +30 -24
  11. fal-1.15.0/src/fal/cli/files.py +70 -0
  12. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/main.py +2 -0
  13. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/profile.py +7 -4
  14. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/teams.py +2 -9
  15. {fal-1.13.5 → fal-1.15.0}/src/fal/config.py +17 -4
  16. {fal-1.13.5 → fal-1.15.0}/src/fal/container.py +3 -4
  17. fal-1.15.0/src/fal/files.py +74 -0
  18. {fal-1.13.5 → fal-1.15.0}/src/fal/sdk.py +16 -4
  19. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/file/providers/fal.py +35 -22
  20. {fal-1.13.5 → fal-1.15.0}/tests/cli/test_deploy.py +1 -1
  21. {fal-1.13.5 → fal-1.15.0}/tests/cli/test_run.py +1 -1
  22. {fal-1.13.5 → fal-1.15.0}/tests/test_apps.py +25 -28
  23. {fal-1.13.5 → fal-1.15.0}/.gitignore +0 -0
  24. {fal-1.13.5 → fal-1.15.0}/Makefile +0 -0
  25. {fal-1.13.5 → fal-1.15.0}/README.md +0 -0
  26. {fal-1.13.5 → fal-1.15.0}/docs/conf.py +0 -0
  27. {fal-1.13.5 → fal-1.15.0}/docs/index.rst +0 -0
  28. {fal-1.13.5 → fal-1.15.0}/fal.egg-info/dependency_links.txt +0 -0
  29. {fal-1.13.5 → fal-1.15.0}/fal.egg-info/entry_points.txt +0 -0
  30. {fal-1.13.5 → fal-1.15.0}/fal.egg-info/top_level.txt +0 -0
  31. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/README.md +0 -0
  32. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  33. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  34. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  35. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  36. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  37. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  38. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  39. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  40. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  41. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  42. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  43. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  44. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  45. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  46. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  47. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  48. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  49. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  50. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  51. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  52. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  53. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  54. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  55. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  56. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  57. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  58. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  59. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  60. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  61. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  62. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  63. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  64. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  65. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  66. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  67. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  68. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  69. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  70. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  71. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  72. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  73. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  74. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  75. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  76. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  77. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  78. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  79. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  80. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  81. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  82. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  83. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  84. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  85. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  86. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  87. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  88. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  89. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  90. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  91. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  92. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  93. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  94. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  95. {fal-1.13.5 → fal-1.15.0}/openapi-fal-rest/pyproject.toml +0 -0
  96. {fal-1.13.5 → fal-1.15.0}/openapi_rest.config.yaml +0 -0
  97. {fal-1.13.5 → fal-1.15.0}/setup.cfg +0 -0
  98. {fal-1.13.5 → fal-1.15.0}/src/fal/__init__.py +0 -0
  99. {fal-1.13.5 → fal-1.15.0}/src/fal/__main__.py +0 -0
  100. {fal-1.13.5 → fal-1.15.0}/src/fal/_serialization.py +0 -0
  101. {fal-1.13.5 → fal-1.15.0}/src/fal/_version.py +0 -0
  102. {fal-1.13.5 → fal-1.15.0}/src/fal/api.py +0 -0
  103. {fal-1.13.5 → fal-1.15.0}/src/fal/app.py +0 -0
  104. {fal-1.13.5 → fal-1.15.0}/src/fal/apps.py +0 -0
  105. {fal-1.13.5 → fal-1.15.0}/src/fal/auth/auth0.py +0 -0
  106. {fal-1.13.5 → fal-1.15.0}/src/fal/auth/local.py +0 -0
  107. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/__init__.py +0 -0
  108. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/api.py +0 -0
  109. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/cli_nested_json.py +0 -0
  110. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/create.py +0 -0
  111. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/debug.py +0 -0
  112. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/deploy.py +0 -0
  113. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/doctor.py +0 -0
  114. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/keys.py +0 -0
  115. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/parser.py +0 -0
  116. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/run.py +0 -0
  117. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/runners.py +0 -0
  118. {fal-1.13.5 → fal-1.15.0}/src/fal/cli/secrets.py +0 -0
  119. {fal-1.13.5 → fal-1.15.0}/src/fal/console/__init__.py +0 -0
  120. {fal-1.13.5 → fal-1.15.0}/src/fal/console/icons.py +0 -0
  121. {fal-1.13.5 → fal-1.15.0}/src/fal/console/ux.py +0 -0
  122. {fal-1.13.5 → fal-1.15.0}/src/fal/exceptions/__init__.py +0 -0
  123. {fal-1.13.5 → fal-1.15.0}/src/fal/exceptions/_base.py +0 -0
  124. {fal-1.13.5 → fal-1.15.0}/src/fal/exceptions/_cuda.py +0 -0
  125. {fal-1.13.5 → fal-1.15.0}/src/fal/exceptions/auth.py +0 -0
  126. {fal-1.13.5 → fal-1.15.0}/src/fal/flags.py +0 -0
  127. {fal-1.13.5 → fal-1.15.0}/src/fal/logging/__init__.py +0 -0
  128. {fal-1.13.5 → fal-1.15.0}/src/fal/logging/isolate.py +0 -0
  129. {fal-1.13.5 → fal-1.15.0}/src/fal/logging/style.py +0 -0
  130. {fal-1.13.5 → fal-1.15.0}/src/fal/logging/trace.py +0 -0
  131. {fal-1.13.5 → fal-1.15.0}/src/fal/logging/user.py +0 -0
  132. /fal-1.13.5/src/fal/files.py → /fal-1.15.0/src/fal/project.py +0 -0
  133. {fal-1.13.5 → fal-1.15.0}/src/fal/py.typed +0 -0
  134. {fal-1.13.5 → fal-1.15.0}/src/fal/rest_client.py +0 -0
  135. {fal-1.13.5 → fal-1.15.0}/src/fal/sync.py +0 -0
  136. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/__init__.py +0 -0
  137. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/exceptions.py +0 -0
  138. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/file/__init__.py +0 -0
  139. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/file/file.py +0 -0
  140. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/file/providers/gcp.py +0 -0
  141. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/file/providers/r2.py +0 -0
  142. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/file/providers/s3.py +0 -0
  143. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/file/types.py +0 -0
  144. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/image/__init__.py +0 -0
  145. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/image/image.py +0 -0
  146. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  147. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  148. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  149. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  150. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  151. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/image/safety_checker.py +0 -0
  152. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/optimize.py +0 -0
  153. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/types.py +0 -0
  154. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/utils/__init__.py +0 -0
  155. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/utils/download_utils.py +0 -0
  156. {fal-1.13.5 → fal-1.15.0}/src/fal/toolkit/utils/retry.py +0 -0
  157. {fal-1.13.5 → fal-1.15.0}/src/fal/utils.py +0 -0
  158. {fal-1.13.5 → fal-1.15.0}/src/fal/workflows.py +0 -0
  159. {fal-1.13.5 → fal-1.15.0}/tests/__init__.py +0 -0
  160. {fal-1.13.5 → fal-1.15.0}/tests/assets/cat.png +0 -0
  161. {fal-1.13.5 → fal-1.15.0}/tests/cli/__init__.py +0 -0
  162. {fal-1.13.5 → fal-1.15.0}/tests/cli/test_apps.py +0 -0
  163. {fal-1.13.5 → fal-1.15.0}/tests/cli/test_auth.py +0 -0
  164. {fal-1.13.5 → fal-1.15.0}/tests/cli/test_keys.py +0 -0
  165. {fal-1.13.5 → fal-1.15.0}/tests/cli/test_secrets.py +0 -0
  166. {fal-1.13.5 → fal-1.15.0}/tests/conftest.py +0 -0
  167. {fal-1.13.5 → fal-1.15.0}/tests/integration_test.py +0 -0
  168. {fal-1.13.5 → fal-1.15.0}/tests/mainify_package/__init__.py +0 -0
  169. {fal-1.13.5 → fal-1.15.0}/tests/mainify_package/impl.py +0 -0
  170. {fal-1.13.5 → fal-1.15.0}/tests/mainify_package/utils.py +0 -0
  171. {fal-1.13.5 → fal-1.15.0}/tests/mainify_target.py +0 -0
  172. {fal-1.13.5 → fal-1.15.0}/tests/test_stability.py +0 -0
  173. {fal-1.13.5 → fal-1.15.0}/tests/toolkit/file_test.py +0 -0
  174. {fal-1.13.5 → fal-1.15.0}/tests/toolkit/image_test.py +0 -0
  175. {fal-1.13.5 → fal-1.15.0}/tests/toolkit/test_types.py +0 -0
  176. {fal-1.13.5 → fal-1.15.0}/tests/toolkit/utils/retry.py +0 -0
  177. {fal-1.13.5 → fal-1.15.0}/tools/demo_script.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.13.5
3
+ Version: 1.15.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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.17.0,>=0.16.2
9
- Requires-Dist: isolate-proto<0.8.0,>=0.7.2
9
+ Requires-Dist: isolate-proto<0.9.0,>=0.8.1
10
10
  Requires-Dist: grpcio==1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -38,6 +38,7 @@ Requires-Dist: uvicorn<1,>=0.29.0
38
38
  Requires-Dist: cookiecutter
39
39
  Requires-Dist: tomli<3,>2
40
40
  Requires-Dist: tomli-w<2,>=1
41
+ Requires-Dist: fsspec
41
42
  Provides-Extra: docs
42
43
  Requires-Dist: sphinx<8.2.0; extra == "docs"
43
44
  Requires-Dist: sphinx-rtd-theme; extra == "docs"
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.13.5
3
+ Version: 1.15.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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.17.0,>=0.16.2
9
- Requires-Dist: isolate-proto<0.8.0,>=0.7.2
9
+ Requires-Dist: isolate-proto<0.9.0,>=0.8.1
10
10
  Requires-Dist: grpcio==1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -38,6 +38,7 @@ Requires-Dist: uvicorn<1,>=0.29.0
38
38
  Requires-Dist: cookiecutter
39
39
  Requires-Dist: tomli<3,>2
40
40
  Requires-Dist: tomli-w<2,>=1
41
+ Requires-Dist: fsspec
41
42
  Provides-Extra: docs
42
43
  Requires-Dist: sphinx<8.2.0; extra == "docs"
43
44
  Requires-Dist: sphinx-rtd-theme; extra == "docs"
@@ -88,6 +88,7 @@ src/fal/config.py
88
88
  src/fal/container.py
89
89
  src/fal/files.py
90
90
  src/fal/flags.py
91
+ src/fal/project.py
91
92
  src/fal/py.typed
92
93
  src/fal/rest_client.py
93
94
  src/fal/sdk.py
@@ -107,6 +108,7 @@ src/fal/cli/create.py
107
108
  src/fal/cli/debug.py
108
109
  src/fal/cli/deploy.py
109
110
  src/fal/cli/doctor.py
111
+ src/fal/cli/files.py
110
112
  src/fal/cli/keys.py
111
113
  src/fal/cli/main.py
112
114
  src/fal/cli/parser.py
@@ -1,5 +1,5 @@
1
1
  isolate[build]<0.17.0,>=0.16.2
2
- isolate-proto<0.8.0,>=0.7.2
2
+ isolate-proto<0.9.0,>=0.8.1
3
3
  grpcio==1.64.0
4
4
  dill==0.3.7
5
5
  cloudpickle==3.0.0
@@ -30,6 +30,7 @@ uvicorn<1,>=0.29.0
30
30
  cookiecutter
31
31
  tomli<3,>2
32
32
  tomli-w<2,>=1
33
+ fsspec
33
34
 
34
35
  [:python_version < "3.10"]
35
36
  importlib-metadata>=4.4
@@ -23,7 +23,7 @@ readme = "README.md"
23
23
  requires-python = ">=3.8"
24
24
  dependencies = [
25
25
  "isolate[build]>=0.16.2,<0.17.0",
26
- "isolate-proto>=0.7.2,<0.8.0",
26
+ "isolate-proto>=0.8.1,<0.9.0",
27
27
  "grpcio==1.64.0",
28
28
  "dill==0.3.7",
29
29
  "cloudpickle==3.0.0",
@@ -58,6 +58,7 @@ dependencies = [
58
58
  "cookiecutter",
59
59
  "tomli>2,<3",
60
60
  "tomli-w>=1,<2",
61
+ "fsspec",
61
62
  ]
62
63
 
63
64
  [project.optional-dependencies]
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.13.5'
21
- __version_tuple__ = version_tuple = (1, 13, 5)
20
+ __version__ = version = '1.15.0'
21
+ __version_tuple__ = version_tuple = (1, 15, 0)
@@ -125,6 +125,26 @@ def _fetch_teams(bearer_token: str) -> list[dict]:
125
125
  raise FalServerlessException("Failed to fetch teams") from exc
126
126
 
127
127
 
128
+ def current_user_info(headers: dict[str, str]) -> dict:
129
+ import json
130
+ from urllib.error import HTTPError
131
+ from urllib.request import Request, urlopen
132
+
133
+ from fal.exceptions import FalServerlessException
134
+ from fal.flags import REST_URL
135
+
136
+ request = Request(
137
+ method="GET",
138
+ url=f"{REST_URL}/users/current",
139
+ headers=headers,
140
+ )
141
+ try:
142
+ with urlopen(request) as response:
143
+ return json.load(response)
144
+ except HTTPError as exc:
145
+ raise FalServerlessException("Failed to fetch user info") from exc
146
+
147
+
128
148
  def login(console):
129
149
  token_data = auth0.login(console)
130
150
  with local.lock_token():
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from fal.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
3
+ from fal.project import find_project_root, find_pyproject_toml, parse_pyproject_toml
4
4
 
5
5
 
6
6
  def get_client(host: str, team: str | None = None):
@@ -123,7 +123,7 @@ def _app_rev_table(revs: list[ApplicationInfo]):
123
123
  def _list_rev(args):
124
124
  client = get_client(args.host, args.team)
125
125
  with client.connect() as connection:
126
- revs = connection.list_applications()
126
+ revs = connection.list_applications(args.app_name)
127
127
  table = _app_rev_table(revs)
128
128
 
129
129
  args.console.print(table)
@@ -137,6 +137,11 @@ def _add_list_rev_parser(subparsers, parents):
137
137
  help=list_help,
138
138
  parents=parents,
139
139
  )
140
+ parser.add_argument(
141
+ "app_name",
142
+ nargs="?",
143
+ help="Application name.",
144
+ )
140
145
  parser.set_defaults(func=_list_rev)
141
146
 
142
147
 
@@ -1,6 +1,10 @@
1
+ from fal.auth import current_user_info
2
+ from fal.cli import profile
3
+ from fal.sdk import get_default_credentials
4
+
5
+
1
6
  def _login(args):
2
7
  from fal.auth import login
3
- from fal.config import Config
4
8
  from fal.console.icons import CHECK_ICON, CROSS_ICON
5
9
  from fal.exceptions import FalServerlessException
6
10
 
@@ -11,15 +15,12 @@ def _login(args):
11
15
  args.console.print(f"{CROSS_ICON} {e}")
12
16
  return
13
17
 
14
- with Config().edit() as config:
15
- config.unset("team")
16
-
18
+ _unset_account(args)
17
19
  _set_account(args)
18
20
 
19
21
 
20
22
  def _logout(args):
21
23
  from fal.auth import logout
22
- from fal.config import Config
23
24
  from fal.console.icons import CHECK_ICON, CROSS_ICON
24
25
  from fal.exceptions import FalServerlessException
25
26
 
@@ -30,8 +31,7 @@ def _logout(args):
30
31
  args.console.print(f"{CROSS_ICON} {e}")
31
32
  return
32
33
 
33
- with Config().edit() as config:
34
- config.unset("team")
34
+ _unset_account(args)
35
35
 
36
36
 
37
37
  def _list_accounts(args):
@@ -43,7 +43,7 @@ def _list_accounts(args):
43
43
 
44
44
  user_access = UserAccess()
45
45
  config = Config()
46
- current_account = config.get("team") or user_access.info["nickname"]
46
+ current_account_name = config.get_internal("team") or user_access.info["nickname"]
47
47
 
48
48
  table = Table(border_style=Style(frame=False), show_header=False)
49
49
  table.add_column("#")
@@ -51,7 +51,7 @@ def _list_accounts(args):
51
51
  table.add_column("Type")
52
52
 
53
53
  for idx, account in enumerate(user_access.accounts):
54
- selected = account["nickname"] == current_account
54
+ selected = account["nickname"] == current_account_name
55
55
  color = "bold yellow" if selected else None
56
56
 
57
57
  table.add_row(
@@ -64,6 +64,13 @@ def _list_accounts(args):
64
64
  args.console.print(table)
65
65
 
66
66
 
67
+ def _unset_account(args):
68
+ from fal.config import Config
69
+
70
+ with Config().edit() as config:
71
+ config.unset_internal("team")
72
+
73
+
67
74
  def _set_account(args):
68
75
  from rich.prompt import Prompt
69
76
 
@@ -105,25 +112,24 @@ def _set_account(args):
105
112
  )
106
113
 
107
114
  with Config().edit() as config:
108
- config.set("team", account["nickname"])
109
-
115
+ config.set_internal("team", account["nickname"])
110
116
 
111
- def _whoami(args):
112
- from fal.auth import UserAccess
113
- from fal.config import Config
117
+ # Unset the profile if set
118
+ if current_profile := config.get_internal("profile"):
119
+ args.console.print(
120
+ f"\n[yellow]Unsetting profile [cyan]{current_profile}[/] "
121
+ "to make team selection effective.[/]"
122
+ )
123
+ profile._unset(args, config=config)
114
124
 
115
- user_access = UserAccess()
116
- config = Config()
117
125
 
118
- team = config.get("team")
119
- if team:
120
- account = user_access.get_account(team)
121
- else:
122
- account = user_access.get_account(user_access.info["nickname"])
126
+ def _whoami(args):
127
+ creds = get_default_credentials()
128
+ user_info = current_user_info(creds.to_headers())
123
129
 
124
- nickname = account["nickname"]
125
- full_name = account["full_name"]
126
- user_id = account["user_id"]
130
+ full_name = user_info["full_name"]
131
+ nickname = user_info["nickname"]
132
+ user_id = user_info["user_id"]
127
133
 
128
134
  args.console.print(f"Hello, {full_name}: {nickname!r} - {user_id!r}")
129
135
 
@@ -0,0 +1,70 @@
1
+ from .parser import FalClientParser
2
+
3
+
4
+ def _list(args):
5
+ import posixpath
6
+
7
+ from fal.files import FalFileSystem
8
+
9
+ fs = FalFileSystem()
10
+
11
+ for entry in fs.ls(args.path, detail=True):
12
+ name = posixpath.basename(entry["name"])
13
+ color = "blue" if entry["type"] == "directory" else "default"
14
+ args.console.print(f"[{color}]{name}[/{color}]")
15
+
16
+
17
+ def _download(args):
18
+ from fal.files import FalFileSystem
19
+
20
+ fs = FalFileSystem()
21
+ fs.get(args.remote_path, args.local_path)
22
+
23
+
24
+ def _upload(args):
25
+ from fal.files import FalFileSystem
26
+
27
+ fs = FalFileSystem()
28
+ fs.put(args.local_path, args.remote_path)
29
+
30
+
31
+ def add_parser(main_subparsers, parents):
32
+ files_help = "Manage fal files."
33
+ parser = main_subparsers.add_parser(
34
+ "files",
35
+ aliases=["file"],
36
+ description=files_help,
37
+ help=files_help,
38
+ parents=parents,
39
+ )
40
+
41
+ subparsers = parser.add_subparsers(
42
+ title="Commands",
43
+ metavar="command",
44
+ required=True,
45
+ parser_class=FalClientParser,
46
+ )
47
+
48
+ list_parser = subparsers.add_parser("list", aliases=["ls"], parents=parents)
49
+ list_parser.add_argument(
50
+ "path",
51
+ nargs="?",
52
+ type=str,
53
+ help="The path to list",
54
+ default="/",
55
+ )
56
+ list_parser.set_defaults(func=_list)
57
+
58
+ download_parser = subparsers.add_parser("download", parents=parents)
59
+ download_parser.add_argument(
60
+ "remote_path", type=str, help="Remote path to download"
61
+ )
62
+ download_parser.add_argument(
63
+ "local_path", type=str, help="Local path to download to"
64
+ )
65
+ download_parser.set_defaults(func=_download)
66
+
67
+ upload_parser = subparsers.add_parser("upload", parents=parents)
68
+ upload_parser.add_argument("local_path", type=str, help="Local path to upload")
69
+ upload_parser.add_argument("remote_path", type=str, help="Remote path to upload to")
70
+ upload_parser.set_defaults(func=_upload)
@@ -13,6 +13,7 @@ from . import (
13
13
  create,
14
14
  deploy,
15
15
  doctor,
16
+ files,
16
17
  keys,
17
18
  profile,
18
19
  run,
@@ -57,6 +58,7 @@ def _get_main_parser() -> argparse.ArgumentParser:
57
58
  create,
58
59
  runners,
59
60
  teams,
61
+ files,
60
62
  ]:
61
63
  cmd.add_parser(subparsers, parents)
62
64
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from rich.table import Table
2
4
 
3
5
  from fal.config import Config
@@ -31,11 +33,12 @@ def _set(args):
31
33
  )
32
34
 
33
35
 
34
- def _unset(args):
35
- with Config().edit() as config:
36
- config.unset_internal("profile")
37
- args.console.print("Default profile unset.")
36
+ def _unset(args, config: Config | None = None):
37
+ config = config or Config()
38
+
39
+ with config.edit() as config:
38
40
  config.profile = None
41
+ args.console.print("Default profile unset.")
39
42
 
40
43
 
41
44
  def _key_set(args):
@@ -1,11 +1,4 @@
1
- from fal.cli.auth import _list_accounts, _set_account
2
-
3
-
4
- def _unset(args):
5
- from fal.config import Config
6
-
7
- with Config().edit() as config:
8
- config.unset("team")
1
+ from fal.cli.auth import _list_accounts, _set_account, _unset_account
9
2
 
10
3
 
11
4
  def add_parser(main_subparsers, parents):
@@ -51,4 +44,4 @@ def add_parser(main_subparsers, parents):
51
44
  help=unset_help,
52
45
  parents=parents,
53
46
  )
54
- unset_parser.set_defaults(func=_unset)
47
+ unset_parser.set_defaults(func=_unset_account)
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  from contextlib import contextmanager
3
- from typing import Dict, List, Optional
5
+ from typing import Dict, Iterator, List, Optional
4
6
 
5
7
  SETTINGS_SECTION = "__internal__" # legacy
6
8
  DEFAULT_PROFILE = "default"
@@ -9,6 +11,7 @@ DEFAULT_PROFILE = "default"
9
11
  class Config:
10
12
  _config: Dict[str, Dict[str, str]]
11
13
  _profile: Optional[str]
14
+ _editing: bool = False
12
15
 
13
16
  DEFAULT_CONFIG_PATH = "~/.fal/config.toml"
14
17
 
@@ -38,7 +41,11 @@ class Config:
38
41
  @profile.setter
39
42
  def profile(self, value: Optional[str]) -> None:
40
43
  if value and value not in self._config:
44
+ # Make sure the section exists
41
45
  self._config[value] = {}
46
+ self.set_internal("profile", value)
47
+ elif not value:
48
+ self.unset_internal("profile")
42
49
 
43
50
  self._profile = value
44
51
 
@@ -96,6 +103,12 @@ class Config:
96
103
  del self._config[profile]
97
104
 
98
105
  @contextmanager
99
- def edit(self):
100
- yield self
101
- self.save()
106
+ def edit(self) -> Iterator[Config]:
107
+ if self._editing:
108
+ # no-op
109
+ yield self
110
+ else:
111
+ self._editing = True
112
+ yield self
113
+ self.save()
114
+ self._editing = False
@@ -1,9 +1,8 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Dict, Literal
2
+ from typing import Dict, Literal, Optional
3
3
 
4
4
  Builder = Literal["depot", "service", "worker"]
5
5
  BUILDERS = {"depot", "service", "worker"}
6
- DEFAULT_BUILDER: Builder = "depot"
7
6
  DEFAULT_COMPRESSION: str = "gzip"
8
7
  DEFAULT_FORCE_COMPRESSION: bool = False
9
8
 
@@ -17,7 +16,7 @@ class ContainerImage:
17
16
  dockerfile_str: str
18
17
  build_args: Dict[str, str] = field(default_factory=dict)
19
18
  registries: Dict[str, Dict[str, str]] = field(default_factory=dict)
20
- builder: Builder = field(default=DEFAULT_BUILDER)
19
+ builder: Optional[Builder] = field(default=None)
21
20
  compression: str = DEFAULT_COMPRESSION
22
21
  force_compression: bool = DEFAULT_FORCE_COMPRESSION
23
22
 
@@ -30,7 +29,7 @@ class ContainerImage:
30
29
  "Username and password are required for each registry"
31
30
  )
32
31
 
33
- if self.builder not in BUILDERS:
32
+ if self.builder and self.builder not in BUILDERS:
34
33
  raise ValueError(
35
34
  f"Invalid builder: {self.builder}, must be one of {BUILDERS}"
36
35
  )
@@ -0,0 +1,74 @@
1
+ import posixpath
2
+ from functools import cached_property
3
+ from typing import TYPE_CHECKING
4
+
5
+ from fsspec import AbstractFileSystem
6
+
7
+ if TYPE_CHECKING:
8
+ import httpx
9
+
10
+ USER_AGENT = "fal-sdk/1.14.0 (python)"
11
+
12
+
13
+ class FalFileSystem(AbstractFileSystem):
14
+ @cached_property
15
+ def _client(self) -> "httpx.Client":
16
+ from httpx import Client
17
+
18
+ from fal.flags import REST_URL
19
+ from fal.sdk import get_default_credentials
20
+
21
+ creds = get_default_credentials()
22
+ return Client(
23
+ base_url=REST_URL,
24
+ headers={
25
+ **creds.to_headers(),
26
+ "User-Agent": USER_AGENT,
27
+ },
28
+ )
29
+
30
+ def ls(self, path, detail=True, **kwargs):
31
+ response = self._client.get(f"/files/list/{path.lstrip('/')}")
32
+ response.raise_for_status()
33
+ files = response.json()
34
+ if detail:
35
+ return sorted(
36
+ (
37
+ {
38
+ "name": entry["path"],
39
+ "size": entry["size"],
40
+ "type": "file" if entry["is_file"] else "directory",
41
+ "mtime": entry["updated_time"],
42
+ }
43
+ for entry in files
44
+ ),
45
+ key=lambda x: x["name"],
46
+ )
47
+ else:
48
+ return sorted(entry["path"] for entry in files)
49
+
50
+ def info(self, path, **kwargs):
51
+ parent = posixpath.dirname(path)
52
+ entries = self.ls(parent, detail=True)
53
+ for entry in entries:
54
+ if entry["name"] == path:
55
+ return entry
56
+ raise FileNotFoundError(f"File not found: {path}")
57
+
58
+ def get_file(self, rpath, lpath, **kwargs):
59
+ with open(lpath, "wb") as fobj:
60
+ response = self._client.get(f"/files/file/{rpath.lstrip('/')}")
61
+ response.raise_for_status()
62
+ fobj.write(response.content)
63
+
64
+ def put_file(self, lpath, rpath, mode="overwrite", **kwargs):
65
+ with open(lpath, "rb") as fobj:
66
+ response = self._client.post(
67
+ f"/files/file/local/{rpath.lstrip('/')}",
68
+ files={"file_upload": (posixpath.basename(lpath), fobj, "text/plain")},
69
+ )
70
+ response.raise_for_status()
71
+
72
+ def rm(self, path, **kwargs):
73
+ response = self._client.delete(f"/files/file/{path.lstrip('/')}")
74
+ response.raise_for_status()
@@ -157,7 +157,15 @@ class AuthenticatedCredentials(Credentials):
157
157
 
158
158
  def to_headers(self) -> dict[str, str]:
159
159
  token = self.user.bearer_token
160
- return {"Authorization": token}
160
+ headers = {
161
+ "Authorization": token,
162
+ }
163
+
164
+ if self.team:
165
+ team_id = self.user.get_account(self.team)["user_id"]
166
+ headers["X-Fal-User-Id"] = team_id
167
+
168
+ return headers
161
169
 
162
170
 
163
171
  @dataclass
@@ -193,7 +201,7 @@ def get_default_credentials(team: str | None = None) -> Credentials:
193
201
  return FalServerlessKeyCredentials(key_creds[0], key_creds[1])
194
202
  else:
195
203
  config = Config()
196
- team = team or config.get("team")
204
+ team = team or config.get_internal("team")
197
205
  return AuthenticatedCredentials(team=team)
198
206
 
199
207
 
@@ -650,8 +658,12 @@ class FalServerlessConnection:
650
658
  )
651
659
  return from_grpc(res.alias_info)
652
660
 
653
- def list_applications(self) -> list[ApplicationInfo]:
654
- request = isolate_proto.ListApplicationsRequest()
661
+ def list_applications(
662
+ self, application_name: str | None = None
663
+ ) -> list[ApplicationInfo]:
664
+ request = isolate_proto.ListApplicationsRequest(
665
+ application_name=application_name
666
+ )
655
667
  res: isolate_proto.ListApplicationsResult = self.stub.ListApplications(request)
656
668
  return [from_grpc(app) for app in res.applications]
657
669