fal 1.26.5__tar.gz → 1.26.6__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.5/fal.egg-info → fal-1.26.6}/PKG-INFO +1 -1
- {fal-1.26.5 → fal-1.26.6/fal.egg-info}/PKG-INFO +1 -1
- {fal-1.26.5 → fal-1.26.6}/src/fal/_fal_version.py +2 -2
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/file/file.py +56 -43
- fal-1.26.6/tests/toolkit/file_test.py +352 -0
- fal-1.26.5/tests/toolkit/file_test.py +0 -92
- {fal-1.26.5 → fal-1.26.6}/.gitignore +0 -0
- {fal-1.26.5 → fal-1.26.6}/Makefile +0 -0
- {fal-1.26.5 → fal-1.26.6}/README.md +0 -0
- {fal-1.26.5 → fal-1.26.6}/docs/conf.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/docs/index.rst +0 -0
- {fal-1.26.5 → fal-1.26.6}/fal.egg-info/SOURCES.txt +0 -0
- {fal-1.26.5 → fal-1.26.6}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.26.5 → fal-1.26.6}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.26.5 → fal-1.26.6}/fal.egg-info/requires.txt +0 -0
- {fal-1.26.5 → fal-1.26.6}/fal.egg-info/top_level.txt +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/README.md +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.26.5 → fal-1.26.6}/openapi_rest.config.yaml +0 -0
- {fal-1.26.5 → fal-1.26.6}/pyproject.toml +0 -0
- {fal-1.26.5 → fal-1.26.6}/setup.cfg +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/__main__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/_serialization.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/_version.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/api.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/app.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/apps.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/auth/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/auth/auth0.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/auth/local.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/_utils.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/api.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/apps.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/auth.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/cli_nested_json.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/create.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/debug.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/deploy.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/doctor.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/files.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/keys.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/main.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/parser.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/profile.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/run.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/runners.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/secrets.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/cli/teams.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/config.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/console/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/console/icons.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/console/ux.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/container.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/exceptions/_base.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/exceptions/auth.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/files.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/flags.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/logging/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/logging/isolate.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/logging/style.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/logging/trace.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/logging/user.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/project.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/py.typed +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/rest_client.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/sdk.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/sync.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/audio/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/audio/audio.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/file/providers/fal.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/file/providers/s3.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/kv.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/types.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/utils/endpoint.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/utils/retry.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/video/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/toolkit/video/video.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/utils.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/src/fal/workflows.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/assets/cat.png +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/cli/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/cli/test_apps.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/cli/test_auth.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/cli/test_deploy.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/cli/test_keys.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/cli/test_run.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/cli/test_secrets.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/conftest.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/integration_test.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/mainify_package/__init__.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/mainify_package/impl.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/mainify_package/utils.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/mainify_target.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/test_apps.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/test_files.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/test_kv.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/test_stability.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/toolkit/file/providers/test_fal_retry.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/toolkit/image_test.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/toolkit/test_types.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tests/toolkit/utils/retry.py +0 -0
- {fal-1.26.5 → fal-1.26.6}/tools/demo_script.py +0 -0
|
@@ -73,6 +73,42 @@ def FileField(*args, **kwargs):
|
|
|
73
73
|
return Field(*args, **kwargs)
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
def _try_with_fallback(
|
|
77
|
+
func: str,
|
|
78
|
+
args: list[Any],
|
|
79
|
+
repository: FileRepository | RepositoryId,
|
|
80
|
+
fallback_repository: Optional[
|
|
81
|
+
FileRepository | RepositoryId | list[FileRepository | RepositoryId]
|
|
82
|
+
],
|
|
83
|
+
save_kwargs: dict,
|
|
84
|
+
fallback_save_kwargs: dict,
|
|
85
|
+
) -> Any:
|
|
86
|
+
if fallback_repository is None:
|
|
87
|
+
fallback_repositories = []
|
|
88
|
+
elif isinstance(fallback_repository, list):
|
|
89
|
+
fallback_repositories = fallback_repository
|
|
90
|
+
else:
|
|
91
|
+
fallback_repositories = [fallback_repository]
|
|
92
|
+
|
|
93
|
+
attempts = [
|
|
94
|
+
(repository, save_kwargs),
|
|
95
|
+
*((fallback, fallback_save_kwargs) for fallback in fallback_repositories),
|
|
96
|
+
]
|
|
97
|
+
for idx, (repo, kwargs) in enumerate(attempts):
|
|
98
|
+
repo_obj = get_builtin_repository(repo)
|
|
99
|
+
try:
|
|
100
|
+
return getattr(repo_obj, func)(*args, **kwargs)
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
if idx >= len(attempts) - 1:
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
traceback.print_exc()
|
|
106
|
+
print(
|
|
107
|
+
f"Failed to {func} to repository {repo}: {exc}, "
|
|
108
|
+
f"falling back to {attempts[idx + 1][0]}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
76
112
|
class File(BaseModel):
|
|
77
113
|
# public properties
|
|
78
114
|
url: str = Field(
|
|
@@ -154,14 +190,12 @@ class File(BaseModel):
|
|
|
154
190
|
file_name: Optional[str] = None,
|
|
155
191
|
repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
|
|
156
192
|
fallback_repository: Optional[
|
|
157
|
-
FileRepository | RepositoryId
|
|
193
|
+
FileRepository | RepositoryId | list[FileRepository | RepositoryId]
|
|
158
194
|
] = FALLBACK_REPOSITORY,
|
|
159
195
|
request: Optional[Request] = None,
|
|
160
196
|
save_kwargs: Optional[dict] = None,
|
|
161
197
|
fallback_save_kwargs: Optional[dict] = None,
|
|
162
198
|
) -> File:
|
|
163
|
-
repo = get_builtin_repository(repository)
|
|
164
|
-
|
|
165
199
|
save_kwargs = save_kwargs or {}
|
|
166
200
|
fallback_save_kwargs = fallback_save_kwargs or {}
|
|
167
201
|
|
|
@@ -177,21 +211,14 @@ class File(BaseModel):
|
|
|
177
211
|
"object_lifecycle_preference", object_lifecycle_preference
|
|
178
212
|
)
|
|
179
213
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
f"Failed to save bytes to repository {repository}: {exc}, "
|
|
189
|
-
f"falling back to {fallback_repository}"
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
fallback_repo = get_builtin_repository(fallback_repository)
|
|
193
|
-
|
|
194
|
-
url = fallback_repo.save(fdata, **fallback_save_kwargs)
|
|
214
|
+
url = _try_with_fallback(
|
|
215
|
+
"save",
|
|
216
|
+
[fdata],
|
|
217
|
+
repository=repository,
|
|
218
|
+
fallback_repository=fallback_repository,
|
|
219
|
+
save_kwargs=save_kwargs,
|
|
220
|
+
fallback_save_kwargs=fallback_save_kwargs,
|
|
221
|
+
)
|
|
195
222
|
|
|
196
223
|
return cls(
|
|
197
224
|
url=url,
|
|
@@ -209,7 +236,7 @@ class File(BaseModel):
|
|
|
209
236
|
repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
|
|
210
237
|
multipart: bool | None = None,
|
|
211
238
|
fallback_repository: Optional[
|
|
212
|
-
FileRepository | RepositoryId
|
|
239
|
+
FileRepository | RepositoryId | list[FileRepository | RepositoryId]
|
|
213
240
|
] = FALLBACK_REPOSITORY,
|
|
214
241
|
request: Optional[Request] = None,
|
|
215
242
|
save_kwargs: Optional[dict] = None,
|
|
@@ -219,8 +246,6 @@ class File(BaseModel):
|
|
|
219
246
|
if not file_path.exists():
|
|
220
247
|
raise FileNotFoundError(f"File {file_path} does not exist")
|
|
221
248
|
|
|
222
|
-
repo = get_builtin_repository(repository)
|
|
223
|
-
|
|
224
249
|
save_kwargs = save_kwargs or {}
|
|
225
250
|
fallback_save_kwargs = fallback_save_kwargs or {}
|
|
226
251
|
|
|
@@ -238,29 +263,17 @@ class File(BaseModel):
|
|
|
238
263
|
save_kwargs.setdefault("multipart", multipart)
|
|
239
264
|
fallback_save_kwargs.setdefault("multipart", multipart)
|
|
240
265
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
file_path,
|
|
244
|
-
content_type=content_type,
|
|
245
|
-
**save_kwargs,
|
|
246
|
-
)
|
|
247
|
-
except Exception as exc:
|
|
248
|
-
if not fallback_repository:
|
|
249
|
-
raise
|
|
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
|
-
|
|
257
|
-
fallback_repo = get_builtin_repository(fallback_repository)
|
|
266
|
+
save_kwargs.setdefault("content_type", content_type)
|
|
267
|
+
fallback_save_kwargs.setdefault("content_type", content_type)
|
|
258
268
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
url, data = _try_with_fallback(
|
|
270
|
+
"save_file",
|
|
271
|
+
[file_path],
|
|
272
|
+
repository=repository,
|
|
273
|
+
fallback_repository=fallback_repository,
|
|
274
|
+
save_kwargs=save_kwargs,
|
|
275
|
+
fallback_save_kwargs=fallback_save_kwargs,
|
|
276
|
+
)
|
|
264
277
|
|
|
265
278
|
return cls(
|
|
266
279
|
url=url,
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from base64 import b64encode
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from fal.toolkit.file.file import File, GoogleStorageRepository, _try_with_fallback
|
|
13
|
+
from fal.toolkit.file.types import FileData, FileRepository
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_binary_content_matches():
|
|
17
|
+
content = b"Hello World"
|
|
18
|
+
content_base64 = b64encode(content).decode("utf-8")
|
|
19
|
+
file = File.from_bytes(content, repository="in_memory")
|
|
20
|
+
assert file.url.endswith(content_base64)
|
|
21
|
+
assert file.as_bytes() == content
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_default_content_type():
|
|
25
|
+
file = File.from_bytes(b"Hello World", repository="in_memory")
|
|
26
|
+
assert file.content_type == "application/octet-stream"
|
|
27
|
+
assert file.file_name
|
|
28
|
+
assert file.file_name.endswith(".bin")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_file_name_from_content_type():
|
|
32
|
+
file = File.from_bytes(
|
|
33
|
+
b"Hello World", content_type="text/plain", repository="in_memory"
|
|
34
|
+
)
|
|
35
|
+
assert file.content_type == "text/plain"
|
|
36
|
+
assert file.file_name
|
|
37
|
+
assert file.file_name.endswith(".txt")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_content_type_from_file_name():
|
|
41
|
+
file = File.from_bytes(
|
|
42
|
+
b"Hello World", file_name="hello.txt", repository="in_memory"
|
|
43
|
+
)
|
|
44
|
+
assert file.content_type == "text/plain"
|
|
45
|
+
assert file.file_name == "hello.txt"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_file_size():
|
|
49
|
+
content = b"Hello World"
|
|
50
|
+
file = File.from_bytes(content, repository="in_memory")
|
|
51
|
+
assert file.file_size == len(content)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_in_memory_repository_url():
|
|
55
|
+
content = b"Hello World"
|
|
56
|
+
file = File.from_bytes(content, repository="in_memory")
|
|
57
|
+
assert file.url.startswith("data:application/octet-stream;base64,")
|
|
58
|
+
assert file.url.endswith(b64encode(content).decode("utf-8"))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_gcp_storage_if_available():
|
|
62
|
+
gcp_sa_json = os.environ.get("GCLOUD_SA_JSON")
|
|
63
|
+
if gcp_sa_json is None:
|
|
64
|
+
pytest.skip(reason="GCLOUD_SA_JSON environment variable is not set")
|
|
65
|
+
|
|
66
|
+
gcp_storage = GoogleStorageRepository(
|
|
67
|
+
gcp_account_json=gcp_sa_json, bucket_name="fal_registry_image_results"
|
|
68
|
+
)
|
|
69
|
+
file = File.from_bytes(b"Hello GCP Storage!", repository=gcp_storage)
|
|
70
|
+
assert file.url.startswith(
|
|
71
|
+
"https://storage.googleapis.com/fal_registry_image_results/"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_load_nested():
|
|
76
|
+
class Input(BaseModel):
|
|
77
|
+
file: File
|
|
78
|
+
|
|
79
|
+
assert (
|
|
80
|
+
Input(file="https://example.com/somefile.txt").file.url
|
|
81
|
+
== "https://example.com/somefile.txt"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
with pytest.raises(ValueError, match="value must be a valid URL"):
|
|
85
|
+
Input(file="not_a_valid_url")
|
|
86
|
+
|
|
87
|
+
file_dict = {
|
|
88
|
+
"url": "https://example.com/somefile.txt",
|
|
89
|
+
"content_type": "text/plain",
|
|
90
|
+
"file_name": "somefile.txt",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
parsed_input = Input(file=file_dict)
|
|
94
|
+
assert parsed_input.file.url == file_dict["url"]
|
|
95
|
+
assert parsed_input.file.content_type == file_dict["content_type"]
|
|
96
|
+
assert parsed_input.file.file_name == file_dict["file_name"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class MockRepository(FileRepository):
|
|
100
|
+
"""Mock repository for testing that can be configured to succeed or fail"""
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
name: str,
|
|
105
|
+
should_fail: bool = False,
|
|
106
|
+
failure_exception: Optional[Exception] = None,
|
|
107
|
+
):
|
|
108
|
+
self.name = name
|
|
109
|
+
self.should_fail = should_fail
|
|
110
|
+
self.failure_exception = failure_exception or Exception(
|
|
111
|
+
f"Mock failure for {name}"
|
|
112
|
+
)
|
|
113
|
+
self.calls: list[tuple[str, Any, dict[str, Any]]] = []
|
|
114
|
+
|
|
115
|
+
def save(self, data: FileData, **kwargs: Any) -> str:
|
|
116
|
+
self.calls.append(("save", data, kwargs))
|
|
117
|
+
if self.should_fail:
|
|
118
|
+
raise self.failure_exception
|
|
119
|
+
return f"success_url_from_{self.name}"
|
|
120
|
+
|
|
121
|
+
def save_file(self, file_path: Path, **kwargs: Any) -> tuple[str, FileData]:
|
|
122
|
+
self.calls.append(("save_file", file_path, kwargs))
|
|
123
|
+
if self.should_fail:
|
|
124
|
+
raise self.failure_exception
|
|
125
|
+
return f"success_url_from_{self.name}", FileData(
|
|
126
|
+
b"mock_data", "text/plain", "mock.txt"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestTryWithFallback:
|
|
131
|
+
"""Test cases for the _try_with_fallback function"""
|
|
132
|
+
|
|
133
|
+
def test_success_on_first_attempt(self):
|
|
134
|
+
"""Test successful execution on the first repository"""
|
|
135
|
+
mock_repo = MockRepository("primary", should_fail=False)
|
|
136
|
+
|
|
137
|
+
with patch(
|
|
138
|
+
"fal.toolkit.file.file.get_builtin_repository", return_value=mock_repo
|
|
139
|
+
):
|
|
140
|
+
result = _try_with_fallback(
|
|
141
|
+
func="save",
|
|
142
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
143
|
+
repository="primary",
|
|
144
|
+
fallback_repository=None,
|
|
145
|
+
save_kwargs={"key1": "value1"},
|
|
146
|
+
fallback_save_kwargs={},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
assert result == "success_url_from_primary"
|
|
150
|
+
assert len(mock_repo.calls) == 1
|
|
151
|
+
assert mock_repo.calls[0][0] == "save"
|
|
152
|
+
|
|
153
|
+
def test_fallback_on_first_failure(self):
|
|
154
|
+
"""Test fallback to second repository when first fails"""
|
|
155
|
+
primary_repo = MockRepository("primary", should_fail=True)
|
|
156
|
+
fallback_repo = MockRepository("fallback", should_fail=False)
|
|
157
|
+
|
|
158
|
+
with patch("fal.toolkit.file.file.get_builtin_repository") as mock_get_repo:
|
|
159
|
+
mock_get_repo.side_effect = [primary_repo, fallback_repo]
|
|
160
|
+
|
|
161
|
+
result = _try_with_fallback(
|
|
162
|
+
func="save",
|
|
163
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
164
|
+
repository="primary",
|
|
165
|
+
fallback_repository="fallback",
|
|
166
|
+
save_kwargs={"key1": "value1"},
|
|
167
|
+
fallback_save_kwargs={"key2": "value2"},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
assert result == "success_url_from_fallback"
|
|
171
|
+
assert len(primary_repo.calls) == 1
|
|
172
|
+
assert len(fallback_repo.calls) == 1
|
|
173
|
+
assert primary_repo.calls[0][2] == {"key1": "value1"}
|
|
174
|
+
assert fallback_repo.calls[0][2] == {"key2": "value2"}
|
|
175
|
+
|
|
176
|
+
def test_fallback_with_list_of_repositories(self):
|
|
177
|
+
"""Test fallback through a list of repositories"""
|
|
178
|
+
repo1 = MockRepository("repo1", should_fail=True)
|
|
179
|
+
repo2 = MockRepository("repo2", should_fail=True)
|
|
180
|
+
repo3 = MockRepository("repo3", should_fail=False)
|
|
181
|
+
|
|
182
|
+
with patch("fal.toolkit.file.file.get_builtin_repository") as mock_get_repo:
|
|
183
|
+
mock_get_repo.side_effect = [repo1, repo2, repo3]
|
|
184
|
+
|
|
185
|
+
result = _try_with_fallback(
|
|
186
|
+
func="save",
|
|
187
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
188
|
+
repository="repo1",
|
|
189
|
+
fallback_repository=["repo2", "repo3"],
|
|
190
|
+
save_kwargs={"key1": "value1"},
|
|
191
|
+
fallback_save_kwargs={"key2": "value2"},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
assert result == "success_url_from_repo3"
|
|
195
|
+
assert len(repo1.calls) == 1
|
|
196
|
+
assert len(repo2.calls) == 1
|
|
197
|
+
assert len(repo3.calls) == 1
|
|
198
|
+
|
|
199
|
+
def test_all_repositories_fail(self):
|
|
200
|
+
"""Test that exception is raised when all repositories fail"""
|
|
201
|
+
repo1 = MockRepository("repo1", should_fail=True)
|
|
202
|
+
repo2 = MockRepository("repo2", should_fail=True)
|
|
203
|
+
|
|
204
|
+
with patch("fal.toolkit.file.file.get_builtin_repository") as mock_get_repo:
|
|
205
|
+
mock_get_repo.side_effect = [repo1, repo2]
|
|
206
|
+
|
|
207
|
+
with pytest.raises(Exception, match="Mock failure for repo2"):
|
|
208
|
+
_try_with_fallback(
|
|
209
|
+
func="save",
|
|
210
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
211
|
+
repository="repo1",
|
|
212
|
+
fallback_repository="repo2",
|
|
213
|
+
save_kwargs={},
|
|
214
|
+
fallback_save_kwargs={},
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def test_no_fallback_repository(self):
|
|
218
|
+
"""Test behavior when no fallback repository is provided"""
|
|
219
|
+
repo = MockRepository("primary", should_fail=True)
|
|
220
|
+
|
|
221
|
+
with patch("fal.toolkit.file.file.get_builtin_repository", return_value=repo):
|
|
222
|
+
with pytest.raises(Exception, match="Mock failure for primary"):
|
|
223
|
+
_try_with_fallback(
|
|
224
|
+
func="save",
|
|
225
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
226
|
+
repository="primary",
|
|
227
|
+
fallback_repository=None,
|
|
228
|
+
save_kwargs={},
|
|
229
|
+
fallback_save_kwargs={},
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def test_save_file_function(self):
|
|
233
|
+
"""Test with save_file function instead of save"""
|
|
234
|
+
mock_repo = MockRepository("primary", should_fail=False)
|
|
235
|
+
test_path = Path("/tmp/test.txt")
|
|
236
|
+
|
|
237
|
+
with patch(
|
|
238
|
+
"fal.toolkit.file.file.get_builtin_repository", return_value=mock_repo
|
|
239
|
+
):
|
|
240
|
+
result = _try_with_fallback(
|
|
241
|
+
func="save_file",
|
|
242
|
+
args=[test_path],
|
|
243
|
+
repository="primary",
|
|
244
|
+
fallback_repository=None,
|
|
245
|
+
save_kwargs={"content_type": "text/plain"},
|
|
246
|
+
fallback_save_kwargs={},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
assert result[0] == "success_url_from_primary"
|
|
250
|
+
assert isinstance(result[1], FileData)
|
|
251
|
+
assert result[1].data == b"mock_data"
|
|
252
|
+
assert result[1].content_type == "text/plain"
|
|
253
|
+
assert result[1].file_name == "mock.txt"
|
|
254
|
+
assert len(mock_repo.calls) == 1
|
|
255
|
+
assert mock_repo.calls[0][0] == "save_file"
|
|
256
|
+
assert mock_repo.calls[0][1] == test_path
|
|
257
|
+
|
|
258
|
+
def test_custom_exception_types(self):
|
|
259
|
+
"""Test with different types of exceptions"""
|
|
260
|
+
custom_exception = ValueError("Custom error message")
|
|
261
|
+
repo1 = MockRepository(
|
|
262
|
+
"repo1", should_fail=True, failure_exception=custom_exception
|
|
263
|
+
)
|
|
264
|
+
repo2 = MockRepository("repo2", should_fail=False)
|
|
265
|
+
|
|
266
|
+
with patch("fal.toolkit.file.file.get_builtin_repository") as mock_get_repo:
|
|
267
|
+
mock_get_repo.side_effect = [repo1, repo2]
|
|
268
|
+
|
|
269
|
+
result = _try_with_fallback(
|
|
270
|
+
func="save",
|
|
271
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
272
|
+
repository="repo1",
|
|
273
|
+
fallback_repository="repo2",
|
|
274
|
+
save_kwargs={},
|
|
275
|
+
fallback_save_kwargs={},
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
assert result == "success_url_from_repo2"
|
|
279
|
+
|
|
280
|
+
def test_empty_fallback_list(self):
|
|
281
|
+
"""Test with empty fallback list"""
|
|
282
|
+
repo = MockRepository("primary", should_fail=True)
|
|
283
|
+
|
|
284
|
+
with patch("fal.toolkit.file.file.get_builtin_repository", return_value=repo):
|
|
285
|
+
with pytest.raises(Exception, match="Mock failure for primary"):
|
|
286
|
+
_try_with_fallback(
|
|
287
|
+
func="save",
|
|
288
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
289
|
+
repository="primary",
|
|
290
|
+
fallback_repository=[],
|
|
291
|
+
save_kwargs={},
|
|
292
|
+
fallback_save_kwargs={},
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def test_repository_id_vs_object(self):
|
|
296
|
+
"""Test that both repository IDs and repository objects work"""
|
|
297
|
+
mock_repo = MockRepository("test_repo", should_fail=False)
|
|
298
|
+
|
|
299
|
+
# Test with repository ID
|
|
300
|
+
with patch(
|
|
301
|
+
"fal.toolkit.file.file.get_builtin_repository", return_value=mock_repo
|
|
302
|
+
):
|
|
303
|
+
result1 = _try_with_fallback(
|
|
304
|
+
func="save",
|
|
305
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
306
|
+
repository="test_repo",
|
|
307
|
+
fallback_repository=None,
|
|
308
|
+
save_kwargs={},
|
|
309
|
+
fallback_save_kwargs={},
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Test with repository object
|
|
313
|
+
result2 = _try_with_fallback(
|
|
314
|
+
func="save",
|
|
315
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
316
|
+
repository=mock_repo,
|
|
317
|
+
fallback_repository=None,
|
|
318
|
+
save_kwargs={},
|
|
319
|
+
fallback_save_kwargs={},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
assert result1 == "success_url_from_test_repo"
|
|
323
|
+
assert result2 == "success_url_from_test_repo"
|
|
324
|
+
|
|
325
|
+
def test_traceback_and_print_output(self):
|
|
326
|
+
"""Test that traceback and print statements are called on failure"""
|
|
327
|
+
repo1 = MockRepository("repo1", should_fail=True)
|
|
328
|
+
repo2 = MockRepository("repo2", should_fail=False)
|
|
329
|
+
|
|
330
|
+
with patch(
|
|
331
|
+
"fal.toolkit.file.file.get_builtin_repository"
|
|
332
|
+
) as mock_get_repo, patch(
|
|
333
|
+
"fal.toolkit.file.file.traceback.print_exc"
|
|
334
|
+
) as mock_traceback, patch("builtins.print") as mock_print:
|
|
335
|
+
mock_get_repo.side_effect = [repo1, repo2]
|
|
336
|
+
|
|
337
|
+
result = _try_with_fallback(
|
|
338
|
+
func="save",
|
|
339
|
+
args=[FileData(b"test_data", "text/plain", "test.txt")],
|
|
340
|
+
repository="repo1",
|
|
341
|
+
fallback_repository="repo2",
|
|
342
|
+
save_kwargs={},
|
|
343
|
+
fallback_save_kwargs={},
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
assert result == "success_url_from_repo2"
|
|
347
|
+
mock_traceback.assert_called_once()
|
|
348
|
+
mock_print.assert_called_once()
|
|
349
|
+
# Check that the print message contains the expected text
|
|
350
|
+
print_call_args = mock_print.call_args[0][0]
|
|
351
|
+
assert "Failed to save to repository repo1" in print_call_args
|
|
352
|
+
assert "falling back to repo2" in print_call_args
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from base64 import b64encode
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
from pydantic import BaseModel
|
|
8
|
-
|
|
9
|
-
from fal.toolkit.file.file import File, GoogleStorageRepository
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def test_binary_content_matches():
|
|
13
|
-
content = b"Hello World"
|
|
14
|
-
content_base64 = b64encode(content).decode("utf-8")
|
|
15
|
-
file = File.from_bytes(content, repository="in_memory")
|
|
16
|
-
assert file.url.endswith(content_base64)
|
|
17
|
-
assert file.as_bytes() == content
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_default_content_type():
|
|
21
|
-
file = File.from_bytes(b"Hello World", repository="in_memory")
|
|
22
|
-
assert file.content_type == "application/octet-stream"
|
|
23
|
-
assert file.file_name
|
|
24
|
-
assert file.file_name.endswith(".bin")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_file_name_from_content_type():
|
|
28
|
-
file = File.from_bytes(
|
|
29
|
-
b"Hello World", content_type="text/plain", repository="in_memory"
|
|
30
|
-
)
|
|
31
|
-
assert file.content_type == "text/plain"
|
|
32
|
-
assert file.file_name
|
|
33
|
-
assert file.file_name.endswith(".txt")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def test_content_type_from_file_name():
|
|
37
|
-
file = File.from_bytes(
|
|
38
|
-
b"Hello World", file_name="hello.txt", repository="in_memory"
|
|
39
|
-
)
|
|
40
|
-
assert file.content_type == "text/plain"
|
|
41
|
-
assert file.file_name == "hello.txt"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def test_file_size():
|
|
45
|
-
content = b"Hello World"
|
|
46
|
-
file = File.from_bytes(content, repository="in_memory")
|
|
47
|
-
assert file.file_size == len(content)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def test_in_memory_repository_url():
|
|
51
|
-
content = b"Hello World"
|
|
52
|
-
file = File.from_bytes(content, repository="in_memory")
|
|
53
|
-
assert file.url.startswith("data:application/octet-stream;base64,")
|
|
54
|
-
assert file.url.endswith(b64encode(content).decode("utf-8"))
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def test_gcp_storage_if_available():
|
|
58
|
-
gcp_sa_json = os.environ.get("GCLOUD_SA_JSON")
|
|
59
|
-
if gcp_sa_json is None:
|
|
60
|
-
pytest.skip(reason="GCLOUD_SA_JSON environment variable is not set")
|
|
61
|
-
|
|
62
|
-
gcp_storage = GoogleStorageRepository(
|
|
63
|
-
gcp_account_json=gcp_sa_json, bucket_name="fal_registry_image_results"
|
|
64
|
-
)
|
|
65
|
-
file = File.from_bytes(b"Hello GCP Storage!", repository=gcp_storage)
|
|
66
|
-
assert file.url.startswith(
|
|
67
|
-
"https://storage.googleapis.com/fal_registry_image_results/"
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_load_nested():
|
|
72
|
-
class Input(BaseModel):
|
|
73
|
-
file: File
|
|
74
|
-
|
|
75
|
-
assert (
|
|
76
|
-
Input(file="https://example.com/somefile.txt").file.url
|
|
77
|
-
== "https://example.com/somefile.txt"
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
with pytest.raises(ValueError, match="value must be a valid URL"):
|
|
81
|
-
Input(file="not_a_valid_url")
|
|
82
|
-
|
|
83
|
-
file_dict = {
|
|
84
|
-
"url": "https://example.com/somefile.txt",
|
|
85
|
-
"content_type": "text/plain",
|
|
86
|
-
"file_name": "somefile.txt",
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
parsed_input = Input(file=file_dict)
|
|
90
|
-
assert parsed_input.file.url == file_dict["url"]
|
|
91
|
-
assert parsed_input.file.content_type == file_dict["content_type"]
|
|
92
|
-
assert parsed_input.file.file_name == file_dict["file_name"]
|
|
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.5 → fal-1.26.6}/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.5 → fal-1.26.6}/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
|