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.
- {fal-0.15.0 → fal-0.15.2}/PKG-INFO +32 -4
- {fal-0.15.0 → fal-0.15.2}/README.md +27 -0
- {fal-0.15.0 → fal-0.15.2}/fal.egg-info/PKG-INFO +32 -4
- {fal-0.15.0 → fal-0.15.2}/fal.egg-info/requires.txt +4 -3
- {fal-0.15.0 → fal-0.15.2}/pyproject.toml +13 -3
- fal-0.15.2/src/fal/__init__.py +25 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/_serialization.py +15 -9
- {fal-0.15.0 → fal-0.15.2}/src/fal/api.py +22 -13
- {fal-0.15.0 → fal-0.15.2}/src/fal/app.py +25 -4
- {fal-0.15.0 → fal-0.15.2}/src/fal/auth/__init__.py +2 -1
- {fal-0.15.0 → fal-0.15.2}/src/fal/auth/auth0.py +4 -2
- {fal-0.15.0 → fal-0.15.2}/src/fal/auth/local.py +2 -1
- {fal-0.15.0 → fal-0.15.2}/src/fal/cli.py +8 -5
- {fal-0.15.0 → fal-0.15.2}/src/fal/exceptions/__init__.py +2 -1
- {fal-0.15.0 → fal-0.15.2}/src/fal/flags.py +0 -2
- {fal-0.15.0 → fal-0.15.2}/src/fal/logging/isolate.py +4 -4
- {fal-0.15.0 → fal-0.15.2}/src/fal/sdk.py +38 -2
- {fal-0.15.0 → fal-0.15.2}/src/fal/sync.py +7 -3
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/file.py +14 -6
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/providers/fal.py +20 -3
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/image/image.py +1 -1
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/optimize.py +0 -1
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/utils/download_utils.py +6 -3
- {fal-0.15.0 → fal-0.15.2}/src/fal/workflows.py +7 -2
- {fal-0.15.0 → fal-0.15.2}/tests/conftest.py +0 -1
- {fal-0.15.0 → fal-0.15.2}/tests/integration_test.py +35 -16
- {fal-0.15.0 → fal-0.15.2}/tests/test_apps.py +21 -21
- {fal-0.15.0 → fal-0.15.2}/tests/test_stability.py +31 -7
- {fal-0.15.0 → fal-0.15.2}/tests/toolkit/file_test.py +2 -1
- {fal-0.15.0 → fal-0.15.2}/tests/toolkit/image_test.py +13 -4
- fal-0.15.0/src/fal/__init__.py +0 -37
- {fal-0.15.0 → fal-0.15.2}/fal.egg-info/SOURCES.txt +0 -0
- {fal-0.15.0 → fal-0.15.2}/fal.egg-info/dependency_links.txt +0 -0
- {fal-0.15.0 → fal-0.15.2}/fal.egg-info/entry_points.txt +0 -0
- {fal-0.15.0 → fal-0.15.2}/fal.egg-info/top_level.txt +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/README.md +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflows_workflows_get.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {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
- {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
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents_type_0.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-0.15.0 → fal-0.15.2}/openapi_rest.config.yaml +0 -0
- {fal-0.15.0 → fal-0.15.2}/setup.cfg +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/__main__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/apps.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/console/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/console/icons.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/console/ux.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/exceptions/_base.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/exceptions/auth.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/exceptions/handlers.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/logging/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/logging/style.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/logging/trace.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/logging/user.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/py.typed +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/rest_client.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/exceptions.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/file/types.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/tests/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/tests/mainify_package/__init__.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/tests/mainify_package/impl.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/tests/mainify_package/utils.py +0 -0
- {fal-0.15.0 → fal-0.15.2}/tests/mainify_target.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
104
|
-
# to be imported in the namespace but are not actually used, resulting
|
|
105
|
-
# Unfortunately this also means that `model_rebuid()` might
|
|
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
|
|
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 = {
|
|
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
|
|
117
|
-
key, value = cls.parse_key(
|
|
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
|
|
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
|
|
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(
|
|
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}
|
|
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
|
|
1016
|
-
f"function uses the following modules
|
|
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
|
|
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,
|
|
1102
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
38
|
-
# is being sent in the gRPC message.
|
|
39
|
-
# The `isolate` version users interpret that message with is out of our
|
|
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],
|