fal 1.5.2__tar.gz → 1.5.4__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.2 → fal-1.5.4}/.gitignore +1 -0
  2. fal-1.5.4/Makefile +24 -0
  3. {fal-1.5.2 → fal-1.5.4}/PKG-INFO +7 -2
  4. fal-1.5.4/docs/conf.py +31 -0
  5. fal-1.5.4/docs/index.rst +18 -0
  6. {fal-1.5.2 → fal-1.5.4}/fal.egg-info/PKG-INFO +7 -2
  7. {fal-1.5.2 → fal-1.5.4}/fal.egg-info/SOURCES.txt +3 -0
  8. {fal-1.5.2 → fal-1.5.4}/fal.egg-info/requires.txt +7 -1
  9. {fal-1.5.2 → fal-1.5.4}/pyproject.toml +7 -1
  10. {fal-1.5.2 → fal-1.5.4}/src/fal/_fal_version.py +2 -2
  11. {fal-1.5.2 → fal-1.5.4}/src/fal/api.py +1 -0
  12. {fal-1.5.2 → fal-1.5.4}/src/fal/app.py +100 -11
  13. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/file/file.py +3 -3
  14. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/file/providers/fal.py +200 -16
  15. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/file/providers/gcp.py +6 -1
  16. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/file/providers/r2.py +6 -1
  17. {fal-1.5.2 → fal-1.5.4}/tests/test_apps.py +9 -9
  18. {fal-1.5.2 → fal-1.5.4}/README.md +0 -0
  19. {fal-1.5.2 → fal-1.5.4}/fal.egg-info/dependency_links.txt +0 -0
  20. {fal-1.5.2 → fal-1.5.4}/fal.egg-info/entry_points.txt +0 -0
  21. {fal-1.5.2 → fal-1.5.4}/fal.egg-info/top_level.txt +0 -0
  22. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/README.md +0 -0
  23. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  24. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  25. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  26. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  27. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  28. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  29. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  30. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  31. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  32. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  33. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  34. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  35. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  36. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  37. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  38. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  39. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  40. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  41. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  42. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  43. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  44. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  45. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  46. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  47. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  48. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  49. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  50. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  51. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  52. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  53. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  54. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  55. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  56. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  57. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  58. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  59. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  60. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  61. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  62. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  63. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  64. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  65. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  66. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  67. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  68. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  69. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  70. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  71. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  72. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  73. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  74. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  75. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  76. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  77. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  78. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  79. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  80. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  81. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  82. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  83. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  84. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  85. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  86. {fal-1.5.2 → fal-1.5.4}/openapi-fal-rest/pyproject.toml +0 -0
  87. {fal-1.5.2 → fal-1.5.4}/openapi_rest.config.yaml +0 -0
  88. {fal-1.5.2 → fal-1.5.4}/setup.cfg +0 -0
  89. {fal-1.5.2 → fal-1.5.4}/src/fal/__init__.py +0 -0
  90. {fal-1.5.2 → fal-1.5.4}/src/fal/__main__.py +0 -0
  91. {fal-1.5.2 → fal-1.5.4}/src/fal/_serialization.py +0 -0
  92. {fal-1.5.2 → fal-1.5.4}/src/fal/_version.py +0 -0
  93. {fal-1.5.2 → fal-1.5.4}/src/fal/apps.py +0 -0
  94. {fal-1.5.2 → fal-1.5.4}/src/fal/auth/__init__.py +0 -0
  95. {fal-1.5.2 → fal-1.5.4}/src/fal/auth/auth0.py +0 -0
  96. {fal-1.5.2 → fal-1.5.4}/src/fal/auth/local.py +0 -0
  97. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/__init__.py +0 -0
  98. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/_utils.py +0 -0
  99. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/apps.py +0 -0
  100. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/auth.py +0 -0
  101. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/create.py +0 -0
  102. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/debug.py +0 -0
  103. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/deploy.py +0 -0
  104. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/doctor.py +0 -0
  105. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/keys.py +0 -0
  106. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/main.py +0 -0
  107. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/parser.py +0 -0
  108. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/run.py +0 -0
  109. {fal-1.5.2 → fal-1.5.4}/src/fal/cli/secrets.py +0 -0
  110. {fal-1.5.2 → fal-1.5.4}/src/fal/console/__init__.py +0 -0
  111. {fal-1.5.2 → fal-1.5.4}/src/fal/console/icons.py +0 -0
  112. {fal-1.5.2 → fal-1.5.4}/src/fal/console/ux.py +0 -0
  113. {fal-1.5.2 → fal-1.5.4}/src/fal/container.py +0 -0
  114. {fal-1.5.2 → fal-1.5.4}/src/fal/exceptions/__init__.py +0 -0
  115. {fal-1.5.2 → fal-1.5.4}/src/fal/exceptions/_base.py +0 -0
  116. {fal-1.5.2 → fal-1.5.4}/src/fal/exceptions/_cuda.py +0 -0
  117. {fal-1.5.2 → fal-1.5.4}/src/fal/exceptions/auth.py +0 -0
  118. {fal-1.5.2 → fal-1.5.4}/src/fal/files.py +0 -0
  119. {fal-1.5.2 → fal-1.5.4}/src/fal/flags.py +0 -0
  120. {fal-1.5.2 → fal-1.5.4}/src/fal/logging/__init__.py +0 -0
  121. {fal-1.5.2 → fal-1.5.4}/src/fal/logging/isolate.py +0 -0
  122. {fal-1.5.2 → fal-1.5.4}/src/fal/logging/style.py +0 -0
  123. {fal-1.5.2 → fal-1.5.4}/src/fal/logging/trace.py +0 -0
  124. {fal-1.5.2 → fal-1.5.4}/src/fal/logging/user.py +0 -0
  125. {fal-1.5.2 → fal-1.5.4}/src/fal/py.typed +0 -0
  126. {fal-1.5.2 → fal-1.5.4}/src/fal/rest_client.py +0 -0
  127. {fal-1.5.2 → fal-1.5.4}/src/fal/sdk.py +0 -0
  128. {fal-1.5.2 → fal-1.5.4}/src/fal/sync.py +0 -0
  129. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/__init__.py +0 -0
  130. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/exceptions.py +0 -0
  131. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/file/__init__.py +0 -0
  132. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/file/types.py +0 -0
  133. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/image/__init__.py +0 -0
  134. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/image/image.py +0 -0
  135. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  136. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  137. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  138. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  139. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  140. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/image/safety_checker.py +0 -0
  141. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/optimize.py +0 -0
  142. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/utils/__init__.py +0 -0
  143. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/utils/download_utils.py +0 -0
  144. {fal-1.5.2 → fal-1.5.4}/src/fal/toolkit/utils/retry.py +0 -0
  145. {fal-1.5.2 → fal-1.5.4}/src/fal/utils.py +0 -0
  146. {fal-1.5.2 → fal-1.5.4}/src/fal/workflows.py +0 -0
  147. {fal-1.5.2 → fal-1.5.4}/tests/__init__.py +0 -0
  148. {fal-1.5.2 → fal-1.5.4}/tests/assets/cat.png +0 -0
  149. {fal-1.5.2 → fal-1.5.4}/tests/cli/__init__.py +0 -0
  150. {fal-1.5.2 → fal-1.5.4}/tests/cli/test_apps.py +0 -0
  151. {fal-1.5.2 → fal-1.5.4}/tests/cli/test_auth.py +0 -0
  152. {fal-1.5.2 → fal-1.5.4}/tests/cli/test_deploy.py +0 -0
  153. {fal-1.5.2 → fal-1.5.4}/tests/cli/test_keys.py +0 -0
  154. {fal-1.5.2 → fal-1.5.4}/tests/cli/test_run.py +0 -0
  155. {fal-1.5.2 → fal-1.5.4}/tests/cli/test_secrets.py +0 -0
  156. {fal-1.5.2 → fal-1.5.4}/tests/conftest.py +0 -0
  157. {fal-1.5.2 → fal-1.5.4}/tests/integration_test.py +0 -0
  158. {fal-1.5.2 → fal-1.5.4}/tests/mainify_package/__init__.py +0 -0
  159. {fal-1.5.2 → fal-1.5.4}/tests/mainify_package/impl.py +0 -0
  160. {fal-1.5.2 → fal-1.5.4}/tests/mainify_package/utils.py +0 -0
  161. {fal-1.5.2 → fal-1.5.4}/tests/mainify_target.py +0 -0
  162. {fal-1.5.2 → fal-1.5.4}/tests/test_stability.py +0 -0
  163. {fal-1.5.2 → fal-1.5.4}/tests/toolkit/file_test.py +0 -0
  164. {fal-1.5.2 → fal-1.5.4}/tests/toolkit/image_test.py +0 -0
  165. {fal-1.5.2 → fal-1.5.4}/tests/toolkit/utils/retry.py +0 -0
  166. {fal-1.5.2 → fal-1.5.4}/tools/demo_script.py +0 -0
