fal 1.15.1__tar.gz → 1.16.1__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.15.1/fal.egg-info → fal-1.16.1}/PKG-INFO +1 -1
- {fal-1.15.1 → fal-1.16.1/fal.egg-info}/PKG-INFO +1 -1
- {fal-1.15.1 → fal-1.16.1}/fal.egg-info/SOURCES.txt +1 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/_fal_version.py +2 -2
- {fal-1.15.1 → fal-1.16.1}/src/fal/_serialization.py +5 -5
- fal-1.16.1/src/fal/_version.py +89 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/api.py +23 -8
- {fal-1.15.1 → fal-1.16.1}/src/fal/app.py +8 -9
- {fal-1.15.1 → fal-1.16.1}/src/fal/auth/__init__.py +7 -2
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/main.py +37 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/profile.py +20 -1
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/runners.py +6 -2
- {fal-1.15.1 → fal-1.16.1}/src/fal/config.py +1 -1
- {fal-1.15.1 → fal-1.16.1}/src/fal/flags.py +6 -1
- fal-1.16.1/src/fal/toolkit/utils/endpoint.py +29 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/utils.py +3 -0
- {fal-1.15.1 → fal-1.16.1}/tests/test_apps.py +82 -8
- fal-1.15.1/src/fal/_version.py +0 -6
- {fal-1.15.1 → fal-1.16.1}/.gitignore +0 -0
- {fal-1.15.1 → fal-1.16.1}/Makefile +0 -0
- {fal-1.15.1 → fal-1.16.1}/README.md +0 -0
- {fal-1.15.1 → fal-1.16.1}/docs/conf.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/docs/index.rst +0 -0
- {fal-1.15.1 → fal-1.16.1}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.15.1 → fal-1.16.1}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.15.1 → fal-1.16.1}/fal.egg-info/requires.txt +0 -0
- {fal-1.15.1 → fal-1.16.1}/fal.egg-info/top_level.txt +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/README.md +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.15.1 → fal-1.16.1}/openapi_rest.config.yaml +0 -0
- {fal-1.15.1 → fal-1.16.1}/pyproject.toml +0 -0
- {fal-1.15.1 → fal-1.16.1}/setup.cfg +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/__main__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/apps.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/auth/auth0.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/auth/local.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/_utils.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/api.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/apps.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/auth.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/cli_nested_json.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/create.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/debug.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/deploy.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/doctor.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/files.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/keys.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/parser.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/run.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/secrets.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/cli/teams.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/console/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/console/icons.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/console/ux.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/container.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/exceptions/_base.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/exceptions/auth.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/files.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/logging/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/logging/isolate.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/logging/style.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/logging/trace.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/logging/user.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/project.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/py.typed +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/rest_client.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/sdk.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/sync.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/file/file.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/file/providers/fal.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/file/providers/s3.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/types.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/toolkit/utils/retry.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/src/fal/workflows.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/assets/cat.png +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/cli/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/cli/test_apps.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/cli/test_auth.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/cli/test_deploy.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/cli/test_keys.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/cli/test_run.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/cli/test_secrets.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/conftest.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/integration_test.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/mainify_package/__init__.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/mainify_package/impl.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/mainify_package/utils.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/mainify_target.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/test_stability.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/toolkit/file_test.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/toolkit/image_test.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/toolkit/test_types.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tests/toolkit/utils/retry.py +0 -0
- {fal-1.15.1 → fal-1.16.1}/tools/demo_script.py +0 -0
|
@@ -150,6 +150,7 @@ src/fal/toolkit/image/nsfw_filter/model.py
|
|
|
150
150
|
src/fal/toolkit/image/nsfw_filter/requirements.txt
|
|
151
151
|
src/fal/toolkit/utils/__init__.py
|
|
152
152
|
src/fal/toolkit/utils/download_utils.py
|
|
153
|
+
src/fal/toolkit/utils/endpoint.py
|
|
153
154
|
src/fal/toolkit/utils/retry.py
|
|
154
155
|
tests/__init__.py
|
|
155
156
|
tests/conftest.py
|
|
@@ -6,7 +6,7 @@ from typing import Any, Callable
|
|
|
6
6
|
import cloudpickle
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def
|
|
9
|
+
def include_module(name) -> None:
|
|
10
10
|
# cloudpickle.register_pickle_by_value wants an imported module object,
|
|
11
11
|
# but there is really no reason to go through that complication, as
|
|
12
12
|
# it might be prone to errors.
|
|
@@ -22,7 +22,7 @@ def include_package_from_path(raw_path: str) -> None:
|
|
|
22
22
|
parent = parent.parent
|
|
23
23
|
|
|
24
24
|
if parent != path:
|
|
25
|
-
|
|
25
|
+
include_module(parent.name)
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def include_modules_from(obj: Any) -> None:
|
|
@@ -33,7 +33,7 @@ def include_modules_from(obj: Any) -> None:
|
|
|
33
33
|
if "." in module_name:
|
|
34
34
|
# Just include the whole package
|
|
35
35
|
package_name, *_ = module_name.partition(".")
|
|
36
|
-
|
|
36
|
+
include_module(package_name)
|
|
37
37
|
return
|
|
38
38
|
|
|
39
39
|
if module_name == "__main__":
|
|
@@ -44,7 +44,7 @@ def include_modules_from(obj: Any) -> None:
|
|
|
44
44
|
include_package_from_path(__main__.__file__)
|
|
45
45
|
return
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
include_module(module_name)
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def _register(cls: Any, func: Callable) -> None:
|
|
@@ -230,4 +230,4 @@ def patch_pickle() -> None:
|
|
|
230
230
|
_patch_console_thread_locals()
|
|
231
231
|
_patch_exceptions()
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
include_module("fal")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from ._fal_version import version as __version__ # type: ignore[import]
|
|
8
|
+
from ._fal_version import version_tuple # type: ignore[import]
|
|
9
|
+
except ImportError:
|
|
10
|
+
__version__ = "UNKNOWN"
|
|
11
|
+
version_tuple = (0, 0, __version__) # type: ignore[assignment]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_PYPI_URL = "https://pypi.org/pypi/fal/json"
|
|
15
|
+
_PYPI_CACHE_TTL = 60 * 60 # 1 hour
|
|
16
|
+
_PYPI_CACHE_PATH = os.path.expanduser("~/.fal/cache/pypi.json")
|
|
17
|
+
_URLOPEN_TIMEOUT = 1
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _write_pypi_cache(data: Dict[str, Any]) -> None:
|
|
21
|
+
cache_dir = os.path.dirname(_PYPI_CACHE_PATH)
|
|
22
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
23
|
+
prefix = os.path.basename(_PYPI_CACHE_PATH) + ".tmp."
|
|
24
|
+
with tempfile.NamedTemporaryFile(
|
|
25
|
+
mode="w",
|
|
26
|
+
dir=cache_dir,
|
|
27
|
+
prefix=prefix,
|
|
28
|
+
delete=False,
|
|
29
|
+
) as fobj:
|
|
30
|
+
fobj.write(json.dumps(data))
|
|
31
|
+
os.rename(fobj.name, _PYPI_CACHE_PATH)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_pypi_cache() -> Optional[Dict[str, Any]]:
|
|
35
|
+
import time
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
mtime = os.path.getmtime(_PYPI_CACHE_PATH)
|
|
39
|
+
except FileNotFoundError:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
if mtime + _PYPI_CACHE_TTL < time.time():
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
with open(_PYPI_CACHE_PATH) as fobj:
|
|
46
|
+
try:
|
|
47
|
+
return json.load(fobj)
|
|
48
|
+
except ValueError:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _fetch_pypi_data() -> Dict[str, Any]:
|
|
53
|
+
from urllib.request import urlopen
|
|
54
|
+
|
|
55
|
+
response = urlopen(_PYPI_URL, timeout=_URLOPEN_TIMEOUT)
|
|
56
|
+
if response.status != 200:
|
|
57
|
+
raise Exception(f"Failed to fetch {_PYPI_URL}")
|
|
58
|
+
|
|
59
|
+
data = response.read()
|
|
60
|
+
return json.loads(data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_latest_version() -> str:
|
|
64
|
+
from fal.logging import get_logger
|
|
65
|
+
|
|
66
|
+
logger = get_logger(__name__)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
data = _get_pypi_cache()
|
|
70
|
+
except Exception:
|
|
71
|
+
logger.warning("Failed to get pypi cache", exc_info=True)
|
|
72
|
+
data = None
|
|
73
|
+
|
|
74
|
+
if data is None:
|
|
75
|
+
try:
|
|
76
|
+
data = _fetch_pypi_data()
|
|
77
|
+
except Exception:
|
|
78
|
+
logger.warning("Failed to get latest fal version", exc_info=True)
|
|
79
|
+
data = {}
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
_write_pypi_cache(data)
|
|
83
|
+
except Exception:
|
|
84
|
+
logger.warning("Failed to write pypi cache", exc_info=True)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
return data["info"]["version"]
|
|
88
|
+
except KeyError:
|
|
89
|
+
return "0.0.0"
|
|
@@ -39,7 +39,7 @@ from pydantic import __version__ as pydantic_version
|
|
|
39
39
|
from typing_extensions import Concatenate, ParamSpec
|
|
40
40
|
|
|
41
41
|
import fal.flags as flags
|
|
42
|
-
from fal._serialization import include_modules_from, patch_pickle
|
|
42
|
+
from fal._serialization import include_module, include_modules_from, patch_pickle
|
|
43
43
|
from fal.container import ContainerImage
|
|
44
44
|
from fal.exceptions import (
|
|
45
45
|
AppException,
|
|
@@ -501,12 +501,7 @@ class FalServerlessHost(Host):
|
|
|
501
501
|
if isinstance(func, ServeWrapper):
|
|
502
502
|
# Assigning in a separate property leaving a place for the user
|
|
503
503
|
# to add more metadata in the future
|
|
504
|
-
|
|
505
|
-
metadata["openapi"] = func.openapi()
|
|
506
|
-
except Exception as e:
|
|
507
|
-
print(
|
|
508
|
-
f"[warning] Failed to generate OpenAPI metadata for function: {e}"
|
|
509
|
-
)
|
|
504
|
+
metadata["openapi"] = func.openapi()
|
|
510
505
|
|
|
511
506
|
for partial_result in self._connection.register(
|
|
512
507
|
partial_func,
|
|
@@ -694,6 +689,7 @@ def function(
|
|
|
694
689
|
serve: Literal[False] = False,
|
|
695
690
|
exposed_port: int | None = None,
|
|
696
691
|
max_concurrency: int | None = None,
|
|
692
|
+
local_python_modules: list[str] | None = None,
|
|
697
693
|
) -> Callable[
|
|
698
694
|
[Callable[Concatenate[ArgsT], ReturnT]], IsolatedFunction[ArgsT, ReturnT]
|
|
699
695
|
]: ...
|
|
@@ -710,6 +706,7 @@ def function(
|
|
|
710
706
|
serve: Literal[True],
|
|
711
707
|
exposed_port: int | None = None,
|
|
712
708
|
max_concurrency: int | None = None,
|
|
709
|
+
local_python_modules: list[str] | None = None,
|
|
713
710
|
) -> Callable[
|
|
714
711
|
[Callable[Concatenate[ArgsT], ReturnT]], ServedIsolatedFunction[ArgsT, ReturnT]
|
|
715
712
|
]: ...
|
|
@@ -727,6 +724,7 @@ def function(
|
|
|
727
724
|
serve: Literal[False] = False,
|
|
728
725
|
exposed_port: int | None = None,
|
|
729
726
|
max_concurrency: int | None = None,
|
|
727
|
+
local_python_modules: list[str] | None = None,
|
|
730
728
|
# FalServerlessHost options
|
|
731
729
|
metadata: dict[str, Any] | None = None,
|
|
732
730
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
@@ -756,6 +754,7 @@ def function(
|
|
|
756
754
|
serve: Literal[True],
|
|
757
755
|
exposed_port: int | None = None,
|
|
758
756
|
max_concurrency: int | None = None,
|
|
757
|
+
local_python_modules: list[str] | None = None,
|
|
759
758
|
# FalServerlessHost options
|
|
760
759
|
metadata: dict[str, Any] | None = None,
|
|
761
760
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
@@ -792,6 +791,7 @@ def function(
|
|
|
792
791
|
serve: Literal[False] = False,
|
|
793
792
|
exposed_port: int | None = None,
|
|
794
793
|
max_concurrency: int | None = None,
|
|
794
|
+
local_python_modules: list[str] | None = None,
|
|
795
795
|
) -> Callable[
|
|
796
796
|
[Callable[Concatenate[ArgsT], ReturnT]], IsolatedFunction[ArgsT, ReturnT]
|
|
797
797
|
]: ...
|
|
@@ -813,6 +813,7 @@ def function(
|
|
|
813
813
|
serve: Literal[True],
|
|
814
814
|
exposed_port: int | None = None,
|
|
815
815
|
max_concurrency: int | None = None,
|
|
816
|
+
local_python_modules: list[str] | None = None,
|
|
816
817
|
) -> Callable[
|
|
817
818
|
[Callable[Concatenate[ArgsT], ReturnT]], ServedIsolatedFunction[ArgsT, ReturnT]
|
|
818
819
|
]: ...
|
|
@@ -835,6 +836,7 @@ def function(
|
|
|
835
836
|
serve: Literal[False] = False,
|
|
836
837
|
exposed_port: int | None = None,
|
|
837
838
|
max_concurrency: int | None = None,
|
|
839
|
+
local_python_modules: list[str] | None = None,
|
|
838
840
|
# FalServerlessHost options
|
|
839
841
|
metadata: dict[str, Any] | None = None,
|
|
840
842
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
@@ -869,6 +871,7 @@ def function(
|
|
|
869
871
|
serve: Literal[True],
|
|
870
872
|
exposed_port: int | None = None,
|
|
871
873
|
max_concurrency: int | None = None,
|
|
874
|
+
local_python_modules: list[str] | None = None,
|
|
872
875
|
# FalServerlessHost options
|
|
873
876
|
metadata: dict[str, Any] | None = None,
|
|
874
877
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
@@ -897,6 +900,7 @@ def function(
|
|
|
897
900
|
serve: Literal[False] = False,
|
|
898
901
|
exposed_port: int | None = None,
|
|
899
902
|
max_concurrency: int | None = None,
|
|
903
|
+
local_python_modules: list[str] | None = None,
|
|
900
904
|
# FalServerlessHost options
|
|
901
905
|
metadata: dict[str, Any] | None = None,
|
|
902
906
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
@@ -925,6 +929,7 @@ def function(
|
|
|
925
929
|
serve: Literal[True],
|
|
926
930
|
exposed_port: int | None = None,
|
|
927
931
|
max_concurrency: int | None = None,
|
|
932
|
+
local_python_modules: list[str] | None = None,
|
|
928
933
|
# FalServerlessHost options
|
|
929
934
|
metadata: dict[str, Any] | None = None,
|
|
930
935
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
@@ -948,6 +953,7 @@ def function( # type: ignore
|
|
|
948
953
|
kind: str = "virtualenv",
|
|
949
954
|
*,
|
|
950
955
|
host: Host | None = None,
|
|
956
|
+
local_python_modules: list[str] | None = None,
|
|
951
957
|
**config: Any,
|
|
952
958
|
):
|
|
953
959
|
if host is None:
|
|
@@ -956,6 +962,10 @@ def function( # type: ignore
|
|
|
956
962
|
|
|
957
963
|
def wrapper(func: Callable[ArgsT, ReturnT]):
|
|
958
964
|
include_modules_from(func)
|
|
965
|
+
|
|
966
|
+
for module_name in local_python_modules or []:
|
|
967
|
+
include_module(module_name)
|
|
968
|
+
|
|
959
969
|
proxy = IsolatedFunction(
|
|
960
970
|
host=host, # type: ignore
|
|
961
971
|
raw_func=func, # type: ignore
|
|
@@ -1154,7 +1164,12 @@ class BaseServable:
|
|
|
1154
1164
|
Build the OpenAPI specification for the served function.
|
|
1155
1165
|
Attach needed metadata for a better integration to fal.
|
|
1156
1166
|
"""
|
|
1157
|
-
|
|
1167
|
+
try:
|
|
1168
|
+
return self._build_app().openapi()
|
|
1169
|
+
except Exception as e:
|
|
1170
|
+
raise FalServerlessException(
|
|
1171
|
+
"Failed to generate OpenAPI metadata for function"
|
|
1172
|
+
) from e
|
|
1158
1173
|
|
|
1159
1174
|
def serve(self) -> None:
|
|
1160
1175
|
import asyncio
|
|
@@ -105,15 +105,12 @@ def wrap_app(cls: type[App], **kwargs) -> IsolatedFunction:
|
|
|
105
105
|
app.serve()
|
|
106
106
|
|
|
107
107
|
metadata = {}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
else:
|
|
115
|
-
routes = app.collect_routes()
|
|
116
|
-
realtime_app = any(route.is_websocket for route in routes)
|
|
108
|
+
app = cls(_allow_init=True)
|
|
109
|
+
|
|
110
|
+
metadata["openapi"] = app.openapi()
|
|
111
|
+
|
|
112
|
+
routes = app.collect_routes()
|
|
113
|
+
realtime_app = any(route.is_websocket for route in routes)
|
|
117
114
|
|
|
118
115
|
kind = cls.host_kwargs.pop("kind", "virtualenv")
|
|
119
116
|
if kind == "container":
|
|
@@ -122,6 +119,7 @@ def wrap_app(cls: type[App], **kwargs) -> IsolatedFunction:
|
|
|
122
119
|
wrapper = fal_function(
|
|
123
120
|
kind,
|
|
124
121
|
requirements=cls.requirements,
|
|
122
|
+
local_python_modules=cls.local_python_modules,
|
|
125
123
|
machine_type=cls.machine_type,
|
|
126
124
|
num_gpus=cls.num_gpus,
|
|
127
125
|
**cls.host_kwargs,
|
|
@@ -265,6 +263,7 @@ def _print_python_packages() -> None:
|
|
|
265
263
|
|
|
266
264
|
class App(BaseServable):
|
|
267
265
|
requirements: ClassVar[list[str]] = []
|
|
266
|
+
local_python_modules: ClassVar[list[str]] = []
|
|
268
267
|
machine_type: ClassVar[str] = "S"
|
|
269
268
|
num_gpus: ClassVar[int | None] = None
|
|
270
269
|
host_kwargs: ClassVar[dict[str, Any]] = {
|
|
@@ -63,8 +63,13 @@ def key_credentials() -> tuple[str, str] | None:
|
|
|
63
63
|
|
|
64
64
|
key = os.environ.get("FAL_KEY") or config.get("key") or get_colab_token()
|
|
65
65
|
if key:
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
try:
|
|
67
|
+
key_id, key_secret = key.split(":", 1)
|
|
68
|
+
return (key_id, key_secret)
|
|
69
|
+
except ValueError:
|
|
70
|
+
print(f"Invalid key format: {key}")
|
|
71
|
+
return None
|
|
72
|
+
|
|
68
73
|
elif "FAL_KEY_ID" in os.environ and "FAL_KEY_SECRET" in os.environ:
|
|
69
74
|
return (os.environ["FAL_KEY_ID"], os.environ["FAL_KEY_SECRET"])
|
|
70
75
|
else:
|
|
@@ -77,11 +77,48 @@ def _print_error(msg):
|
|
|
77
77
|
console.print(f"{CROSS_ICON} {msg}")
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def _check_latest_version():
|
|
81
|
+
from packaging.version import parse
|
|
82
|
+
from rich.emoji import Emoji
|
|
83
|
+
from rich.panel import Panel
|
|
84
|
+
from rich.text import Text
|
|
85
|
+
|
|
86
|
+
from fal._version import get_latest_version, version_tuple
|
|
87
|
+
|
|
88
|
+
latest_version = get_latest_version()
|
|
89
|
+
parsed = parse(latest_version)
|
|
90
|
+
latest_version_tuple = (parsed.major, parsed.minor, parsed.micro)
|
|
91
|
+
if latest_version_tuple <= version_tuple:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
if not console.is_terminal:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
line1 = Text.assemble(
|
|
98
|
+
(Emoji.replace(":warning-emoji: "), "bold white"),
|
|
99
|
+
("A new version of fal is available: ", "bold white"),
|
|
100
|
+
(latest_version, "bold green"),
|
|
101
|
+
)
|
|
102
|
+
line2 = Text.assemble(("pip install --upgrade fal", "bold cyan"))
|
|
103
|
+
line2.align("center", width=len(line1))
|
|
104
|
+
|
|
105
|
+
panel = Panel(
|
|
106
|
+
line1 + "\n\n" + line2,
|
|
107
|
+
border_style="yellow",
|
|
108
|
+
padding=(1, 2),
|
|
109
|
+
highlight=True,
|
|
110
|
+
expand=False,
|
|
111
|
+
)
|
|
112
|
+
console.print(panel)
|
|
113
|
+
|
|
114
|
+
|
|
80
115
|
def main(argv=None) -> int:
|
|
81
116
|
import grpc
|
|
82
117
|
|
|
83
118
|
from fal.api import UserFunctionException
|
|
84
119
|
|
|
120
|
+
_check_latest_version()
|
|
121
|
+
|
|
85
122
|
ret = 1
|
|
86
123
|
try:
|
|
87
124
|
args = parse_args(argv)
|
|
@@ -55,12 +55,18 @@ def _key_set(args):
|
|
|
55
55
|
args.console.print(f"Key set for profile [cyan]{config.profile}[/].")
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def _host_set(args):
|
|
59
|
+
with Config().edit() as config:
|
|
60
|
+
config.set("host", args.HOST)
|
|
61
|
+
args.console.print(f"Fal host set to [cyan]{args.HOST}[/].")
|
|
62
|
+
|
|
63
|
+
|
|
58
64
|
def _delete(args):
|
|
59
65
|
with Config().edit() as config:
|
|
60
66
|
if config.profile == args.PROFILE:
|
|
61
67
|
config.set_internal("profile", None)
|
|
62
68
|
|
|
63
|
-
config.
|
|
69
|
+
config.delete_profile(args.PROFILE)
|
|
64
70
|
args.console.print(f"Profile [cyan]{args.PROFILE}[/] deleted.")
|
|
65
71
|
|
|
66
72
|
|
|
@@ -121,6 +127,19 @@ def add_parser(main_subparsers, parents):
|
|
|
121
127
|
)
|
|
122
128
|
key_set_parser.set_defaults(func=_key_set)
|
|
123
129
|
|
|
130
|
+
host_set_help = "Set fal host."
|
|
131
|
+
host_set_parser = subparsers.add_parser(
|
|
132
|
+
"host",
|
|
133
|
+
description=host_set_help,
|
|
134
|
+
help=host_set_help,
|
|
135
|
+
parents=parents,
|
|
136
|
+
)
|
|
137
|
+
host_set_parser.add_argument(
|
|
138
|
+
"HOST",
|
|
139
|
+
help="Fal host.",
|
|
140
|
+
)
|
|
141
|
+
host_set_parser.set_defaults(func=_host_set)
|
|
142
|
+
|
|
124
143
|
delete_help = "Delete profile."
|
|
125
144
|
delete_parser = subparsers.add_parser(
|
|
126
145
|
"delete",
|
|
@@ -21,17 +21,21 @@ def runners_table(runners: List[RunnerInfo]):
|
|
|
21
21
|
table.add_column("Revision")
|
|
22
22
|
|
|
23
23
|
for runner in runners:
|
|
24
|
+
external_metadata = runner.external_metadata
|
|
25
|
+
present = external_metadata.get("present_in_group", True)
|
|
26
|
+
|
|
24
27
|
num_leases_with_request = len(
|
|
25
28
|
[
|
|
26
29
|
lease
|
|
27
|
-
for lease in
|
|
30
|
+
for lease in external_metadata.get("leases", [])
|
|
28
31
|
if lease.get("request_id") is not None
|
|
29
32
|
]
|
|
30
33
|
)
|
|
31
34
|
|
|
32
35
|
table.add_row(
|
|
33
36
|
runner.alias,
|
|
34
|
-
|
|
37
|
+
# Mark lost runners in red
|
|
38
|
+
runner.runner_id if present else f"[red]{runner.runner_id}[/]",
|
|
35
39
|
str(runner.in_flight_requests),
|
|
36
40
|
str(runner.in_flight_requests - num_leases_with_request),
|
|
37
41
|
(
|
|
@@ -99,7 +99,7 @@ class Config:
|
|
|
99
99
|
def unset_internal(self, key: str) -> None:
|
|
100
100
|
self._config.get(SETTINGS_SECTION, {}).pop(key, None)
|
|
101
101
|
|
|
102
|
-
def
|
|
102
|
+
def delete_profile(self, profile: str) -> None:
|
|
103
103
|
del self._config[profile]
|
|
104
104
|
|
|
105
105
|
@contextmanager
|
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
+
from fal.config import Config
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
def bool_envvar(name: str):
|
|
7
9
|
if name in os.environ:
|
|
@@ -14,7 +16,10 @@ DEBUG = bool_envvar("DEBUG")
|
|
|
14
16
|
TEST_MODE = bool_envvar("ISOLATE_TEST_MODE")
|
|
15
17
|
AUTH_DISABLED = bool_envvar("ISOLATE_AUTH_DISABLED")
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
config = Config()
|
|
20
|
+
config_host = config.get("host")
|
|
21
|
+
|
|
22
|
+
GRPC_HOST = config_host or os.getenv("FAL_HOST") or "api.alpha.fal.ai"
|
|
18
23
|
if not TEST_MODE:
|
|
19
24
|
assert GRPC_HOST.startswith("api"), "FAL_HOST must start with 'api'"
|
|
20
25
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
|
|
3
|
+
from anyio import create_task_group
|
|
4
|
+
from fastapi import Request
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@asynccontextmanager
|
|
8
|
+
async def cancel_on_disconnect(request: Request):
|
|
9
|
+
"""
|
|
10
|
+
Async context manager for async code that needs to be cancelled if client
|
|
11
|
+
disconnects prematurely.
|
|
12
|
+
The client disconnect is monitored through the Request object.
|
|
13
|
+
"""
|
|
14
|
+
async with create_task_group() as tg:
|
|
15
|
+
|
|
16
|
+
async def watch_disconnect():
|
|
17
|
+
while True:
|
|
18
|
+
message = await request.receive()
|
|
19
|
+
|
|
20
|
+
if message["type"] == "http.disconnect":
|
|
21
|
+
tg.cancel_scope.cancel()
|
|
22
|
+
break
|
|
23
|
+
|
|
24
|
+
tg.start_soon(watch_disconnect)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
yield
|
|
28
|
+
finally:
|
|
29
|
+
tg.cancel_scope.cancel()
|
|
@@ -21,8 +21,11 @@ def load_function_from(
|
|
|
21
21
|
file_path: str,
|
|
22
22
|
function_name: str | None = None,
|
|
23
23
|
) -> LoadedFunction:
|
|
24
|
+
import os
|
|
24
25
|
import runpy
|
|
26
|
+
import sys
|
|
25
27
|
|
|
28
|
+
sys.path.append(os.getcwd())
|
|
26
29
|
module = runpy.run_path(file_path)
|
|
27
30
|
if function_name is None:
|
|
28
31
|
fal_objects = {
|