fal 1.5.1__tar.gz → 1.5.3__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 (166) hide show
  1. {fal-1.5.1 → fal-1.5.3}/.gitignore +1 -0
  2. fal-1.5.3/Makefile +24 -0
  3. {fal-1.5.1 → fal-1.5.3}/PKG-INFO +6 -2
  4. fal-1.5.3/docs/conf.py +31 -0
  5. fal-1.5.3/docs/index.rst +18 -0
  6. {fal-1.5.1 → fal-1.5.3}/fal.egg-info/PKG-INFO +6 -2
  7. {fal-1.5.1 → fal-1.5.3}/fal.egg-info/SOURCES.txt +4 -0
  8. {fal-1.5.1 → fal-1.5.3}/fal.egg-info/requires.txt +6 -1
  9. {fal-1.5.1 → fal-1.5.3}/pyproject.toml +6 -1
  10. {fal-1.5.1 → fal-1.5.3}/src/fal/_fal_version.py +2 -2
  11. {fal-1.5.1 → fal-1.5.3}/src/fal/app.py +14 -7
  12. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/file/file.py +33 -2
  13. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/file/providers/fal.py +60 -5
  14. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/file/types.py +11 -4
  15. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/image/image.py +5 -0
  16. fal-1.5.3/tests/assets/cat.png +0 -0
  17. {fal-1.5.1 → fal-1.5.3}/tests/integration_test.py +6 -7
  18. {fal-1.5.1 → fal-1.5.3}/tests/test_stability.py +1 -0
  19. {fal-1.5.1 → fal-1.5.3}/README.md +0 -0
  20. {fal-1.5.1 → fal-1.5.3}/fal.egg-info/dependency_links.txt +0 -0
  21. {fal-1.5.1 → fal-1.5.3}/fal.egg-info/entry_points.txt +0 -0
  22. {fal-1.5.1 → fal-1.5.3}/fal.egg-info/top_level.txt +0 -0
  23. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/README.md +0 -0
  24. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  25. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  26. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  27. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  28. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  29. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  30. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  31. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  32. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  33. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  34. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  35. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  36. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  37. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  38. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  39. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  40. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  41. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  42. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  43. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  44. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  45. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  46. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  47. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  48. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  49. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  50. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  51. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  52. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  53. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  54. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  55. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  56. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  57. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  58. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  59. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  60. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  61. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  62. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  63. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  64. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  65. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  66. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  67. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  68. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  69. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  70. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  71. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  72. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  73. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  74. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  75. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  76. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  77. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  78. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  79. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  80. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  81. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  82. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  83. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  84. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  85. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  86. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  87. {fal-1.5.1 → fal-1.5.3}/openapi-fal-rest/pyproject.toml +0 -0
  88. {fal-1.5.1 → fal-1.5.3}/openapi_rest.config.yaml +0 -0
  89. {fal-1.5.1 → fal-1.5.3}/setup.cfg +0 -0
  90. {fal-1.5.1 → fal-1.5.3}/src/fal/__init__.py +0 -0
  91. {fal-1.5.1 → fal-1.5.3}/src/fal/__main__.py +0 -0
  92. {fal-1.5.1 → fal-1.5.3}/src/fal/_serialization.py +0 -0
  93. {fal-1.5.1 → fal-1.5.3}/src/fal/_version.py +0 -0
  94. {fal-1.5.1 → fal-1.5.3}/src/fal/api.py +0 -0
  95. {fal-1.5.1 → fal-1.5.3}/src/fal/apps.py +0 -0
  96. {fal-1.5.1 → fal-1.5.3}/src/fal/auth/__init__.py +0 -0
  97. {fal-1.5.1 → fal-1.5.3}/src/fal/auth/auth0.py +0 -0
  98. {fal-1.5.1 → fal-1.5.3}/src/fal/auth/local.py +0 -0
  99. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/__init__.py +0 -0
  100. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/_utils.py +0 -0
  101. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/apps.py +0 -0
  102. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/auth.py +0 -0
  103. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/create.py +0 -0
  104. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/debug.py +0 -0
  105. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/deploy.py +0 -0
  106. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/doctor.py +0 -0
  107. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/keys.py +0 -0
  108. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/main.py +0 -0
  109. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/parser.py +0 -0
  110. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/run.py +0 -0
  111. {fal-1.5.1 → fal-1.5.3}/src/fal/cli/secrets.py +0 -0
  112. {fal-1.5.1 → fal-1.5.3}/src/fal/console/__init__.py +0 -0
  113. {fal-1.5.1 → fal-1.5.3}/src/fal/console/icons.py +0 -0
  114. {fal-1.5.1 → fal-1.5.3}/src/fal/console/ux.py +0 -0
  115. {fal-1.5.1 → fal-1.5.3}/src/fal/container.py +0 -0
  116. {fal-1.5.1 → fal-1.5.3}/src/fal/exceptions/__init__.py +0 -0
  117. {fal-1.5.1 → fal-1.5.3}/src/fal/exceptions/_base.py +0 -0
  118. {fal-1.5.1 → fal-1.5.3}/src/fal/exceptions/_cuda.py +0 -0
  119. {fal-1.5.1 → fal-1.5.3}/src/fal/exceptions/auth.py +0 -0
  120. {fal-1.5.1 → fal-1.5.3}/src/fal/files.py +0 -0
  121. {fal-1.5.1 → fal-1.5.3}/src/fal/flags.py +0 -0
  122. {fal-1.5.1 → fal-1.5.3}/src/fal/logging/__init__.py +0 -0
  123. {fal-1.5.1 → fal-1.5.3}/src/fal/logging/isolate.py +0 -0
  124. {fal-1.5.1 → fal-1.5.3}/src/fal/logging/style.py +0 -0
  125. {fal-1.5.1 → fal-1.5.3}/src/fal/logging/trace.py +0 -0
  126. {fal-1.5.1 → fal-1.5.3}/src/fal/logging/user.py +0 -0
  127. {fal-1.5.1 → fal-1.5.3}/src/fal/py.typed +0 -0
  128. {fal-1.5.1 → fal-1.5.3}/src/fal/rest_client.py +0 -0
  129. {fal-1.5.1 → fal-1.5.3}/src/fal/sdk.py +0 -0
  130. {fal-1.5.1 → fal-1.5.3}/src/fal/sync.py +0 -0
  131. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/__init__.py +0 -0
  132. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/exceptions.py +0 -0
  133. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/file/__init__.py +0 -0
  134. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/file/providers/gcp.py +0 -0
  135. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/file/providers/r2.py +0 -0
  136. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/image/__init__.py +0 -0
  137. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  138. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  139. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  140. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  141. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  142. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/image/safety_checker.py +0 -0
  143. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/optimize.py +0 -0
  144. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/utils/__init__.py +0 -0
  145. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/utils/download_utils.py +0 -0
  146. {fal-1.5.1 → fal-1.5.3}/src/fal/toolkit/utils/retry.py +0 -0
  147. {fal-1.5.1 → fal-1.5.3}/src/fal/utils.py +0 -0
  148. {fal-1.5.1 → fal-1.5.3}/src/fal/workflows.py +0 -0
  149. {fal-1.5.1 → fal-1.5.3}/tests/__init__.py +0 -0
  150. {fal-1.5.1 → fal-1.5.3}/tests/cli/__init__.py +0 -0
  151. {fal-1.5.1 → fal-1.5.3}/tests/cli/test_apps.py +0 -0
  152. {fal-1.5.1 → fal-1.5.3}/tests/cli/test_auth.py +0 -0
  153. {fal-1.5.1 → fal-1.5.3}/tests/cli/test_deploy.py +0 -0
  154. {fal-1.5.1 → fal-1.5.3}/tests/cli/test_keys.py +0 -0
  155. {fal-1.5.1 → fal-1.5.3}/tests/cli/test_run.py +0 -0
  156. {fal-1.5.1 → fal-1.5.3}/tests/cli/test_secrets.py +0 -0
  157. {fal-1.5.1 → fal-1.5.3}/tests/conftest.py +0 -0
  158. {fal-1.5.1 → fal-1.5.3}/tests/mainify_package/__init__.py +0 -0
  159. {fal-1.5.1 → fal-1.5.3}/tests/mainify_package/impl.py +0 -0
  160. {fal-1.5.1 → fal-1.5.3}/tests/mainify_package/utils.py +0 -0
  161. {fal-1.5.1 → fal-1.5.3}/tests/mainify_target.py +0 -0
  162. {fal-1.5.1 → fal-1.5.3}/tests/test_apps.py +0 -0
  163. {fal-1.5.1 → fal-1.5.3}/tests/toolkit/file_test.py +0 -0
  164. {fal-1.5.1 → fal-1.5.3}/tests/toolkit/image_test.py +0 -0
  165. {fal-1.5.1 → fal-1.5.3}/tests/toolkit/utils/retry.py +0 -0
  166. {fal-1.5.1 → fal-1.5.3}/tools/demo_script.py +0 -0
