fal 1.49.3__tar.gz → 1.50.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.49.3/fal.egg-info → fal-1.50.0}/PKG-INFO +2 -2
- {fal-1.49.3 → fal-1.50.0/fal.egg-info}/PKG-INFO +2 -2
- {fal-1.49.3 → fal-1.50.0}/fal.egg-info/requires.txt +1 -1
- {fal-1.49.3 → fal-1.50.0}/pyproject.toml +1 -1
- {fal-1.49.3 → fal-1.50.0}/src/fal/_fal_version.py +2 -2
- {fal-1.49.3 → fal-1.50.0}/src/fal/api/client.py +6 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/api/runners.py +10 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/runners.py +140 -5
- {fal-1.49.3 → fal-1.50.0}/src/fal/logging/__init__.py +0 -5
- {fal-1.49.3 → fal-1.50.0}/src/fal/sdk.py +4 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/file/providers/fal.py +33 -10
- {fal-1.49.3 → fal-1.50.0}/tests/test_apps.py +106 -3
- {fal-1.49.3 → fal-1.50.0}/tests/toolkit/file/providers/test_fal_retry.py +40 -1
- {fal-1.49.3 → fal-1.50.0}/.gitignore +0 -0
- {fal-1.49.3 → fal-1.50.0}/Makefile +0 -0
- {fal-1.49.3 → fal-1.50.0}/README.md +0 -0
- {fal-1.49.3 → fal-1.50.0}/docs/conf.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/docs/index.rst +0 -0
- {fal-1.49.3 → fal-1.50.0}/fal.egg-info/SOURCES.txt +0 -0
- {fal-1.49.3 → fal-1.50.0}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.49.3 → fal-1.50.0}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.49.3 → fal-1.50.0}/fal.egg-info/top_level.txt +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/README.md +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.49.3 → fal-1.50.0}/openapi_rest.config.yaml +0 -0
- {fal-1.49.3 → fal-1.50.0}/setup.cfg +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/__main__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/_serialization.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/_version.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/api/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/api/api.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/api/apps.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/api/deploy.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/app.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/apps.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/auth/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/auth/auth0.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/auth/local.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/_utils.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/api.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/apps.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/auth.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/cli_nested_json.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/create.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/debug.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/deploy.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/doctor.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/files.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/keys.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/main.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/parser.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/profile.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/queue.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/run.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/secrets.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/cli/teams.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/config.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/console/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/console/icons.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/console/ux.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/container.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/distributed/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/distributed/utils.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/distributed/worker.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/exceptions/_base.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/exceptions/auth.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/file_sync.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/files.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/flags.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/logging/isolate.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/logging/style.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/logging/trace.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/project.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/py.typed +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/rest_client.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/sync.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/audio/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/audio/audio.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/file/file.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/file/providers/s3.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/kv.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/types.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/utils/endpoint.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/utils/retry.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/utils/setup_utils.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/video/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/toolkit/video/video.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/utils.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/src/fal/workflows.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/assets/cat.png +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/cli/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/cli/test_apps.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/cli/test_auth.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/cli/test_deploy.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/cli/test_keys.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/cli/test_run.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/cli/test_secrets.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/conftest.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/integration_test.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/mainify_package/__init__.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/mainify_package/impl.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/mainify_package/utils.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/mainify_target.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/test_file_sync.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/test_files.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/test_kv.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/test_stability.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/toolkit/file_test.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/toolkit/image_test.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/toolkit/test_types.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/toolkit/utils/retry.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/unit/distributed/test_integration.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/unit/distributed/test_utils.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/unit/distributed/test_worker.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tests/unit/test_app.py +0 -0
- {fal-1.49.3 → fal-1.50.0}/tools/demo_script.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.50.0
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
5
|
Author: Features & Labels <support@fal.ai>
|
|
6
6
|
Requires-Python: >=3.8
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Requires-Dist: isolate[build]<0.21.0,>=0.18.0
|
|
9
|
-
Requires-Dist: isolate-proto<0.
|
|
9
|
+
Requires-Dist: isolate-proto<0.23.0,>=0.22.0
|
|
10
10
|
Requires-Dist: grpcio<2,>=1.64.0
|
|
11
11
|
Requires-Dist: dill==0.3.7
|
|
12
12
|
Requires-Dist: cloudpickle==3.0.0
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.50.0
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
5
|
Author: Features & Labels <support@fal.ai>
|
|
6
6
|
Requires-Python: >=3.8
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Requires-Dist: isolate[build]<0.21.0,>=0.18.0
|
|
9
|
-
Requires-Dist: isolate-proto<0.
|
|
9
|
+
Requires-Dist: isolate-proto<0.23.0,>=0.22.0
|
|
10
10
|
Requires-Dist: grpcio<2,>=1.64.0
|
|
11
11
|
Requires-Dist: dill==0.3.7
|
|
12
12
|
Requires-Dist: cloudpickle==3.0.0
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.
|
|
32
|
-
__version_tuple__ = version_tuple = (1,
|
|
31
|
+
__version__ = version = '1.50.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 50, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -66,6 +66,12 @@ class _RunnersNamespace:
|
|
|
66
66
|
def list(self, *, since=None) -> List[RunnerInfo]:
|
|
67
67
|
return runners_api.list_runners(self.client, since=since)
|
|
68
68
|
|
|
69
|
+
def stop(self, runner_id: str) -> None:
|
|
70
|
+
return runners_api.stop_runner(self.client, runner_id)
|
|
71
|
+
|
|
72
|
+
def kill(self, runner_id: str) -> None:
|
|
73
|
+
return runners_api.kill_runner(self.client, runner_id)
|
|
74
|
+
|
|
69
75
|
|
|
70
76
|
@dataclass
|
|
71
77
|
class SyncServerlessClient:
|
|
@@ -14,3 +14,13 @@ def list_runners(
|
|
|
14
14
|
) -> List[RunnerInfo]:
|
|
15
15
|
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
16
16
|
return conn.list_runners(start_time=since)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def stop_runner(client: SyncServerlessClient, runner_id: str) -> None:
|
|
20
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
21
|
+
conn.stop_runner(runner_id)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def kill_runner(client: SyncServerlessClient, runner_id: str) -> None:
|
|
25
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
26
|
+
conn.kill_runner(runner_id)
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import argparse
|
|
4
|
+
import fcntl
|
|
3
5
|
import json
|
|
6
|
+
import os
|
|
7
|
+
import signal
|
|
8
|
+
import struct
|
|
9
|
+
import sys
|
|
10
|
+
import termios
|
|
11
|
+
import tty
|
|
4
12
|
from collections import deque
|
|
5
13
|
from dataclasses import dataclass
|
|
6
14
|
from datetime import datetime, timedelta, timezone
|
|
7
15
|
from http import HTTPStatus
|
|
16
|
+
from queue import Empty, Queue
|
|
17
|
+
from threading import Thread
|
|
8
18
|
from typing import Iterator, List
|
|
9
19
|
|
|
20
|
+
import grpc
|
|
10
21
|
import httpx
|
|
11
22
|
from httpx_sse import connect_sse
|
|
12
23
|
from rich.console import Console
|
|
@@ -14,7 +25,7 @@ from structlog.typing import EventDict
|
|
|
14
25
|
|
|
15
26
|
from fal.api.client import SyncServerlessClient
|
|
16
27
|
from fal.rest_client import REST_CLIENT
|
|
17
|
-
from fal.sdk import
|
|
28
|
+
from fal.sdk import RunnerInfo, RunnerState
|
|
18
29
|
|
|
19
30
|
from .parser import FalClientParser, SinceAction, get_output_parser
|
|
20
31
|
|
|
@@ -95,12 +106,108 @@ def runners_requests_table(runners: list[RunnerInfo]):
|
|
|
95
106
|
return table
|
|
96
107
|
|
|
97
108
|
|
|
109
|
+
def _get_tty_size():
|
|
110
|
+
"""Get current terminal dimensions."""
|
|
111
|
+
try:
|
|
112
|
+
h, w = struct.unpack("HH", fcntl.ioctl(0, termios.TIOCGWINSZ, b"\0" * 4))[:2]
|
|
113
|
+
return h, w
|
|
114
|
+
except (OSError, ValueError):
|
|
115
|
+
return 24, 80 # Fallback to standard size
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _shell(args):
|
|
119
|
+
"""Execute interactive shell in runner."""
|
|
120
|
+
import isolate_proto
|
|
121
|
+
|
|
122
|
+
client = SyncServerlessClient(host=args.host, team=args.team)
|
|
123
|
+
stub = client._create_host()._connection.stub
|
|
124
|
+
runner_id = args.id
|
|
125
|
+
|
|
126
|
+
# Setup terminal for raw mode
|
|
127
|
+
fd = sys.stdin.fileno()
|
|
128
|
+
old_settings = termios.tcgetattr(fd)
|
|
129
|
+
tty.setraw(fd)
|
|
130
|
+
|
|
131
|
+
# Message queue for stdin data and resize events
|
|
132
|
+
messages = Queue() # type: ignore
|
|
133
|
+
stop_flag = False
|
|
134
|
+
|
|
135
|
+
def handle_resize(*_):
|
|
136
|
+
messages.put(("resize", None))
|
|
137
|
+
|
|
138
|
+
signal.signal(signal.SIGWINCH, handle_resize)
|
|
139
|
+
|
|
140
|
+
def read_stdin():
|
|
141
|
+
"""Read stdin in a background thread."""
|
|
142
|
+
nonlocal stop_flag
|
|
143
|
+
while not stop_flag:
|
|
144
|
+
try:
|
|
145
|
+
data = os.read(fd, 4096)
|
|
146
|
+
if not data:
|
|
147
|
+
break
|
|
148
|
+
messages.put(("data", data))
|
|
149
|
+
except OSError:
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
reader = Thread(target=read_stdin, daemon=True)
|
|
153
|
+
reader.start()
|
|
154
|
+
|
|
155
|
+
def stream_inputs():
|
|
156
|
+
"""Generate input stream for gRPC."""
|
|
157
|
+
# Send initial message with runner_id and terminal size
|
|
158
|
+
msg = isolate_proto.ShellRunnerInput(runner_id=runner_id)
|
|
159
|
+
h, w = _get_tty_size()
|
|
160
|
+
msg.tty_size.height = h
|
|
161
|
+
msg.tty_size.width = w
|
|
162
|
+
yield msg
|
|
163
|
+
|
|
164
|
+
# Stream stdin data and resize events
|
|
165
|
+
while True:
|
|
166
|
+
try:
|
|
167
|
+
msg_type, data = messages.get(timeout=0.1)
|
|
168
|
+
except Empty:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
if msg_type == "data":
|
|
172
|
+
yield isolate_proto.ShellRunnerInput(data=data)
|
|
173
|
+
elif msg_type == "resize":
|
|
174
|
+
msg = isolate_proto.ShellRunnerInput()
|
|
175
|
+
h, w = _get_tty_size()
|
|
176
|
+
msg.tty_size.height = h
|
|
177
|
+
msg.tty_size.width = w
|
|
178
|
+
yield msg
|
|
179
|
+
|
|
180
|
+
exit_code = 1
|
|
181
|
+
try:
|
|
182
|
+
for output in stub.ShellRunner(stream_inputs()):
|
|
183
|
+
if output.HasField("exit_code"):
|
|
184
|
+
exit_code = output.exit_code
|
|
185
|
+
break
|
|
186
|
+
if output.data:
|
|
187
|
+
sys.stdout.buffer.write(output.data)
|
|
188
|
+
sys.stdout.buffer.flush()
|
|
189
|
+
if output.close:
|
|
190
|
+
break
|
|
191
|
+
exit_code = exit_code or 0
|
|
192
|
+
except grpc.RpcError as exc:
|
|
193
|
+
args.console.print(f"\n[red]Connection error:[/] {exc.details()}")
|
|
194
|
+
except Exception as exc:
|
|
195
|
+
args.console.print(f"\n[red]Error:[/] {exc}")
|
|
196
|
+
finally:
|
|
197
|
+
stop_flag = True
|
|
198
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
199
|
+
|
|
200
|
+
return exit_code
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _stop(args):
|
|
204
|
+
client = SyncServerlessClient(host=args.host, team=args.team)
|
|
205
|
+
client.runners.stop(args.id)
|
|
206
|
+
|
|
207
|
+
|
|
98
208
|
def _kill(args):
|
|
99
209
|
client = SyncServerlessClient(host=args.host, team=args.team)
|
|
100
|
-
|
|
101
|
-
client._grpc_host, client._credentials
|
|
102
|
-
).connect() as connection:
|
|
103
|
-
connection.kill_runner(args.id)
|
|
210
|
+
client.runners.kill(args.id)
|
|
104
211
|
|
|
105
212
|
|
|
106
213
|
def _list_json(args, runners: list[RunnerInfo]):
|
|
@@ -161,6 +268,21 @@ def _list(args):
|
|
|
161
268
|
raise AssertionError(f"Invalid output format: {args.output}")
|
|
162
269
|
|
|
163
270
|
|
|
271
|
+
def _add_stop_parser(subparsers, parents):
|
|
272
|
+
stop_help = "Stop a runner gracefully."
|
|
273
|
+
parser = subparsers.add_parser(
|
|
274
|
+
"stop",
|
|
275
|
+
description=stop_help,
|
|
276
|
+
help=stop_help,
|
|
277
|
+
parents=parents,
|
|
278
|
+
)
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"id",
|
|
281
|
+
help="Runner ID.",
|
|
282
|
+
)
|
|
283
|
+
parser.set_defaults(func=_stop)
|
|
284
|
+
|
|
285
|
+
|
|
164
286
|
def _add_kill_parser(subparsers, parents):
|
|
165
287
|
kill_help = "Kill a runner."
|
|
166
288
|
parser = subparsers.add_parser(
|
|
@@ -546,6 +668,17 @@ def _add_logs_parser(subparsers, parents):
|
|
|
546
668
|
parser.set_defaults(func=_logs)
|
|
547
669
|
|
|
548
670
|
|
|
671
|
+
def _add_shell_parser(subparsers, parents):
|
|
672
|
+
"""Add hidden shell command parser."""
|
|
673
|
+
parser = subparsers.add_parser(
|
|
674
|
+
"shell",
|
|
675
|
+
help=argparse.SUPPRESS,
|
|
676
|
+
parents=parents,
|
|
677
|
+
)
|
|
678
|
+
parser.add_argument("id", help="Runner ID.")
|
|
679
|
+
parser.set_defaults(func=_shell)
|
|
680
|
+
|
|
681
|
+
|
|
549
682
|
def add_parser(main_subparsers, parents):
|
|
550
683
|
runners_help = "Manage fal runners."
|
|
551
684
|
parser = main_subparsers.add_parser(
|
|
@@ -563,6 +696,8 @@ def add_parser(main_subparsers, parents):
|
|
|
563
696
|
parser_class=FalClientParser,
|
|
564
697
|
)
|
|
565
698
|
|
|
699
|
+
_add_stop_parser(subparsers, parents)
|
|
566
700
|
_add_kill_parser(subparsers, parents)
|
|
567
701
|
_add_list_parser(subparsers, parents)
|
|
568
702
|
_add_logs_parser(subparsers, parents)
|
|
703
|
+
_add_shell_parser(subparsers, parents)
|
|
@@ -7,11 +7,6 @@ from structlog.typing import EventDict, WrappedLogger
|
|
|
7
7
|
|
|
8
8
|
from .style import LEVEL_STYLES
|
|
9
9
|
|
|
10
|
-
# Unfortunately structlog console processor does not support
|
|
11
|
-
# more general theming as a public API. Consider a PR on the
|
|
12
|
-
# structlog repo to add better support for it.
|
|
13
|
-
structlog.dev._ColorfulStyles.bright = ""
|
|
14
|
-
|
|
15
10
|
|
|
16
11
|
class DebugConsoleLogProcessor:
|
|
17
12
|
"""
|
|
@@ -884,6 +884,10 @@ class FalServerlessConnection:
|
|
|
884
884
|
for secret in response.secrets
|
|
885
885
|
]
|
|
886
886
|
|
|
887
|
+
def stop_runner(self, runner_id: str) -> None:
|
|
888
|
+
request = isolate_proto.StopRunnerRequest(runner_id=runner_id)
|
|
889
|
+
self.stub.StopRunner(request)
|
|
890
|
+
|
|
887
891
|
def kill_runner(self, runner_id: str) -> None:
|
|
888
892
|
request = isolate_proto.KillRunnerRequest(runner_id=runner_id)
|
|
889
893
|
self.stub.KillRunner(request)
|
|
@@ -56,21 +56,44 @@ def _should_retry(exc: Exception) -> bool:
|
|
|
56
56
|
return False
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
class _RetryingRequestContext:
|
|
60
|
+
def __init__(self, request: Request, kwargs: dict[str, Any]):
|
|
61
|
+
self.request = request
|
|
62
|
+
self.kwargs = kwargs
|
|
63
|
+
self._cm: Any | None = None
|
|
64
|
+
self._response: addinfourl | None = None
|
|
65
|
+
|
|
66
|
+
def __enter__(self) -> addinfourl:
|
|
67
|
+
def _enter_once() -> addinfourl:
|
|
68
|
+
# Obtain the original context manager and explicitly enter it
|
|
69
|
+
self._cm = _urlopen(self.request, **self.kwargs)
|
|
70
|
+
return self._cm.__enter__()
|
|
71
|
+
|
|
72
|
+
_enter_with_retry = retry(
|
|
73
|
+
max_retries=MAX_ATTEMPTS,
|
|
74
|
+
base_delay=BASE_DELAY,
|
|
75
|
+
max_delay=MAX_DELAY,
|
|
76
|
+
backoff_type="exponential",
|
|
77
|
+
jitter=True,
|
|
78
|
+
should_retry=_should_retry,
|
|
79
|
+
)(_enter_once)
|
|
80
|
+
|
|
81
|
+
self._response = _enter_with_retry()
|
|
82
|
+
assert self._response is not None
|
|
83
|
+
return self._response
|
|
84
|
+
|
|
85
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
86
|
+
if self._cm is not None:
|
|
87
|
+
return self._cm.__exit__(exc_type, exc_val, exc_tb)
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
|
|
59
91
|
@contextmanager
|
|
60
92
|
def _maybe_retry_request(
|
|
61
93
|
request: Request,
|
|
62
94
|
**kwargs: Any,
|
|
63
95
|
) -> Generator[addinfourl, None, None]:
|
|
64
|
-
|
|
65
|
-
max_retries=MAX_ATTEMPTS,
|
|
66
|
-
base_delay=BASE_DELAY,
|
|
67
|
-
max_delay=MAX_DELAY,
|
|
68
|
-
backoff_type="exponential",
|
|
69
|
-
jitter=True,
|
|
70
|
-
should_retry=_should_retry,
|
|
71
|
-
)(_urlopen)
|
|
72
|
-
|
|
73
|
-
with _urlopen_with_retry(request, **kwargs) as response:
|
|
96
|
+
with _RetryingRequestContext(request, kwargs) as response:
|
|
74
97
|
yield response
|
|
75
98
|
|
|
76
99
|
|
|
@@ -31,6 +31,7 @@ from fal.exceptions import (
|
|
|
31
31
|
)
|
|
32
32
|
from fal.exceptions._cuda import _CUDA_OOM_MESSAGE, _CUDA_OOM_STATUS_CODE
|
|
33
33
|
from fal.rest_client import REST_CLIENT
|
|
34
|
+
from fal.sdk import RunnerState
|
|
34
35
|
from fal.toolkit.utils.endpoint import cancel_on_disconnect
|
|
35
36
|
from fal.workflows import Workflow
|
|
36
37
|
|
|
@@ -1004,7 +1005,70 @@ def test_field_exception_billing(test_exception_app: AppClient):
|
|
|
1004
1005
|
assert not hasattr(response.headers, "x-fal-billable-units")
|
|
1005
1006
|
|
|
1006
1007
|
|
|
1008
|
+
def test_stop_runner(host: api.FalServerlessHost, test_sleep_app: str):
|
|
1009
|
+
def submit_and_wait_for_runner():
|
|
1010
|
+
handle = apps.submit(test_sleep_app, arguments={"wait_time": 1})
|
|
1011
|
+
|
|
1012
|
+
while True:
|
|
1013
|
+
status = handle.status()
|
|
1014
|
+
if isinstance(status, apps.InProgress):
|
|
1015
|
+
break
|
|
1016
|
+
elif isinstance(status, apps.Queued):
|
|
1017
|
+
time.sleep(1)
|
|
1018
|
+
else:
|
|
1019
|
+
raise Exception(f"Failed to start the app: {status}")
|
|
1020
|
+
|
|
1021
|
+
return handle
|
|
1022
|
+
|
|
1023
|
+
# Submit a runner and wait for it to be idle
|
|
1024
|
+
submit_and_wait_for_runner()
|
|
1025
|
+
|
|
1026
|
+
with host._connection as client:
|
|
1027
|
+
timeout = 10
|
|
1028
|
+
start_time = time.time()
|
|
1029
|
+
while True:
|
|
1030
|
+
_, _, app_alias = test_sleep_app.partition("/")
|
|
1031
|
+
runners = client.list_alias_runners(app_alias)
|
|
1032
|
+
assert len(runners) == 1
|
|
1033
|
+
|
|
1034
|
+
if runners[0].in_flight_requests == 0:
|
|
1035
|
+
break
|
|
1036
|
+
elif time.time() - start_time > timeout:
|
|
1037
|
+
raise Exception(f"Timeout waiting for runner to be idle: {runners[0]}")
|
|
1038
|
+
time.sleep(1)
|
|
1039
|
+
|
|
1040
|
+
# Because the runner is not requested to be stopped, it should be reused
|
|
1041
|
+
submit_and_wait_for_runner()
|
|
1042
|
+
|
|
1043
|
+
with host._connection as client:
|
|
1044
|
+
_, _, app_alias = test_sleep_app.partition("/")
|
|
1045
|
+
runners = client.list_alias_runners(app_alias)
|
|
1046
|
+
assert len(runners) == 1
|
|
1047
|
+
|
|
1048
|
+
# Request to stop the runner
|
|
1049
|
+
with host._connection as client:
|
|
1050
|
+
with pytest.raises(Exception) as e:
|
|
1051
|
+
client.stop_runner("1234567890")
|
|
1052
|
+
|
|
1053
|
+
assert "not found" in str(e).lower()
|
|
1054
|
+
|
|
1055
|
+
_, _, app_alias = test_sleep_app.partition("/")
|
|
1056
|
+
runners = client.list_alias_runners(app_alias)
|
|
1057
|
+
assert len(runners) == 1
|
|
1058
|
+
|
|
1059
|
+
client.stop_runner(runners[0].runner_id)
|
|
1060
|
+
|
|
1061
|
+
# Because the runner is requested to be stopped,
|
|
1062
|
+
# it should not be reused and a new runner should be created
|
|
1063
|
+
submit_and_wait_for_runner()
|
|
1064
|
+
|
|
1065
|
+
with host._connection as client:
|
|
1066
|
+
runners = client.list_alias_runners(app_alias)
|
|
1067
|
+
assert len(runners) == 2
|
|
1068
|
+
|
|
1069
|
+
|
|
1007
1070
|
def test_kill_runner(host: api.FalServerlessHost, test_sleep_app: str):
|
|
1071
|
+
# Kill all the replicas of the app that is already running
|
|
1008
1072
|
handle = apps.submit(test_sleep_app, arguments={"wait_time": 10})
|
|
1009
1073
|
|
|
1010
1074
|
while True:
|
|
@@ -1024,13 +1088,52 @@ def test_kill_runner(host: api.FalServerlessHost, test_sleep_app: str):
|
|
|
1024
1088
|
|
|
1025
1089
|
_, _, app_alias = test_sleep_app.partition("/")
|
|
1026
1090
|
runners = client.list_alias_runners(app_alias)
|
|
1027
|
-
|
|
1091
|
+
existing_runners = len(
|
|
1092
|
+
[runner for runner in runners if runner.state == RunnerState.RUNNING]
|
|
1093
|
+
)
|
|
1028
1094
|
|
|
1029
1095
|
client.kill_runner(runners[0].runner_id)
|
|
1030
1096
|
|
|
1031
1097
|
runners = client.list_alias_runners(app_alias)
|
|
1032
|
-
num_runners = len(
|
|
1033
|
-
|
|
1098
|
+
num_runners = len(
|
|
1099
|
+
[runner for runner in runners if runner.state == RunnerState.RUNNING]
|
|
1100
|
+
)
|
|
1101
|
+
assert num_runners == existing_runners - 1
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
def test_shell_runner(host: api.FalServerlessHost, test_sleep_app: str):
|
|
1105
|
+
handle = apps.submit(test_sleep_app, arguments={"wait_time": 30})
|
|
1106
|
+
|
|
1107
|
+
while True:
|
|
1108
|
+
status = handle.status()
|
|
1109
|
+
if isinstance(status, apps.InProgress):
|
|
1110
|
+
break
|
|
1111
|
+
elif isinstance(status, apps.Queued):
|
|
1112
|
+
time.sleep(1)
|
|
1113
|
+
else:
|
|
1114
|
+
raise Exception(f"Failed to start the app: {status}")
|
|
1115
|
+
|
|
1116
|
+
with host._connection as client:
|
|
1117
|
+
_, _, app_alias = test_sleep_app.partition("/")
|
|
1118
|
+
runners = client.list_alias_runners(app_alias)
|
|
1119
|
+
assert len(runners) == 1
|
|
1120
|
+
runner_id = runners[0].runner_id
|
|
1121
|
+
|
|
1122
|
+
proc = subprocess.Popen(
|
|
1123
|
+
["python", "-m", "fal", "runners", "shell", runner_id],
|
|
1124
|
+
stdin=subprocess.PIPE,
|
|
1125
|
+
stdout=subprocess.PIPE,
|
|
1126
|
+
stderr=subprocess.PIPE,
|
|
1127
|
+
)
|
|
1128
|
+
|
|
1129
|
+
try:
|
|
1130
|
+
commands = b"echo 'a' > t.txt\ncat t.txt\nexit\n"
|
|
1131
|
+
stdout, stderr = proc.communicate(input=commands, timeout=10)
|
|
1132
|
+
assert b"a" in stdout, f"Expected 'a' in output, got: {stdout.decode()}"
|
|
1133
|
+
finally:
|
|
1134
|
+
if proc.poll() is None:
|
|
1135
|
+
proc.kill()
|
|
1136
|
+
proc.wait()
|
|
1034
1137
|
|
|
1035
1138
|
|
|
1036
1139
|
def test_container_app_client(test_container_app: str):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from unittest import mock
|
|
4
|
-
from urllib.error import HTTPError
|
|
4
|
+
from urllib.error import HTTPError, URLError
|
|
5
5
|
from urllib.request import Request
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
@@ -163,3 +163,42 @@ def test_max_retries_exhausted_for_retryable_errors():
|
|
|
163
163
|
|
|
164
164
|
assert exc_info.value.code == 500
|
|
165
165
|
assert call_count == 5
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_retry_on_urlerror_during_context_enter():
|
|
169
|
+
request = Request("https://example.com/test")
|
|
170
|
+
|
|
171
|
+
calls = {"open": 0, "enter": 0}
|
|
172
|
+
|
|
173
|
+
class EnterRaisesOnce:
|
|
174
|
+
def __init__(self):
|
|
175
|
+
self.data = b'{"result": "success"}'
|
|
176
|
+
self.headers = {"Content-Type": "application/json"}
|
|
177
|
+
|
|
178
|
+
def read(self):
|
|
179
|
+
return self.data
|
|
180
|
+
|
|
181
|
+
def __enter__(self):
|
|
182
|
+
calls["enter"] += 1
|
|
183
|
+
if calls["enter"] == 1:
|
|
184
|
+
raise URLError("Temporary failure in name resolution")
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
def mock_urlopen_side_effect(*args, **kwargs):
|
|
191
|
+
calls["open"] += 1
|
|
192
|
+
return EnterRaisesOnce()
|
|
193
|
+
|
|
194
|
+
with mock.patch(
|
|
195
|
+
"fal.toolkit.file.providers.fal._urlopen", side_effect=mock_urlopen_side_effect
|
|
196
|
+
):
|
|
197
|
+
with mock.patch("fal.toolkit.utils.retry.time.sleep"):
|
|
198
|
+
# Expect success after a retry (second __enter__ returns normally)
|
|
199
|
+
with _maybe_retry_request(request) as response:
|
|
200
|
+
assert response is not None
|
|
201
|
+
|
|
202
|
+
# Should have attempted to open at least twice due to retry
|
|
203
|
+
assert calls["open"] >= 2
|
|
204
|
+
assert calls["enter"] >= 2
|
|
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.49.3 → fal-1.50.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.49.3 → fal-1.50.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.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py
RENAMED
|
File without changes
|
{fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py
RENAMED
|
File without changes
|
{fal-1.49.3 → fal-1.50.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.49.3 → fal-1.50.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.49.3 → fal-1.50.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|