fal 1.11.2__tar.gz → 1.11.4__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.11.2/fal.egg-info → fal-1.11.4}/PKG-INFO +4 -4
- {fal-1.11.2 → fal-1.11.4/fal.egg-info}/PKG-INFO +4 -4
- {fal-1.11.2 → fal-1.11.4}/fal.egg-info/requires.txt +3 -3
- {fal-1.11.2 → fal-1.11.4}/pyproject.toml +6 -3
- {fal-1.11.2 → fal-1.11.4}/src/fal/_fal_version.py +2 -2
- {fal-1.11.2 → fal-1.11.4}/src/fal/api.py +4 -1
- {fal-1.11.2 → fal-1.11.4}/src/fal/app.py +1 -1
- {fal-1.11.2 → fal-1.11.4}/src/fal/auth/__init__.py +2 -3
- {fal-1.11.2 → fal-1.11.4}/src/fal/auth/auth0.py +6 -6
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/apps.py +9 -49
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/deploy.py +1 -1
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/main.py +1 -1
- fal-1.11.4/src/fal/cli/runners.py +133 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/config.py +1 -1
- {fal-1.11.2 → fal-1.11.4}/src/fal/sdk.py +11 -1
- {fal-1.11.2 → fal-1.11.4}/tests/test_apps.py +51 -35
- {fal-1.11.2 → fal-1.11.4}/tests/test_stability.py +4 -2
- fal-1.11.2/src/fal/cli/runners.py +0 -43
- {fal-1.11.2 → fal-1.11.4}/.gitignore +0 -0
- {fal-1.11.2 → fal-1.11.4}/Makefile +0 -0
- {fal-1.11.2 → fal-1.11.4}/README.md +0 -0
- {fal-1.11.2 → fal-1.11.4}/docs/conf.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/docs/index.rst +0 -0
- {fal-1.11.2 → fal-1.11.4}/fal.egg-info/SOURCES.txt +0 -0
- {fal-1.11.2 → fal-1.11.4}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.11.2 → fal-1.11.4}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.11.2 → fal-1.11.4}/fal.egg-info/top_level.txt +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/README.md +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.11.2 → fal-1.11.4}/openapi_rest.config.yaml +0 -0
- {fal-1.11.2 → fal-1.11.4}/setup.cfg +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/__main__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/_serialization.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/_version.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/apps.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/auth/local.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/_utils.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/api.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/auth.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/cli_nested_json.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/create.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/debug.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/doctor.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/keys.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/parser.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/profile.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/run.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/secrets.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/cli/teams.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/console/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/console/icons.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/console/ux.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/container.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/exceptions/_base.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/exceptions/auth.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/files.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/flags.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/logging/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/logging/isolate.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/logging/style.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/logging/trace.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/logging/user.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/py.typed +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/rest_client.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/sync.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/file.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/providers/fal.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/providers/s3.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/types.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/toolkit/utils/retry.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/utils.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/src/fal/workflows.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/assets/cat.png +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/cli/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/cli/test_apps.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/cli/test_auth.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/cli/test_deploy.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/cli/test_keys.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/cli/test_run.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/cli/test_secrets.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/conftest.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/integration_test.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/mainify_package/__init__.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/mainify_package/impl.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/mainify_package/utils.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/mainify_target.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/toolkit/file_test.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/toolkit/image_test.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/toolkit/test_types.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tests/toolkit/utils/retry.py +0 -0
- {fal-1.11.2 → fal-1.11.4}/tools/demo_script.py +0 -0
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.11.
|
|
3
|
+
Version: 1.11.4
|
|
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.17.0,>=0.16.1
|
|
9
|
-
Requires-Dist: isolate-proto<0.8.0,>=0.7.
|
|
9
|
+
Requires-Dist: isolate-proto<0.8.0,>=0.7.2
|
|
10
10
|
Requires-Dist: grpcio==1.64.0
|
|
11
11
|
Requires-Dist: dill==0.3.7
|
|
12
12
|
Requires-Dist: cloudpickle==3.0.0
|
|
13
13
|
Requires-Dist: typing-extensions<5,>=4.7.1
|
|
14
|
-
Requires-Dist: click<9,>=8.1.3
|
|
15
14
|
Requires-Dist: structlog<23,>=22.3.0
|
|
16
15
|
Requires-Dist: opentelemetry-api<2,>=1.15.0
|
|
17
16
|
Requires-Dist: opentelemetry-sdk<2,>=1.15.0
|
|
@@ -22,7 +21,7 @@ Requires-Dist: rich<14,>=13.3.2
|
|
|
22
21
|
Requires-Dist: rich_argparse
|
|
23
22
|
Requires-Dist: packaging>=21.3
|
|
24
23
|
Requires-Dist: pathspec<1,>=0.11.1
|
|
25
|
-
Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<
|
|
24
|
+
Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
|
|
26
25
|
Requires-Dist: structlog>=22.0
|
|
27
26
|
Requires-Dist: fastapi<1,>=0.99.1
|
|
28
27
|
Requires-Dist: starlette-exporter>=0.21.0
|
|
@@ -47,6 +46,7 @@ Provides-Extra: test
|
|
|
47
46
|
Requires-Dist: pytest<8; extra == "test"
|
|
48
47
|
Requires-Dist: pytest-asyncio; extra == "test"
|
|
49
48
|
Requires-Dist: pytest-xdist; extra == "test"
|
|
49
|
+
Requires-Dist: pytest-timeout; extra == "test"
|
|
50
50
|
Requires-Dist: flaky; extra == "test"
|
|
51
51
|
Requires-Dist: boto3; extra == "test"
|
|
52
52
|
Provides-Extra: dev
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.11.
|
|
3
|
+
Version: 1.11.4
|
|
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.17.0,>=0.16.1
|
|
9
|
-
Requires-Dist: isolate-proto<0.8.0,>=0.7.
|
|
9
|
+
Requires-Dist: isolate-proto<0.8.0,>=0.7.2
|
|
10
10
|
Requires-Dist: grpcio==1.64.0
|
|
11
11
|
Requires-Dist: dill==0.3.7
|
|
12
12
|
Requires-Dist: cloudpickle==3.0.0
|
|
13
13
|
Requires-Dist: typing-extensions<5,>=4.7.1
|
|
14
|
-
Requires-Dist: click<9,>=8.1.3
|
|
15
14
|
Requires-Dist: structlog<23,>=22.3.0
|
|
16
15
|
Requires-Dist: opentelemetry-api<2,>=1.15.0
|
|
17
16
|
Requires-Dist: opentelemetry-sdk<2,>=1.15.0
|
|
@@ -22,7 +21,7 @@ Requires-Dist: rich<14,>=13.3.2
|
|
|
22
21
|
Requires-Dist: rich_argparse
|
|
23
22
|
Requires-Dist: packaging>=21.3
|
|
24
23
|
Requires-Dist: pathspec<1,>=0.11.1
|
|
25
|
-
Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<
|
|
24
|
+
Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
|
|
26
25
|
Requires-Dist: structlog>=22.0
|
|
27
26
|
Requires-Dist: fastapi<1,>=0.99.1
|
|
28
27
|
Requires-Dist: starlette-exporter>=0.21.0
|
|
@@ -47,6 +46,7 @@ Provides-Extra: test
|
|
|
47
46
|
Requires-Dist: pytest<8; extra == "test"
|
|
48
47
|
Requires-Dist: pytest-asyncio; extra == "test"
|
|
49
48
|
Requires-Dist: pytest-xdist; extra == "test"
|
|
49
|
+
Requires-Dist: pytest-timeout; extra == "test"
|
|
50
50
|
Requires-Dist: flaky; extra == "test"
|
|
51
51
|
Requires-Dist: boto3; extra == "test"
|
|
52
52
|
Provides-Extra: dev
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
isolate[build]<0.17.0,>=0.16.1
|
|
2
|
-
isolate-proto<0.8.0,>=0.7.
|
|
2
|
+
isolate-proto<0.8.0,>=0.7.2
|
|
3
3
|
grpcio==1.64.0
|
|
4
4
|
dill==0.3.7
|
|
5
5
|
cloudpickle==3.0.0
|
|
6
6
|
typing-extensions<5,>=4.7.1
|
|
7
|
-
click<9,>=8.1.3
|
|
8
7
|
structlog<23,>=22.3.0
|
|
9
8
|
opentelemetry-api<2,>=1.15.0
|
|
10
9
|
opentelemetry-sdk<2,>=1.15.0
|
|
@@ -15,7 +14,7 @@ rich<14,>=13.3.2
|
|
|
15
14
|
rich_argparse
|
|
16
15
|
packaging>=21.3
|
|
17
16
|
pathspec<1,>=0.11.1
|
|
18
|
-
pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<
|
|
17
|
+
pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
|
|
19
18
|
structlog>=22.0
|
|
20
19
|
fastapi<1,>=0.99.1
|
|
21
20
|
starlette-exporter>=0.21.0
|
|
@@ -48,5 +47,6 @@ sphinx-autodoc-typehints
|
|
|
48
47
|
pytest<8
|
|
49
48
|
pytest-asyncio
|
|
50
49
|
pytest-xdist
|
|
50
|
+
pytest-timeout
|
|
51
51
|
flaky
|
|
52
52
|
boto3
|
|
@@ -23,12 +23,11 @@ readme = "README.md"
|
|
|
23
23
|
requires-python = ">=3.8"
|
|
24
24
|
dependencies = [
|
|
25
25
|
"isolate[build]>=0.16.1,<0.17.0",
|
|
26
|
-
"isolate-proto>=0.7.
|
|
26
|
+
"isolate-proto>=0.7.2,<0.8.0",
|
|
27
27
|
"grpcio==1.64.0",
|
|
28
28
|
"dill==0.3.7",
|
|
29
29
|
"cloudpickle==3.0.0",
|
|
30
30
|
"typing-extensions>=4.7.1,<5",
|
|
31
|
-
"click>=8.1.3,<9",
|
|
32
31
|
"structlog>=22.3.0,<23",
|
|
33
32
|
"opentelemetry-api>=1.15.0,<2",
|
|
34
33
|
"opentelemetry-sdk>=1.15.0,<2",
|
|
@@ -39,7 +38,7 @@ dependencies = [
|
|
|
39
38
|
"rich_argparse",
|
|
40
39
|
"packaging>=21.3",
|
|
41
40
|
"pathspec>=0.11.1,<1",
|
|
42
|
-
"pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<
|
|
41
|
+
"pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11",
|
|
43
42
|
# serve=True dependencies
|
|
44
43
|
"structlog>=22.0",
|
|
45
44
|
"fastapi>=0.99.1,<1",
|
|
@@ -71,6 +70,7 @@ test = [
|
|
|
71
70
|
"pytest<8",
|
|
72
71
|
"pytest-asyncio",
|
|
73
72
|
"pytest-xdist",
|
|
73
|
+
"pytest-timeout",
|
|
74
74
|
"flaky",
|
|
75
75
|
"boto3",
|
|
76
76
|
]
|
|
@@ -93,3 +93,6 @@ known-first-party = ["fal"]
|
|
|
93
93
|
|
|
94
94
|
[tool.ruff.lint.pyupgrade]
|
|
95
95
|
keep-runtime-typing = true
|
|
96
|
+
|
|
97
|
+
[tool.pytest.ini_options]
|
|
98
|
+
timeout = 300
|
|
@@ -1088,7 +1088,10 @@ class BaseServable:
|
|
|
1088
1088
|
type(exc), exc, exc.__traceback__
|
|
1089
1089
|
)
|
|
1090
1090
|
|
|
1091
|
-
print(
|
|
1091
|
+
print(
|
|
1092
|
+
json.dumps({"traceback": "".join(formatted_exception[::-1])}),
|
|
1093
|
+
flush=True,
|
|
1094
|
+
)
|
|
1092
1095
|
|
|
1093
1096
|
if _is_cuda_oom_exception(exc):
|
|
1094
1097
|
return await cuda_out_of_memory_exception_handler(
|
|
@@ -268,7 +268,7 @@ class App(fal.api.BaseServable):
|
|
|
268
268
|
"keep_alive": 60,
|
|
269
269
|
}
|
|
270
270
|
app_name: ClassVar[str]
|
|
271
|
-
app_auth: ClassVar[Literal["private", "public", "shared"]] =
|
|
271
|
+
app_auth: ClassVar[Literal["private", "public", "shared", None]] = None
|
|
272
272
|
request_timeout: ClassVar[int | None] = None
|
|
273
273
|
startup_timeout: ClassVar[int | None] = None
|
|
274
274
|
|
|
@@ -5,12 +5,11 @@ from dataclasses import dataclass, field
|
|
|
5
5
|
from threading import Lock
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
|
-
import click
|
|
9
|
-
|
|
10
8
|
from fal.auth import auth0, local
|
|
11
9
|
from fal.config import Config
|
|
12
10
|
from fal.console import console
|
|
13
11
|
from fal.console.icons import CHECK_ICON
|
|
12
|
+
from fal.exceptions import FalServerlessException
|
|
14
13
|
from fal.exceptions.auth import UnauthenticatedException
|
|
15
14
|
|
|
16
15
|
|
|
@@ -85,7 +84,7 @@ def login():
|
|
|
85
84
|
def logout():
|
|
86
85
|
refresh_token, _ = local.load_token()
|
|
87
86
|
if refresh_token is None:
|
|
88
|
-
raise
|
|
87
|
+
raise FalServerlessException("You're not logged in")
|
|
89
88
|
auth0.revoke(refresh_token)
|
|
90
89
|
with local.lock_token():
|
|
91
90
|
local.delete_token()
|
|
@@ -4,12 +4,12 @@ import functools
|
|
|
4
4
|
import time
|
|
5
5
|
import warnings
|
|
6
6
|
|
|
7
|
-
import click
|
|
8
7
|
import httpx
|
|
9
8
|
|
|
10
9
|
from fal.console import console
|
|
11
10
|
from fal.console.icons import CHECK_ICON
|
|
12
11
|
from fal.console.ux import maybe_open_browser_tab
|
|
12
|
+
from fal.exceptions import FalServerlessException
|
|
13
13
|
|
|
14
14
|
WEBSITE_URL = "https://fal.ai"
|
|
15
15
|
|
|
@@ -55,7 +55,7 @@ def login() -> dict:
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
if device_code_response.status_code != 200:
|
|
58
|
-
raise
|
|
58
|
+
raise FalServerlessException("Error generating the device code")
|
|
59
59
|
|
|
60
60
|
device_code_data = device_code_response.json()
|
|
61
61
|
device_user_code = device_code_data["user_code"]
|
|
@@ -92,7 +92,7 @@ def login() -> dict:
|
|
|
92
92
|
|
|
93
93
|
elif token_data["error"] not in ("authorization_pending", "slow_down"):
|
|
94
94
|
status.update(spinner=None)
|
|
95
|
-
raise
|
|
95
|
+
raise FalServerlessException(token_data["error_description"])
|
|
96
96
|
|
|
97
97
|
else:
|
|
98
98
|
time.sleep(device_code_data["interval"])
|
|
@@ -115,7 +115,7 @@ def refresh(token: str) -> dict:
|
|
|
115
115
|
|
|
116
116
|
return token_data
|
|
117
117
|
else:
|
|
118
|
-
raise
|
|
118
|
+
raise FalServerlessException(token_data["error_description"])
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
def revoke(token: str):
|
|
@@ -130,7 +130,7 @@ def revoke(token: str):
|
|
|
130
130
|
|
|
131
131
|
if token_response.status_code != 200:
|
|
132
132
|
token_data = token_response.json()
|
|
133
|
-
raise
|
|
133
|
+
raise FalServerlessException(token_data["error_description"])
|
|
134
134
|
|
|
135
135
|
_open_browser(logout_url(WEBSITE_URL), None)
|
|
136
136
|
|
|
@@ -142,7 +142,7 @@ def get_user_info(bearer_token: str) -> dict:
|
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
if userinfo_response.status_code != 200:
|
|
145
|
-
raise
|
|
145
|
+
raise FalServerlessException(userinfo_response.content.decode("utf-8"))
|
|
146
146
|
|
|
147
147
|
return userinfo_response.json()
|
|
148
148
|
|
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
import fal.cli.runners as runners
|
|
6
|
+
|
|
5
7
|
from ._utils import get_client
|
|
6
8
|
from .parser import FalClientParser
|
|
7
9
|
|
|
@@ -268,59 +270,17 @@ def _add_set_rev_parser(subparsers, parents):
|
|
|
268
270
|
|
|
269
271
|
|
|
270
272
|
def _runners(args):
|
|
271
|
-
from rich.table import Table
|
|
272
|
-
|
|
273
273
|
client = get_client(args.host, args.team)
|
|
274
274
|
with client.connect() as connection:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
table = Table()
|
|
278
|
-
table.add_column("Runner ID")
|
|
279
|
-
table.add_column("In Flight Requests")
|
|
280
|
-
table.add_column("Missing Leases")
|
|
281
|
-
table.add_column("Expires In")
|
|
282
|
-
table.add_column("Uptime")
|
|
283
|
-
|
|
284
|
-
for runner in runners:
|
|
285
|
-
num_leases_with_request = len(
|
|
286
|
-
[
|
|
287
|
-
lease
|
|
288
|
-
for lease in runner.external_metadata.get("leases", [])
|
|
289
|
-
if lease.get("request_id") is not None
|
|
290
|
-
]
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
table.add_row(
|
|
294
|
-
runner.runner_id,
|
|
295
|
-
str(runner.in_flight_requests),
|
|
296
|
-
str(runner.in_flight_requests - num_leases_with_request),
|
|
297
|
-
(
|
|
298
|
-
"N/A (active)"
|
|
299
|
-
if runner.expiration_countdown is None
|
|
300
|
-
else f"{runner.expiration_countdown}s"
|
|
301
|
-
),
|
|
302
|
-
f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
args.console.print(f"Runners: {len(runners)}")
|
|
306
|
-
args.console.print(table)
|
|
307
|
-
|
|
308
|
-
requests_table = Table()
|
|
309
|
-
requests_table.add_column("Runner ID")
|
|
310
|
-
requests_table.add_column("Request ID")
|
|
311
|
-
requests_table.add_column("Caller ID")
|
|
312
|
-
|
|
313
|
-
for runner in runners:
|
|
314
|
-
for lease in runner.external_metadata.get("leases", []):
|
|
315
|
-
if not (req_id := lease.get("request_id")):
|
|
316
|
-
continue
|
|
275
|
+
alias_runners = connection.list_alias_runners(alias=args.app_name)
|
|
317
276
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
277
|
+
runners_table = runners.runners_table(alias_runners)
|
|
278
|
+
args.console.print(f"Runners: {len(alias_runners)}")
|
|
279
|
+
# Drop the alias column, which is the first column
|
|
280
|
+
runners_table.columns.pop(0)
|
|
281
|
+
args.console.print(runners_table)
|
|
323
282
|
|
|
283
|
+
requests_table = runners.runners_requests_table(alias_runners)
|
|
324
284
|
args.console.print(f"Requests: {len(requests_table.rows)}")
|
|
325
285
|
args.console.print(requests_table)
|
|
326
286
|
|
|
@@ -97,7 +97,7 @@ def _deploy_from_reference(
|
|
|
97
97
|
)
|
|
98
98
|
isolated_function = loaded.function
|
|
99
99
|
app_name = app_name or loaded.app_name # type: ignore
|
|
100
|
-
app_auth = auth or loaded.app_auth
|
|
100
|
+
app_auth = auth or loaded.app_auth
|
|
101
101
|
deployment_strategy = deployment_strategy or "recreate"
|
|
102
102
|
|
|
103
103
|
app_id = host.register(
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from fal.sdk import RunnerInfo
|
|
6
|
+
|
|
7
|
+
from ._utils import get_client
|
|
8
|
+
from .parser import FalClientParser
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def runners_table(runners: List[RunnerInfo]):
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
table = Table()
|
|
15
|
+
table.add_column("Alias")
|
|
16
|
+
table.add_column("Runner ID")
|
|
17
|
+
table.add_column("In Flight Requests")
|
|
18
|
+
table.add_column("Missing Leases")
|
|
19
|
+
table.add_column("Expires In")
|
|
20
|
+
table.add_column("Uptime")
|
|
21
|
+
table.add_column("Revision")
|
|
22
|
+
|
|
23
|
+
for runner in runners:
|
|
24
|
+
num_leases_with_request = len(
|
|
25
|
+
[
|
|
26
|
+
lease
|
|
27
|
+
for lease in runner.external_metadata.get("leases", [])
|
|
28
|
+
if lease.get("request_id") is not None
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
table.add_row(
|
|
33
|
+
runner.alias,
|
|
34
|
+
runner.runner_id,
|
|
35
|
+
str(runner.in_flight_requests),
|
|
36
|
+
str(runner.in_flight_requests - num_leases_with_request),
|
|
37
|
+
(
|
|
38
|
+
"N/A (active)"
|
|
39
|
+
if runner.expiration_countdown is None
|
|
40
|
+
else f"{runner.expiration_countdown}s"
|
|
41
|
+
),
|
|
42
|
+
f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
|
|
43
|
+
runner.revision,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return table
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def runners_requests_table(runners: list[RunnerInfo]):
|
|
50
|
+
from rich.table import Table
|
|
51
|
+
|
|
52
|
+
table = Table()
|
|
53
|
+
table.add_column("Runner ID")
|
|
54
|
+
table.add_column("Request ID")
|
|
55
|
+
table.add_column("Caller ID")
|
|
56
|
+
|
|
57
|
+
for runner in runners:
|
|
58
|
+
for lease in runner.external_metadata.get("leases", []):
|
|
59
|
+
if not (req_id := lease.get("request_id")):
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
table.add_row(
|
|
63
|
+
runner.runner_id,
|
|
64
|
+
req_id,
|
|
65
|
+
lease.get("caller_user_id") or "",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return table
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _kill(args):
|
|
72
|
+
client = get_client(args.host, args.team)
|
|
73
|
+
with client.connect() as connection:
|
|
74
|
+
connection.kill_runner(args.id)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _list(args):
|
|
78
|
+
client = get_client(args.host, args.team)
|
|
79
|
+
with client.connect() as connection:
|
|
80
|
+
runners = connection.list_runners()
|
|
81
|
+
args.console.print(f"Runners: {len(runners)}")
|
|
82
|
+
args.console.print(runners_table(runners))
|
|
83
|
+
|
|
84
|
+
requests_table = runners_requests_table(runners)
|
|
85
|
+
args.console.print(f"Requests: {len(requests_table.rows)}")
|
|
86
|
+
args.console.print(requests_table)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _add_kill_parser(subparsers, parents):
|
|
90
|
+
kill_help = "Kill a runner."
|
|
91
|
+
parser = subparsers.add_parser(
|
|
92
|
+
"kill",
|
|
93
|
+
description=kill_help,
|
|
94
|
+
help=kill_help,
|
|
95
|
+
parents=parents,
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"id",
|
|
99
|
+
help="Runner ID.",
|
|
100
|
+
)
|
|
101
|
+
parser.set_defaults(func=_kill)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _add_list_parser(subparsers, parents):
|
|
105
|
+
list_help = "List runners."
|
|
106
|
+
parser = subparsers.add_parser(
|
|
107
|
+
"list",
|
|
108
|
+
description=list_help,
|
|
109
|
+
help=list_help,
|
|
110
|
+
parents=parents,
|
|
111
|
+
)
|
|
112
|
+
parser.set_defaults(func=_list)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def add_parser(main_subparsers, parents):
|
|
116
|
+
runners_help = "Manage fal runners."
|
|
117
|
+
parser = main_subparsers.add_parser(
|
|
118
|
+
"runners",
|
|
119
|
+
description=runners_help,
|
|
120
|
+
help=runners_help,
|
|
121
|
+
parents=parents,
|
|
122
|
+
aliases=["machine"], # backwards compatibility
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
subparsers = parser.add_subparsers(
|
|
126
|
+
title="Commands",
|
|
127
|
+
metavar="command",
|
|
128
|
+
required=True,
|
|
129
|
+
parser_class=FalClientParser,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
_add_kill_parser(subparsers, parents)
|
|
133
|
+
_add_list_parser(subparsers, parents)
|
|
@@ -72,7 +72,7 @@ class Config:
|
|
|
72
72
|
if not self.profile:
|
|
73
73
|
raise ValueError("No profile set.")
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
self._config.get(self.profile, {}).pop(key, None)
|
|
76
76
|
|
|
77
77
|
def get_internal(self, key: str) -> Optional[str]:
|
|
78
78
|
if SETTINGS_SECTION not in self._config:
|
|
@@ -246,6 +246,8 @@ class RunnerInfo:
|
|
|
246
246
|
expiration_countdown: Optional[int]
|
|
247
247
|
uptime: timedelta
|
|
248
248
|
external_metadata: dict[str, Any]
|
|
249
|
+
revision: str
|
|
250
|
+
alias: str
|
|
249
251
|
|
|
250
252
|
|
|
251
253
|
@dataclass
|
|
@@ -392,6 +394,8 @@ def _from_grpc_runner_info(message: isolate_proto.RunnerInfo) -> RunnerInfo:
|
|
|
392
394
|
),
|
|
393
395
|
uptime=timedelta(seconds=message.uptime),
|
|
394
396
|
external_metadata=external_metadata,
|
|
397
|
+
revision=message.revision,
|
|
398
|
+
alias=message.alias,
|
|
395
399
|
)
|
|
396
400
|
|
|
397
401
|
|
|
@@ -571,11 +575,12 @@ class FalServerlessConnection:
|
|
|
571
575
|
else:
|
|
572
576
|
wrapped_requirements = None
|
|
573
577
|
|
|
578
|
+
auth_mode = None
|
|
574
579
|
if application_auth_mode == "public":
|
|
575
580
|
auth_mode = isolate_proto.ApplicationAuthMode.PUBLIC
|
|
576
581
|
elif application_auth_mode == "shared":
|
|
577
582
|
auth_mode = isolate_proto.ApplicationAuthMode.SHARED
|
|
578
|
-
|
|
583
|
+
elif application_auth_mode == "private":
|
|
579
584
|
auth_mode = isolate_proto.ApplicationAuthMode.PRIVATE
|
|
580
585
|
|
|
581
586
|
struct_metadata = None
|
|
@@ -750,3 +755,8 @@ class FalServerlessConnection:
|
|
|
750
755
|
def kill_runner(self, runner_id: str) -> None:
|
|
751
756
|
request = isolate_proto.KillRunnerRequest(runner_id=runner_id)
|
|
752
757
|
self.stub.KillRunner(request)
|
|
758
|
+
|
|
759
|
+
def list_runners(self) -> list[RunnerInfo]:
|
|
760
|
+
request = isolate_proto.ListRunnersRequest()
|
|
761
|
+
response = self.stub.ListRunners(request)
|
|
762
|
+
return [from_grpc(runner) for runner in response.runners]
|
|
@@ -576,6 +576,41 @@ def test_app_client_async():
|
|
|
576
576
|
assert result == {"slept": True}
|
|
577
577
|
|
|
578
578
|
|
|
579
|
+
# If the logging subsystem is not working for some nodes, this test will flake
|
|
580
|
+
@pytest.mark.flaky(max_runs=10)
|
|
581
|
+
def test_traceback_logs(test_exception_app: AppClient):
|
|
582
|
+
date = (datetime.utcnow() - timedelta(seconds=1)).isoformat()
|
|
583
|
+
|
|
584
|
+
with pytest.raises(AppClientError):
|
|
585
|
+
test_exception_app.fail({})
|
|
586
|
+
|
|
587
|
+
with httpx.Client(
|
|
588
|
+
base_url=REST_CLIENT.base_url,
|
|
589
|
+
headers=REST_CLIENT.get_headers(),
|
|
590
|
+
timeout=300,
|
|
591
|
+
) as client:
|
|
592
|
+
# Give some time for logs to propagate through the logging subsystem.
|
|
593
|
+
for _ in range(10):
|
|
594
|
+
time.sleep(2)
|
|
595
|
+
response = client.get(
|
|
596
|
+
REST_CLIENT.base_url + f"/logs/?traceback=true&since={date}"
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
logs = response.json()
|
|
600
|
+
if len(logs) > 0:
|
|
601
|
+
break
|
|
602
|
+
|
|
603
|
+
assert len(logs) > 0
|
|
604
|
+
for log in logs:
|
|
605
|
+
assert log["message"].count("\n") > 1, "Logs should be multi-line"
|
|
606
|
+
assert (
|
|
607
|
+
'{"traceback":' not in log["message"]
|
|
608
|
+
), "Logs should not be JSON-wrapped"
|
|
609
|
+
assert (
|
|
610
|
+
"this app is designed to fail" in log["message"]
|
|
611
|
+
), "Logs should contain the traceback message"
|
|
612
|
+
|
|
613
|
+
|
|
579
614
|
def test_app_openapi_spec_metadata(test_app: str, request: pytest.FixtureRequest):
|
|
580
615
|
user_id, _, app_id = test_app.partition("/")
|
|
581
616
|
res = app_metadata.sync_detailed(
|
|
@@ -617,6 +652,18 @@ def test_404_response(test_app: str, request: pytest.FixtureRequest):
|
|
|
617
652
|
apps.run(test_app, path="/other", arguments={"lhs": 1, "rhs": 2})
|
|
618
653
|
|
|
619
654
|
|
|
655
|
+
def test_app_no_auth():
|
|
656
|
+
# This will just pass for users with shared apps access
|
|
657
|
+
app_alias = str(uuid.uuid4()) + "-alias"
|
|
658
|
+
with pytest.raises(api.FalServerlessError, match="Must specify auth_mode"):
|
|
659
|
+
addition_app.host.register(
|
|
660
|
+
func=addition_app.func,
|
|
661
|
+
options=addition_app.options,
|
|
662
|
+
# random enough
|
|
663
|
+
application_name=app_alias,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
|
|
620
667
|
def test_app_deploy_scale(aliased_app: Tuple[str, str]):
|
|
621
668
|
from dataclasses import replace
|
|
622
669
|
|
|
@@ -658,6 +705,8 @@ def test_app_deploy_scale(aliased_app: Tuple[str, str]):
|
|
|
658
705
|
assert found.max_multiplexing == 30
|
|
659
706
|
|
|
660
707
|
|
|
708
|
+
# List aliases is taking long
|
|
709
|
+
@pytest.mark.timeout(600)
|
|
661
710
|
def test_app_update_app(aliased_app: Tuple[str, str]):
|
|
662
711
|
app_revision, app_alias = aliased_app
|
|
663
712
|
|
|
@@ -708,6 +757,8 @@ def test_app_update_app(aliased_app: Tuple[str, str]):
|
|
|
708
757
|
assert res.max_multiplexing == new_max_multiplexing
|
|
709
758
|
|
|
710
759
|
|
|
760
|
+
# List aliases is taking long
|
|
761
|
+
@pytest.mark.timeout(600)
|
|
711
762
|
def test_app_set_delete_alias(aliased_app: Tuple[str, str]):
|
|
712
763
|
app_revision, app_alias = aliased_app
|
|
713
764
|
|
|
@@ -831,41 +882,6 @@ def test_workflows(test_app: str):
|
|
|
831
882
|
assert data["result"] == 10
|
|
832
883
|
|
|
833
884
|
|
|
834
|
-
# If the logging subsystem is not working for some nodes, this test will flake
|
|
835
|
-
@pytest.mark.flaky(max_runs=5)
|
|
836
|
-
def test_traceback_logs(test_exception_app: AppClient):
|
|
837
|
-
date = (datetime.utcnow() - timedelta(seconds=1)).isoformat()
|
|
838
|
-
|
|
839
|
-
with pytest.raises(AppClientError):
|
|
840
|
-
test_exception_app.fail({})
|
|
841
|
-
|
|
842
|
-
with httpx.Client(
|
|
843
|
-
base_url=REST_CLIENT.base_url,
|
|
844
|
-
headers=REST_CLIENT.get_headers(),
|
|
845
|
-
timeout=300,
|
|
846
|
-
) as client:
|
|
847
|
-
# Give some time for logs to propagate through the logging subsystem.
|
|
848
|
-
for _ in range(10):
|
|
849
|
-
time.sleep(2)
|
|
850
|
-
response = client.get(
|
|
851
|
-
REST_CLIENT.base_url + f"/logs/?traceback=true&since={date}"
|
|
852
|
-
)
|
|
853
|
-
|
|
854
|
-
logs = response.json()
|
|
855
|
-
if len(logs) > 0:
|
|
856
|
-
break
|
|
857
|
-
|
|
858
|
-
assert len(logs) > 0
|
|
859
|
-
for log in logs:
|
|
860
|
-
assert log["message"].count("\n") > 1, "Logs should be multi-line"
|
|
861
|
-
assert (
|
|
862
|
-
'{"traceback":' not in log["message"]
|
|
863
|
-
), "Logs should not be JSON-wrapped"
|
|
864
|
-
assert (
|
|
865
|
-
"this app is designed to fail" in log["message"]
|
|
866
|
-
), "Logs should contain the traceback message"
|
|
867
|
-
|
|
868
|
-
|
|
869
885
|
def test_app_exceptions(test_exception_app: AppClient):
|
|
870
886
|
with pytest.raises(AppClientError) as app_exc:
|
|
871
887
|
test_exception_app.app_exception({})
|
|
@@ -559,6 +559,8 @@ def test_serve_on_off(isolated_client):
|
|
|
559
559
|
|
|
560
560
|
|
|
561
561
|
def test_worker_env_vars(isolated_client):
|
|
562
|
+
from fal.flags import GRPC_HOST
|
|
563
|
+
|
|
562
564
|
@isolated_client("virtualenv", keep_alive=5)
|
|
563
565
|
def get_env_var(name: str) -> str | None:
|
|
564
566
|
import os
|
|
@@ -567,8 +569,8 @@ def test_worker_env_vars(isolated_client):
|
|
|
567
569
|
|
|
568
570
|
fal_host = get_env_var("FAL_HOST")
|
|
569
571
|
assert fal_host, "FAL_HOST is not set"
|
|
570
|
-
assert fal_host
|
|
571
|
-
|
|
572
|
+
assert fal_host == GRPC_HOST, "FAL_HOST is not set to the correct value"
|
|
573
|
+
print(f"FAL_HOST: {fal_host}, GRPC_HOST: {GRPC_HOST}")
|
|
572
574
|
|
|
573
575
|
fal_key_id = get_env_var("FAL_KEY_ID")
|
|
574
576
|
assert fal_key_id, "FAL_KEY_ID is not set"
|