@@ -1 +1,2 @@
1
1
  /src/fal/_fal_version.py
2
+ /docs/*
fal-1.5.3/Makefile ADDED
@@ -0,0 +1,24 @@
1
+ MAKEFILE_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
2
+
3
+ SPHINXOPTS = --fresh-env
4
+ SPHINXBUILD = sphinx-build
5
+ SPHINXAPIDOC = sphinx-apidoc
6
+ SOURCEDIR = $(MAKEFILE_DIR)/docs
7
+ BUILDDIR = $(MAKEFILE_DIR)/docs/_build
8
+ PROJECTDIR = $(MAKEFILE_DIR)/src/fal
9
+
10
+ help:
11
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
12
+
13
+ .PHONY: help Makefile clean apidoc html docs
14
+
15
+ clean:
16
+ rm -rf $(BUILDDIR)/*
17
+
18
+ apidoc:
19
+ $(SPHINXAPIDOC) -f -o "$(SOURCEDIR)" "$(PROJECTDIR)"
20
+
21
+ html:
22
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
23
+
24
+ docs: apidoc html
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 1.5.1
3
+ Version: 1.5.3
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
@@ -37,6 +37,10 @@ Requires-Dist: pyjwt[crypto]<3,>=2.8.0
37
37
  Requires-Dist: uvicorn<1,>=0.29.0
38
38
  Requires-Dist: cookiecutter
39
39
  Requires-Dist: tomli
40
+ Provides-Extra: docs
41
+ Requires-Dist: sphinx; extra == "docs"
42
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
43
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
40
44
  Provides-Extra: test
41
45
  Requires-Dist: pytest<8; extra == "test"
42
46
  Requires-Dist: pytest-asyncio; extra == "test"
@@ -44,7 +48,7 @@ Requires-Dist: pytest-xdist; extra == "test"
44
48
  Requires-Dist: flaky; extra == "test"
45
49
  Requires-Dist: boto3; extra == "test"
46
50
  Provides-Extra: dev
47
- Requires-Dist: fal[test]; extra == "dev"
51
+ Requires-Dist: fal[docs,test]; extra == "dev"
48
52
  Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
49
53
 
50
54
  [![PyPI](https://img.shields.io/pypi/v/fal.svg?logo=PyPI)](https://pypi.org/project/fal)
fal-1.5.3/docs/conf.py ADDED
@@ -0,0 +1,31 @@
1
+ # Configuration file for the Sphinx documentation builder.
2
+ #
3
+ # For the full list of built-in configuration values, see the documentation:
4
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html
5
+
6
+ # -- Project information -----------------------------------------------------
7
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8
+
9
+ project = 'fal_sdk'
10
+ copyright = '2024, FAL Team'
11
+ author = 'FAL Team'
12
+
13
+ # -- General configuration ---------------------------------------------------
14
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
15
+
16
+ extensions = [
17
+ 'sphinx.ext.autodoc',
18
+ 'sphinx.ext.napoleon',
19
+ 'sphinx_autodoc_typehints'
20
+ ]
21
+
22
+ templates_path = ['_templates']
23
+ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
24
+
25
+
26
+
27
+ # -- Options for HTML output -------------------------------------------------
28
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
29
+
30
+ html_theme = 'alabaster'
31
+ html_static_path = ['_static']
@@ -0,0 +1,18 @@
1
+ .. fal_sdk documentation master file, created by
2
+ sphinx-quickstart on Wed Oct 2 05:09:49 2024.
3
+ You can adapt this file completely to your liking, but it should at least
4
+ contain the root `toctree` directive.
5
+
6
+ fal_sdk documentation
7
+ =====================
8
+
9
+ Add your content using ``reStructuredText`` syntax. See the
10
+ `reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
11
+ documentation for details.
12
+
13
+
14
+ .. toctree::
15
+ :maxdepth: 2
16
+ :caption: Contents:
17
+
18
+ modules
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 1.5.1
3
+ Version: 1.5.3
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
@@ -37,6 +37,10 @@ Requires-Dist: pyjwt[crypto]<3,>=2.8.0
37
37
  Requires-Dist: uvicorn<1,>=0.29.0
38
38
  Requires-Dist: cookiecutter
39
39
  Requires-Dist: tomli
40
+ Provides-Extra: docs
41
+ Requires-Dist: sphinx; extra == "docs"
42
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
43
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
40
44
  Provides-Extra: test
41
45
  Requires-Dist: pytest<8; extra == "test"
42
46
  Requires-Dist: pytest-asyncio; extra == "test"
@@ -44,7 +48,7 @@ Requires-Dist: pytest-xdist; extra == "test"
44
48
  Requires-Dist: flaky; extra == "test"
45
49
  Requires-Dist: boto3; extra == "test"
46
50
  Provides-Extra: dev
47
- Requires-Dist: fal[test]; extra == "dev"
51
+ Requires-Dist: fal[docs,test]; extra == "dev"
48
52
  Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
49
53
 
50
54
  [![PyPI](https://img.shields.io/pypi/v/fal.svg?logo=PyPI)](https://pypi.org/project/fal)
@@ -1,7 +1,10 @@
1
1
  .gitignore
2
+ Makefile
2
3
  README.md
3
4
  openapi_rest.config.yaml
4
5
  pyproject.toml
6
+ docs/conf.py
7
+ docs/index.rst
5
8
  fal.egg-info/PKG-INFO
6
9
  fal.egg-info/SOURCES.txt
7
10
  fal.egg-info/dependency_links.txt
@@ -144,6 +147,7 @@ tests/integration_test.py
144
147
  tests/mainify_target.py
145
148
  tests/test_apps.py
146
149
  tests/test_stability.py
150
+ tests/assets/cat.png
147
151
  tests/cli/__init__.py
148
152
  tests/cli/test_apps.py
149
153
  tests/cli/test_auth.py
@@ -34,9 +34,14 @@ tomli
34
34
  importlib-metadata>=4.4
35
35
 
36
36
  [dev]
37
- fal[test]
37
+ fal[docs,test]
38
38
  openapi-python-client<1,>=0.14.1
39
39
 
40
+ [docs]
41
+ sphinx
42
+ sphinx-rtd-theme
43
+ sphinx-autodoc-typehints
44
+
40
45
  [test]
41
46
  pytest<8
42
47
  pytest-asyncio
@@ -60,6 +60,11 @@ dependencies = [
60
60
  ]
61
61
 
62
62
  [project.optional-dependencies]
63
+ docs = [
64
+ "sphinx",
65
+ "sphinx-rtd-theme",
66
+ "sphinx-autodoc-typehints",
67
+ ]
63
68
  test = [
64
69
  "pytest<8",
65
70
  "pytest-asyncio",
@@ -68,7 +73,7 @@ test = [
68
73
  "boto3",
69
74
  ]
70
75
  dev = [
71
- "fal[test]",
76
+ "fal[docs,test]",
72
77
  "openapi-python-client>=0.14.1,<1",
73
78
  ]
74
79
 
@@ -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.1'
16
- __version_tuple__ = version_tuple = (1, 5, 1)
15
+ __version__ = version = '1.5.3'
16
+ __version_tuple__ = version_tuple = (1, 5, 3)
@@ -19,7 +19,8 @@ from fal._serialization import include_modules_from
19
19
  from fal.api import RouteSignature
20
20
  from fal.exceptions import RequestCancelledException
21
21
  from fal.logging import get_logger
22
- from fal.toolkit.file.providers import fal as fal_provider_module
22
+ from fal.toolkit.file import get_lifecycle_preference
23
+ from fal.toolkit.file.providers.fal import GLOBAL_LIFECYCLE_PREFERENCE
23
24
 
24
25
  REALTIME_APP_REQUIREMENTS = ["websockets", "msgpack"]
25
26
 
@@ -266,11 +267,14 @@ class App(fal.api.BaseServable):
266
267
 
267
268
  @app.middleware("http")
268
269
  async def set_global_object_preference(request, call_next):
269
- response = await call_next(request)
270
270
  try:
271
- fal_provider_module.GLOBAL_LIFECYCLE_PREFERENCE = request.headers.get(
272
- "X-Fal-Object-Lifecycle-Preference"
273
- )
271
+ preference_dict = get_lifecycle_preference(request) or {}
272
+ expiration_duration = preference_dict.get("expiration_duration_seconds")
273
+ if expiration_duration is not None:
274
+ GLOBAL_LIFECYCLE_PREFERENCE.expiration_duration_seconds = int(
275
+ expiration_duration
276
+ )
277
+
274
278
  except Exception:
275
279
  from fastapi.logger import logger
276
280
 
@@ -278,7 +282,7 @@ class App(fal.api.BaseServable):
278
282
  "Failed set a global lifecycle preference %s",
279
283
  self.__class__.__name__,
280
284
  )
281
- return response
285
+ return await call_next(request)
282
286
 
283
287
  @app.exception_handler(RequestCancelledException)
284
288
  async def value_error_exception_handler(
@@ -402,7 +406,10 @@ def _fal_websocket_template(
402
406
  batch.append(next_input)
403
407
 
404
408
  t0 = loop.time()
405
- output = await loop.run_in_executor(None, func, self, *batch) # type: ignore
409
+ if inspect.iscoroutinefunction(func):
410
+ output = await func(self, *batch)
411
+ else:
412
+ output = await loop.run_in_executor(None, func, self, *batch) # type: ignore
406
413
  total_time = loop.time() - t0
407
414
  if not isinstance(output, dict):
408
415
  # Handle pydantic output modal
@@ -8,6 +8,7 @@ from urllib.parse import urlparse
8
8
  from zipfile import ZipFile
9
9
 
10
10
  import pydantic
11
+ from fastapi import Request
11
12
 
12
13
  # https://github.com/pydantic/pydantic/pull/2573
13
14
  if not hasattr(pydantic, "__version__") or pydantic.__version__.startswith("1."):
@@ -24,6 +25,7 @@ from fal.toolkit.file.providers.fal import (
24
25
  FalCDNFileRepository,
25
26
  FalFileRepository,
26
27
  FalFileRepositoryV2,
28
+ FalFileRepositoryV3,
27
29
  InMemoryRepository,
28
30
  )
29
31
  from fal.toolkit.file.providers.gcp import GoogleStorageRepository
@@ -36,6 +38,7 @@ FileRepositoryFactory = Callable[[], FileRepository]
36
38
  BUILT_IN_REPOSITORIES: dict[RepositoryId, FileRepositoryFactory] = {
37
39
  "fal": lambda: FalFileRepository(),
38
40
  "fal_v2": lambda: FalFileRepositoryV2(),
41
+ "fal_v3": lambda: FalFileRepositoryV3(),
39
42
  "in_memory": lambda: InMemoryRepository(),
40
43
  "gcp_storage": lambda: GoogleStorageRepository(),
41
44
  "r2": lambda: R2Repository(),
@@ -53,6 +56,7 @@ get_builtin_repository.__module__ = "__main__"
53
56
 
54
57
  DEFAULT_REPOSITORY: FileRepository | RepositoryId = "fal_v2"
55
58
  FALLBACK_REPOSITORY: FileRepository | RepositoryId = "cdn"
59
+ OBJECT_LIFECYCLE_PREFERENCE_KEY = "x-fal-object-lifecycle-preference"
56
60
 
57
61
 
58
62
  class File(BaseModel):
@@ -130,6 +134,7 @@ class File(BaseModel):
130
134
  fallback_repository: Optional[
131
135
  FileRepository | RepositoryId
132
136
  ] = FALLBACK_REPOSITORY,
137
+ request: Optional[Request] = None,
133
138
  ) -> File:
134
139
  repo = (
135
140
  repository
@@ -139,8 +144,10 @@ class File(BaseModel):
139
144
 
140
145
  fdata = FileData(data, content_type, file_name)
141
146
 
147
+ object_lifecycle_preference = get_lifecycle_preference(request)
148
+
142
149
  try:
143
- url = repo.save(fdata)
150
+ url = repo.save(fdata, object_lifecycle_preference)
144
151
  except Exception:
145
152
  if not fallback_repository:
146
153
  raise
@@ -151,7 +158,7 @@ class File(BaseModel):
151
158
  else get_builtin_repository(fallback_repository)
152
159
  )
153
160
 
154
- url = fallback_repo.save(fdata)
161
+ url = fallback_repo.save(fdata, object_lifecycle_preference)
155
162
 
156
163
  return cls(
157
164
  url=url,
@@ -171,6 +178,7 @@ class File(BaseModel):
171
178
  fallback_repository: Optional[
172
179
  FileRepository | RepositoryId
173
180
  ] = FALLBACK_REPOSITORY,
181
+ request: Optional[Request] = None,
174
182
  ) -> File:
175
183
  file_path = Path(path)
176
184
  if not file_path.exists():
@@ -183,12 +191,14 @@ class File(BaseModel):
183
191
  )
184
192
 
185
193
  content_type = content_type or "application/octet-stream"
194
+ object_lifecycle_preference = get_lifecycle_preference(request)
186
195
 
187
196
  try:
188
197
  url, data = repo.save_file(
189
198
  file_path,
190
199
  content_type=content_type,
191
200
  multipart=multipart,
201
+ object_lifecycle_preference=object_lifecycle_preference,
192
202
  )
193
203
  except Exception:
194
204
  if not fallback_repository:
@@ -204,6 +214,7 @@ class File(BaseModel):
204
214
  file_path,
205
215
  content_type=content_type,
206
216
  multipart=multipart,
217
+ object_lifecycle_preference=object_lifecycle_preference,
207
218
  )
208
219
 
209
220
  return cls(
@@ -261,3 +272,23 @@ class CompressedFile(File):
261
272
  def __del__(self):
262
273
  if self.extract_dir:
263
274
  shutil.rmtree(self.extract_dir)
275
+
276
+
277
+ def get_lifecycle_preference(request: Request) -> dict[str, str] | None:
278
+ import json
279
+
280
+ preference_str = (
281
+ request.headers.get(OBJECT_LIFECYCLE_PREFERENCE_KEY)
282
+ if request is not None
283
+ else None
284
+ )
285
+ if preference_str is None:
286
+ return None
287
+
288
+ object_lifecycle_preference = {}
289
+ try:
290
+ object_lifecycle_preference = json.loads(preference_str)
291
+ return object_lifecycle_preference
292
+ except Exception as e:
293
+ print(f"Failed to parse object lifecycle preference: {e}")
294
+ return None
@@ -19,6 +19,7 @@ from fal.toolkit.file.types import FileData, FileRepository
19
19
  from fal.toolkit.utils.retry import retry
20
20
 
21
21
  _FAL_CDN = "https://fal.media"
22
+ _FAL_CDN_V3 = "https://v3.fal.media"
22
23
 
23
24
 
24
25
  @dataclass
@@ -91,11 +92,11 @@ fal_v2_token_manager = FalV2TokenManager()
91
92
 
92
93
  @dataclass
93
94
  class ObjectLifecyclePreference:
94
- expriation_duration_seconds: int
95
+ expiration_duration_seconds: int
95
96
 
96
97
 
97
98
  GLOBAL_LIFECYCLE_PREFERENCE = ObjectLifecyclePreference(
98
- expriation_duration_seconds=86400
99
+ expiration_duration_seconds=86400
99
100
  )
100
101
 
101
102
 
@@ -158,7 +159,9 @@ class FalFileRepositoryBase(FileRepository):
158
159
 
159
160
  @dataclass
160
161
  class FalFileRepository(FalFileRepositoryBase):
161
- def save(self, file: FileData) -> str:
162
+ def save(
163
+ self, file: FileData, object_lifecycle_preference: dict[str, str] | None = None
164
+ ) -> str:
162
165
  return self._save(file, "gcs")
163
166
 
164
167
 
@@ -275,7 +278,9 @@ class MultipartUpload:
275
278
  @dataclass
276
279
  class FalFileRepositoryV2(FalFileRepositoryBase):
277
280
  @retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
278
- def save(self, file: FileData) -> str:
281
+ def save(
282
+ self, file: FileData, object_lifecycle_preference: dict[str, str] | None = None
283
+ ) -> str:
279
284
  token = fal_v2_token_manager.get_token()
280
285
  headers = {
281
286
  "Authorization": f"{token.token_type} {token.token}",
@@ -327,6 +332,7 @@ class FalFileRepositoryV2(FalFileRepositoryBase):
327
332
  multipart_threshold: int | None = None,
328
333
  multipart_chunk_size: int | None = None,
329
334
  multipart_max_concurrency: int | None = None,
335
+ object_lifecycle_preference: dict[str, str] | None = None,
330
336
  ) -> tuple[str, FileData | None]:
331
337
  if multipart is None:
332
338
  threshold = multipart_threshold or MultipartUpload.MULTIPART_THRESHOLD
@@ -347,7 +353,7 @@ class FalFileRepositoryV2(FalFileRepositoryBase):
347
353
  content_type=content_type,
348
354
  file_name=os.path.basename(file_path),
349
355
  )
350
- url = self.save(data)
356
+ url = self.save(data, object_lifecycle_preference)
351
357
 
352
358
  return url, data
353
359
 
@@ -357,6 +363,7 @@ class InMemoryRepository(FileRepository):
357
363
  def save(
358
364
  self,
359
365
  file: FileData,
366
+ object_lifecycle_preference: dict[str, str] | None = None,
360
367
  ) -> str:
361
368
  return f'data:{file.content_type};base64,{b64encode(file.data).decode("utf-8")}'
362
369
 
@@ -367,6 +374,7 @@ class FalCDNFileRepository(FileRepository):
367
374
  def save(
368
375
  self,
369
376
  file: FileData,
377
+ object_lifecycle_preference: dict[str, str] | None = None,
370
378
  ) -> str:
371
379
  headers = {
372
380
  **self.auth_headers,
@@ -401,3 +409,50 @@ class FalCDNFileRepository(FileRepository):
401
409
  "Authorization": f"Bearer {key_id}:{key_secret}",
402
410
  "User-Agent": "fal/0.1.0",
403
411
  }
412
+
413
+
414
+ @dataclass
415
+ class FalFileRepositoryV3(FileRepository):
416
+ @retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
417
+ def save(
418
+ self, file: FileData, user_lifecycle_preference: dict[str, str] | None
419
+ ) -> str:
420
+ object_lifecycle_preference = dataclasses.asdict(GLOBAL_LIFECYCLE_PREFERENCE)
421
+
422
+ if user_lifecycle_preference is not None:
423
+ object_lifecycle_preference = {
424
+ key: user_lifecycle_preference[key]
425
+ if key in user_lifecycle_preference
426
+ else value
427
+ for key, value in object_lifecycle_preference.items()
428
+ }
429
+
430
+ headers = {
431
+ **self.auth_headers,
432
+ "Accept": "application/json",
433
+ "Content-Type": file.content_type,
434
+ "X-Fal-File-Name": file.file_name,
435
+ "X-Fal-Object-Lifecycle-Preference": json.dumps(
436
+ object_lifecycle_preference
437
+ ),
438
+ }
439
+ url = os.getenv("FAL_CDN_V3_HOST", _FAL_CDN_V3) + "/files/upload"
440
+ request = Request(url, headers=headers, method="POST", data=file.data)
441
+ try:
442
+ with urlopen(request) as response:
443
+ result = json.load(response)
444
+ except HTTPError as e:
445
+ raise FileUploadException(
446
+ f"Error initiating upload. Status {e.status}: {e.reason}"
447
+ )
448
+
449
+ access_url = result["access_url"]
450
+ return access_url
451
+
452
+ @property
453
+ def auth_headers(self) -> dict[str, str]:
454
+ token = fal_v2_token_manager.get_token()
455
+ return {
456
+ "Authorization": f"{token.token_type} {token.token}",
457
+ "User-Agent": "fal/0.1.0",
458
+ }
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from mimetypes import guess_extension, guess_type
5
5
  from pathlib import Path
6
- from typing import Literal
6
+ from typing import Literal, Optional
7
7
  from uuid import uuid4
8
8
 
9
9
 
@@ -29,12 +29,18 @@ class FileData:
29
29
  self.file_name = file_name
30
30
 
31
31
 
32
- RepositoryId = Literal["fal", "fal_v2", "in_memory", "gcp_storage", "r2", "cdn"]
32
+ RepositoryId = Literal[
33
+ "fal", "fal_v2", "fal_v3", "in_memory", "gcp_storage", "r2", "cdn"
34
+ ]
33
35
 
34
36
 
35
37
  @dataclass
36
38
  class FileRepository:
37
- def save(self, data: FileData) -> str:
39
+ def save(
40
+ self,
41
+ data: FileData,
42
+ object_lifecycle_preference: Optional[dict[str, str]] = None,
43
+ ) -> str:
38
44
  raise NotImplementedError()
39
45
 
40
46
  def save_file(
@@ -45,6 +51,7 @@ class FileRepository:
45
51
  multipart_threshold: int | None = None,
46
52
  multipart_chunk_size: int | None = None,
47
53
  multipart_max_concurrency: int | None = None,
54
+ object_lifecycle_preference: Optional[dict[str, str]] = None,
48
55
  ) -> tuple[str, FileData | None]:
49
56
  if multipart:
50
57
  raise NotImplementedError()
@@ -52,4 +59,4 @@ class FileRepository:
52
59
  with open(file_path, "rb") as fobj:
53
60
  data = FileData(fobj.read(), content_type, Path(file_path).name)
54
61
 
55
- return self.save(data), data
62
+ return self.save(data, object_lifecycle_preference), data
@@ -4,6 +4,7 @@ import io
4
4
  from tempfile import NamedTemporaryFile
5
5
  from typing import TYPE_CHECKING, Literal, Optional, Union
6
6
 
7
+ from fastapi import Request
7
8
  from pydantic import BaseModel, Field
8
9
 
9
10
  from fal.toolkit.file.file import DEFAULT_REPOSITORY, FALLBACK_REPOSITORY, File
@@ -82,6 +83,7 @@ class Image(File):
82
83
  fallback_repository: Optional[
83
84
  FileRepository | RepositoryId
84
85
  ] = FALLBACK_REPOSITORY,
86
+ request: Optional[Request] = None,
85
87
  ) -> Image:
86
88
  obj = super().from_bytes(
87
89
  data,
@@ -89,6 +91,7 @@ class Image(File):
89
91
  file_name=file_name,
90
92
  repository=repository,
91
93
  fallback_repository=fallback_repository,
94
+ request=request,
92
95
  )
93
96
  obj.width = size.width if size else None
94
97
  obj.height = size.height if size else None
@@ -104,6 +107,7 @@ class Image(File):
104
107
  fallback_repository: Optional[
105
108
  FileRepository | RepositoryId
106
109
  ] = FALLBACK_REPOSITORY,
110
+ request: Optional[Request] = None,
107
111
  ) -> Image:
108
112
  size = ImageSize(width=pil_image.width, height=pil_image.height)
109
113
  if format is None:
@@ -133,6 +137,7 @@ class Image(File):
133
137
  file_name,
134
138
  repository,
135
139
  fallback_repository=fallback_repository,
140
+ request=request,
136
141
  )
137
142
 
138
143
  def to_pil(self, mode: str = "RGB") -> PILImage.Image:
Binary file
@@ -23,6 +23,8 @@ from fal.toolkit.utils.download_utils import _get_git_revision_hash, _hash_url
23
23
  from pydantic import BaseModel, Field
24
24
  from pydantic import __version__ as pydantic_version
25
25
 
26
+ EXAMPLE_FILE_URL = "https://raw.githubusercontent.com/fal-ai/fal/main/projects/fal/tests/assets/cat.png"
27
+
26
28
 
27
29
  @pytest.mark.flaky(max_runs=3)
28
30
  def test_isolated(isolated_client: Callable[..., Callable[..., IsolatedFunction]]):
@@ -197,11 +199,9 @@ def mock_fal_persistent_dirs(monkeypatch):
197
199
  def test_download_file(isolated_client, mock_fal_persistent_dirs):
198
200
  from fal.toolkit.utils.download_utils import FAL_PERSISTENT_DIR
199
201
 
200
- EXAMPLE_FILE_URL = "https://raw.githubusercontent.com/fal-ai/isolate/d553f927348206530208442556f481f39b161732/README.md"
201
-
202
202
  relative_directory = "test"
203
203
  output_directory = FAL_PERSISTENT_DIR / relative_directory
204
- expected_path = output_directory / "README.md"
204
+ expected_path = output_directory / "cat.png"
205
205
 
206
206
  @isolated_client()
207
207
  def absolute_path_persistent_dir():
@@ -301,8 +301,7 @@ def test_download_model_weights(isolated_client, mock_fal_persistent_dirs):
301
301
 
302
302
  print(FAL_MODEL_WEIGHTS_DIR)
303
303
 
304
- EXAMPLE_FILE_URL = "https://raw.githubusercontent.com/fal-ai/isolate/d553f927348206530208442556f481f39b161732/README.md"
305
- expected_path = FAL_MODEL_WEIGHTS_DIR / _hash_url(EXAMPLE_FILE_URL) / "README.md"
304
+ expected_path = FAL_MODEL_WEIGHTS_DIR / _hash_url(EXAMPLE_FILE_URL) / "cat.png"
306
305
 
307
306
  @isolated_client()
308
307
  def download_weights():
@@ -510,8 +509,8 @@ def test_fal_file_save(isolated_client):
510
509
  "file_url, expected_content",
511
510
  [
512
511
  (
513
- "https://raw.githubusercontent.com/fal-ai/fal/fe0e2a1aa4b46a42a93bad0fbd9aca4aefcb4296/README.md",
514
- "projects/fal/README.md",
512
+ EXAMPLE_FILE_URL,
513
+ "projects/fal/cat.png",
515
514
  ),
516
515
  ("data:text/plain;charset=UTF-8,fal", "fal"),
517
516
  ],
@@ -580,6 +580,7 @@ def test_worker_env_vars(isolated_client):
580
580
  "https://storage.googleapis.com/isolate-dev-smiling-shark_toolkit_bucket/",
581
581
  ),
582
582
  ("fal_v2", "https://v2.fal.media/files"),
583
+ ("fal_v3", "https://v3.fal.media/files"),
583
584
  ],
584
585
  )
585
586
  def test_fal_storage(isolated_client, repo_type, url_prefix):
File without changes
File without changes
File without changes
File without changes