fal 1.3.0__tar.gz → 1.3.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-1.3.0 → fal-1.3.2}/PKG-INFO +3 -2
- {fal-1.3.0 → fal-1.3.2}/fal.egg-info/PKG-INFO +3 -2
- {fal-1.3.0 → fal-1.3.2}/fal.egg-info/SOURCES.txt +2 -0
- {fal-1.3.0 → fal-1.3.2}/fal.egg-info/requires.txt +2 -1
- {fal-1.3.0 → fal-1.3.2}/pyproject.toml +2 -1
- {fal-1.3.0 → fal-1.3.2}/src/fal/__main__.py +3 -1
- {fal-1.3.0 → fal-1.3.2}/src/fal/_fal_version.py +2 -2
- {fal-1.3.0 → fal-1.3.2}/src/fal/api.py +2 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/app.py +21 -1
- {fal-1.3.0 → fal-1.3.2}/src/fal/apps.py +9 -0
- fal-1.3.2/src/fal/cli/_utils.py +34 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/deploy.py +71 -16
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/parser.py +11 -7
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/run.py +12 -1
- fal-1.3.2/src/fal/exceptions/__init__.py +9 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/exceptions/_base.py +17 -9
- fal-1.3.2/src/fal/files.py +81 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/sdk.py +33 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/file/file.py +21 -4
- fal-1.3.2/src/fal/toolkit/file/providers/fal.py +319 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/file/types.py +18 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/utils.py +14 -2
- fal-1.3.2/tests/cli/test_deploy.py +148 -0
- fal-1.3.2/tests/cli/test_run.py +109 -0
- {fal-1.3.0 → fal-1.3.2}/tests/test_apps.py +90 -6
- {fal-1.3.0 → fal-1.3.2}/tests/test_stability.py +18 -8
- fal-1.3.0/src/fal/exceptions/__init__.py +0 -4
- fal-1.3.0/src/fal/toolkit/file/providers/fal.py +0 -143
- fal-1.3.0/tests/cli/test_deploy.py +0 -8
- fal-1.3.0/tests/cli/test_run.py +0 -8
- {fal-1.3.0 → fal-1.3.2}/.gitignore +0 -0
- {fal-1.3.0 → fal-1.3.2}/README.md +0 -0
- {fal-1.3.0 → fal-1.3.2}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.3.0 → fal-1.3.2}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.3.0 → fal-1.3.2}/fal.egg-info/top_level.txt +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/README.md +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.3.0 → fal-1.3.2}/openapi_rest.config.yaml +0 -0
- {fal-1.3.0 → fal-1.3.2}/setup.cfg +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/_serialization.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/_version.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/auth/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/auth/auth0.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/auth/local.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/apps.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/auth.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/create.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/debug.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/doctor.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/keys.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/main.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/cli/secrets.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/console/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/console/icons.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/console/ux.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/container.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/exceptions/auth.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/flags.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/logging/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/logging/isolate.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/logging/style.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/logging/trace.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/logging/user.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/py.typed +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/rest_client.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/sync.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/src/fal/workflows.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/cli/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/cli/test_apps.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/cli/test_auth.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/cli/test_keys.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/cli/test_secrets.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/conftest.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/integration_test.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/mainify_package/__init__.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/mainify_package/impl.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/mainify_package/utils.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/mainify_target.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/toolkit/file_test.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tests/toolkit/image_test.py +0 -0
- {fal-1.3.0 → fal-1.3.2}/tools/demo_script.py +0 -0
{fal-1.3.0 → fal-1.3.2}/PKG-INFO
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
5
|
Author: Features & Labels <support@fal.ai>
|
|
6
6
|
Requires-Python: >=3.8
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Requires-Dist: isolate[build]<1.14.0,>=0.13.0
|
|
9
|
-
Requires-Dist: isolate-proto==0.5.
|
|
9
|
+
Requires-Dist: isolate-proto==0.5.3
|
|
10
10
|
Requires-Dist: grpcio==1.64.0
|
|
11
11
|
Requires-Dist: dill==0.3.7
|
|
12
12
|
Requires-Dist: cloudpickle==3.0.0
|
|
@@ -36,6 +36,7 @@ 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
|
Requires-Dist: cookiecutter
|
|
39
|
+
Requires-Dist: tomli
|
|
39
40
|
Provides-Extra: test
|
|
40
41
|
Requires-Dist: pytest<8; extra == "test"
|
|
41
42
|
Requires-Dist: pytest-asyncio; extra == "test"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
5
|
Author: Features & Labels <support@fal.ai>
|
|
6
6
|
Requires-Python: >=3.8
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Requires-Dist: isolate[build]<1.14.0,>=0.13.0
|
|
9
|
-
Requires-Dist: isolate-proto==0.5.
|
|
9
|
+
Requires-Dist: isolate-proto==0.5.3
|
|
10
10
|
Requires-Dist: grpcio==1.64.0
|
|
11
11
|
Requires-Dist: dill==0.3.7
|
|
12
12
|
Requires-Dist: cloudpickle==3.0.0
|
|
@@ -36,6 +36,7 @@ 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
|
Requires-Dist: cookiecutter
|
|
39
|
+
Requires-Dist: tomli
|
|
39
40
|
Provides-Extra: test
|
|
40
41
|
Requires-Dist: pytest<8; extra == "test"
|
|
41
42
|
Requires-Dist: pytest-asyncio; extra == "test"
|
|
@@ -82,6 +82,7 @@ src/fal/api.py
|
|
|
82
82
|
src/fal/app.py
|
|
83
83
|
src/fal/apps.py
|
|
84
84
|
src/fal/container.py
|
|
85
|
+
src/fal/files.py
|
|
85
86
|
src/fal/flags.py
|
|
86
87
|
src/fal/py.typed
|
|
87
88
|
src/fal/rest_client.py
|
|
@@ -93,6 +94,7 @@ src/fal/auth/__init__.py
|
|
|
93
94
|
src/fal/auth/auth0.py
|
|
94
95
|
src/fal/auth/local.py
|
|
95
96
|
src/fal/cli/__init__.py
|
|
97
|
+
src/fal/cli/_utils.py
|
|
96
98
|
src/fal/cli/apps.py
|
|
97
99
|
src/fal/cli/auth.py
|
|
98
100
|
src/fal/cli/create.py
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
isolate[build]<1.14.0,>=0.13.0
|
|
2
|
-
isolate-proto==0.5.
|
|
2
|
+
isolate-proto==0.5.3
|
|
3
3
|
grpcio==1.64.0
|
|
4
4
|
dill==0.3.7
|
|
5
5
|
cloudpickle==3.0.0
|
|
@@ -28,6 +28,7 @@ pillow<11,>=10.2.0
|
|
|
28
28
|
pyjwt[crypto]<3,>=2.8.0
|
|
29
29
|
uvicorn<1,>=0.29.0
|
|
30
30
|
cookiecutter
|
|
31
|
+
tomli
|
|
31
32
|
|
|
32
33
|
[:python_version < "3.10"]
|
|
33
34
|
importlib-metadata>=4.4
|
|
@@ -23,7 +23,7 @@ readme = "README.md"
|
|
|
23
23
|
requires-python = ">=3.8"
|
|
24
24
|
dependencies = [
|
|
25
25
|
"isolate[build]>=0.13.0,<1.14.0",
|
|
26
|
-
"isolate-proto==0.5.
|
|
26
|
+
"isolate-proto==0.5.3",
|
|
27
27
|
"grpcio==1.64.0",
|
|
28
28
|
"dill==0.3.7",
|
|
29
29
|
"cloudpickle==3.0.0",
|
|
@@ -56,6 +56,7 @@ dependencies = [
|
|
|
56
56
|
"pyjwt[crypto]>=2.8.0,<3",
|
|
57
57
|
"uvicorn>=0.29.0,<1",
|
|
58
58
|
"cookiecutter",
|
|
59
|
+
"tomli"
|
|
59
60
|
]
|
|
60
61
|
|
|
61
62
|
[project.optional-dependencies]
|
|
@@ -425,6 +425,7 @@ class FalServerlessHost(Host):
|
|
|
425
425
|
application_name: str | None = None,
|
|
426
426
|
application_auth_mode: Literal["public", "shared", "private"] | None = None,
|
|
427
427
|
metadata: dict[str, Any] | None = None,
|
|
428
|
+
deployment_strategy: Literal["recreate", "rolling"] = "recreate",
|
|
428
429
|
) -> str | None:
|
|
429
430
|
environment_options = options.environment.copy()
|
|
430
431
|
environment_options.setdefault("python_version", active_python())
|
|
@@ -477,6 +478,7 @@ class FalServerlessHost(Host):
|
|
|
477
478
|
application_auth_mode=application_auth_mode,
|
|
478
479
|
machine_requirements=machine_requirements,
|
|
479
480
|
metadata=metadata,
|
|
481
|
+
deployment_strategy=deployment_strategy,
|
|
480
482
|
):
|
|
481
483
|
for log in partial_result.logs:
|
|
482
484
|
self._log_printer.print(log)
|
|
@@ -17,6 +17,7 @@ from fastapi import FastAPI
|
|
|
17
17
|
import fal.api
|
|
18
18
|
from fal._serialization import include_modules_from
|
|
19
19
|
from fal.api import RouteSignature
|
|
20
|
+
from fal.exceptions import RequestCancelledException
|
|
20
21
|
from fal.logging import get_logger
|
|
21
22
|
from fal.toolkit.file.providers import fal as fal_provider_module
|
|
22
23
|
|
|
@@ -143,7 +144,7 @@ class AppClient:
|
|
|
143
144
|
with httpx.Client() as client:
|
|
144
145
|
retries = 100
|
|
145
146
|
for _ in range(retries):
|
|
146
|
-
resp = client.get(info.url + "/health")
|
|
147
|
+
resp = client.get(info.url + "/health", timeout=60)
|
|
147
148
|
|
|
148
149
|
if resp.is_success:
|
|
149
150
|
break
|
|
@@ -205,6 +206,14 @@ class App(fal.api.BaseServable):
|
|
|
205
206
|
"Running apps through SDK is not implemented yet."
|
|
206
207
|
)
|
|
207
208
|
|
|
209
|
+
@classmethod
|
|
210
|
+
def get_endpoints(cls) -> list[str]:
|
|
211
|
+
return [
|
|
212
|
+
signature.path
|
|
213
|
+
for _, endpoint in inspect.getmembers(cls, inspect.isfunction)
|
|
214
|
+
if (signature := getattr(endpoint, "route_signature", None))
|
|
215
|
+
]
|
|
216
|
+
|
|
208
217
|
def collect_routes(self) -> dict[RouteSignature, Callable[..., Any]]:
|
|
209
218
|
return {
|
|
210
219
|
signature: endpoint
|
|
@@ -264,6 +273,17 @@ class App(fal.api.BaseServable):
|
|
|
264
273
|
)
|
|
265
274
|
return response
|
|
266
275
|
|
|
276
|
+
@app.exception_handler(RequestCancelledException)
|
|
277
|
+
async def value_error_exception_handler(
|
|
278
|
+
request, exc: RequestCancelledException
|
|
279
|
+
):
|
|
280
|
+
from fastapi.responses import JSONResponse
|
|
281
|
+
|
|
282
|
+
# A 499 status code is not an officially recognized HTTP status code,
|
|
283
|
+
# but it is sometimes used by servers to indicate that a client has closed
|
|
284
|
+
# the connection without receiving a response
|
|
285
|
+
return JSONResponse({"detail": str(exc)}, 499)
|
|
286
|
+
|
|
267
287
|
def _add_extra_routes(self, app: FastAPI):
|
|
268
288
|
@app.get("/health")
|
|
269
289
|
def health():
|
|
@@ -97,6 +97,15 @@ class RequestHandle:
|
|
|
97
97
|
else:
|
|
98
98
|
raise ValueError(f"Unknown status: {data['status']}")
|
|
99
99
|
|
|
100
|
+
def cancel(self) -> None:
|
|
101
|
+
"""Cancel an async inference request."""
|
|
102
|
+
url = (
|
|
103
|
+
_QUEUE_URL_FORMAT.format(app_id=self.app_id)
|
|
104
|
+
+ f"/requests/{self.request_id}/cancel"
|
|
105
|
+
)
|
|
106
|
+
response = _HTTP_CLIENT.put(url, headers=self._creds.to_headers())
|
|
107
|
+
response.raise_for_status()
|
|
108
|
+
|
|
100
109
|
def iter_events(
|
|
101
110
|
self,
|
|
102
111
|
*,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fal.files import find_pyproject_toml, parse_pyproject_toml
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_app_name(app_ref: tuple[str, str | None]) -> bool:
|
|
7
|
+
is_single_file = app_ref[1] is None
|
|
8
|
+
is_python_file = app_ref[0].endswith(".py")
|
|
9
|
+
|
|
10
|
+
return is_single_file and not is_python_file
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_app_data_from_toml(app_name):
|
|
14
|
+
toml_path = find_pyproject_toml()
|
|
15
|
+
|
|
16
|
+
if toml_path is None:
|
|
17
|
+
raise ValueError("No pyproject.toml file found.")
|
|
18
|
+
|
|
19
|
+
fal_data = parse_pyproject_toml(toml_path)
|
|
20
|
+
apps = fal_data.get("apps", {})
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
app_data = apps[app_name]
|
|
24
|
+
except KeyError:
|
|
25
|
+
raise ValueError(f"App {app_name} not found in pyproject.toml")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
app_ref = app_data["ref"]
|
|
29
|
+
except KeyError:
|
|
30
|
+
raise ValueError(f"App {app_name} does not have a ref key in pyproject.toml")
|
|
31
|
+
|
|
32
|
+
app_auth = app_data.get("auth", "private")
|
|
33
|
+
|
|
34
|
+
return app_ref, app_auth
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
from collections import namedtuple
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import Optional, Union
|
|
4
5
|
|
|
6
|
+
from ._utils import get_app_data_from_toml, is_app_name
|
|
5
7
|
from .parser import FalClientParser, RefAction
|
|
6
8
|
|
|
7
9
|
User = namedtuple("User", ["user_id", "username"])
|
|
@@ -60,11 +62,13 @@ def _get_user() -> User:
|
|
|
60
62
|
raise FalServerlessError(f"Could not parse the user data: {e}")
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
def
|
|
65
|
+
def _deploy_from_reference(
|
|
66
|
+
app_ref: tuple[Optional[Union[Path, str]], ...], app_name: str, auth: str, args
|
|
67
|
+
):
|
|
64
68
|
from fal.api import FalServerlessError, FalServerlessHost
|
|
65
69
|
from fal.utils import load_function_from
|
|
66
70
|
|
|
67
|
-
file_path, func_name =
|
|
71
|
+
file_path, func_name = app_ref
|
|
68
72
|
if file_path is None:
|
|
69
73
|
# Try to find a python file in the current directory
|
|
70
74
|
options = list(Path(".").glob("*.py"))
|
|
@@ -77,23 +81,27 @@ def _deploy(args):
|
|
|
77
81
|
)
|
|
78
82
|
|
|
79
83
|
[file_path] = options
|
|
80
|
-
file_path = str(file_path)
|
|
84
|
+
file_path = str(file_path) # type: ignore
|
|
81
85
|
|
|
82
86
|
user = _get_user()
|
|
83
87
|
host = FalServerlessHost(args.host)
|
|
84
|
-
|
|
88
|
+
loaded = load_function_from(
|
|
85
89
|
host,
|
|
86
|
-
file_path,
|
|
87
|
-
func_name,
|
|
90
|
+
file_path, # type: ignore
|
|
91
|
+
func_name, # type: ignore
|
|
88
92
|
)
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
isolated_function = loaded.function
|
|
94
|
+
app_name = app_name or loaded.app_name # type: ignore
|
|
95
|
+
app_auth = auth or loaded.app_auth or "private"
|
|
96
|
+
deployment_strategy = args.strategy or "default"
|
|
97
|
+
|
|
91
98
|
app_id = host.register(
|
|
92
99
|
func=isolated_function.func,
|
|
93
100
|
options=isolated_function.options,
|
|
94
101
|
application_name=app_name,
|
|
95
102
|
application_auth_mode=app_auth,
|
|
96
103
|
metadata=isolated_function.options.host.get("metadata", {}),
|
|
104
|
+
deployment_strategy=deployment_strategy,
|
|
97
105
|
)
|
|
98
106
|
|
|
99
107
|
if app_id:
|
|
@@ -106,12 +114,36 @@ def _deploy(args):
|
|
|
106
114
|
"Registered a new revision for function "
|
|
107
115
|
f"'{app_name}' (revision='{app_id}')."
|
|
108
116
|
)
|
|
109
|
-
args.console.print(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
117
|
+
args.console.print("Playground:")
|
|
118
|
+
for endpoint in loaded.endpoints:
|
|
119
|
+
args.console.print(
|
|
120
|
+
f"\thttps://fal.ai/models/{user.username}/{app_name}{endpoint}"
|
|
121
|
+
)
|
|
122
|
+
args.console.print("Endpoints:")
|
|
123
|
+
for endpoint in loaded.endpoints:
|
|
124
|
+
args.console.print(
|
|
125
|
+
f"\thttps://{gateway_host}/{user.username}/{app_name}{endpoint}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _deploy(args):
|
|
130
|
+
# my-app
|
|
131
|
+
if is_app_name(args.app_ref):
|
|
132
|
+
# we do not allow --app-name and --auth to be used with app name
|
|
133
|
+
if args.app_name or args.auth:
|
|
134
|
+
raise ValueError("Cannot use --app-name or --auth with app name reference.")
|
|
135
|
+
|
|
136
|
+
app_name = args.app_ref[0]
|
|
137
|
+
app_ref, app_auth = get_app_data_from_toml(app_name)
|
|
138
|
+
file_path, func_name = RefAction.split_ref(app_ref)
|
|
139
|
+
|
|
140
|
+
# path/to/myfile.py::MyApp
|
|
141
|
+
else:
|
|
142
|
+
file_path, func_name = args.app_ref
|
|
143
|
+
app_name = args.app_name
|
|
144
|
+
app_auth = args.auth
|
|
145
|
+
|
|
146
|
+
_deploy_from_reference((file_path, func_name), app_name, app_auth, args)
|
|
115
147
|
|
|
116
148
|
|
|
117
149
|
def add_parser(main_subparsers, parents):
|
|
@@ -122,14 +154,22 @@ def add_parser(main_subparsers, parents):
|
|
|
122
154
|
raise argparse.ArgumentTypeError(f"{option} is not a auth option")
|
|
123
155
|
return option
|
|
124
156
|
|
|
125
|
-
deploy_help =
|
|
157
|
+
deploy_help = (
|
|
158
|
+
"Deploy a fal application. "
|
|
159
|
+
"If no app reference is provided, the command will look for a "
|
|
160
|
+
"pyproject.toml file with a [tool.fal.apps] section and deploy the "
|
|
161
|
+
"application specified with the provided app name."
|
|
162
|
+
)
|
|
163
|
+
|
|
126
164
|
epilog = (
|
|
127
165
|
"Examples:\n"
|
|
128
166
|
" fal deploy\n"
|
|
129
167
|
" fal deploy path/to/myfile.py\n"
|
|
130
168
|
" fal deploy path/to/myfile.py::MyApp\n"
|
|
131
169
|
" fal deploy path/to/myfile.py::MyApp --app-name myapp --auth public\n"
|
|
170
|
+
" fal deploy my-app\n"
|
|
132
171
|
)
|
|
172
|
+
|
|
133
173
|
parser = main_subparsers.add_parser(
|
|
134
174
|
"deploy",
|
|
135
175
|
parents=[*parents, FalClientParser(add_help=False)],
|
|
@@ -137,21 +177,36 @@ def add_parser(main_subparsers, parents):
|
|
|
137
177
|
help=deploy_help,
|
|
138
178
|
epilog=epilog,
|
|
139
179
|
)
|
|
180
|
+
|
|
140
181
|
parser.add_argument(
|
|
141
182
|
"app_ref",
|
|
142
183
|
nargs="?",
|
|
143
184
|
action=RefAction,
|
|
144
185
|
help=(
|
|
145
|
-
"Application reference.
|
|
186
|
+
"Application reference. Either a file path or a file path and a "
|
|
187
|
+
"function name separated by '::'. If no reference is provided, the "
|
|
188
|
+
"command will look for a pyproject.toml file with a [tool.fal.apps] "
|
|
189
|
+
"section and deploy the application specified with the provided app name.\n"
|
|
190
|
+
"File path example: path/to/myfile.py::MyApp\n"
|
|
191
|
+
"App name example: my-app\n"
|
|
146
192
|
),
|
|
147
193
|
)
|
|
194
|
+
|
|
148
195
|
parser.add_argument(
|
|
149
196
|
"--app-name",
|
|
150
197
|
help="Application name to deploy with.",
|
|
151
198
|
)
|
|
199
|
+
|
|
152
200
|
parser.add_argument(
|
|
153
201
|
"--auth",
|
|
154
202
|
type=valid_auth_option,
|
|
155
203
|
help="Application authentication mode (private, public).",
|
|
156
204
|
)
|
|
205
|
+
parser.add_argument(
|
|
206
|
+
"--strategy",
|
|
207
|
+
choices=["default", "rolling"],
|
|
208
|
+
help="Deployment strategy.",
|
|
209
|
+
default="default",
|
|
210
|
+
)
|
|
211
|
+
|
|
157
212
|
parser.set_defaults(func=_deploy)
|
|
@@ -14,14 +14,18 @@ class RefAction(argparse.Action):
|
|
|
14
14
|
kwargs.setdefault("default", (None, None))
|
|
15
15
|
super().__init__(*args, **kwargs)
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
@classmethod
|
|
18
|
+
def split_ref(cls, value):
|
|
19
|
+
if isinstance(value, tuple):
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
if value.find("::") > 1:
|
|
23
|
+
return value.split("::", 1)
|
|
24
24
|
|
|
25
|
+
return value, None
|
|
26
|
+
|
|
27
|
+
def __call__(self, parser, args, values, option_string=None): # noqa: ARG002
|
|
28
|
+
file_path, obj_path = self.split_ref(values)
|
|
25
29
|
setattr(args, self.dest, (file_path, obj_path))
|
|
26
30
|
|
|
27
31
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from ._utils import get_app_data_from_toml, is_app_name
|
|
1
2
|
from .parser import FalClientParser, RefAction
|
|
2
3
|
|
|
3
4
|
|
|
@@ -6,7 +7,17 @@ def _run(args):
|
|
|
6
7
|
from fal.utils import load_function_from
|
|
7
8
|
|
|
8
9
|
host = FalServerlessHost(args.host)
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
if is_app_name(args.func_ref):
|
|
12
|
+
app_name = args.func_ref[0]
|
|
13
|
+
app_ref, _ = get_app_data_from_toml(app_name)
|
|
14
|
+
file_path, func_name = RefAction.split_ref(app_ref)
|
|
15
|
+
else:
|
|
16
|
+
file_path, func_name = args.func_ref
|
|
17
|
+
|
|
18
|
+
loaded = load_function_from(host, file_path, func_name)
|
|
19
|
+
|
|
20
|
+
isolated_function = loaded.function
|
|
10
21
|
# let our exc handlers handle UserFunctionException
|
|
11
22
|
isolated_function.reraise = False
|
|
12
23
|
isolated_function()
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Sequence
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
class FalServerlessException(Exception):
|
|
@@ -40,11 +39,20 @@ class FieldException(FalServerlessException):
|
|
|
40
39
|
status_code: int = 422
|
|
41
40
|
type: str = "value_error"
|
|
42
41
|
|
|
43
|
-
def to_pydantic_format(self) ->
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
def to_pydantic_format(self) -> dict[str, list[dict]]:
|
|
43
|
+
return dict(
|
|
44
|
+
detail=[
|
|
45
|
+
{
|
|
46
|
+
"loc": ["body", self.field],
|
|
47
|
+
"msg": self.message,
|
|
48
|
+
"type": self.type,
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class RequestCancelledException(FalServerlessException):
|
|
56
|
+
"""Exception raised when the request is cancelled by the client."""
|
|
57
|
+
|
|
58
|
+
message: str = "Request cancelled by the client."
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Dict, Optional, Sequence, Tuple, Union
|
|
4
|
+
|
|
5
|
+
import tomli
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@lru_cache
|
|
9
|
+
def _load_toml(path: Union[Path, str]) -> Dict[str, Any]:
|
|
10
|
+
with open(path, "rb") as f:
|
|
11
|
+
return tomli.load(f)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@lru_cache
|
|
15
|
+
def _cached_resolve(path: Path) -> Path:
|
|
16
|
+
return path.resolve()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@lru_cache
|
|
20
|
+
def find_project_root(srcs: Optional[Sequence[str]]) -> Tuple[Path, str]:
|
|
21
|
+
"""Return a directory containing .git, or pyproject.toml.
|
|
22
|
+
|
|
23
|
+
That directory will be a common parent of all files and directories
|
|
24
|
+
passed in `srcs`.
|
|
25
|
+
|
|
26
|
+
If no directory in the tree contains a marker that would specify it's the
|
|
27
|
+
project root, the root of the file system is returned.
|
|
28
|
+
|
|
29
|
+
Returns a two-tuple with the first element as the project root path and
|
|
30
|
+
the second element as a string describing the method by which the
|
|
31
|
+
project root was discovered.
|
|
32
|
+
"""
|
|
33
|
+
if not srcs:
|
|
34
|
+
srcs = [str(_cached_resolve(Path.cwd()))]
|
|
35
|
+
|
|
36
|
+
path_srcs = [_cached_resolve(Path(Path.cwd(), src)) for src in srcs]
|
|
37
|
+
|
|
38
|
+
# A list of lists of parents for each 'src'. 'src' is included as a
|
|
39
|
+
# "parent" of itself if it is a directory
|
|
40
|
+
src_parents = [
|
|
41
|
+
list(path.parents) + ([path] if path.is_dir() else []) for path in path_srcs
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
common_base = max(
|
|
45
|
+
set.intersection(*(set(parents) for parents in src_parents)),
|
|
46
|
+
key=lambda path: path.parts,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
for directory in (common_base, *common_base.parents):
|
|
50
|
+
if (directory / ".git").exists():
|
|
51
|
+
return directory, ".git directory"
|
|
52
|
+
|
|
53
|
+
if (directory / "pyproject.toml").is_file():
|
|
54
|
+
pyproject_toml = _load_toml(directory / "pyproject.toml")
|
|
55
|
+
if "fal" in pyproject_toml.get("tool", {}):
|
|
56
|
+
return directory, "pyproject.toml"
|
|
57
|
+
|
|
58
|
+
return directory, "file system root"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def find_pyproject_toml(
|
|
62
|
+
path_search_start: Optional[Tuple[str, ...]] = None,
|
|
63
|
+
) -> Optional[str]:
|
|
64
|
+
"""Find the absolute filepath to a pyproject.toml if it exists"""
|
|
65
|
+
path_project_root, _ = find_project_root(path_search_start)
|
|
66
|
+
path_pyproject_toml = path_project_root / "pyproject.toml"
|
|
67
|
+
|
|
68
|
+
if path_pyproject_toml.is_file():
|
|
69
|
+
return str(path_pyproject_toml)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def parse_pyproject_toml(path_config: str) -> Dict[str, Any]:
|
|
73
|
+
"""Parse a pyproject toml file, pulling out relevant parts for fal.
|
|
74
|
+
|
|
75
|
+
If parsing fails, will raise a tomli.TOMLDecodeError.
|
|
76
|
+
"""
|
|
77
|
+
pyproject_toml = _load_toml(path_config)
|
|
78
|
+
config: Dict[str, Any] = pyproject_toml.get("tool", {}).get("fal", {})
|
|
79
|
+
config = {k.replace("--", "").replace("-", "_"): v for k, v in config.items()}
|
|
80
|
+
|
|
81
|
+
return config
|
|
@@ -275,6 +275,33 @@ class KeyScope(enum.Enum):
|
|
|
275
275
|
raise ValueError(f"Unknown KeyScope: {proto}")
|
|
276
276
|
|
|
277
277
|
|
|
278
|
+
class DeploymentStrategy(enum.Enum):
|
|
279
|
+
RECREATE = "recreate"
|
|
280
|
+
ROLLING = "rolling"
|
|
281
|
+
|
|
282
|
+
@staticmethod
|
|
283
|
+
def from_proto(
|
|
284
|
+
proto: isolate_proto.DeploymentStrategy.ValueType | None,
|
|
285
|
+
) -> DeploymentStrategy:
|
|
286
|
+
if proto is None:
|
|
287
|
+
return DeploymentStrategy.RECREATE
|
|
288
|
+
|
|
289
|
+
if proto is isolate_proto.DeploymentStrategy.RECREATE:
|
|
290
|
+
return DeploymentStrategy.RECREATE
|
|
291
|
+
elif proto is isolate_proto.DeploymentStrategy.ROLLING:
|
|
292
|
+
return DeploymentStrategy.ROLLING
|
|
293
|
+
else:
|
|
294
|
+
raise ValueError(f"Unknown DeploymentStrategy: {proto}")
|
|
295
|
+
|
|
296
|
+
def to_proto(self) -> isolate_proto.DeploymentStrategy.ValueType:
|
|
297
|
+
if self is DeploymentStrategy.RECREATE:
|
|
298
|
+
return isolate_proto.DeploymentStrategy.RECREATE
|
|
299
|
+
elif self is DeploymentStrategy.ROLLING:
|
|
300
|
+
return isolate_proto.DeploymentStrategy.ROLLING
|
|
301
|
+
else:
|
|
302
|
+
raise ValueError(f"Unknown DeploymentStrategy: {self}")
|
|
303
|
+
|
|
304
|
+
|
|
278
305
|
@from_grpc.register(isolate_proto.ApplicationInfo)
|
|
279
306
|
def _from_grpc_application_info(
|
|
280
307
|
message: isolate_proto.ApplicationInfo,
|
|
@@ -457,6 +484,7 @@ class FalServerlessConnection:
|
|
|
457
484
|
serialization_method: str = _DEFAULT_SERIALIZATION_METHOD,
|
|
458
485
|
machine_requirements: MachineRequirements | None = None,
|
|
459
486
|
metadata: dict[str, Any] | None = None,
|
|
487
|
+
deployment_strategy: Literal["recreate", "rolling"] = "recreate",
|
|
460
488
|
) -> Iterator[isolate_proto.RegisterApplicationResult]:
|
|
461
489
|
wrapped_function = to_serialized_object(function, serialization_method)
|
|
462
490
|
if machine_requirements:
|
|
@@ -488,6 +516,10 @@ class FalServerlessConnection:
|
|
|
488
516
|
struct_metadata = isolate_proto.Struct()
|
|
489
517
|
struct_metadata.update(metadata)
|
|
490
518
|
|
|
519
|
+
deployment_strategy_proto = DeploymentStrategy[
|
|
520
|
+
deployment_strategy.upper()
|
|
521
|
+
].to_proto()
|
|
522
|
+
|
|
491
523
|
request = isolate_proto.RegisterApplicationRequest(
|
|
492
524
|
function=wrapped_function,
|
|
493
525
|
environments=environments,
|
|
@@ -495,6 +527,7 @@ class FalServerlessConnection:
|
|
|
495
527
|
application_name=application_name,
|
|
496
528
|
auth_mode=auth_mode,
|
|
497
529
|
metadata=struct_metadata,
|
|
530
|
+
deployment_strategy=deployment_strategy_proto,
|
|
498
531
|
)
|
|
499
532
|
for partial_result in self.stub.RegisterApplication(request):
|
|
500
533
|
yield from_grpc(partial_result)
|
|
@@ -149,14 +149,31 @@ class File(BaseModel):
|
|
|
149
149
|
path: str | Path,
|
|
150
150
|
content_type: Optional[str] = None,
|
|
151
151
|
repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
|
|
152
|
+
multipart: bool | None = None,
|
|
152
153
|
) -> File:
|
|
153
154
|
file_path = Path(path)
|
|
154
155
|
if not file_path.exists():
|
|
155
156
|
raise FileNotFoundError(f"File {file_path} does not exist")
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
|
|
158
|
+
repo = (
|
|
159
|
+
repository
|
|
160
|
+
if isinstance(repository, FileRepository)
|
|
161
|
+
else get_builtin_repository(repository)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
content_type = content_type or "application/octet-stream"
|
|
165
|
+
|
|
166
|
+
url, data = repo.save_file(
|
|
167
|
+
file_path,
|
|
168
|
+
content_type=content_type,
|
|
169
|
+
multipart=multipart,
|
|
170
|
+
)
|
|
171
|
+
return cls(
|
|
172
|
+
url=url,
|
|
173
|
+
file_data=data.data if data else None,
|
|
174
|
+
content_type=content_type,
|
|
175
|
+
file_name=file_path.name,
|
|
176
|
+
file_size=file_path.stat().st_size,
|
|
160
177
|
)
|
|
161
178
|
|
|
162
179
|
def as_bytes(self) -> bytes:
|