@@ -1 +1,2 @@
1
1
  /src/fal/_fal_version.py
2
+ /docs/*
fal-1.5.4/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.2
3
+ Version: 1.5.4
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
@@ -23,6 +23,7 @@ Requires-Dist: rich_argparse
23
23
  Requires-Dist: packaging>=21.3
24
24
  Requires-Dist: pathspec<1,>=0.11.1
25
25
  Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
26
+ Requires-Dist: structlog>=22.0
26
27
  Requires-Dist: fastapi<1,>=0.99.1
27
28
  Requires-Dist: starlette-exporter>=0.21.0
28
29
  Requires-Dist: httpx>=0.15.4
@@ -37,6 +38,10 @@ Requires-Dist: pyjwt[crypto]<3,>=2.8.0
37
38
  Requires-Dist: uvicorn<1,>=0.29.0
38
39
  Requires-Dist: cookiecutter
39
40
  Requires-Dist: tomli
41
+ Provides-Extra: docs
42
+ Requires-Dist: sphinx; extra == "docs"
43
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
44
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
40
45
  Provides-Extra: test
41
46
  Requires-Dist: pytest<8; extra == "test"
42
47
  Requires-Dist: pytest-asyncio; extra == "test"
@@ -44,7 +49,7 @@ Requires-Dist: pytest-xdist; extra == "test"
44
49
  Requires-Dist: flaky; extra == "test"
45
50
  Requires-Dist: boto3; extra == "test"
46
51
  Provides-Extra: dev
47
- Requires-Dist: fal[test]; extra == "dev"
52
+ Requires-Dist: fal[docs,test]; extra == "dev"
48
53
  Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
49
54
 
50
55
  [![PyPI](https://img.shields.io/pypi/v/fal.svg?logo=PyPI)](https://pypi.org/project/fal)
fal-1.5.4/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.2
3
+ Version: 1.5.4
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
@@ -23,6 +23,7 @@ Requires-Dist: rich_argparse
23
23
  Requires-Dist: packaging>=21.3
24
24
  Requires-Dist: pathspec<1,>=0.11.1
25
25
  Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
26
+ Requires-Dist: structlog>=22.0
26
27
  Requires-Dist: fastapi<1,>=0.99.1
27
28
  Requires-Dist: starlette-exporter>=0.21.0
28
29
  Requires-Dist: httpx>=0.15.4
@@ -37,6 +38,10 @@ Requires-Dist: pyjwt[crypto]<3,>=2.8.0
37
38
  Requires-Dist: uvicorn<1,>=0.29.0
38
39
  Requires-Dist: cookiecutter
39
40
  Requires-Dist: tomli
41
+ Provides-Extra: docs
42
+ Requires-Dist: sphinx; extra == "docs"
43
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
44
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
40
45
  Provides-Extra: test
41
46
  Requires-Dist: pytest<8; extra == "test"
42
47
  Requires-Dist: pytest-asyncio; extra == "test"
@@ -44,7 +49,7 @@ Requires-Dist: pytest-xdist; extra == "test"
44
49
  Requires-Dist: flaky; extra == "test"
45
50
  Requires-Dist: boto3; extra == "test"
46
51
  Provides-Extra: dev
47
- Requires-Dist: fal[test]; extra == "dev"
52
+ Requires-Dist: fal[docs,test]; extra == "dev"
48
53
  Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
49
54
 
50
55
  [![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
@@ -16,6 +16,7 @@ rich_argparse
16
16
  packaging>=21.3
17
17
  pathspec<1,>=0.11.1
18
18
  pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
19
+ structlog>=22.0
19
20
  fastapi<1,>=0.99.1
20
21
  starlette-exporter>=0.21.0
21
22
  httpx>=0.15.4
@@ -34,9 +35,14 @@ tomli
34
35
  importlib-metadata>=4.4
35
36
 
36
37
  [dev]
37
- fal[test]
38
+ fal[docs,test]
38
39
  openapi-python-client<1,>=0.14.1
39
40
 
41
+ [docs]
42
+ sphinx
43
+ sphinx-rtd-theme
44
+ sphinx-autodoc-typehints
45
+
40
46
  [test]
41
47
  pytest<8
42
48
  pytest-asyncio
@@ -41,6 +41,7 @@ dependencies = [
41
41
  "pathspec>=0.11.1,<1",
42
42
  "pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3",
43
43
  # serve=True dependencies
44
+ "structlog>=22.0",
44
45
  "fastapi>=0.99.1,<1",
45
46
  "starlette-exporter>=0.21.0",
46
47
  # rest-api-client dependencies
@@ -60,6 +61,11 @@ dependencies = [
60
61
  ]
61
62
 
62
63
  [project.optional-dependencies]
64
+ docs = [
65
+ "sphinx",
66
+ "sphinx-rtd-theme",
67
+ "sphinx-autodoc-typehints",
68
+ ]
63
69
  test = [
64
70
  "pytest<8",
65
71
  "pytest-asyncio",
@@ -68,7 +74,7 @@ test = [
68
74
  "boto3",
69
75
  ]
70
76
  dev = [
71
- "fal[test]",
77
+ "fal[docs,test]",
72
78
  "openapi-python-client>=0.14.1,<1",
73
79
  ]
74
80
 
@@ -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.2'
16
- __version_tuple__ = version_tuple = (1, 5, 2)
15
+ __version__ = version = '1.5.4'
16
+ __version_tuple__ = version_tuple = (1, 5, 4)
@@ -76,6 +76,7 @@ SERVE_REQUIREMENTS = [
76
76
  f"pydantic=={pydantic_version}",
77
77
  "uvicorn",
78
78
  "starlette_exporter",
79
+ "structlog",
79
80
  ]
80
81
 
81
82
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import inspect
4
5
  import json
5
6
  import os
@@ -8,20 +9,25 @@ import re
8
9
  import threading
9
10
  import time
10
11
  import typing
11
- from contextlib import asynccontextmanager, contextmanager
12
+ from contextlib import AsyncExitStack, asynccontextmanager, contextmanager
13
+ from dataclasses import dataclass
12
14
  from typing import Any, Callable, ClassVar, Literal, TypeVar
13
15
 
16
+ import grpc.aio as async_grpc
14
17
  import httpx
15
18
  from fastapi import FastAPI
19
+ from isolate.server import definitions
16
20
 
17
21
  import fal.api
18
22
  from fal._serialization import include_modules_from
19
23
  from fal.api import RouteSignature
20
- from fal.exceptions import RequestCancelledException
24
+ from fal.exceptions import FalServerlessException, RequestCancelledException
21
25
  from fal.logging import get_logger
22
- from fal.toolkit.file.providers import fal as fal_provider_module
26
+ from fal.toolkit.file import get_lifecycle_preference
27
+ from fal.toolkit.file.providers.fal import GLOBAL_LIFECYCLE_PREFERENCE
23
28
 
24
29
  REALTIME_APP_REQUIREMENTS = ["websockets", "msgpack"]
30
+ REQUEST_ID_KEY = "x-fal-request-id"
25
31
 
26
32
  EndpointT = TypeVar("EndpointT", bound=Callable[..., Any])
27
33
  logger = get_logger(__name__)
@@ -34,6 +40,48 @@ async def _call_any_fn(fn, *args, **kwargs):
34
40
  return fn(*args, **kwargs)
35
41
 
36
42
 
43
+ async def open_isolate_channel(address: str) -> async_grpc.Channel:
44
+ _stack = AsyncExitStack()
45
+ channel = await _stack.enter_async_context(
46
+ async_grpc.insecure_channel(
47
+ address,
48
+ options=[
49
+ ("grpc.max_send_message_length", -1),
50
+ ("grpc.max_receive_message_length", -1),
51
+ ("grpc.min_reconnect_backoff_ms", 0),
52
+ ("grpc.max_reconnect_backoff_ms", 100),
53
+ ("grpc.dns_min_time_between_resolutions_ms", 100),
54
+ ],
55
+ )
56
+ )
57
+
58
+ channel_status = channel.channel_ready()
59
+ try:
60
+ await asyncio.wait_for(channel_status, timeout=1)
61
+ except asyncio.TimeoutError:
62
+ await _stack.aclose()
63
+ raise Exception("Timed out trying to connect to local isolate")
64
+
65
+ return channel
66
+
67
+
68
+ async def _set_logger_labels(
69
+ logger_labels: dict[str, str], channel: async_grpc.Channel
70
+ ):
71
+ try:
72
+ isolate = definitions.IsolateStub(channel)
73
+ isolate_request = definitions.SetMetadataRequest(
74
+ # TODO: when submit is shipped, get task_id from an env var
75
+ task_id="RUN",
76
+ metadata=definitions.TaskMetadata(logger_labels=logger_labels),
77
+ )
78
+ res = isolate.SetMetadata(isolate_request)
79
+ code = await res.code()
80
+ assert str(code) == "StatusCode.OK"
81
+ except BaseException:
82
+ logger.exception("Failed to set logger labels")
83
+
84
+
37
85
  def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
38
86
  include_modules_from(cls)
39
87
 
@@ -75,6 +123,12 @@ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
75
123
  return fn
76
124
 
77
125
 
126
+ @dataclass
127
+ class AppClientError(FalServerlessException):
128
+ message: str
129
+ status_code: int
130
+
131
+
78
132
  class EndpointClient:
79
133
  def __init__(self, url, endpoint, signature, timeout: int | None = None):
80
134
  self.url = url
@@ -87,12 +141,19 @@ class EndpointClient:
87
141
 
88
142
  def __call__(self, data):
89
143
  with httpx.Client() as client:
144
+ url = self.url + self.signature.path
90
145
  resp = client.post(
91
146
  self.url + self.signature.path,
92
147
  json=data.dict() if hasattr(data, "dict") else dict(data),
93
148
  timeout=self.timeout,
94
149
  )
95
- resp.raise_for_status()
150
+ if not resp.is_success:
151
+ # allow logs to be printed before raising the exception
152
+ time.sleep(1)
153
+ raise AppClientError(
154
+ f"Failed to POST {url}: {resp.status_code} {resp.text}",
155
+ status_code=resp.status_code,
156
+ )
96
157
  resp_dict = resp.json()
97
158
 
98
159
  if not self.return_type:
@@ -145,12 +206,16 @@ class AppClient:
145
206
  with httpx.Client() as client:
146
207
  retries = 100
147
208
  for _ in range(retries):
148
- resp = client.get(info.url + "/health", timeout=60)
209
+ url = info.url + "/health"
210
+ resp = client.get(url, timeout=60)
149
211
 
150
212
  if resp.is_success:
151
213
  break
152
214
  elif resp.status_code not in (500, 404):
153
- resp.raise_for_status()
215
+ raise AppClientError(
216
+ f"Failed to GET {url}: {resp.status_code} {resp.text}",
217
+ status_code=resp.status_code,
218
+ )
154
219
  time.sleep(0.1)
155
220
 
156
221
  client = cls(app_cls, info.url)
@@ -191,6 +256,8 @@ class App(fal.api.BaseServable):
191
256
  app_auth: ClassVar[Literal["private", "public", "shared"]] = "private"
192
257
  request_timeout: ClassVar[int | None] = None
193
258
 
259
+ isolate_channel: async_grpc.Channel | None = None
260
+
194
261
  def __init_subclass__(cls, **kwargs):
195
262
  app_name = kwargs.pop("name", None) or _to_fal_app_name(cls.__name__)
196
263
  parent_settings = getattr(cls, "host_kwargs", {})
@@ -266,11 +333,14 @@ class App(fal.api.BaseServable):
266
333
 
267
334
  @app.middleware("http")
268
335
  async def set_global_object_preference(request, call_next):
269
- response = await call_next(request)
270
336
  try:
271
- fal_provider_module.GLOBAL_LIFECYCLE_PREFERENCE = request.headers.get(
272
- "X-Fal-Object-Lifecycle-Preference"
273
- )
337
+ preference_dict = get_lifecycle_preference(request) or {}
338
+ expiration_duration = preference_dict.get("expiration_duration_seconds")
339
+ if expiration_duration is not None:
340
+ GLOBAL_LIFECYCLE_PREFERENCE.expiration_duration_seconds = int(
341
+ expiration_duration
342
+ )
343
+
274
344
  except Exception:
275
345
  from fastapi.logger import logger
276
346
 
@@ -278,7 +348,26 @@ class App(fal.api.BaseServable):
278
348
  "Failed set a global lifecycle preference %s",
279
349
  self.__class__.__name__,
280
350
  )
281
- return response
351
+
352
+ return await call_next(request)
353
+
354
+ @app.middleware("http")
355
+ async def set_request_id(request, call_next):
356
+ if self.isolate_channel is None:
357
+ grpc_port = os.environ.get("NOMAD_ALLOC_PORT_grpc")
358
+ self.isolate_channel = await open_isolate_channel(
359
+ f"localhost:{grpc_port}"
360
+ )
361
+
362
+ request_id = request.headers.get(REQUEST_ID_KEY)
363
+ if request_id is not None:
364
+ await _set_logger_labels(
365
+ {"fal_request_id": request_id}, channel=self.isolate_channel
366
+ )
367
+ try:
368
+ return await call_next(request)
369
+ finally:
370
+ await _set_logger_labels({}, channel=self.isolate_channel)
282
371
 
283
372
  @app.exception_handler(RequestCancelledException)
284
373
  async def value_error_exception_handler(
@@ -144,7 +144,7 @@ class File(BaseModel):
144
144
 
145
145
  fdata = FileData(data, content_type, file_name)
146
146
 
147
- object_lifecycle_preference = _get_lifecycle_preference(request)
147
+ object_lifecycle_preference = get_lifecycle_preference(request)
148
148
 
149
149
  try:
150
150
  url = repo.save(fdata, object_lifecycle_preference)
@@ -191,7 +191,7 @@ class File(BaseModel):
191
191
  )
192
192
 
193
193
  content_type = content_type or "application/octet-stream"
194
- object_lifecycle_preference = _get_lifecycle_preference(request)
194
+ object_lifecycle_preference = get_lifecycle_preference(request)
195
195
 
196
196
  try:
197
197
  url, data = repo.save_file(
@@ -274,7 +274,7 @@ class CompressedFile(File):
274
274
  shutil.rmtree(self.extract_dir)
275
275
 
276
276
 
277
- def _get_lifecycle_preference(request: Request) -> dict[str, str] | None:
277
+ def get_lifecycle_preference(request: Request) -> dict[str, str] | None:
278
278
  import json
279
279
 
280
280
  preference_str = (