fal 1.26.0__tar.gz → 1.26.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.26.0/fal.egg-info → fal-1.26.2}/PKG-INFO +1 -1
- {fal-1.26.0 → fal-1.26.2/fal.egg-info}/PKG-INFO +1 -1
- {fal-1.26.0 → fal-1.26.2}/src/fal/_fal_version.py +2 -2
- {fal-1.26.0 → fal-1.26.2}/src/fal/api.py +1 -1
- {fal-1.26.0 → fal-1.26.2}/src/fal/app.py +50 -13
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/apps.py +1 -1
- {fal-1.26.0 → fal-1.26.2}/src/fal/files.py +96 -6
- {fal-1.26.0 → fal-1.26.2}/src/fal/sdk.py +17 -11
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/file/file.py +15 -2
- {fal-1.26.0 → fal-1.26.2}/tests/test_apps.py +3 -3
- {fal-1.26.0 → fal-1.26.2}/.gitignore +0 -0
- {fal-1.26.0 → fal-1.26.2}/Makefile +0 -0
- {fal-1.26.0 → fal-1.26.2}/README.md +0 -0
- {fal-1.26.0 → fal-1.26.2}/docs/conf.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/docs/index.rst +0 -0
- {fal-1.26.0 → fal-1.26.2}/fal.egg-info/SOURCES.txt +0 -0
- {fal-1.26.0 → fal-1.26.2}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.26.0 → fal-1.26.2}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.26.0 → fal-1.26.2}/fal.egg-info/requires.txt +0 -0
- {fal-1.26.0 → fal-1.26.2}/fal.egg-info/top_level.txt +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/README.md +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.26.0 → fal-1.26.2}/openapi_rest.config.yaml +0 -0
- {fal-1.26.0 → fal-1.26.2}/pyproject.toml +0 -0
- {fal-1.26.0 → fal-1.26.2}/setup.cfg +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/__main__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/_serialization.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/_version.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/apps.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/auth/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/auth/auth0.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/auth/local.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/_utils.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/api.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/auth.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/cli_nested_json.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/create.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/debug.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/deploy.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/doctor.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/files.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/keys.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/main.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/parser.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/profile.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/run.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/runners.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/secrets.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/cli/teams.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/config.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/console/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/console/icons.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/console/ux.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/container.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/exceptions/_base.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/exceptions/auth.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/flags.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/logging/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/logging/isolate.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/logging/style.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/logging/trace.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/logging/user.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/project.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/py.typed +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/rest_client.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/sync.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/audio/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/audio/audio.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/file/providers/fal.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/file/providers/s3.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/kv.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/types.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/utils/endpoint.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/utils/retry.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/video/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/toolkit/video/video.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/utils.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/src/fal/workflows.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/assets/cat.png +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/cli/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/cli/test_apps.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/cli/test_auth.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/cli/test_deploy.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/cli/test_keys.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/cli/test_run.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/cli/test_secrets.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/conftest.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/integration_test.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/mainify_package/__init__.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/mainify_package/impl.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/mainify_package/utils.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/mainify_target.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/test_kv.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/test_stability.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/toolkit/file/providers/test_fal_retry.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/toolkit/file_test.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/toolkit/image_test.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/toolkit/test_types.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tests/toolkit/utils/retry.py +0 -0
- {fal-1.26.0 → fal-1.26.2}/tools/demo_script.py +0 -0
|
@@ -500,7 +500,7 @@ class FalServerlessHost(Host):
|
|
|
500
500
|
partial_func,
|
|
501
501
|
environments,
|
|
502
502
|
application_name=application_name,
|
|
503
|
-
|
|
503
|
+
auth_mode=application_auth_mode,
|
|
504
504
|
machine_requirements=machine_requirements,
|
|
505
505
|
metadata=metadata,
|
|
506
506
|
deployment_strategy=deployment_strategy,
|
|
@@ -197,7 +197,14 @@ class AppClient:
|
|
|
197
197
|
|
|
198
198
|
@classmethod
|
|
199
199
|
@contextmanager
|
|
200
|
-
def connect(
|
|
200
|
+
def connect(
|
|
201
|
+
cls,
|
|
202
|
+
app_cls,
|
|
203
|
+
*,
|
|
204
|
+
health_request_timeout: int = 30,
|
|
205
|
+
startup_timeout: int = 60,
|
|
206
|
+
health_check_interval: float = 0.5,
|
|
207
|
+
):
|
|
201
208
|
app = wrap_app(app_cls)
|
|
202
209
|
info = app.spawn()
|
|
203
210
|
_shutdown_event = threading.Event()
|
|
@@ -214,20 +221,50 @@ class AppClient:
|
|
|
214
221
|
_log_printer.start()
|
|
215
222
|
|
|
216
223
|
try:
|
|
224
|
+
if info.url is None:
|
|
225
|
+
raise AppClientError(
|
|
226
|
+
"App spawn failed: no URL returned",
|
|
227
|
+
status_code=500,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
start_time = time.perf_counter()
|
|
231
|
+
url = info.url + "/health"
|
|
232
|
+
last_error = None
|
|
233
|
+
attempt = 0
|
|
234
|
+
|
|
217
235
|
with httpx.Client() as client:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
raise AppClientError(
|
|
227
|
-
f"Failed to GET {url}: {resp.status_code} {resp.text}",
|
|
228
|
-
status_code=resp.status_code,
|
|
236
|
+
while time.perf_counter() - start_time < startup_timeout:
|
|
237
|
+
attempt += 1
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
resp = client.get(url, timeout=health_request_timeout)
|
|
241
|
+
except httpx.TimeoutException:
|
|
242
|
+
last_error = (
|
|
243
|
+
f"Request timed out after {health_request_timeout} seconds"
|
|
229
244
|
)
|
|
230
|
-
|
|
245
|
+
except httpx.TransportError as e:
|
|
246
|
+
last_error = f"Network error: {e}"
|
|
247
|
+
else:
|
|
248
|
+
if resp.is_success:
|
|
249
|
+
break
|
|
250
|
+
|
|
251
|
+
if resp.status_code in (500, 404):
|
|
252
|
+
last_error = f"Server not ready (HTTP {resp.status_code})"
|
|
253
|
+
else:
|
|
254
|
+
raise AppClientError(
|
|
255
|
+
"Health check failed with non-retryable error: "
|
|
256
|
+
f"{resp.status_code} {resp.text}",
|
|
257
|
+
status_code=resp.status_code,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
time.sleep(health_check_interval)
|
|
261
|
+
else:
|
|
262
|
+
# retry loop completed without success
|
|
263
|
+
raise AppClientError(
|
|
264
|
+
f"Health check failed after {startup_timeout}s "
|
|
265
|
+
f"({attempt} attempts). Last error: {last_error}",
|
|
266
|
+
status_code=500,
|
|
267
|
+
)
|
|
231
268
|
|
|
232
269
|
client = cls(app_cls, info.url)
|
|
233
270
|
yield client
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import math
|
|
1
3
|
import os
|
|
2
4
|
import posixpath
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
3
6
|
from functools import cached_property
|
|
4
7
|
from typing import TYPE_CHECKING
|
|
5
8
|
|
|
@@ -9,6 +12,19 @@ if TYPE_CHECKING:
|
|
|
9
12
|
import httpx
|
|
10
13
|
|
|
11
14
|
USER_AGENT = "fal-sdk/1.14.0 (python)"
|
|
15
|
+
MULTIPART_THRESHOLD = 10 * 1024 * 1024 # 10MB
|
|
16
|
+
MULTIPART_CHUNK_SIZE = 10 * 1024 * 1024 # 10MB
|
|
17
|
+
MULTIPART_WORKERS = 2 # only 2 because our REST is currently struggling with more
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _compute_md5(lpath, chunk_size=8192):
|
|
21
|
+
import hashlib
|
|
22
|
+
|
|
23
|
+
hasher = hashlib.md5()
|
|
24
|
+
with open(lpath, "rb") as fobj:
|
|
25
|
+
for chunk in iter(lambda: fobj.read(chunk_size), b""):
|
|
26
|
+
hasher.update(chunk)
|
|
27
|
+
return hasher.hexdigest()
|
|
12
28
|
|
|
13
29
|
|
|
14
30
|
class FalFileSystem(AbstractFileSystem):
|
|
@@ -95,16 +111,90 @@ class FalFileSystem(AbstractFileSystem):
|
|
|
95
111
|
response = self._request("GET", f"/files/file/{rpath}")
|
|
96
112
|
fobj.write(response.content)
|
|
97
113
|
|
|
114
|
+
def _put_file_part(self, rpath, lpath, upload_id, part_number, chunk_size):
|
|
115
|
+
offset = (part_number - 1) * chunk_size
|
|
116
|
+
with open(lpath, "rb") as fobj:
|
|
117
|
+
fobj.seek(offset)
|
|
118
|
+
chunk = fobj.read(chunk_size)
|
|
119
|
+
response = self._request(
|
|
120
|
+
"PUT",
|
|
121
|
+
f"/files/file/multipart/{rpath}/{upload_id}/{part_number}",
|
|
122
|
+
files={"file_upload": (posixpath.basename(lpath), chunk)},
|
|
123
|
+
)
|
|
124
|
+
data = response.json()
|
|
125
|
+
return {
|
|
126
|
+
"part_number": data["part_number"],
|
|
127
|
+
"etag": data["etag"],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
def _put_file_multipart(self, lpath, rpath, size, progress):
|
|
131
|
+
response = self._request(
|
|
132
|
+
"POST",
|
|
133
|
+
f"/files/file/multipart/{rpath}/initiate",
|
|
134
|
+
)
|
|
135
|
+
upload_id = response.json()["upload_id"]
|
|
136
|
+
|
|
137
|
+
parts = []
|
|
138
|
+
num_parts = math.ceil(size / MULTIPART_CHUNK_SIZE)
|
|
139
|
+
md5 = _compute_md5(lpath)
|
|
140
|
+
|
|
141
|
+
task = progress.add_task(f"{os.path.basename(lpath)}", total=num_parts)
|
|
142
|
+
|
|
143
|
+
with ThreadPoolExecutor(max_workers=MULTIPART_WORKERS) as executor:
|
|
144
|
+
futures = []
|
|
145
|
+
|
|
146
|
+
for part_number in range(1, num_parts + 1):
|
|
147
|
+
futures.append(
|
|
148
|
+
executor.submit(
|
|
149
|
+
self._put_file_part,
|
|
150
|
+
rpath,
|
|
151
|
+
lpath,
|
|
152
|
+
upload_id,
|
|
153
|
+
part_number,
|
|
154
|
+
MULTIPART_CHUNK_SIZE,
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
for future in concurrent.futures.as_completed(futures):
|
|
159
|
+
parts.append(future.result())
|
|
160
|
+
progress.advance(task)
|
|
161
|
+
|
|
162
|
+
response = self._request(
|
|
163
|
+
"POST",
|
|
164
|
+
f"/files/file/multipart/{rpath}/{upload_id}/complete",
|
|
165
|
+
json={"parts": parts},
|
|
166
|
+
)
|
|
167
|
+
data = response.json()
|
|
168
|
+
if data["etag"] != md5:
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
f"MD5 mismatch on {rpath}: {data['etag']} != {md5}, "
|
|
171
|
+
"please contact support"
|
|
172
|
+
)
|
|
173
|
+
|
|
98
174
|
def put_file(self, lpath, rpath, mode="overwrite", **kwargs):
|
|
175
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
|
|
176
|
+
|
|
99
177
|
if os.path.isdir(lpath):
|
|
100
178
|
return
|
|
101
179
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
180
|
+
size = os.path.getsize(lpath)
|
|
181
|
+
with Progress(
|
|
182
|
+
SpinnerColumn(),
|
|
183
|
+
TextColumn("[progress.description]{task.description}"),
|
|
184
|
+
BarColumn(),
|
|
185
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
186
|
+
) as progress:
|
|
187
|
+
if size > MULTIPART_THRESHOLD:
|
|
188
|
+
self._put_file_multipart(lpath, rpath, size, progress)
|
|
189
|
+
else:
|
|
190
|
+
task = progress.add_task(f"{os.path.basename(lpath)}", total=1)
|
|
191
|
+
with open(lpath, "rb") as fobj:
|
|
192
|
+
self._request(
|
|
193
|
+
"POST",
|
|
194
|
+
f"/files/file/local/{rpath}",
|
|
195
|
+
files={"file_upload": (posixpath.basename(lpath), fobj)},
|
|
196
|
+
)
|
|
197
|
+
progress.advance(task)
|
|
108
198
|
self.dircache.clear()
|
|
109
199
|
|
|
110
200
|
def put_file_from_url(self, url, rpath, mode="overwrite", **kwargs):
|
|
@@ -45,6 +45,9 @@ logger = get_logger(__name__)
|
|
|
45
45
|
patch_pickle()
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
AuthMode = Optional[Literal["public", "private", "shared"]]
|
|
49
|
+
|
|
50
|
+
|
|
48
51
|
class ServerCredentials:
|
|
49
52
|
def to_grpc(self) -> grpc.ChannelCredentials:
|
|
50
53
|
raise NotImplementedError
|
|
@@ -565,7 +568,7 @@ class FalServerlessConnection:
|
|
|
565
568
|
function: Callable[..., ResultT],
|
|
566
569
|
environments: list[isolate_proto.EnvironmentDefinition],
|
|
567
570
|
application_name: str | None = None,
|
|
568
|
-
|
|
571
|
+
auth_mode: AuthMode = None,
|
|
569
572
|
*,
|
|
570
573
|
serialization_method: str = _DEFAULT_SERIALIZATION_METHOD,
|
|
571
574
|
machine_requirements: MachineRequirements | None = None,
|
|
@@ -598,13 +601,14 @@ class FalServerlessConnection:
|
|
|
598
601
|
else:
|
|
599
602
|
wrapped_requirements = None
|
|
600
603
|
|
|
601
|
-
auth_mode
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
604
|
+
if auth_mode == "public":
|
|
605
|
+
auth = isolate_proto.ApplicationAuthMode.PUBLIC
|
|
606
|
+
elif auth_mode == "shared":
|
|
607
|
+
auth = isolate_proto.ApplicationAuthMode.SHARED
|
|
608
|
+
elif auth_mode == "private":
|
|
609
|
+
auth = isolate_proto.ApplicationAuthMode.PRIVATE
|
|
610
|
+
else:
|
|
611
|
+
auth = None
|
|
608
612
|
|
|
609
613
|
struct_metadata = None
|
|
610
614
|
if metadata:
|
|
@@ -620,7 +624,7 @@ class FalServerlessConnection:
|
|
|
620
624
|
environments=environments,
|
|
621
625
|
machine_requirements=wrapped_requirements,
|
|
622
626
|
application_name=application_name,
|
|
623
|
-
auth_mode=
|
|
627
|
+
auth_mode=auth,
|
|
624
628
|
metadata=struct_metadata,
|
|
625
629
|
deployment_strategy=deployment_strategy_proto,
|
|
626
630
|
scale=scale,
|
|
@@ -730,14 +734,16 @@ class FalServerlessConnection:
|
|
|
730
734
|
self,
|
|
731
735
|
alias: str,
|
|
732
736
|
revision: str,
|
|
733
|
-
auth_mode:
|
|
737
|
+
auth_mode: AuthMode,
|
|
734
738
|
):
|
|
735
739
|
if auth_mode == "public":
|
|
736
740
|
auth = isolate_proto.ApplicationAuthMode.PUBLIC
|
|
737
741
|
elif auth_mode == "shared":
|
|
738
742
|
auth = isolate_proto.ApplicationAuthMode.SHARED
|
|
739
|
-
|
|
743
|
+
elif auth_mode == "private":
|
|
740
744
|
auth = isolate_proto.ApplicationAuthMode.PRIVATE
|
|
745
|
+
else:
|
|
746
|
+
auth = None
|
|
741
747
|
|
|
742
748
|
request = isolate_proto.SetAliasRequest(
|
|
743
749
|
alias=alias,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import shutil
|
|
4
|
+
import traceback
|
|
4
5
|
from functools import wraps
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from tempfile import NamedTemporaryFile, mkdtemp
|
|
@@ -178,10 +179,16 @@ class File(BaseModel):
|
|
|
178
179
|
|
|
179
180
|
try:
|
|
180
181
|
url = repo.save(fdata, **save_kwargs)
|
|
181
|
-
except Exception:
|
|
182
|
+
except Exception as exc:
|
|
182
183
|
if not fallback_repository:
|
|
183
184
|
raise
|
|
184
185
|
|
|
186
|
+
traceback.print_exc()
|
|
187
|
+
print(
|
|
188
|
+
f"Failed to save bytes to repository {repository}: {exc}, "
|
|
189
|
+
f"falling back to {fallback_repository}"
|
|
190
|
+
)
|
|
191
|
+
|
|
185
192
|
fallback_repo = get_builtin_repository(fallback_repository)
|
|
186
193
|
|
|
187
194
|
url = fallback_repo.save(fdata, **fallback_save_kwargs)
|
|
@@ -237,10 +244,16 @@ class File(BaseModel):
|
|
|
237
244
|
content_type=content_type,
|
|
238
245
|
**save_kwargs,
|
|
239
246
|
)
|
|
240
|
-
except Exception:
|
|
247
|
+
except Exception as exc:
|
|
241
248
|
if not fallback_repository:
|
|
242
249
|
raise
|
|
243
250
|
|
|
251
|
+
traceback.print_exc()
|
|
252
|
+
print(
|
|
253
|
+
f"Failed to save file to repository {repository}: {exc}, "
|
|
254
|
+
f"falling back to {fallback_repository}"
|
|
255
|
+
)
|
|
256
|
+
|
|
244
257
|
fallback_repo = get_builtin_repository(fallback_repository)
|
|
245
258
|
|
|
246
259
|
url, data = fallback_repo.save_file(
|
|
@@ -235,7 +235,7 @@ class ExceptionApp(fal.App, keep_alive=300, max_concurrency=1):
|
|
|
235
235
|
raise RuntimeError("cuDNN error: CUDNN_STATUS_INTERNAL_ERROR")
|
|
236
236
|
|
|
237
237
|
|
|
238
|
-
class CancellableApp(fal.App, keep_alive=300, max_concurrency=1, request_timeout=
|
|
238
|
+
class CancellableApp(fal.App, keep_alive=300, max_concurrency=1, request_timeout=2):
|
|
239
239
|
task = None
|
|
240
240
|
running = 0
|
|
241
241
|
|
|
@@ -555,7 +555,7 @@ def test_app_disconnect_behavior(test_app: str, test_cancellable_app: str):
|
|
|
555
555
|
with pytest.raises(HTTPStatusError) as e:
|
|
556
556
|
apps.run(
|
|
557
557
|
test_cancellable_app,
|
|
558
|
-
arguments={"lhs": 1, "rhs": 2, "wait_time":
|
|
558
|
+
arguments={"lhs": 1, "rhs": 2, "wait_time": 6},
|
|
559
559
|
path="/well-handled",
|
|
560
560
|
)
|
|
561
561
|
assert (
|
|
@@ -575,7 +575,7 @@ def test_app_disconnect_behavior(test_app: str, test_cancellable_app: str):
|
|
|
575
575
|
with pytest.raises(HTTPStatusError) as e:
|
|
576
576
|
apps.run(
|
|
577
577
|
test_cancellable_app,
|
|
578
|
-
arguments={"lhs": 1, "rhs": 2, "wait_time":
|
|
578
|
+
arguments={"lhs": 1, "rhs": 2, "wait_time": 6},
|
|
579
579
|
)
|
|
580
580
|
assert (
|
|
581
581
|
e.value.response.status_code == 504
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py
RENAMED
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py
RENAMED
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py
RENAMED
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py
RENAMED
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.26.0 → fal-1.26.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|