fal 1.5.1__tar.gz → 1.5.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.5.1 → fal-1.5.2}/PKG-INFO +1 -1
- {fal-1.5.1 → fal-1.5.2}/fal.egg-info/PKG-INFO +1 -1
- {fal-1.5.1 → fal-1.5.2}/fal.egg-info/SOURCES.txt +1 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/_fal_version.py +2 -2
- {fal-1.5.1 → fal-1.5.2}/src/fal/app.py +4 -1
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/file/file.py +33 -2
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/file/providers/fal.py +60 -5
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/file/types.py +11 -4
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/image/image.py +5 -0
- fal-1.5.2/tests/assets/cat.png +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/integration_test.py +6 -7
- {fal-1.5.1 → fal-1.5.2}/tests/test_stability.py +1 -0
- {fal-1.5.1 → fal-1.5.2}/.gitignore +0 -0
- {fal-1.5.1 → fal-1.5.2}/README.md +0 -0
- {fal-1.5.1 → fal-1.5.2}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.5.1 → fal-1.5.2}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.5.1 → fal-1.5.2}/fal.egg-info/requires.txt +0 -0
- {fal-1.5.1 → fal-1.5.2}/fal.egg-info/top_level.txt +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/README.md +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.5.1 → fal-1.5.2}/openapi_rest.config.yaml +0 -0
- {fal-1.5.1 → fal-1.5.2}/pyproject.toml +0 -0
- {fal-1.5.1 → fal-1.5.2}/setup.cfg +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/__main__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/_serialization.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/_version.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/api.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/apps.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/auth/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/auth/auth0.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/auth/local.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/_utils.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/apps.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/auth.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/create.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/debug.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/deploy.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/doctor.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/keys.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/main.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/parser.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/run.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/cli/secrets.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/console/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/console/icons.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/console/ux.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/container.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/exceptions/_base.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/exceptions/auth.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/files.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/flags.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/logging/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/logging/isolate.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/logging/style.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/logging/trace.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/logging/user.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/py.typed +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/rest_client.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/sdk.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/sync.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/toolkit/utils/retry.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/utils.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/src/fal/workflows.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/cli/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/cli/test_apps.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/cli/test_auth.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/cli/test_deploy.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/cli/test_keys.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/cli/test_run.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/cli/test_secrets.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/conftest.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/mainify_package/__init__.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/mainify_package/impl.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/mainify_package/utils.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/mainify_target.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/test_apps.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/toolkit/file_test.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/toolkit/image_test.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tests/toolkit/utils/retry.py +0 -0
- {fal-1.5.1 → fal-1.5.2}/tools/demo_script.py +0 -0
{fal-1.5.1 → fal-1.5.2}/PKG-INFO
RENAMED
|
@@ -402,7 +402,10 @@ def _fal_websocket_template(
|
|
|
402
402
|
batch.append(next_input)
|
|
403
403
|
|
|
404
404
|
t0 = loop.time()
|
|
405
|
-
|
|
405
|
+
if inspect.iscoroutinefunction(func):
|
|
406
|
+
output = await func(self, *batch)
|
|
407
|
+
else:
|
|
408
|
+
output = await loop.run_in_executor(None, func, self, *batch) # type: ignore
|
|
406
409
|
total_time = loop.time() - t0
|
|
407
410
|
if not isinstance(output, dict):
|
|
408
411
|
# Handle pydantic output modal
|
|
@@ -8,6 +8,7 @@ from urllib.parse import urlparse
|
|
|
8
8
|
from zipfile import ZipFile
|
|
9
9
|
|
|
10
10
|
import pydantic
|
|
11
|
+
from fastapi import Request
|
|
11
12
|
|
|
12
13
|
# https://github.com/pydantic/pydantic/pull/2573
|
|
13
14
|
if not hasattr(pydantic, "__version__") or pydantic.__version__.startswith("1."):
|
|
@@ -24,6 +25,7 @@ from fal.toolkit.file.providers.fal import (
|
|
|
24
25
|
FalCDNFileRepository,
|
|
25
26
|
FalFileRepository,
|
|
26
27
|
FalFileRepositoryV2,
|
|
28
|
+
FalFileRepositoryV3,
|
|
27
29
|
InMemoryRepository,
|
|
28
30
|
)
|
|
29
31
|
from fal.toolkit.file.providers.gcp import GoogleStorageRepository
|
|
@@ -36,6 +38,7 @@ FileRepositoryFactory = Callable[[], FileRepository]
|
|
|
36
38
|
BUILT_IN_REPOSITORIES: dict[RepositoryId, FileRepositoryFactory] = {
|
|
37
39
|
"fal": lambda: FalFileRepository(),
|
|
38
40
|
"fal_v2": lambda: FalFileRepositoryV2(),
|
|
41
|
+
"fal_v3": lambda: FalFileRepositoryV3(),
|
|
39
42
|
"in_memory": lambda: InMemoryRepository(),
|
|
40
43
|
"gcp_storage": lambda: GoogleStorageRepository(),
|
|
41
44
|
"r2": lambda: R2Repository(),
|
|
@@ -53,6 +56,7 @@ get_builtin_repository.__module__ = "__main__"
|
|
|
53
56
|
|
|
54
57
|
DEFAULT_REPOSITORY: FileRepository | RepositoryId = "fal_v2"
|
|
55
58
|
FALLBACK_REPOSITORY: FileRepository | RepositoryId = "cdn"
|
|
59
|
+
OBJECT_LIFECYCLE_PREFERENCE_KEY = "x-fal-object-lifecycle-preference"
|
|
56
60
|
|
|
57
61
|
|
|
58
62
|
class File(BaseModel):
|
|
@@ -130,6 +134,7 @@ class File(BaseModel):
|
|
|
130
134
|
fallback_repository: Optional[
|
|
131
135
|
FileRepository | RepositoryId
|
|
132
136
|
] = FALLBACK_REPOSITORY,
|
|
137
|
+
request: Optional[Request] = None,
|
|
133
138
|
) -> File:
|
|
134
139
|
repo = (
|
|
135
140
|
repository
|
|
@@ -139,8 +144,10 @@ class File(BaseModel):
|
|
|
139
144
|
|
|
140
145
|
fdata = FileData(data, content_type, file_name)
|
|
141
146
|
|
|
147
|
+
object_lifecycle_preference = _get_lifecycle_preference(request)
|
|
148
|
+
|
|
142
149
|
try:
|
|
143
|
-
url = repo.save(fdata)
|
|
150
|
+
url = repo.save(fdata, object_lifecycle_preference)
|
|
144
151
|
except Exception:
|
|
145
152
|
if not fallback_repository:
|
|
146
153
|
raise
|
|
@@ -151,7 +158,7 @@ class File(BaseModel):
|
|
|
151
158
|
else get_builtin_repository(fallback_repository)
|
|
152
159
|
)
|
|
153
160
|
|
|
154
|
-
url = fallback_repo.save(fdata)
|
|
161
|
+
url = fallback_repo.save(fdata, object_lifecycle_preference)
|
|
155
162
|
|
|
156
163
|
return cls(
|
|
157
164
|
url=url,
|
|
@@ -171,6 +178,7 @@ class File(BaseModel):
|
|
|
171
178
|
fallback_repository: Optional[
|
|
172
179
|
FileRepository | RepositoryId
|
|
173
180
|
] = FALLBACK_REPOSITORY,
|
|
181
|
+
request: Optional[Request] = None,
|
|
174
182
|
) -> File:
|
|
175
183
|
file_path = Path(path)
|
|
176
184
|
if not file_path.exists():
|
|
@@ -183,12 +191,14 @@ class File(BaseModel):
|
|
|
183
191
|
)
|
|
184
192
|
|
|
185
193
|
content_type = content_type or "application/octet-stream"
|
|
194
|
+
object_lifecycle_preference = _get_lifecycle_preference(request)
|
|
186
195
|
|
|
187
196
|
try:
|
|
188
197
|
url, data = repo.save_file(
|
|
189
198
|
file_path,
|
|
190
199
|
content_type=content_type,
|
|
191
200
|
multipart=multipart,
|
|
201
|
+
object_lifecycle_preference=object_lifecycle_preference,
|
|
192
202
|
)
|
|
193
203
|
except Exception:
|
|
194
204
|
if not fallback_repository:
|
|
@@ -204,6 +214,7 @@ class File(BaseModel):
|
|
|
204
214
|
file_path,
|
|
205
215
|
content_type=content_type,
|
|
206
216
|
multipart=multipart,
|
|
217
|
+
object_lifecycle_preference=object_lifecycle_preference,
|
|
207
218
|
)
|
|
208
219
|
|
|
209
220
|
return cls(
|
|
@@ -261,3 +272,23 @@ class CompressedFile(File):
|
|
|
261
272
|
def __del__(self):
|
|
262
273
|
if self.extract_dir:
|
|
263
274
|
shutil.rmtree(self.extract_dir)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _get_lifecycle_preference(request: Request) -> dict[str, str] | None:
|
|
278
|
+
import json
|
|
279
|
+
|
|
280
|
+
preference_str = (
|
|
281
|
+
request.headers.get(OBJECT_LIFECYCLE_PREFERENCE_KEY)
|
|
282
|
+
if request is not None
|
|
283
|
+
else None
|
|
284
|
+
)
|
|
285
|
+
if preference_str is None:
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
object_lifecycle_preference = {}
|
|
289
|
+
try:
|
|
290
|
+
object_lifecycle_preference = json.loads(preference_str)
|
|
291
|
+
return object_lifecycle_preference
|
|
292
|
+
except Exception as e:
|
|
293
|
+
print(f"Failed to parse object lifecycle preference: {e}")
|
|
294
|
+
return None
|
|
@@ -19,6 +19,7 @@ from fal.toolkit.file.types import FileData, FileRepository
|
|
|
19
19
|
from fal.toolkit.utils.retry import retry
|
|
20
20
|
|
|
21
21
|
_FAL_CDN = "https://fal.media"
|
|
22
|
+
_FAL_CDN_V3 = "https://v3.fal.media"
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
@dataclass
|
|
@@ -91,11 +92,11 @@ fal_v2_token_manager = FalV2TokenManager()
|
|
|
91
92
|
|
|
92
93
|
@dataclass
|
|
93
94
|
class ObjectLifecyclePreference:
|
|
94
|
-
|
|
95
|
+
expiration_duration_seconds: int
|
|
95
96
|
|
|
96
97
|
|
|
97
98
|
GLOBAL_LIFECYCLE_PREFERENCE = ObjectLifecyclePreference(
|
|
98
|
-
|
|
99
|
+
expiration_duration_seconds=86400
|
|
99
100
|
)
|
|
100
101
|
|
|
101
102
|
|
|
@@ -158,7 +159,9 @@ class FalFileRepositoryBase(FileRepository):
|
|
|
158
159
|
|
|
159
160
|
@dataclass
|
|
160
161
|
class FalFileRepository(FalFileRepositoryBase):
|
|
161
|
-
def save(
|
|
162
|
+
def save(
|
|
163
|
+
self, file: FileData, object_lifecycle_preference: dict[str, str] | None = None
|
|
164
|
+
) -> str:
|
|
162
165
|
return self._save(file, "gcs")
|
|
163
166
|
|
|
164
167
|
|
|
@@ -275,7 +278,9 @@ class MultipartUpload:
|
|
|
275
278
|
@dataclass
|
|
276
279
|
class FalFileRepositoryV2(FalFileRepositoryBase):
|
|
277
280
|
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
278
|
-
def save(
|
|
281
|
+
def save(
|
|
282
|
+
self, file: FileData, object_lifecycle_preference: dict[str, str] | None = None
|
|
283
|
+
) -> str:
|
|
279
284
|
token = fal_v2_token_manager.get_token()
|
|
280
285
|
headers = {
|
|
281
286
|
"Authorization": f"{token.token_type} {token.token}",
|
|
@@ -327,6 +332,7 @@ class FalFileRepositoryV2(FalFileRepositoryBase):
|
|
|
327
332
|
multipart_threshold: int | None = None,
|
|
328
333
|
multipart_chunk_size: int | None = None,
|
|
329
334
|
multipart_max_concurrency: int | None = None,
|
|
335
|
+
object_lifecycle_preference: dict[str, str] | None = None,
|
|
330
336
|
) -> tuple[str, FileData | None]:
|
|
331
337
|
if multipart is None:
|
|
332
338
|
threshold = multipart_threshold or MultipartUpload.MULTIPART_THRESHOLD
|
|
@@ -347,7 +353,7 @@ class FalFileRepositoryV2(FalFileRepositoryBase):
|
|
|
347
353
|
content_type=content_type,
|
|
348
354
|
file_name=os.path.basename(file_path),
|
|
349
355
|
)
|
|
350
|
-
url = self.save(data)
|
|
356
|
+
url = self.save(data, object_lifecycle_preference)
|
|
351
357
|
|
|
352
358
|
return url, data
|
|
353
359
|
|
|
@@ -357,6 +363,7 @@ class InMemoryRepository(FileRepository):
|
|
|
357
363
|
def save(
|
|
358
364
|
self,
|
|
359
365
|
file: FileData,
|
|
366
|
+
object_lifecycle_preference: dict[str, str] | None = None,
|
|
360
367
|
) -> str:
|
|
361
368
|
return f'data:{file.content_type};base64,{b64encode(file.data).decode("utf-8")}'
|
|
362
369
|
|
|
@@ -367,6 +374,7 @@ class FalCDNFileRepository(FileRepository):
|
|
|
367
374
|
def save(
|
|
368
375
|
self,
|
|
369
376
|
file: FileData,
|
|
377
|
+
object_lifecycle_preference: dict[str, str] | None = None,
|
|
370
378
|
) -> str:
|
|
371
379
|
headers = {
|
|
372
380
|
**self.auth_headers,
|
|
@@ -401,3 +409,50 @@ class FalCDNFileRepository(FileRepository):
|
|
|
401
409
|
"Authorization": f"Bearer {key_id}:{key_secret}",
|
|
402
410
|
"User-Agent": "fal/0.1.0",
|
|
403
411
|
}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@dataclass
|
|
415
|
+
class FalFileRepositoryV3(FileRepository):
|
|
416
|
+
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
417
|
+
def save(
|
|
418
|
+
self, file: FileData, user_lifecycle_preference: dict[str, str] | None
|
|
419
|
+
) -> str:
|
|
420
|
+
object_lifecycle_preference = dataclasses.asdict(GLOBAL_LIFECYCLE_PREFERENCE)
|
|
421
|
+
|
|
422
|
+
if user_lifecycle_preference is not None:
|
|
423
|
+
object_lifecycle_preference = {
|
|
424
|
+
key: user_lifecycle_preference[key]
|
|
425
|
+
if key in user_lifecycle_preference
|
|
426
|
+
else value
|
|
427
|
+
for key, value in object_lifecycle_preference.items()
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
headers = {
|
|
431
|
+
**self.auth_headers,
|
|
432
|
+
"Accept": "application/json",
|
|
433
|
+
"Content-Type": file.content_type,
|
|
434
|
+
"X-Fal-File-Name": file.file_name,
|
|
435
|
+
"X-Fal-Object-Lifecycle-Preference": json.dumps(
|
|
436
|
+
object_lifecycle_preference
|
|
437
|
+
),
|
|
438
|
+
}
|
|
439
|
+
url = os.getenv("FAL_CDN_V3_HOST", _FAL_CDN_V3) + "/files/upload"
|
|
440
|
+
request = Request(url, headers=headers, method="POST", data=file.data)
|
|
441
|
+
try:
|
|
442
|
+
with urlopen(request) as response:
|
|
443
|
+
result = json.load(response)
|
|
444
|
+
except HTTPError as e:
|
|
445
|
+
raise FileUploadException(
|
|
446
|
+
f"Error initiating upload. Status {e.status}: {e.reason}"
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
access_url = result["access_url"]
|
|
450
|
+
return access_url
|
|
451
|
+
|
|
452
|
+
@property
|
|
453
|
+
def auth_headers(self) -> dict[str, str]:
|
|
454
|
+
token = fal_v2_token_manager.get_token()
|
|
455
|
+
return {
|
|
456
|
+
"Authorization": f"{token.token_type} {token.token}",
|
|
457
|
+
"User-Agent": "fal/0.1.0",
|
|
458
|
+
}
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from mimetypes import guess_extension, guess_type
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Literal
|
|
6
|
+
from typing import Literal, Optional
|
|
7
7
|
from uuid import uuid4
|
|
8
8
|
|
|
9
9
|
|
|
@@ -29,12 +29,18 @@ class FileData:
|
|
|
29
29
|
self.file_name = file_name
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
RepositoryId = Literal[
|
|
32
|
+
RepositoryId = Literal[
|
|
33
|
+
"fal", "fal_v2", "fal_v3", "in_memory", "gcp_storage", "r2", "cdn"
|
|
34
|
+
]
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
@dataclass
|
|
36
38
|
class FileRepository:
|
|
37
|
-
def save(
|
|
39
|
+
def save(
|
|
40
|
+
self,
|
|
41
|
+
data: FileData,
|
|
42
|
+
object_lifecycle_preference: Optional[dict[str, str]] = None,
|
|
43
|
+
) -> str:
|
|
38
44
|
raise NotImplementedError()
|
|
39
45
|
|
|
40
46
|
def save_file(
|
|
@@ -45,6 +51,7 @@ class FileRepository:
|
|
|
45
51
|
multipart_threshold: int | None = None,
|
|
46
52
|
multipart_chunk_size: int | None = None,
|
|
47
53
|
multipart_max_concurrency: int | None = None,
|
|
54
|
+
object_lifecycle_preference: Optional[dict[str, str]] = None,
|
|
48
55
|
) -> tuple[str, FileData | None]:
|
|
49
56
|
if multipart:
|
|
50
57
|
raise NotImplementedError()
|
|
@@ -52,4 +59,4 @@ class FileRepository:
|
|
|
52
59
|
with open(file_path, "rb") as fobj:
|
|
53
60
|
data = FileData(fobj.read(), content_type, Path(file_path).name)
|
|
54
61
|
|
|
55
|
-
return self.save(data), data
|
|
62
|
+
return self.save(data, object_lifecycle_preference), data
|
|
@@ -4,6 +4,7 @@ import io
|
|
|
4
4
|
from tempfile import NamedTemporaryFile
|
|
5
5
|
from typing import TYPE_CHECKING, Literal, Optional, Union
|
|
6
6
|
|
|
7
|
+
from fastapi import Request
|
|
7
8
|
from pydantic import BaseModel, Field
|
|
8
9
|
|
|
9
10
|
from fal.toolkit.file.file import DEFAULT_REPOSITORY, FALLBACK_REPOSITORY, File
|
|
@@ -82,6 +83,7 @@ class Image(File):
|
|
|
82
83
|
fallback_repository: Optional[
|
|
83
84
|
FileRepository | RepositoryId
|
|
84
85
|
] = FALLBACK_REPOSITORY,
|
|
86
|
+
request: Optional[Request] = None,
|
|
85
87
|
) -> Image:
|
|
86
88
|
obj = super().from_bytes(
|
|
87
89
|
data,
|
|
@@ -89,6 +91,7 @@ class Image(File):
|
|
|
89
91
|
file_name=file_name,
|
|
90
92
|
repository=repository,
|
|
91
93
|
fallback_repository=fallback_repository,
|
|
94
|
+
request=request,
|
|
92
95
|
)
|
|
93
96
|
obj.width = size.width if size else None
|
|
94
97
|
obj.height = size.height if size else None
|
|
@@ -104,6 +107,7 @@ class Image(File):
|
|
|
104
107
|
fallback_repository: Optional[
|
|
105
108
|
FileRepository | RepositoryId
|
|
106
109
|
] = FALLBACK_REPOSITORY,
|
|
110
|
+
request: Optional[Request] = None,
|
|
107
111
|
) -> Image:
|
|
108
112
|
size = ImageSize(width=pil_image.width, height=pil_image.height)
|
|
109
113
|
if format is None:
|
|
@@ -133,6 +137,7 @@ class Image(File):
|
|
|
133
137
|
file_name,
|
|
134
138
|
repository,
|
|
135
139
|
fallback_repository=fallback_repository,
|
|
140
|
+
request=request,
|
|
136
141
|
)
|
|
137
142
|
|
|
138
143
|
def to_pil(self, mode: str = "RGB") -> PILImage.Image:
|
|
Binary file
|
|
@@ -23,6 +23,8 @@ from fal.toolkit.utils.download_utils import _get_git_revision_hash, _hash_url
|
|
|
23
23
|
from pydantic import BaseModel, Field
|
|
24
24
|
from pydantic import __version__ as pydantic_version
|
|
25
25
|
|
|
26
|
+
EXAMPLE_FILE_URL = "https://raw.githubusercontent.com/fal-ai/fal/main/projects/fal/tests/assets/cat.png"
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
@pytest.mark.flaky(max_runs=3)
|
|
28
30
|
def test_isolated(isolated_client: Callable[..., Callable[..., IsolatedFunction]]):
|
|
@@ -197,11 +199,9 @@ def mock_fal_persistent_dirs(monkeypatch):
|
|
|
197
199
|
def test_download_file(isolated_client, mock_fal_persistent_dirs):
|
|
198
200
|
from fal.toolkit.utils.download_utils import FAL_PERSISTENT_DIR
|
|
199
201
|
|
|
200
|
-
EXAMPLE_FILE_URL = "https://raw.githubusercontent.com/fal-ai/isolate/d553f927348206530208442556f481f39b161732/README.md"
|
|
201
|
-
|
|
202
202
|
relative_directory = "test"
|
|
203
203
|
output_directory = FAL_PERSISTENT_DIR / relative_directory
|
|
204
|
-
expected_path = output_directory / "
|
|
204
|
+
expected_path = output_directory / "cat.png"
|
|
205
205
|
|
|
206
206
|
@isolated_client()
|
|
207
207
|
def absolute_path_persistent_dir():
|
|
@@ -301,8 +301,7 @@ def test_download_model_weights(isolated_client, mock_fal_persistent_dirs):
|
|
|
301
301
|
|
|
302
302
|
print(FAL_MODEL_WEIGHTS_DIR)
|
|
303
303
|
|
|
304
|
-
|
|
305
|
-
expected_path = FAL_MODEL_WEIGHTS_DIR / _hash_url(EXAMPLE_FILE_URL) / "README.md"
|
|
304
|
+
expected_path = FAL_MODEL_WEIGHTS_DIR / _hash_url(EXAMPLE_FILE_URL) / "cat.png"
|
|
306
305
|
|
|
307
306
|
@isolated_client()
|
|
308
307
|
def download_weights():
|
|
@@ -510,8 +509,8 @@ def test_fal_file_save(isolated_client):
|
|
|
510
509
|
"file_url, expected_content",
|
|
511
510
|
[
|
|
512
511
|
(
|
|
513
|
-
|
|
514
|
-
"projects/fal/
|
|
512
|
+
EXAMPLE_FILE_URL,
|
|
513
|
+
"projects/fal/cat.png",
|
|
515
514
|
),
|
|
516
515
|
("data:text/plain;charset=UTF-8,fal", "fal"),
|
|
517
516
|
],
|
|
@@ -580,6 +580,7 @@ def test_worker_env_vars(isolated_client):
|
|
|
580
580
|
"https://storage.googleapis.com/isolate-dev-smiling-shark_toolkit_bucket/",
|
|
581
581
|
),
|
|
582
582
|
("fal_v2", "https://v2.fal.media/files"),
|
|
583
|
+
("fal_v3", "https://v3.fal.media/files"),
|
|
583
584
|
],
|
|
584
585
|
)
|
|
585
586
|
def test_fal_storage(isolated_client, repo_type, url_prefix):
|
|
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
|
{fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/api/workflows/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
|
|
File without changes
|
|
File without changes
|
{fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py
RENAMED
|
File without changes
|
{fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.5.1 → fal-1.5.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.5.1 → fal-1.5.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.5.1 → fal-1.5.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.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.5.1 → fal-1.5.2}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.5.1 → fal-1.5.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
|
|
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
|