fal 1.5.13__tar.gz → 1.5.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fal might be problematic. Click here for more details.
- {fal-1.5.13/fal.egg-info → fal-1.5.15}/PKG-INFO +1 -1
- {fal-1.5.13 → fal-1.5.15/fal.egg-info}/PKG-INFO +1 -1
- {fal-1.5.13 → fal-1.5.15}/fal.egg-info/SOURCES.txt +1 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/_fal_version.py +2 -2
- {fal-1.5.13 → fal-1.5.15}/src/fal/apps.py +129 -1
- fal-1.5.15/src/fal/cli/machine.py +43 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/main.py +2 -2
- {fal-1.5.13 → fal-1.5.15}/src/fal/sdk.py +4 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/file/providers/fal.py +16 -1
- {fal-1.5.13 → fal-1.5.15}/tests/test_apps.py +71 -0
- {fal-1.5.13 → fal-1.5.15}/.gitignore +0 -0
- {fal-1.5.13 → fal-1.5.15}/Makefile +0 -0
- {fal-1.5.13 → fal-1.5.15}/README.md +0 -0
- {fal-1.5.13 → fal-1.5.15}/docs/conf.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/docs/index.rst +0 -0
- {fal-1.5.13 → fal-1.5.15}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.5.13 → fal-1.5.15}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.5.13 → fal-1.5.15}/fal.egg-info/requires.txt +0 -0
- {fal-1.5.13 → fal-1.5.15}/fal.egg-info/top_level.txt +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/README.md +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.5.13 → fal-1.5.15}/openapi_rest.config.yaml +0 -0
- {fal-1.5.13 → fal-1.5.15}/pyproject.toml +0 -0
- {fal-1.5.13 → fal-1.5.15}/setup.cfg +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/__main__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/_serialization.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/_version.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/api.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/app.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/auth/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/auth/auth0.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/auth/local.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/_utils.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/apps.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/auth.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/create.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/debug.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/deploy.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/doctor.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/keys.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/parser.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/run.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/cli/secrets.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/console/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/console/icons.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/console/ux.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/container.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/exceptions/_base.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/exceptions/_cuda.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/exceptions/auth.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/files.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/flags.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/logging/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/logging/isolate.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/logging/style.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/logging/trace.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/logging/user.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/py.typed +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/rest_client.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/sync.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/file/file.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/file/providers/s3.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/image/safety_checker.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/utils/download_utils.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/toolkit/utils/retry.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/utils.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/src/fal/workflows.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/assets/cat.png +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/cli/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/cli/test_apps.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/cli/test_auth.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/cli/test_deploy.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/cli/test_keys.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/cli/test_run.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/cli/test_secrets.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/conftest.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/integration_test.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/mainify_package/__init__.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/mainify_package/impl.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/mainify_package/utils.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/mainify_target.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/test_stability.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/toolkit/file_test.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/toolkit/image_test.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tests/toolkit/utils/retry.py +0 -0
- {fal-1.5.13 → fal-1.5.15}/tools/demo_script.py +0 -0
|
@@ -4,15 +4,19 @@ import json
|
|
|
4
4
|
import time
|
|
5
5
|
from contextlib import contextmanager
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import Any, Iterator
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Iterator
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
|
|
11
11
|
from fal import flags
|
|
12
12
|
from fal.sdk import Credentials, get_default_credentials
|
|
13
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from websockets.sync.connection import Connection
|
|
16
|
+
|
|
14
17
|
_QUEUE_URL_FORMAT = f"https://queue.{flags.FAL_RUN_HOST}/{{app_id}}"
|
|
15
18
|
_REALTIME_URL_FORMAT = f"wss://{flags.FAL_RUN_HOST}/{{app_id}}"
|
|
19
|
+
_WS_URL_FORMAT = f"wss://ws.{flags.FAL_RUN_HOST}/{{app_id}}"
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
def _backwards_compatible_app_id(app_id: str) -> str:
|
|
@@ -245,3 +249,127 @@ def _connect(app_id: str, *, path: str = "/realtime") -> Iterator[_RealtimeConne
|
|
|
245
249
|
url, additional_headers=creds.to_headers(), open_timeout=90
|
|
246
250
|
) as ws:
|
|
247
251
|
yield _RealtimeConnection(ws)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class _MetaMessageFound(Exception): ...
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass
|
|
258
|
+
class _WSConnection:
|
|
259
|
+
"""A WS connection to an HTTP Fal app."""
|
|
260
|
+
|
|
261
|
+
_ws: Connection
|
|
262
|
+
_buffer: str | bytes | None = None
|
|
263
|
+
|
|
264
|
+
def run(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
265
|
+
"""Run an inference task on the app and return the result."""
|
|
266
|
+
self.send(arguments)
|
|
267
|
+
return self.recv()
|
|
268
|
+
|
|
269
|
+
def send(self, arguments: dict[str, Any]) -> None:
|
|
270
|
+
import json
|
|
271
|
+
|
|
272
|
+
payload = json.dumps(arguments)
|
|
273
|
+
self._ws.send(payload)
|
|
274
|
+
|
|
275
|
+
def _peek(self) -> bytes | str:
|
|
276
|
+
if self._buffer is None:
|
|
277
|
+
self._buffer = self._ws.recv()
|
|
278
|
+
|
|
279
|
+
return self._buffer
|
|
280
|
+
|
|
281
|
+
def _consume(self) -> None:
|
|
282
|
+
if self._buffer is None:
|
|
283
|
+
raise ValueError("No data to consume")
|
|
284
|
+
|
|
285
|
+
self._buffer = None
|
|
286
|
+
|
|
287
|
+
@contextmanager
|
|
288
|
+
def _recv(self) -> Iterator[str | bytes]:
|
|
289
|
+
res = self._peek()
|
|
290
|
+
|
|
291
|
+
yield res
|
|
292
|
+
|
|
293
|
+
# Only consume if it went through the context manager without raising
|
|
294
|
+
self._consume()
|
|
295
|
+
|
|
296
|
+
def _is_meta(self, res: str | bytes) -> bool:
|
|
297
|
+
if not isinstance(res, str):
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
json_payload: Any = json.loads(res)
|
|
302
|
+
except json.JSONDecodeError:
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
if not isinstance(json_payload, dict):
|
|
306
|
+
return False
|
|
307
|
+
|
|
308
|
+
return "type" in json_payload and "request_id" in json_payload
|
|
309
|
+
|
|
310
|
+
def _recv_meta(self, type: str) -> dict[str, Any]:
|
|
311
|
+
with self._recv() as res:
|
|
312
|
+
if not self._is_meta(res):
|
|
313
|
+
raise ValueError(f"Expected a {type} message")
|
|
314
|
+
|
|
315
|
+
json_payload: dict = json.loads(res)
|
|
316
|
+
if json_payload.get("type") != type:
|
|
317
|
+
raise ValueError(f"Expected a {type} message")
|
|
318
|
+
|
|
319
|
+
return json_payload
|
|
320
|
+
|
|
321
|
+
def _recv_response(self) -> Any:
|
|
322
|
+
import msgpack
|
|
323
|
+
|
|
324
|
+
body: bytes = b""
|
|
325
|
+
while True:
|
|
326
|
+
try:
|
|
327
|
+
with self._recv() as res:
|
|
328
|
+
if self._is_meta(res):
|
|
329
|
+
# Keep the meta message for later
|
|
330
|
+
raise _MetaMessageFound()
|
|
331
|
+
|
|
332
|
+
if isinstance(res, str):
|
|
333
|
+
return res
|
|
334
|
+
else:
|
|
335
|
+
body += res
|
|
336
|
+
except _MetaMessageFound:
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
if not body:
|
|
340
|
+
raise ValueError("Empty response body")
|
|
341
|
+
|
|
342
|
+
return msgpack.unpackb(body)
|
|
343
|
+
|
|
344
|
+
def recv(self) -> Any:
|
|
345
|
+
start = self._recv_meta("start")
|
|
346
|
+
request_id = start["request_id"]
|
|
347
|
+
|
|
348
|
+
response = self._recv_response()
|
|
349
|
+
|
|
350
|
+
end = self._recv_meta("end")
|
|
351
|
+
if end["request_id"] != request_id:
|
|
352
|
+
raise ValueError("Mismatched request_id in end message")
|
|
353
|
+
|
|
354
|
+
return response
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@contextmanager
|
|
358
|
+
def ws(app_id: str, *, path: str = "") -> Iterator[_WSConnection]:
|
|
359
|
+
"""Connect to a HTTP endpoint but with websocket protocol. This is an internal and
|
|
360
|
+
experimental API, use it at your own risk."""
|
|
361
|
+
|
|
362
|
+
from websockets.sync import client
|
|
363
|
+
|
|
364
|
+
app_id = _backwards_compatible_app_id(app_id)
|
|
365
|
+
url = _WS_URL_FORMAT.format(app_id=app_id)
|
|
366
|
+
if path:
|
|
367
|
+
_path = path[len("/") :] if path.startswith("/") else path
|
|
368
|
+
url += "/" + _path
|
|
369
|
+
|
|
370
|
+
creds = get_default_credentials()
|
|
371
|
+
|
|
372
|
+
with client.connect(
|
|
373
|
+
url, additional_headers=creds.to_headers(), open_timeout=90
|
|
374
|
+
) as ws:
|
|
375
|
+
yield _WSConnection(ws)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .parser import FalClientParser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _kill(args):
|
|
5
|
+
from fal.sdk import FalServerlessClient
|
|
6
|
+
|
|
7
|
+
client = FalServerlessClient(args.host)
|
|
8
|
+
with client.connect() as connection:
|
|
9
|
+
connection.kill_runner(args.id)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _add_kill_parser(subparsers, parents):
|
|
13
|
+
kill_help = "Kill a machine."
|
|
14
|
+
parser = subparsers.add_parser(
|
|
15
|
+
"kill",
|
|
16
|
+
description=kill_help,
|
|
17
|
+
help=kill_help,
|
|
18
|
+
parents=parents,
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"id",
|
|
22
|
+
help="Runner ID.",
|
|
23
|
+
)
|
|
24
|
+
parser.set_defaults(func=_kill)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def add_parser(main_subparsers, parents):
|
|
28
|
+
machine_help = "Manage fal machines."
|
|
29
|
+
parser = main_subparsers.add_parser(
|
|
30
|
+
"machine",
|
|
31
|
+
description=machine_help,
|
|
32
|
+
help=machine_help,
|
|
33
|
+
parents=parents,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
subparsers = parser.add_subparsers(
|
|
37
|
+
title="Commands",
|
|
38
|
+
metavar="command",
|
|
39
|
+
required=True,
|
|
40
|
+
parser_class=FalClientParser,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
_add_kill_parser(subparsers, parents)
|
|
@@ -6,7 +6,7 @@ from fal import __version__
|
|
|
6
6
|
from fal.console import console
|
|
7
7
|
from fal.console.icons import CROSS_ICON
|
|
8
8
|
|
|
9
|
-
from . import apps, auth, create, deploy, doctor, keys, run, secrets
|
|
9
|
+
from . import apps, auth, create, deploy, doctor, keys, machine, run, secrets
|
|
10
10
|
from .debug import debugtools, get_debug_parser
|
|
11
11
|
from .parser import FalParser, FalParserExit
|
|
12
12
|
|
|
@@ -31,7 +31,7 @@ def _get_main_parser() -> argparse.ArgumentParser:
|
|
|
31
31
|
required=True,
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
for cmd in [auth, apps, deploy, run, keys, secrets, doctor, create]:
|
|
34
|
+
for cmd in [auth, apps, deploy, run, keys, secrets, doctor, create, machine]:
|
|
35
35
|
cmd.add_parser(subparsers, parents)
|
|
36
36
|
|
|
37
37
|
return parser
|
|
@@ -180,6 +180,14 @@ class FalFileRepository(FalFileRepositoryBase):
|
|
|
180
180
|
return self._save(file, "gcs")
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
@dataclass
|
|
184
|
+
class FalFileRepositoryV3(FalFileRepositoryBase):
|
|
185
|
+
def save(
|
|
186
|
+
self, file: FileData, object_lifecycle_preference: dict[str, str] | None = None
|
|
187
|
+
) -> str:
|
|
188
|
+
return self._save(file, "fal-cdn-v3")
|
|
189
|
+
|
|
190
|
+
|
|
183
191
|
class MultipartUpload:
|
|
184
192
|
MULTIPART_THRESHOLD = 100 * 1024 * 1024
|
|
185
193
|
MULTIPART_CHUNK_SIZE = 100 * 1024 * 1024
|
|
@@ -548,8 +556,15 @@ class FalCDNFileRepository(FileRepository):
|
|
|
548
556
|
}
|
|
549
557
|
|
|
550
558
|
|
|
559
|
+
# This is only available for internal users to have long-lived access tokens
|
|
551
560
|
@dataclass
|
|
552
|
-
class
|
|
561
|
+
class InternalFalFileRepositoryV3(FileRepository):
|
|
562
|
+
"""
|
|
563
|
+
InternalFalFileRepositoryV3 is a file repository that uses the FAL CDN V3.
|
|
564
|
+
But generates and uses long-lived access tokens.
|
|
565
|
+
That way it can avoid the need to refresh the token for every upload.
|
|
566
|
+
"""
|
|
567
|
+
|
|
553
568
|
@retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
|
|
554
569
|
def save(
|
|
555
570
|
self, file: FileData, object_lifecycle_preference: dict[str, str] | None
|
|
@@ -154,6 +154,23 @@ class StatefulAdditionApp(fal.App, keep_alive=300, max_concurrency=1):
|
|
|
154
154
|
return Output(result=self.counter)
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
class SleepInput(BaseModel):
|
|
158
|
+
wait_time: int
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class SleepOutput(BaseModel):
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class SleepApp(fal.App, keep_alive=300, max_concurrency=1):
|
|
166
|
+
machine_type = "XS"
|
|
167
|
+
|
|
168
|
+
@fal.endpoint("/")
|
|
169
|
+
async def sleep(self, input: SleepInput) -> SleepOutput:
|
|
170
|
+
await asyncio.sleep(input.wait_time)
|
|
171
|
+
return SleepOutput()
|
|
172
|
+
|
|
173
|
+
|
|
157
174
|
class ExceptionApp(fal.App, keep_alive=300, max_concurrency=1):
|
|
158
175
|
machine_type = "XS"
|
|
159
176
|
|
|
@@ -378,6 +395,21 @@ def test_app_client(test_app: str, test_nomad_app: str):
|
|
|
378
395
|
assert response["result"] == 5
|
|
379
396
|
|
|
380
397
|
|
|
398
|
+
def test_ws_client(test_app: str):
|
|
399
|
+
with apps.ws(test_app) as connection:
|
|
400
|
+
for i in range(3):
|
|
401
|
+
response = json.loads(connection.run({"lhs": 1, "rhs": i}))
|
|
402
|
+
assert response["result"] == 1 + i
|
|
403
|
+
|
|
404
|
+
for i in range(3):
|
|
405
|
+
connection.send({"lhs": 2, "rhs": i})
|
|
406
|
+
|
|
407
|
+
for i in range(3):
|
|
408
|
+
# they should be in order
|
|
409
|
+
response = json.loads(connection.recv())
|
|
410
|
+
assert response["result"] == 2 + i
|
|
411
|
+
|
|
412
|
+
|
|
381
413
|
def test_app_client_old_format(test_app: str):
|
|
382
414
|
assert test_app.count("/") == 1, "Test app should be in new format"
|
|
383
415
|
old_format = test_app.replace("/", "-")
|
|
@@ -772,3 +804,42 @@ def test_app_exceptions(test_exception_app: AppClient):
|
|
|
772
804
|
|
|
773
805
|
assert cuda_exc.value.status_code == _CUDA_OOM_STATUS_CODE
|
|
774
806
|
assert _CUDA_OOM_MESSAGE in cuda_exc.value.message
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def test_kill_runner():
|
|
810
|
+
import uuid
|
|
811
|
+
|
|
812
|
+
app_alias = str(uuid.uuid4()) + "-sleep-alias"
|
|
813
|
+
app = fal.wrap_app(SleepApp)
|
|
814
|
+
app_revision = app.host.register(
|
|
815
|
+
func=app.func,
|
|
816
|
+
options=app.options,
|
|
817
|
+
application_name=app_alias,
|
|
818
|
+
application_auth_mode="private",
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
host: api.FalServerlessHost = app.host # type: ignore
|
|
822
|
+
|
|
823
|
+
user = _get_user()
|
|
824
|
+
|
|
825
|
+
handle = apps.submit(f"{user.user_id}/{app_revision}", arguments={"wait_time": 10})
|
|
826
|
+
|
|
827
|
+
while True:
|
|
828
|
+
status = handle.status()
|
|
829
|
+
if isinstance(status, apps.InProgress):
|
|
830
|
+
break
|
|
831
|
+
elif isinstance(status, apps.Queued):
|
|
832
|
+
time.sleep(1)
|
|
833
|
+
else:
|
|
834
|
+
raise Exception(f"Failed to start the app: {status}")
|
|
835
|
+
|
|
836
|
+
with host._connection as client:
|
|
837
|
+
try:
|
|
838
|
+
client.kill_runner("1234567890")
|
|
839
|
+
except Exception as e:
|
|
840
|
+
assert "not found" in str(e).lower()
|
|
841
|
+
|
|
842
|
+
runners = client.list_alias_runners(app_alias)
|
|
843
|
+
assert len(runners) == 1
|
|
844
|
+
|
|
845
|
+
client.kill_runner(runners[0].runner_id)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/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.5.13 → fal-1.5.15}/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.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py
RENAMED
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py
RENAMED
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/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.5.13 → fal-1.5.15}/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.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py
RENAMED
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py
RENAMED
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fal-1.5.13 → fal-1.5.15}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|