fal 0.15.0__tar.gz → 0.15.2__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 (109) hide show
  1. {fal-0.15.0 → fal-0.15.2}/PKG-INFO +32 -4
  2. {fal-0.15.0 → fal-0.15.2}/README.md +27 -0
  3. {fal-0.15.0 → fal-0.15.2}/fal.egg-info/PKG-INFO +32 -4
  4. {fal-0.15.0 → fal-0.15.2}/fal.egg-info/requires.txt +4 -3
  5. {fal-0.15.0 → fal-0.15.2}/pyproject.toml +13 -3
  6. fal-0.15.2/src/fal/__init__.py +25 -0
  7. {fal-0.15.0 → fal-0.15.2}/src/fal/_serialization.py +15 -9
  8. {fal-0.15.0 → fal-0.15.2}/src/fal/api.py +22 -13
  9. {fal-0.15.0 → fal-0.15.2}/src/fal/app.py +25 -4
  10. {fal-0.15.0 → fal-0.15.2}/src/fal/auth/__init__.py +2 -1
  11. {fal-0.15.0 → fal-0.15.2}/src/fal/auth/auth0.py +4 -2
  12. {fal-0.15.0 → fal-0.15.2}/src/fal/auth/local.py +2 -1
  13. {fal-0.15.0 → fal-0.15.2}/src/fal/cli.py +8 -5
  14. {fal-0.15.0 → fal-0.15.2}/src/fal/exceptions/__init__.py +2 -1
  15. {fal-0.15.0 → fal-0.15.2}/src/fal/flags.py +0 -2
  16. {fal-0.15.0 → fal-0.15.2}/src/fal/logging/isolate.py +4 -4
  17. {fal-0.15.0 → fal-0.15.2}/src/fal/sdk.py +38 -2
  18. {fal-0.15.0 → fal-0.15.2}/src/fal/sync.py +7 -3
  19. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/file.py +14 -6
  20. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/providers/fal.py +20 -3
  21. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/image/image.py +1 -1
  22. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/optimize.py +0 -1
  23. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/utils/download_utils.py +6 -3
  24. {fal-0.15.0 → fal-0.15.2}/src/fal/workflows.py +7 -2
  25. {fal-0.15.0 → fal-0.15.2}/tests/conftest.py +0 -1
  26. {fal-0.15.0 → fal-0.15.2}/tests/integration_test.py +35 -16
  27. {fal-0.15.0 → fal-0.15.2}/tests/test_apps.py +21 -21
  28. {fal-0.15.0 → fal-0.15.2}/tests/test_stability.py +31 -7
  29. {fal-0.15.0 → fal-0.15.2}/tests/toolkit/file_test.py +2 -1
  30. {fal-0.15.0 → fal-0.15.2}/tests/toolkit/image_test.py +13 -4
  31. fal-0.15.0/src/fal/__init__.py +0 -37
  32. {fal-0.15.0 → fal-0.15.2}/fal.egg-info/SOURCES.txt +0 -0
  33. {fal-0.15.0 → fal-0.15.2}/fal.egg-info/dependency_links.txt +0 -0
  34. {fal-0.15.0 → fal-0.15.2}/fal.egg-info/entry_points.txt +0 -0
  35. {fal-0.15.0 → fal-0.15.2}/fal.egg-info/top_level.txt +0 -0
  36. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/README.md +0 -0
  37. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  38. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  39. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  40. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  41. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  42. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  43. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  44. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  45. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  46. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  47. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_or_update_workflow_workflows_post.py +0 -0
  48. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow_workflows_user_id_workflow_name_delete.py +0 -0
  49. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/execute_workflow_workflows_user_id_workflow_name_post.py +0 -0
  50. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow_workflows_user_id_workflow_name_get.py +0 -0
  51. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflows_workflows_get.py +0 -0
  52. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  53. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  54. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  55. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  56. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  57. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  58. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_json_body_type_0.py +0 -0
  59. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_response_200_type_0.py +0 -0
  60. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  61. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  62. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  63. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  64. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  65. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  66. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  67. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  68. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  69. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  70. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents_type_0.py +0 -0
  71. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  72. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  73. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  74. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  75. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  76. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  77. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  78. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  79. {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/pyproject.toml +0 -0
  80. {fal-0.15.0 → fal-0.15.2}/openapi_rest.config.yaml +0 -0
  81. {fal-0.15.0 → fal-0.15.2}/setup.cfg +0 -0
  82. {fal-0.15.0 → fal-0.15.2}/src/fal/__main__.py +0 -0
  83. {fal-0.15.0 → fal-0.15.2}/src/fal/apps.py +0 -0
  84. {fal-0.15.0 → fal-0.15.2}/src/fal/console/__init__.py +0 -0
  85. {fal-0.15.0 → fal-0.15.2}/src/fal/console/icons.py +0 -0
  86. {fal-0.15.0 → fal-0.15.2}/src/fal/console/ux.py +0 -0
  87. {fal-0.15.0 → fal-0.15.2}/src/fal/exceptions/_base.py +0 -0
  88. {fal-0.15.0 → fal-0.15.2}/src/fal/exceptions/auth.py +0 -0
  89. {fal-0.15.0 → fal-0.15.2}/src/fal/exceptions/handlers.py +0 -0
  90. {fal-0.15.0 → fal-0.15.2}/src/fal/logging/__init__.py +0 -0
  91. {fal-0.15.0 → fal-0.15.2}/src/fal/logging/style.py +0 -0
  92. {fal-0.15.0 → fal-0.15.2}/src/fal/logging/trace.py +0 -0
  93. {fal-0.15.0 → fal-0.15.2}/src/fal/logging/user.py +0 -0
  94. {fal-0.15.0 → fal-0.15.2}/src/fal/py.typed +0 -0
  95. {fal-0.15.0 → fal-0.15.2}/src/fal/rest_client.py +0 -0
  96. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/__init__.py +0 -0
  97. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/exceptions.py +0 -0
  98. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/__init__.py +0 -0
  99. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/providers/gcp.py +0 -0
  100. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/providers/r2.py +0 -0
  101. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/types.py +0 -0
  102. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/image/__init__.py +0 -0
  103. {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/utils/__init__.py +0 -0
  104. {fal-0.15.0 → fal-0.15.2}/tests/__init__.py +0 -0
  105. {fal-0.15.0 → fal-0.15.2}/tests/mainify_package/__init__.py +0 -0
  106. {fal-0.15.0 → fal-0.15.2}/tests/mainify_package/impl.py +0 -0
  107. {fal-0.15.0 → fal-0.15.2}/tests/mainify_package/utils.py +0 -0
  108. {fal-0.15.0 → fal-0.15.2}/tests/mainify_target.py +0 -0
  109. {fal-0.15.0 → fal-0.15.2}/tools/demo_script.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 0.15.0
3
+ Version: 0.15.2
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <hello@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<1.0,>=0.12.3
9
- Requires-Dist: isolate-proto==0.3.4
9
+ Requires-Dist: isolate-proto==0.4.0
10
10
  Requires-Dist: grpcio<2,>=1.50.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -36,9 +36,10 @@ Requires-Dist: pillow<11,>=10.2.0
36
36
  Requires-Dist: pyjwt[crypto]<3,>=2.8.0
37
37
  Requires-Dist: uvicorn<1,>=0.29.0
38
38
  Provides-Extra: test
39
- Requires-Dist: pytest; extra == "test"
40
- Requires-Dist: pytest-xdist; extra == "test"
39
+ Requires-Dist: pytest<8; extra == "test"
41
40
  Requires-Dist: pytest-asyncio; extra == "test"
41
+ Requires-Dist: pytest-xdist; extra == "test"
42
+ Requires-Dist: flaky; extra == "test"
42
43
  Provides-Extra: dev
43
44
  Requires-Dist: fal[test]; extra == "dev"
44
45
  Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
@@ -89,3 +90,30 @@ A new virtual environment will be created by fal in the cloud and the set of req
89
90
  ## Next steps
90
91
 
91
92
  If you would like to find out more about the capabilities of fal, check out to the [docs](https://fal.ai/docs). You can learn more about persistent storage, function caches and deploying your functions as API endpoints.
93
+
94
+ ## Contributing
95
+
96
+ ### Installing in editable mode with dev dependencies
97
+
98
+ ```py
99
+ pip install -e 'projects/fal[dev]'
100
+ pip install -e 'projects/fal_client[dev]'
101
+ pip install -e 'projects/isolate_proto[dev]'
102
+ ```
103
+
104
+ ### Running tests
105
+
106
+ ```py
107
+ pytest
108
+ ```
109
+
110
+ ### Pre-commit
111
+
112
+ ```
113
+ cd projects/fal
114
+ pre-commit install
115
+ ```
116
+
117
+ ### Commit format
118
+
119
+ Please follow [conventional commits specification](https://www.conventionalcommits.org/) for descriptions/messages.
@@ -44,3 +44,30 @@ A new virtual environment will be created by fal in the cloud and the set of req
44
44
  ## Next steps
45
45
 
46
46
  If you would like to find out more about the capabilities of fal, check out to the [docs](https://fal.ai/docs). You can learn more about persistent storage, function caches and deploying your functions as API endpoints.
47
+
48
+ ## Contributing
49
+
50
+ ### Installing in editable mode with dev dependencies
51
+
52
+ ```py
53
+ pip install -e 'projects/fal[dev]'
54
+ pip install -e 'projects/fal_client[dev]'
55
+ pip install -e 'projects/isolate_proto[dev]'
56
+ ```
57
+
58
+ ### Running tests
59
+
60
+ ```py
61
+ pytest
62
+ ```
63
+
64
+ ### Pre-commit
65
+
66
+ ```
67
+ cd projects/fal
68
+ pre-commit install
69
+ ```
70
+
71
+ ### Commit format
72
+
73
+ Please follow [conventional commits specification](https://www.conventionalcommits.org/) for descriptions/messages.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 0.15.0
3
+ Version: 0.15.2
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <hello@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<1.0,>=0.12.3
9
- Requires-Dist: isolate-proto==0.3.4
9
+ Requires-Dist: isolate-proto==0.4.0
10
10
  Requires-Dist: grpcio<2,>=1.50.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -36,9 +36,10 @@ Requires-Dist: pillow<11,>=10.2.0
36
36
  Requires-Dist: pyjwt[crypto]<3,>=2.8.0
37
37
  Requires-Dist: uvicorn<1,>=0.29.0
38
38
  Provides-Extra: test
39
- Requires-Dist: pytest; extra == "test"
40
- Requires-Dist: pytest-xdist; extra == "test"
39
+ Requires-Dist: pytest<8; extra == "test"
41
40
  Requires-Dist: pytest-asyncio; extra == "test"
41
+ Requires-Dist: pytest-xdist; extra == "test"
42
+ Requires-Dist: flaky; extra == "test"
42
43
  Provides-Extra: dev
43
44
  Requires-Dist: fal[test]; extra == "dev"
44
45
  Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
@@ -89,3 +90,30 @@ A new virtual environment will be created by fal in the cloud and the set of req
89
90
  ## Next steps
90
91
 
91
92
  If you would like to find out more about the capabilities of fal, check out to the [docs](https://fal.ai/docs). You can learn more about persistent storage, function caches and deploying your functions as API endpoints.
93
+
94
+ ## Contributing
95
+
96
+ ### Installing in editable mode with dev dependencies
97
+
98
+ ```py
99
+ pip install -e 'projects/fal[dev]'
100
+ pip install -e 'projects/fal_client[dev]'
101
+ pip install -e 'projects/isolate_proto[dev]'
102
+ ```
103
+
104
+ ### Running tests
105
+
106
+ ```py
107
+ pytest
108
+ ```
109
+
110
+ ### Pre-commit
111
+
112
+ ```
113
+ cd projects/fal
114
+ pre-commit install
115
+ ```
116
+
117
+ ### Commit format
118
+
119
+ Please follow [conventional commits specification](https://www.conventionalcommits.org/) for descriptions/messages.
@@ -1,5 +1,5 @@
1
1
  isolate[build]<1.0,>=0.12.3
2
- isolate-proto==0.3.4
2
+ isolate-proto==0.4.0
3
3
  grpcio<2,>=1.50.0
4
4
  dill==0.3.7
5
5
  cloudpickle==3.0.0
@@ -36,6 +36,7 @@ fal[test]
36
36
  openapi-python-client<1,>=0.14.1
37
37
 
38
38
  [test]
39
- pytest
40
- pytest-xdist
39
+ pytest<8
41
40
  pytest-asyncio
41
+ pytest-xdist
42
+ flaky
@@ -22,7 +22,7 @@ readme = "README.md"
22
22
  requires-python = ">=3.8"
23
23
  dependencies = [
24
24
  "isolate[build]>=0.12.3,<1.0",
25
- "isolate-proto==0.3.4",
25
+ "isolate-proto==0.4.0",
26
26
  "grpcio>=1.50.0,<2",
27
27
  "dill==0.3.7",
28
28
  "cloudpickle==3.0.0",
@@ -58,9 +58,10 @@ dependencies = [
58
58
 
59
59
  [project.optional-dependencies]
60
60
  test = [
61
- "pytest",
62
- "pytest-xdist",
61
+ "pytest<8",
63
62
  "pytest-asyncio",
63
+ "pytest-xdist",
64
+ "flaky",
64
65
  ]
65
66
  dev = [
66
67
  "fal[test]",
@@ -69,3 +70,12 @@ dev = [
69
70
 
70
71
  [project.scripts]
71
72
  fal = "fal.cli:cli"
73
+
74
+ [tool.ruff]
75
+ target-version = "py38"
76
+
77
+ [tool.ruff.lint]
78
+ select = ["E", "F", "W", "PLC", "PLE", "PLW", "I", "UP"]
79
+
80
+ [tool.ruff.lint.pyupgrade]
81
+ keep-runtime-typing = true
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from fal import apps # noqa: F401
4
+ from fal.api import FalServerlessHost, LocalHost, cached, function
5
+ from fal.api import function as isolated # noqa: F401
6
+ from fal.app import App, endpoint, realtime, wrap_app # noqa: F401
7
+ from fal.sdk import FalServerlessKeyCredentials
8
+ from fal.sync import sync_dir
9
+
10
+ local = LocalHost()
11
+ serverless = FalServerlessHost()
12
+
13
+ # DEPRECATED - use serverless instead
14
+ cloud = FalServerlessHost()
15
+
16
+ __all__ = [
17
+ "function",
18
+ "cached",
19
+ "App",
20
+ "endpoint",
21
+ "realtime",
22
+ # "wrap_app",
23
+ "FalServerlessKeyCredentials",
24
+ "sync_dir",
25
+ ]
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import pickle
3
4
  from typing import Any, Callable
4
5
 
5
- import pickle
6
6
  import cloudpickle
7
7
 
8
8
 
@@ -98,11 +98,12 @@ def _patch_pydantic_field_serialization() -> None:
98
98
 
99
99
 
100
100
  def _patch_pydantic_model_serialization() -> None:
101
- # If user has created new pydantic models in his namespace, we will try to pickle those
102
- # by value, which means recreating class skeleton, which will stumble upon
103
- # __pydantic_parent_namespace__ in its __dict__ and it may contain modules that happened
104
- # to be imported in the namespace but are not actually used, resulting in pickling errors.
105
- # Unfortunately this also means that `model_rebuid()` might not work.
101
+ # If user has created new pydantic models in his namespace, we will try to pickle
102
+ # those by value, which means recreating class skeleton, which will stumble upon
103
+ # __pydantic_parent_namespace__ in its __dict__ and it may contain modules that
104
+ # happened to be imported in the namespace but are not actually used, resulting
105
+ # in pickling errors. Unfortunately this also means that `model_rebuid()` might
106
+ # not work.
106
107
  try:
107
108
  import pydantic
108
109
  except ImportError:
@@ -133,7 +134,8 @@ def _patch_lru_cache() -> None:
133
134
  # https://github.com/cloudpipe/cloudpickle/issues/178
134
135
  # https://github.com/uqfoundation/dill/blob/70f569b0dd268d2b1e85c0f300951b11f53c5d53/dill/_dill.py#L1429
135
136
 
136
- from functools import lru_cache, _lru_cache_wrapper as LRUCacheType
137
+ from functools import _lru_cache_wrapper as LRUCacheType
138
+ from functools import lru_cache
137
139
 
138
140
  def create_lru_cache(func: Callable, kwargs: dict) -> LRUCacheType:
139
141
  return lru_cache(**kwargs)(func)
@@ -155,8 +157,8 @@ def _patch_lru_cache() -> None:
155
157
 
156
158
  def _patch_lock() -> None:
157
159
  # https://github.com/uqfoundation/dill/blob/70f569b0dd268d2b1e85c0f300951b11f53c5d53/dill/_dill.py#L1310
158
- from threading import Lock
159
160
  from _thread import LockType
161
+ from threading import Lock
160
162
 
161
163
  def create_lock(locked: bool) -> Lock:
162
164
  lock = Lock()
@@ -199,7 +201,11 @@ def _patch_console_thread_locals() -> None:
199
201
  return ConsoleThreadLocals(**kwargs)
200
202
 
201
203
  def pickle_locals(obj: ConsoleThreadLocals) -> tuple[Callable, tuple]:
202
- kwargs = {"theme_stack": obj.theme_stack, "buffer": obj.buffer, "buffer_index": obj.buffer_index}
204
+ kwargs = {
205
+ "theme_stack": obj.theme_stack,
206
+ "buffer": obj.buffer,
207
+ "buffer_index": obj.buffer_index,
208
+ }
203
209
  return create_locals, (kwargs, )
204
210
 
205
211
  _register(ConsoleThreadLocals, pickle_locals)
@@ -40,8 +40,8 @@ from pydantic import __version__ as pydantic_version
40
40
  from typing_extensions import Concatenate, ParamSpec
41
41
 
42
42
  import fal.flags as flags
43
- from fal.exceptions import FalServerlessException
44
43
  from fal._serialization import include_modules_from, patch_pickle
44
+ from fal.exceptions import FalServerlessException
45
45
  from fal.logging.isolate import IsolateLogPrinter
46
46
  from fal.sdk import (
47
47
  FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
@@ -57,7 +57,7 @@ from fal.sdk import (
57
57
  )
58
58
 
59
59
  ArgsT = ParamSpec("ArgsT")
60
- ReturnT = TypeVar("ReturnT", covariant=True)
60
+ ReturnT = TypeVar("ReturnT", covariant=True) # noqa: PLC0105
61
61
 
62
62
  BasicConfig = Dict[str, Any]
63
63
  _UNSET = object()
@@ -113,8 +113,8 @@ class Host(Generic[ArgsT, ReturnT]):
113
113
  environment options."""
114
114
 
115
115
  options = Options()
116
- for key, value in config.items():
117
- key, value = cls.parse_key(key, value)
116
+ for item in config.items():
117
+ key, value = cls.parse_key(*item)
118
118
  if key in cls._SUPPORTED_KEYS:
119
119
  options.host[key] = value
120
120
  elif key in cls._GATEWAY_KEYS:
@@ -545,7 +545,8 @@ _DEFAULT_HOST = FalServerlessHost()
545
545
  _SERVE_PORT = 8080
546
546
 
547
547
  # Overload @function to help users identify the correct signature.
548
- # NOTE: This is both in sync with host options and with environment configs from `isolate` package.
548
+ # NOTE: This is both in sync with host options and with environment configs from
549
+ # `isolate` package.
549
550
 
550
551
 
551
552
  ## virtualenv
@@ -785,7 +786,8 @@ class FalFastAPI(FastAPI):
785
786
  """
786
787
  Add x-fal-order-* keys to the OpenAPI specification to help the rendering of UI.
787
788
 
788
- NOTE: We rely on the fact that fastapi and Python dicts keep the order of properties.
789
+ NOTE: We rely on the fact that fastapi and Python dicts keep the order of
790
+ properties.
789
791
  """
790
792
 
791
793
  def mark_order(obj: dict[str, Any], key: str):
@@ -918,7 +920,9 @@ class BaseServable:
918
920
  asyncio.create_task(metrics_server.serve()): metrics_server,
919
921
  }
920
922
 
921
- _, pending = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
923
+ _, pending = await asyncio.wait(
924
+ tasks.keys(), return_when=asyncio.FIRST_COMPLETED,
925
+ )
922
926
  if not pending:
923
927
  return
924
928
 
@@ -1007,13 +1011,15 @@ class IsolatedFunction(Generic[ArgsT, ReturnT]):
1007
1011
  lines = []
1008
1012
  for used_modules, references in pairs:
1009
1013
  lines.append(
1010
- f"\t- {used_modules!r} (accessed through {', '.join(map(repr, references))})"
1014
+ f"\t- {used_modules!r} "
1015
+ f"(accessed through {', '.join(map(repr, references))})"
1011
1016
  )
1012
1017
 
1013
1018
  function_name = self.func.__name__
1014
1019
  raise FalServerlessError(
1015
- f"Couldn't deserialize your function on the remote server. \n\n[Hint] {function_name!r} "
1016
- f"function uses the following modules which weren't present in the environment definition:\n"
1020
+ f"Couldn't deserialize your function on the remote server. \n\n"
1021
+ f"[Hint] {function_name!r} function uses the following modules "
1022
+ "which weren't present in the environment definition:\n"
1017
1023
  + "\n".join(lines)
1018
1024
  ) from None
1019
1025
  except Exception as exc:
@@ -1065,7 +1071,8 @@ class IsolatedFunction(Generic[ArgsT, ReturnT]):
1065
1071
  def func(self) -> Callable[ArgsT, ReturnT]:
1066
1072
  serve_mode = self.options.gateway.get("serve")
1067
1073
  if serve_mode:
1068
- # This type can be safely ignored because this case only happens when it is a ServedIsolatedFunction
1074
+ # This type can be safely ignored because this case only happens when it
1075
+ # is a ServedIsolatedFunction
1069
1076
  serve_func: Callable[[], None] = ServeWrapper(self.raw_func)
1070
1077
  return serve_func # type: ignore
1071
1078
  else:
@@ -1098,8 +1105,10 @@ class ServedIsolatedFunction(
1098
1105
 
1099
1106
  class Server(uvicorn.Server):
1100
1107
  """Server is a uvicorn.Server that actually plays nicely with signals.
1101
- By default, uvicorn's Server class overwrites the signal handler for SIGINT, swallowing the signal and preventing other tasks from cancelling.
1102
- This class allows the task to be gracefully cancelled using asyncio's built-in task cancellation or with an event, like aiohttp.
1108
+ By default, uvicorn's Server class overwrites the signal handler for SIGINT,
1109
+ swallowing the signal and preventing other tasks from cancelling.
1110
+ This class allows the task to be gracefully cancelled using asyncio's built-in task
1111
+ cancellation or with an event, like aiohttp.
1103
1112
  """
1104
1113
 
1105
1114
  def install_signal_handlers(self) -> None:
@@ -10,9 +10,11 @@ from typing import Any, Callable, ClassVar, TypeVar
10
10
  from fastapi import FastAPI
11
11
 
12
12
  import fal.api
13
+ from fal._serialization import include_modules_from
13
14
  from fal.api import RouteSignature
14
15
  from fal.logging import get_logger
15
- from fal._serialization import include_modules_from
16
+ from fal.toolkit.file.providers import fal as fal_provider_module
17
+
16
18
  REALTIME_APP_REQUIREMENTS = ["websockets", "msgpack"]
17
19
 
18
20
  EndpointT = TypeVar("EndpointT", bound=Callable[..., Any])
@@ -123,6 +125,22 @@ class App(fal.api.BaseServable):
123
125
  )
124
126
  return response
125
127
 
128
+ @app.middleware("http")
129
+ async def set_global_object_preference(request, call_next):
130
+ response = await call_next(request)
131
+ try:
132
+ fal_provider_module.GLOBAL_LIFECYCLE_PREFERENCE = request.headers.get(
133
+ "X-Fal-Object-Lifecycle-Preference"
134
+ )
135
+ except Exception:
136
+ from fastapi.logger import logger
137
+
138
+ logger.exception(
139
+ "Failed set a global lifecycle preference %s",
140
+ self.__class__.__name__,
141
+ )
142
+ return response
143
+
126
144
  def provide_hints(self) -> list[str]:
127
145
  """Provide hints for routing the application."""
128
146
  raise NotImplementedError
@@ -237,14 +255,16 @@ def _fal_websocket_template(
237
255
  output = output.dict()
238
256
  else:
239
257
  raise TypeError(
240
- f"Expected a dict or pydantic model as output, got {type(output)}"
258
+ "Expected a dict or pydantic model as output, got "
259
+ f"{type(output)}"
241
260
  )
242
261
 
243
262
  messages = [
244
263
  msgpack.packb(output, use_bin_type=True),
245
264
  ]
246
265
  if route_signature.emit_timings:
247
- # We emit x-fal messages in JSON, no matter what the input/output format is.
266
+ # We emit x-fal messages in JSON, no matter what the
267
+ # input/output format is.
248
268
  timings = {
249
269
  "type": "x-fal-message",
250
270
  "action": "timings",
@@ -354,7 +374,8 @@ def realtime(
354
374
 
355
375
  if hasattr(original_func, "route_signature"):
356
376
  raise ValueError(
357
- f"Can't set multiple routes for the same function: {original_func.__name__}"
377
+ "Can't set multiple routes for the same function: "
378
+ f"{original_func.__name__}"
358
379
  )
359
380
 
360
381
  if input_modal is _SENTINEL:
@@ -51,7 +51,8 @@ def _fetch_access_token() -> str:
51
51
  Load the refresh token, request a new access_token (refreshing the refresh token)
52
52
  and return the access_token.
53
53
  """
54
- # We need to lock both read and write access because we could be reading a soon invalid refresh_token
54
+ # We need to lock both read and write access because we could be reading a soon
55
+ # invalid refresh_token
55
56
  with local.lock_token():
56
57
  refresh_token, access_token = local.load_token()
57
58
 
@@ -30,7 +30,8 @@ def _open_browser(url: str, code: str | None) -> None:
30
30
  maybe_open_browser_tab(url)
31
31
 
32
32
  console.print(
33
- "If browser didn't open automatically, on your computer or mobile device navigate to"
33
+ "If browser didn't open automatically, "
34
+ "on your computer or mobile device navigate to"
34
35
  )
35
36
  console.print(url)
36
37
 
@@ -155,7 +156,8 @@ def build_jwk_client():
155
156
 
156
157
  def validate_id_token(token: str):
157
158
  """
158
- id_token is intended for the client (this sdk) only. Never send one to another service.
159
+ id_token is intended for the client (this sdk) only.
160
+ Never send one to another service.
159
161
  """
160
162
  from jwt import decode
161
163
 
@@ -62,7 +62,8 @@ def delete_token() -> None:
62
62
  @contextmanager
63
63
  def lock_token():
64
64
  """
65
- Lock the access to the token file to avoid race conditions when running multiple scripts at the same time.
65
+ Lock the access to the token file to avoid race conditions when running multiple
66
+ scripts at the same time.
66
67
  """
67
68
  lock_file = _check_dir_exist() / _LOCK_FILE
68
69
  with portalocker.utils.TemporaryFileLock(
@@ -89,12 +89,13 @@ class MainGroup(RichGroup):
89
89
  except Exception as exception:
90
90
  logger.error(exception)
91
91
  if state.debug:
92
- # Here we supress detailed errors on click lines because
93
- # they're mostly decorator calls, irrelevant to the dev's error tracing
92
+ # Here we supress detailed errors on click lines because they're
93
+ # mostly decorator calls, irrelevant to the dev's error tracing
94
94
  console.print_exception(suppress=[click])
95
95
  console.print()
96
96
  console.print(
97
- f"The [markdown.code]invocation_id[/] for this operation is: [white]{state.invocation_id}[/]"
97
+ "The [markdown.code]invocation_id[/] for this operation is: "
98
+ f"[white]{state.invocation_id}[/]"
98
99
  )
99
100
  else:
100
101
  self._exception_handler.handle(exception)
@@ -207,7 +208,8 @@ def key_generate(client: sdk.FalServerlessClient, scope: str, alias: str | None)
207
208
  print(
208
209
  f"Generated key id and key secret, with the scope `{scope}`.\n"
209
210
  "This is the only time the secret will be visible.\n"
210
- "You will need to generate a new key pair if you lose access to this secret."
211
+ "You will need to generate a new key pair if you lose access to this "
212
+ "secret."
211
213
  )
212
214
  print(f"FAL_KEY='{result[1]}:{result[0]}'")
213
215
 
@@ -306,7 +308,8 @@ def register_application(
306
308
  gateway_options = isolated_function.options.gateway
307
309
  if "serve" not in gateway_options and "exposed_port" not in gateway_options:
308
310
  raise api.FalServerlessError(
309
- "One of `serve` or `exposed_port` options needs to be specified in the isolated annotation to register a function"
311
+ "One of `serve` or `exposed_port` options needs to be specified "
312
+ "in the isolated annotation to register a function"
310
313
  )
311
314
  elif (
312
315
  "exposed_port" in gateway_options
@@ -13,7 +13,8 @@ class ApplicationExceptionHandler:
13
13
 
14
14
  This exception handler is capable of handling, i.e. customize the output
15
15
  and add behavior, of any type of exception. Click handles all `ClickException`
16
- types by default, but prints the stack for other exception not wrapped in ClickException.
16
+ types by default, but prints the stack for other exception not wrapped in
17
+ ClickException.
17
18
 
18
19
  The handler also allows for central metrics and logging collection.
19
20
  """
@@ -18,8 +18,6 @@ GRPC_HOST = os.getenv("FAL_HOST", "api.alpha.fal.ai")
18
18
  if not TEST_MODE:
19
19
  assert GRPC_HOST.startswith("api"), "FAL_HOST must start with 'api'"
20
20
 
21
- GATEWAY_HOST = GRPC_HOST.replace("api", "gateway", 1)
22
-
23
21
  REST_HOST = GRPC_HOST.replace("api", "rest", 1)
24
22
  REST_SCHEME = "http" if TEST_MODE or AUTH_DISABLED else "https"
25
23
  REST_URL = f"{REST_SCHEME}://{REST_HOST}"
@@ -34,10 +34,10 @@ class IsolateLogPrinter:
34
34
  timestamp = log.timestamp
35
35
  else:
36
36
  # Default value for timestamp if user has old `isolate` version.
37
- # Even if the controller version is controller by us, which means that the timestamp
38
- # is being sent in the gRPC message.
39
- # The `isolate` version users interpret that message with is out of our control.
40
- # So we need to handle this case.
37
+ # Even if the controller version is controller by us, which means that
38
+ # the timestamp is being sent in the gRPC message.
39
+ # The `isolate` version users interpret that message with is out of our
40
+ # control. So we need to handle this case.
41
41
  timestamp = datetime.now(timezone.utc)
42
42
 
43
43
  event: EventDict = {
@@ -8,17 +8,17 @@ from enum import Enum
8
8
  from typing import Any, Callable, Generic, Iterator, Literal, TypeVar
9
9
 
10
10
  import grpc
11
+ import isolate_proto
11
12
  from isolate.connections.common import is_agent
12
13
  from isolate.logs import Log
13
14
  from isolate.server.interface import from_grpc, to_serialized_object, to_struct
15
+ from isolate_proto.configuration import GRPC_OPTIONS
14
16
 
15
- import isolate_proto
16
17
  from fal import flags
17
18
  from fal._serialization import patch_pickle
18
19
  from fal.auth import USER, key_credentials
19
20
  from fal.logging import get_logger
20
21
  from fal.logging.trace import TraceContextInterceptor
21
- from isolate_proto.configuration import GRPC_OPTIONS
22
22
 
23
23
  ResultT = TypeVar("ResultT")
24
24
  InputT = TypeVar("InputT")
@@ -187,6 +187,16 @@ class HostedRunStatus:
187
187
  state: HostedRunState
188
188
 
189
189
 
190
+ @dataclass
191
+ class ApplicationInfo:
192
+ application_id: str
193
+ keep_alive: int
194
+ max_concurrency: int
195
+ max_multiplexing: int
196
+ active_runners: int
197
+ min_concurrency: int
198
+
199
+
190
200
  @dataclass
191
201
  class AliasInfo:
192
202
  alias: str
@@ -263,6 +273,20 @@ class KeyScope(enum.Enum):
263
273
  raise ValueError(f"Unknown KeyScope: {proto}")
264
274
 
265
275
 
276
+ @from_grpc.register(isolate_proto.ApplicationInfo)
277
+ def _from_grpc_application_info(
278
+ message: isolate_proto.ApplicationInfo
279
+ ) -> ApplicationInfo:
280
+ return ApplicationInfo(
281
+ application_id=message.application_id,
282
+ keep_alive=message.keep_alive,
283
+ max_concurrency=message.max_concurrency,
284
+ max_multiplexing=message.max_multiplexing,
285
+ active_runners=message.active_runners,
286
+ min_concurrency=message.min_concurrency,
287
+ )
288
+
289
+
266
290
  @from_grpc.register(isolate_proto.AliasInfo)
267
291
  def _from_grpc_alias_info(message: isolate_proto.AliasInfo) -> AliasInfo:
268
292
  if message.auth_mode is isolate_proto.ApplicationAuthMode.PUBLIC:
@@ -496,6 +520,18 @@ class FalServerlessConnection:
496
520
  )
497
521
  return from_grpc(res.alias_info)
498
522
 
523
+ def list_applications(self) -> list[ApplicationInfo]:
524
+ request = isolate_proto.ListApplicationsRequest()
525
+ res: isolate_proto.ListApplicationsResult = self.stub.ListApplications(request)
526
+ return [from_grpc(app) for app in res.applications]
527
+
528
+ def delete_application(
529
+ self,
530
+ application_id: str,
531
+ ) -> None:
532
+ request = isolate_proto.DeleteApplicationRequest(application_id=application_id)
533
+ self.stub.DeleteApplication(request)
534
+
499
535
  def run(
500
536
  self,
501
537
  function: Callable[..., ResultT],