fal 1.19.2__tar.gz → 1.20.0__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.19.2/fal.egg-info → fal-1.20.0}/PKG-INFO +1 -1
- {fal-1.19.2 → fal-1.20.0/fal.egg-info}/PKG-INFO +1 -1
- {fal-1.19.2 → fal-1.20.0}/fal.egg-info/SOURCES.txt +1 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/_fal_version.py +2 -2
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/file/providers/fal.py +54 -32
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/utils/retry.py +5 -1
- {fal-1.19.2 → fal-1.20.0}/tests/test_apps.py +4 -2
- fal-1.20.0/tests/toolkit/file/providers/test_fal_retry.py +165 -0
- {fal-1.19.2 → fal-1.20.0}/.gitignore +0 -0
- {fal-1.19.2 → fal-1.20.0}/Makefile +0 -0
- {fal-1.19.2 → fal-1.20.0}/README.md +0 -0
- {fal-1.19.2 → fal-1.20.0}/docs/conf.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/docs/index.rst +0 -0
- {fal-1.19.2 → fal-1.20.0}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.19.2 → fal-1.20.0}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.19.2 → fal-1.20.0}/fal.egg-info/requires.txt +0 -0
- {fal-1.19.2 → fal-1.20.0}/fal.egg-info/top_level.txt +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/README.md +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.19.2 → fal-1.20.0}/openapi_rest.config.yaml +0 -0
- {fal-1.19.2 → fal-1.20.0}/pyproject.toml +0 -0
- {fal-1.19.2 → fal-1.20.0}/setup.cfg +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/__main__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/_serialization.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/_version.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/api.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/app.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/apps.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/auth/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/auth/auth0.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/auth/local.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/_utils.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/api.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/apps.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/auth.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/cli_nested_json.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/create.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/debug.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/deploy.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/doctor.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/files.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/keys.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/main.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/parser.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/profile.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/run.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/runners.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/secrets.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/cli/teams.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/config.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/console/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/console/icons.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/console/ux.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/container.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/exceptions/_base.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/exceptions/auth.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/files.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/flags.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/logging/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/logging/isolate.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/logging/style.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/logging/trace.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/logging/user.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/project.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/py.typed +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/rest_client.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/sdk.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/sync.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/audio/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/audio/audio.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/file/file.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/file/providers/s3.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/types.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/utils/endpoint.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/video/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/toolkit/video/video.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/utils.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/src/fal/workflows.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/assets/cat.png +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/cli/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/cli/test_apps.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/cli/test_auth.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/cli/test_deploy.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/cli/test_keys.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/cli/test_run.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/cli/test_secrets.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/conftest.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/integration_test.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/mainify_package/__init__.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/mainify_package/impl.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/mainify_package/utils.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/mainify_target.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/test_stability.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/toolkit/file_test.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/toolkit/image_test.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/toolkit/test_types.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tests/toolkit/utils/retry.py +0 -0
- {fal-1.19.2 → fal-1.20.0}/tools/demo_script.py +0 -0
|
@@ -9,7 +9,7 @@ from contextlib import contextmanager
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from datetime import datetime, timezone
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Generator, Generic, TypeVar
|
|
12
|
+
from typing import Any, Generator, Generic, TypeVar
|
|
13
13
|
from urllib.error import HTTPError
|
|
14
14
|
from urllib.parse import urlparse, urlunparse
|
|
15
15
|
from urllib.request import Request, urlopen
|
|
@@ -26,15 +26,46 @@ _FAL_CDN_V3 = "https://v3.fal.media"
|
|
|
26
26
|
DEFAULT_REQUEST_TIMEOUT = 10
|
|
27
27
|
PUT_REQUEST_TIMEOUT = 5 * 60
|
|
28
28
|
|
|
29
|
+
MAX_ATTEMPTS = 5
|
|
30
|
+
BASE_DELAY = 0.1
|
|
31
|
+
MAX_DELAY = 30
|
|
32
|
+
RETRY_CODES = [408, 409, 429, 500, 502, 503, 504]
|
|
33
|
+
|
|
29
34
|
|
|
30
35
|
@contextmanager
|
|
31
36
|
def _urlopen(
|
|
32
|
-
request: Request,
|
|
37
|
+
request: Request,
|
|
38
|
+
timeout: int = DEFAULT_REQUEST_TIMEOUT,
|
|
33
39
|
) -> Generator[addinfourl, None, None]:
|
|
34
40
|
with urlopen(request, timeout=timeout) as response:
|
|
35
41
|
yield response
|
|
36
42
|
|
|
37
43
|
|
|
44
|
+
def _should_retry(exc: Exception) -> bool:
|
|
45
|
+
if isinstance(exc, HTTPError) and exc.code in RETRY_CODES:
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@contextmanager
|
|
52
|
+
def _maybe_retry_request(
|
|
53
|
+
request: Request,
|
|
54
|
+
**kwargs: Any,
|
|
55
|
+
) -> Generator[addinfourl, None, None]:
|
|
56
|
+
_urlopen_with_retry = retry(
|
|
57
|
+
max_retries=MAX_ATTEMPTS,
|
|
58
|
+
base_delay=BASE_DELAY,
|
|
59
|
+
max_delay=MAX_DELAY,
|
|
60
|
+
backoff_type="exponential",
|
|
61
|
+
jitter=True,
|
|
62
|
+
should_retry=_should_retry,
|
|
63
|
+
)(_urlopen)
|
|
64
|
+
|
|
65
|
+
with _urlopen_with_retry(request, **kwargs) as response:
|
|
66
|
+
yield response
|
|
67
|
+
|
|
68
|
+
|
|
38
69
|
@dataclass
|
|
39
70
|
class FalV2Token:
|
|
40
71
|
token: str
|
|
@@ -92,7 +123,7 @@ class FalV2TokenManager:
|
|
|
92
123
|
data=b"{}",
|
|
93
124
|
method="POST",
|
|
94
125
|
)
|
|
95
|
-
with
|
|
126
|
+
with _maybe_retry_request(req) as response:
|
|
96
127
|
result = json.load(response)
|
|
97
128
|
|
|
98
129
|
parsed_base_url = urlparse(result["base_url"])
|
|
@@ -137,7 +168,6 @@ LIFECYCLE_PREFERENCE: VariableReference[dict[str, str] | None] = VariableReferen
|
|
|
137
168
|
|
|
138
169
|
@dataclass
|
|
139
170
|
class FalFileRepositoryBase(FileRepository):
|
|
140
|
-
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
141
171
|
def _save(
|
|
142
172
|
self, file: FileData, storage_type: str, headers: dict[str, str] | None = None
|
|
143
173
|
) -> str:
|
|
@@ -171,7 +201,7 @@ class FalFileRepositoryBase(FileRepository):
|
|
|
171
201
|
headers=headers,
|
|
172
202
|
method="POST",
|
|
173
203
|
)
|
|
174
|
-
with
|
|
204
|
+
with _maybe_retry_request(req) as response:
|
|
175
205
|
result = json.load(response)
|
|
176
206
|
|
|
177
207
|
upload_url = result["upload_url"]
|
|
@@ -188,7 +218,7 @@ class FalFileRepositoryBase(FileRepository):
|
|
|
188
218
|
headers={"Content-Type": file.content_type},
|
|
189
219
|
)
|
|
190
220
|
|
|
191
|
-
with
|
|
221
|
+
with _maybe_retry_request(req, timeout=PUT_REQUEST_TIMEOUT):
|
|
192
222
|
pass
|
|
193
223
|
|
|
194
224
|
return result["file_url"]
|
|
@@ -265,7 +295,7 @@ class MultipartUploadGCS:
|
|
|
265
295
|
).encode(),
|
|
266
296
|
)
|
|
267
297
|
|
|
268
|
-
with
|
|
298
|
+
with _maybe_retry_request(req) as response:
|
|
269
299
|
result = json.load(response)
|
|
270
300
|
self._access_url = result["file_url"]
|
|
271
301
|
self._upload_url = result["upload_url"]
|
|
@@ -275,7 +305,6 @@ class MultipartUploadGCS:
|
|
|
275
305
|
f"Error initiating upload. Status {exc.status}: {exc.reason}"
|
|
276
306
|
)
|
|
277
307
|
|
|
278
|
-
@retry(max_retries=5, base_delay=1, backoff_type="exponential", jitter=True)
|
|
279
308
|
def upload_part(self, part_number: int, data: bytes) -> None:
|
|
280
309
|
initiate_upload_url = self.upload_url + f"/{part_number}"
|
|
281
310
|
req = Request(
|
|
@@ -285,7 +314,7 @@ class MultipartUploadGCS:
|
|
|
285
314
|
)
|
|
286
315
|
|
|
287
316
|
try:
|
|
288
|
-
with
|
|
317
|
+
with _maybe_retry_request(req) as response:
|
|
289
318
|
result = json.load(response)
|
|
290
319
|
upload_url = result["upload_url"]
|
|
291
320
|
except HTTPError as exc:
|
|
@@ -301,7 +330,7 @@ class MultipartUploadGCS:
|
|
|
301
330
|
)
|
|
302
331
|
|
|
303
332
|
try:
|
|
304
|
-
with
|
|
333
|
+
with _maybe_retry_request(req, timeout=PUT_REQUEST_TIMEOUT) as resp:
|
|
305
334
|
self._parts.append(
|
|
306
335
|
{
|
|
307
336
|
"part_number": part_number,
|
|
@@ -331,7 +360,7 @@ class MultipartUploadGCS:
|
|
|
331
360
|
}
|
|
332
361
|
).encode(),
|
|
333
362
|
)
|
|
334
|
-
with
|
|
363
|
+
with _maybe_retry_request(req):
|
|
335
364
|
pass
|
|
336
365
|
except HTTPError as e:
|
|
337
366
|
raise FileUploadException(
|
|
@@ -427,7 +456,6 @@ class FalFileRepository(FalFileRepositoryBase):
|
|
|
427
456
|
if object_lifecycle_preference:
|
|
428
457
|
headers["X-Fal-Object-Lifecycle"] = json.dumps(object_lifecycle_preference)
|
|
429
458
|
|
|
430
|
-
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
431
459
|
def save(
|
|
432
460
|
self,
|
|
433
461
|
file: FileData,
|
|
@@ -536,7 +564,7 @@ class MultipartUpload:
|
|
|
536
564
|
}
|
|
537
565
|
).encode(),
|
|
538
566
|
)
|
|
539
|
-
with
|
|
567
|
+
with _maybe_retry_request(req) as response:
|
|
540
568
|
result = json.load(response)
|
|
541
569
|
self._upload_url = result["upload_url"]
|
|
542
570
|
self._file_url = result["file_url"]
|
|
@@ -556,7 +584,7 @@ class MultipartUpload:
|
|
|
556
584
|
)
|
|
557
585
|
|
|
558
586
|
try:
|
|
559
|
-
with
|
|
587
|
+
with _maybe_retry_request(req, timeout=PUT_REQUEST_TIMEOUT) as resp:
|
|
560
588
|
self._parts.append(
|
|
561
589
|
{
|
|
562
590
|
"part_number": part_number,
|
|
@@ -581,7 +609,7 @@ class MultipartUpload:
|
|
|
581
609
|
},
|
|
582
610
|
data=json.dumps({"parts": self._parts}).encode(),
|
|
583
611
|
)
|
|
584
|
-
with
|
|
612
|
+
with _maybe_retry_request(req):
|
|
585
613
|
pass
|
|
586
614
|
except HTTPError as e:
|
|
587
615
|
raise FileUploadException(
|
|
@@ -734,7 +762,7 @@ class MultipartUploadV3:
|
|
|
734
762
|
).encode(),
|
|
735
763
|
)
|
|
736
764
|
|
|
737
|
-
with
|
|
765
|
+
with _maybe_retry_request(req) as response:
|
|
738
766
|
result = json.load(response)
|
|
739
767
|
self._access_url = result["file_url"]
|
|
740
768
|
self._upload_url = result["upload_url"]
|
|
@@ -744,7 +772,6 @@ class MultipartUploadV3:
|
|
|
744
772
|
f"Error initiating upload. Status {exc.status}: {exc.reason}"
|
|
745
773
|
)
|
|
746
774
|
|
|
747
|
-
@retry(max_retries=5, base_delay=1, backoff_type="exponential", jitter=True)
|
|
748
775
|
def upload_part(self, part_number: int, data: bytes) -> None:
|
|
749
776
|
parsed = urlparse(self.upload_url)
|
|
750
777
|
part_path = parsed.path + f"/{part_number}"
|
|
@@ -760,7 +787,7 @@ class MultipartUploadV3:
|
|
|
760
787
|
)
|
|
761
788
|
|
|
762
789
|
try:
|
|
763
|
-
with
|
|
790
|
+
with _maybe_retry_request(req, timeout=PUT_REQUEST_TIMEOUT) as resp:
|
|
764
791
|
self._parts.append(
|
|
765
792
|
{
|
|
766
793
|
"partNumber": part_number,
|
|
@@ -788,7 +815,7 @@ class MultipartUploadV3:
|
|
|
788
815
|
},
|
|
789
816
|
data=json.dumps({"parts": self._parts}).encode(),
|
|
790
817
|
)
|
|
791
|
-
with
|
|
818
|
+
with _maybe_retry_request(req):
|
|
792
819
|
pass
|
|
793
820
|
except HTTPError as e:
|
|
794
821
|
raise FileUploadException(
|
|
@@ -928,7 +955,7 @@ class InternalMultipartUploadV3:
|
|
|
928
955
|
"X-Fal-File-Name": self.file_name,
|
|
929
956
|
},
|
|
930
957
|
)
|
|
931
|
-
with
|
|
958
|
+
with _maybe_retry_request(req) as response:
|
|
932
959
|
result = json.load(response)
|
|
933
960
|
self._access_url = result["access_url"]
|
|
934
961
|
self._upload_id = result["uploadId"]
|
|
@@ -938,7 +965,6 @@ class InternalMultipartUploadV3:
|
|
|
938
965
|
f"Error initiating upload. Status {exc.status}: {exc.reason}"
|
|
939
966
|
)
|
|
940
967
|
|
|
941
|
-
@retry(max_retries=5, base_delay=1, backoff_type="exponential", jitter=True)
|
|
942
968
|
def upload_part(self, part_number: int, data: bytes) -> None:
|
|
943
969
|
url = f"{self.access_url}/multipart/{self.upload_id}/{part_number}"
|
|
944
970
|
|
|
@@ -953,7 +979,7 @@ class InternalMultipartUploadV3:
|
|
|
953
979
|
)
|
|
954
980
|
|
|
955
981
|
try:
|
|
956
|
-
with
|
|
982
|
+
with _maybe_retry_request(req, timeout=PUT_REQUEST_TIMEOUT) as resp:
|
|
957
983
|
self._parts.append(
|
|
958
984
|
{
|
|
959
985
|
"partNumber": part_number,
|
|
@@ -979,7 +1005,7 @@ class InternalMultipartUploadV3:
|
|
|
979
1005
|
},
|
|
980
1006
|
data=json.dumps({"parts": self._parts}).encode(),
|
|
981
1007
|
)
|
|
982
|
-
with
|
|
1008
|
+
with _maybe_retry_request(req):
|
|
983
1009
|
pass
|
|
984
1010
|
except HTTPError as e:
|
|
985
1011
|
raise FileUploadException(
|
|
@@ -1067,7 +1093,6 @@ class InternalMultipartUploadV3:
|
|
|
1067
1093
|
|
|
1068
1094
|
@dataclass
|
|
1069
1095
|
class FalFileRepositoryV2(FalFileRepositoryBase):
|
|
1070
|
-
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
1071
1096
|
def save(
|
|
1072
1097
|
self,
|
|
1073
1098
|
file: FileData,
|
|
@@ -1105,7 +1130,7 @@ class FalFileRepositoryV2(FalFileRepositoryBase):
|
|
|
1105
1130
|
headers=headers,
|
|
1106
1131
|
method="PUT",
|
|
1107
1132
|
)
|
|
1108
|
-
with
|
|
1133
|
+
with _maybe_retry_request(req, timeout=PUT_REQUEST_TIMEOUT) as response:
|
|
1109
1134
|
result = json.load(response)
|
|
1110
1135
|
|
|
1111
1136
|
return result["file_url"]
|
|
@@ -1177,7 +1202,6 @@ class FalCDNFileRepository(FileRepository):
|
|
|
1177
1202
|
object_lifecycle_preference
|
|
1178
1203
|
)
|
|
1179
1204
|
|
|
1180
|
-
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
1181
1205
|
def save(
|
|
1182
1206
|
self,
|
|
1183
1207
|
file: FileData,
|
|
@@ -1199,7 +1223,7 @@ class FalCDNFileRepository(FileRepository):
|
|
|
1199
1223
|
url = os.getenv("FAL_CDN_HOST", _FAL_CDN) + "/files/upload"
|
|
1200
1224
|
request = Request(url, headers=headers, method="POST", data=file.data)
|
|
1201
1225
|
try:
|
|
1202
|
-
with
|
|
1226
|
+
with _maybe_retry_request(request) as response:
|
|
1203
1227
|
result = json.load(response)
|
|
1204
1228
|
except HTTPError as e:
|
|
1205
1229
|
raise FileUploadException(
|
|
@@ -1236,7 +1260,6 @@ class FalFileRepositoryV3(FileRepository):
|
|
|
1236
1260
|
"User-Agent": "fal/0.1.0",
|
|
1237
1261
|
}
|
|
1238
1262
|
|
|
1239
|
-
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
1240
1263
|
def save(
|
|
1241
1264
|
self,
|
|
1242
1265
|
file: FileData,
|
|
@@ -1279,7 +1302,7 @@ class FalFileRepositoryV3(FileRepository):
|
|
|
1279
1302
|
).encode(),
|
|
1280
1303
|
)
|
|
1281
1304
|
try:
|
|
1282
|
-
with
|
|
1305
|
+
with _maybe_retry_request(request) as response:
|
|
1283
1306
|
result = json.load(response)
|
|
1284
1307
|
file_url = result["file_url"]
|
|
1285
1308
|
upload_url = result["upload_url"]
|
|
@@ -1295,7 +1318,7 @@ class FalFileRepositoryV3(FileRepository):
|
|
|
1295
1318
|
data=file.data,
|
|
1296
1319
|
)
|
|
1297
1320
|
try:
|
|
1298
|
-
with
|
|
1321
|
+
with _maybe_retry_request(request, timeout=PUT_REQUEST_TIMEOUT):
|
|
1299
1322
|
pass
|
|
1300
1323
|
except HTTPError as e:
|
|
1301
1324
|
raise FileUploadException(
|
|
@@ -1358,7 +1381,6 @@ class InternalFalFileRepositoryV3(FileRepository):
|
|
|
1358
1381
|
if object_lifecycle_preference:
|
|
1359
1382
|
headers["X-Fal-Object-Lifecycle"] = json.dumps(object_lifecycle_preference)
|
|
1360
1383
|
|
|
1361
|
-
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
1362
1384
|
def save(
|
|
1363
1385
|
self,
|
|
1364
1386
|
file: FileData,
|
|
@@ -1393,7 +1415,7 @@ class InternalFalFileRepositoryV3(FileRepository):
|
|
|
1393
1415
|
url = os.getenv("FAL_CDN_V3_HOST", _FAL_CDN_V3) + "/files/upload"
|
|
1394
1416
|
request = Request(url, headers=headers, method="POST", data=file.data)
|
|
1395
1417
|
try:
|
|
1396
|
-
with
|
|
1418
|
+
with _maybe_retry_request(request) as response:
|
|
1397
1419
|
result = json.load(response)
|
|
1398
1420
|
except HTTPError as e:
|
|
1399
1421
|
raise FileUploadException(
|
|
@@ -2,7 +2,7 @@ import functools
|
|
|
2
2
|
import random
|
|
3
3
|
import time
|
|
4
4
|
import traceback
|
|
5
|
-
from typing import Any, Callable, Literal
|
|
5
|
+
from typing import Any, Callable, Literal, Optional
|
|
6
6
|
|
|
7
7
|
BackoffType = Literal["exponential", "fixed"]
|
|
8
8
|
|
|
@@ -13,6 +13,7 @@ def retry(
|
|
|
13
13
|
max_delay: float = 60.0,
|
|
14
14
|
backoff_type: BackoffType = "exponential",
|
|
15
15
|
jitter: bool = False,
|
|
16
|
+
should_retry: Optional[Callable[[Exception], bool]] = None,
|
|
16
17
|
) -> Callable:
|
|
17
18
|
def decorator(func: Callable) -> Callable:
|
|
18
19
|
@functools.wraps(func)
|
|
@@ -22,6 +23,9 @@ def retry(
|
|
|
22
23
|
try:
|
|
23
24
|
return func(*args, **kwargs)
|
|
24
25
|
except Exception as e:
|
|
26
|
+
if should_retry is not None and not should_retry(e):
|
|
27
|
+
raise
|
|
28
|
+
|
|
25
29
|
retries += 1
|
|
26
30
|
print(f"Retrying {retries} of {max_retries}...")
|
|
27
31
|
if retries == max_retries:
|
|
@@ -5,7 +5,7 @@ import subprocess
|
|
|
5
5
|
import time
|
|
6
6
|
import uuid
|
|
7
7
|
from contextlib import contextmanager, suppress
|
|
8
|
-
from datetime import datetime, timedelta
|
|
8
|
+
from datetime import datetime, timedelta, timezone
|
|
9
9
|
from typing import Generator, List, Tuple
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
@@ -626,7 +626,9 @@ def test_app_client_async(test_sleep_app: str):
|
|
|
626
626
|
# If the logging subsystem is not working for some nodes, this test will flake
|
|
627
627
|
@pytest.mark.flaky(max_runs=10)
|
|
628
628
|
def test_traceback_logs(test_exception_app: AppClient):
|
|
629
|
-
date = (
|
|
629
|
+
date = (
|
|
630
|
+
datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(seconds=1)
|
|
631
|
+
).isoformat()
|
|
630
632
|
|
|
631
633
|
with pytest.raises(AppClientError):
|
|
632
634
|
test_exception_app.fail({})
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest import mock
|
|
4
|
+
from urllib.error import HTTPError
|
|
5
|
+
from urllib.request import Request
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from fal.toolkit.file.providers.fal import _maybe_retry_request
|
|
10
|
+
|
|
11
|
+
RETRY_CODES = [408, 409, 429, 500, 502, 503, 504]
|
|
12
|
+
NON_RETRY_CODES = [400, 401, 403, 404, 422]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MockResponse:
|
|
16
|
+
"""Mock response object that mimics urllib.response.addinfourl"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, data: str = '{"result": "success"}', status: int = 200):
|
|
19
|
+
self.data = data.encode()
|
|
20
|
+
self.status = status
|
|
21
|
+
self.headers = {"Content-Type": "application/json"}
|
|
22
|
+
|
|
23
|
+
def read(self):
|
|
24
|
+
return self.data
|
|
25
|
+
|
|
26
|
+
def __enter__(self):
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_successful_request_no_retry():
|
|
34
|
+
request = Request("https://example.com/test")
|
|
35
|
+
|
|
36
|
+
with mock.patch("fal.toolkit.file.providers.fal._urlopen") as mock_urlopen:
|
|
37
|
+
mock_response = MockResponse()
|
|
38
|
+
mock_context = mock.MagicMock()
|
|
39
|
+
mock_context.__enter__.return_value = mock_response
|
|
40
|
+
mock_context.__exit__.return_value = None
|
|
41
|
+
mock_urlopen.return_value = mock_context
|
|
42
|
+
|
|
43
|
+
with _maybe_retry_request(request) as response:
|
|
44
|
+
assert response == mock_response
|
|
45
|
+
|
|
46
|
+
assert mock_urlopen.call_count == 1
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.parametrize("error_code", RETRY_CODES)
|
|
50
|
+
def test_retry_on_retryable_http_codes(error_code):
|
|
51
|
+
request = Request("https://example.com/test")
|
|
52
|
+
|
|
53
|
+
call_count = 0
|
|
54
|
+
|
|
55
|
+
def mock_urlopen_side_effect(*args, **kwargs):
|
|
56
|
+
nonlocal call_count
|
|
57
|
+
call_count += 1
|
|
58
|
+
|
|
59
|
+
if call_count == 1:
|
|
60
|
+
raise HTTPError(
|
|
61
|
+
url="https://example.com/test",
|
|
62
|
+
code=error_code,
|
|
63
|
+
msg=f"HTTP {error_code} Error",
|
|
64
|
+
hdrs={},
|
|
65
|
+
fp=None,
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
mock_response = MockResponse()
|
|
69
|
+
mock_context = mock.MagicMock()
|
|
70
|
+
mock_context.__enter__.return_value = mock_response
|
|
71
|
+
mock_context.__exit__.return_value = None
|
|
72
|
+
return mock_context
|
|
73
|
+
|
|
74
|
+
with mock.patch(
|
|
75
|
+
"fal.toolkit.file.providers.fal._urlopen", side_effect=mock_urlopen_side_effect
|
|
76
|
+
):
|
|
77
|
+
with mock.patch(
|
|
78
|
+
"fal.toolkit.utils.retry.time.sleep"
|
|
79
|
+
): # Mock sleep to speed up tests
|
|
80
|
+
with _maybe_retry_request(request) as response:
|
|
81
|
+
assert response is not None
|
|
82
|
+
|
|
83
|
+
assert call_count == 2
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.parametrize("error_code", NON_RETRY_CODES)
|
|
87
|
+
def test_no_retry_on_non_retryable_http_codes(error_code):
|
|
88
|
+
request = Request("https://example.com/test")
|
|
89
|
+
|
|
90
|
+
call_count = 0
|
|
91
|
+
|
|
92
|
+
def mock_urlopen_side_effect(*args, **kwargs):
|
|
93
|
+
nonlocal call_count
|
|
94
|
+
call_count += 1
|
|
95
|
+
raise HTTPError(
|
|
96
|
+
url="https://example.com/test",
|
|
97
|
+
code=error_code,
|
|
98
|
+
msg=f"HTTP {error_code} Error",
|
|
99
|
+
hdrs={},
|
|
100
|
+
fp=None,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
with mock.patch(
|
|
104
|
+
"fal.toolkit.file.providers.fal._urlopen", side_effect=mock_urlopen_side_effect
|
|
105
|
+
):
|
|
106
|
+
with pytest.raises(HTTPError) as exc_info:
|
|
107
|
+
with _maybe_retry_request(request):
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
assert exc_info.value.code == error_code
|
|
111
|
+
assert call_count == 1
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_non_http_exception_not_retried():
|
|
115
|
+
"""Test that non-HTTP exceptions are not retried"""
|
|
116
|
+
request = Request("https://example.com/test")
|
|
117
|
+
|
|
118
|
+
call_count = 0
|
|
119
|
+
|
|
120
|
+
def mock_urlopen_side_effect(*args, **kwargs):
|
|
121
|
+
nonlocal call_count
|
|
122
|
+
call_count += 1
|
|
123
|
+
raise ValueError("Network error")
|
|
124
|
+
|
|
125
|
+
with mock.patch(
|
|
126
|
+
"fal.toolkit.file.providers.fal._urlopen", side_effect=mock_urlopen_side_effect
|
|
127
|
+
):
|
|
128
|
+
with pytest.raises(ValueError) as exc_info:
|
|
129
|
+
with _maybe_retry_request(request):
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
assert str(exc_info.value) == "Network error"
|
|
133
|
+
assert call_count == 1
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_max_retries_exhausted_for_retryable_errors():
|
|
137
|
+
"""Test that retries are exhausted after MAX_ATTEMPTS for retryable HTTP errors"""
|
|
138
|
+
request = Request("https://example.com/test")
|
|
139
|
+
|
|
140
|
+
call_count = 0
|
|
141
|
+
|
|
142
|
+
def mock_urlopen_side_effect(*args, **kwargs):
|
|
143
|
+
nonlocal call_count
|
|
144
|
+
call_count += 1
|
|
145
|
+
# Always fail with a retryable error
|
|
146
|
+
raise HTTPError(
|
|
147
|
+
url="https://example.com/test",
|
|
148
|
+
code=500,
|
|
149
|
+
msg="Internal Server Error",
|
|
150
|
+
hdrs={},
|
|
151
|
+
fp=None,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
with mock.patch(
|
|
155
|
+
"fal.toolkit.file.providers.fal._urlopen", side_effect=mock_urlopen_side_effect
|
|
156
|
+
):
|
|
157
|
+
with mock.patch(
|
|
158
|
+
"fal.toolkit.utils.retry.time.sleep"
|
|
159
|
+
): # Mock sleep to speed up tests
|
|
160
|
+
with pytest.raises(HTTPError) as exc_info:
|
|
161
|
+
with _maybe_retry_request(request):
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
assert exc_info.value.code == 500
|
|
165
|
+
assert call_count == 5
|
|
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.19.2 → fal-1.20.0}/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.19.2 → fal-1.20.0}/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.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py
RENAMED
|
File without changes
|
{fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py
RENAMED
|
File without changes
|
{fal-1.19.2 → fal-1.20.0}/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.19.2 → fal-1.20.0}/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.19.2 → fal-1.20.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py
RENAMED
|
File without changes
|