fal 1.0.1__tar.gz → 1.0.3__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.0.1 → fal-1.0.3}/PKG-INFO +3 -3
- {fal-1.0.1 → fal-1.0.3}/fal.egg-info/PKG-INFO +3 -3
- {fal-1.0.1 → fal-1.0.3}/fal.egg-info/requires.txt +1 -1
- {fal-1.0.1 → fal-1.0.3}/pyproject.toml +2 -2
- {fal-1.0.1 → fal-1.0.3}/src/fal/_fal_version.py +2 -2
- {fal-1.0.1 → fal-1.0.3}/src/fal/_serialization.py +4 -1
- {fal-1.0.1 → fal-1.0.3}/src/fal/api.py +0 -1
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/parser.py +23 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/utils/download_utils.py +53 -14
- {fal-1.0.1 → fal-1.0.3}/src/fal/utils.py +0 -1
- {fal-1.0.1 → fal-1.0.3}/tests/test_apps.py +1 -17
- {fal-1.0.1 → fal-1.0.3}/tests/test_stability.py +35 -17
- {fal-1.0.1 → fal-1.0.3}/.gitignore +0 -0
- {fal-1.0.1 → fal-1.0.3}/README.md +0 -0
- {fal-1.0.1 → fal-1.0.3}/fal.egg-info/SOURCES.txt +0 -0
- {fal-1.0.1 → fal-1.0.3}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.0.1 → fal-1.0.3}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.0.1 → fal-1.0.3}/fal.egg-info/top_level.txt +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/README.md +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_or_update_workflow_workflows_post.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow_workflows_user_id_workflow_name_delete.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/execute_workflow_workflows_user_id_workflow_name_post.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow_workflows_user_id_workflow_name_get.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflows_workflows_get.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_json_body_type_0.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/execute_workflow_workflows_user_id_workflow_name_post_response_200_type_0.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents_type_0.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.0.1 → fal-1.0.3}/openapi_rest.config.yaml +0 -0
- {fal-1.0.1 → fal-1.0.3}/setup.cfg +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/__main__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/_version.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/app.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/apps.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/auth/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/auth/auth0.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/auth/local.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/apps.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/auth.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/debug.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/deploy.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/keys.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/main.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/run.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/cli/secrets.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/console/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/console/icons.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/console/ux.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/exceptions/_base.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/exceptions/auth.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/flags.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/logging/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/logging/isolate.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/logging/style.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/logging/trace.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/logging/user.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/py.typed +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/rest_client.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/sdk.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/sync.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/file/file.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/file/providers/fal.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/image/image.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/src/fal/workflows.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/cli/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/cli/test_apps.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/cli/test_auth.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/cli/test_deploy.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/cli/test_keys.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/cli/test_run.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/cli/test_secrets.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/conftest.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/integration_test.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/mainify_package/__init__.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/mainify_package/impl.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/mainify_package/utils.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/mainify_target.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/toolkit/file_test.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tests/toolkit/image_test.py +0 -0
- {fal-1.0.1 → fal-1.0.3}/tools/demo_script.py +0 -0
{fal-1.0.1 → fal-1.0.3}/PKG-INFO
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
|
-
Author: Features & Labels <
|
|
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]<1.0,>=0.12.3
|
|
@@ -20,7 +20,7 @@ Requires-Dist: colorama<1,>=0.4.6
|
|
|
20
20
|
Requires-Dist: portalocker<3,>=2.7.0
|
|
21
21
|
Requires-Dist: rich<14,>=13.3.2
|
|
22
22
|
Requires-Dist: rich_argparse
|
|
23
|
-
Requires-Dist: packaging
|
|
23
|
+
Requires-Dist: packaging>=21.3
|
|
24
24
|
Requires-Dist: pathspec<1,>=0.11.1
|
|
25
25
|
Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
|
|
26
26
|
Requires-Dist: fastapi<1,>=0.99.1
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
|
-
Author: Features & Labels <
|
|
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]<1.0,>=0.12.3
|
|
@@ -20,7 +20,7 @@ Requires-Dist: colorama<1,>=0.4.6
|
|
|
20
20
|
Requires-Dist: portalocker<3,>=2.7.0
|
|
21
21
|
Requires-Dist: rich<14,>=13.3.2
|
|
22
22
|
Requires-Dist: rich_argparse
|
|
23
|
-
Requires-Dist: packaging
|
|
23
|
+
Requires-Dist: packaging>=21.3
|
|
24
24
|
Requires-Dist: pathspec<1,>=0.11.1
|
|
25
25
|
Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
|
|
26
26
|
Requires-Dist: fastapi<1,>=0.99.1
|
|
@@ -18,7 +18,7 @@ namespaces = false
|
|
|
18
18
|
name = "fal"
|
|
19
19
|
dynamic = ["version"]
|
|
20
20
|
description = "fal is an easy-to-use Serverless Python Framework"
|
|
21
|
-
authors = [{ name = "Features & Labels <
|
|
21
|
+
authors = [{ name = "Features & Labels <support@fal.ai>"}]
|
|
22
22
|
readme = "README.md"
|
|
23
23
|
requires-python = ">=3.8"
|
|
24
24
|
dependencies = [
|
|
@@ -37,7 +37,7 @@ dependencies = [
|
|
|
37
37
|
"portalocker>=2.7.0,<3",
|
|
38
38
|
"rich>=13.3.2,<14",
|
|
39
39
|
"rich_argparse",
|
|
40
|
-
"packaging>=21.3
|
|
40
|
+
"packaging>=21.3",
|
|
41
41
|
"pathspec>=0.11.1,<1",
|
|
42
42
|
"pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3",
|
|
43
43
|
# serve=True dependencies
|
|
@@ -195,7 +195,10 @@ def _patch_rlock() -> None:
|
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
def _patch_console_thread_locals() -> None:
|
|
198
|
-
|
|
198
|
+
try:
|
|
199
|
+
from rich.console import ConsoleThreadLocals
|
|
200
|
+
except ModuleNotFoundError:
|
|
201
|
+
return
|
|
199
202
|
|
|
200
203
|
def create_locals(kwargs: dict) -> ConsoleThreadLocals:
|
|
201
204
|
return ConsoleThreadLocals(**kwargs)
|
|
@@ -282,7 +282,6 @@ def _handle_grpc_error():
|
|
|
282
282
|
"This is likely due to resource overflow. "
|
|
283
283
|
"You can try again by setting a bigger `machine_type`"
|
|
284
284
|
)
|
|
285
|
-
|
|
286
285
|
elif e.code() == grpc.StatusCode.INVALID_ARGUMENT and (
|
|
287
286
|
"The function function could not be deserialized" in e.details()
|
|
288
287
|
):
|
|
@@ -51,6 +51,22 @@ class DictAction(argparse.Action):
|
|
|
51
51
|
setattr(args, self.dest, d)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def _find_parser(parser, func):
|
|
55
|
+
defaults = parser._defaults
|
|
56
|
+
if not func or func == defaults.get("func"):
|
|
57
|
+
return parser
|
|
58
|
+
|
|
59
|
+
actions = parser._actions
|
|
60
|
+
for action in actions:
|
|
61
|
+
if not isinstance(action.choices, dict):
|
|
62
|
+
continue
|
|
63
|
+
for subparser in action.choices.values():
|
|
64
|
+
par = _find_parser(subparser, func)
|
|
65
|
+
if par:
|
|
66
|
+
return par
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
54
70
|
class FalParser(argparse.ArgumentParser):
|
|
55
71
|
def __init__(self, *args, **kwargs):
|
|
56
72
|
kwargs.setdefault("formatter_class", rich_argparse.RawTextRichHelpFormatter)
|
|
@@ -61,6 +77,13 @@ class FalParser(argparse.ArgumentParser):
|
|
|
61
77
|
self._print_message(message, sys.stderr)
|
|
62
78
|
raise FalParserExit(status)
|
|
63
79
|
|
|
80
|
+
def parse_args(self, args=None, namespace=None):
|
|
81
|
+
args, argv = self.parse_known_args(args, namespace)
|
|
82
|
+
if argv:
|
|
83
|
+
parser = _find_parser(self, getattr(args, "func", None)) or self
|
|
84
|
+
parser.error("unrecognized arguments: %s" % " ".join(argv))
|
|
85
|
+
return args
|
|
86
|
+
|
|
64
87
|
|
|
65
88
|
class FalClientParser(FalParser):
|
|
66
89
|
def __init__(self, *args, **kwargs):
|
|
@@ -4,7 +4,6 @@ import hashlib
|
|
|
4
4
|
import shutil
|
|
5
5
|
import subprocess
|
|
6
6
|
import sys
|
|
7
|
-
from functools import lru_cache
|
|
8
7
|
from pathlib import Path, PurePath
|
|
9
8
|
from tempfile import TemporaryDirectory
|
|
10
9
|
from urllib.parse import urlparse
|
|
@@ -40,8 +39,10 @@ def _hash_url(url: str) -> str:
|
|
|
40
39
|
return hashlib.sha256(url.encode("utf-8")).hexdigest()
|
|
41
40
|
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
def _get_remote_file_properties(
|
|
43
|
+
url: str,
|
|
44
|
+
request_headers: dict[str, str] | None = None,
|
|
45
|
+
) -> tuple[str, int]:
|
|
45
46
|
"""Retrieves the file name and content length of a remote file.
|
|
46
47
|
|
|
47
48
|
This function sends an HTTP request to the remote URL and retrieves the
|
|
@@ -60,11 +61,15 @@ def _get_remote_file_properties(url: str) -> tuple[str, int]:
|
|
|
60
61
|
|
|
61
62
|
Args:
|
|
62
63
|
url: The URL of the remote file.
|
|
64
|
+
request_headers: A dictionary containing additional headers to be included in
|
|
65
|
+
the HTTP request.
|
|
63
66
|
|
|
64
67
|
Returns:
|
|
65
68
|
A tuple containing the file name and the content length of the remote file.
|
|
66
69
|
"""
|
|
67
|
-
|
|
70
|
+
headers = {**TEMP_HEADERS, **(request_headers or {})}
|
|
71
|
+
request = Request(url, headers=headers)
|
|
72
|
+
|
|
68
73
|
with urlopen(request) as response:
|
|
69
74
|
file_name = response.headers.get_filename()
|
|
70
75
|
content_length = int(response.headers.get("Content-Length", -1))
|
|
@@ -81,7 +86,9 @@ def _get_remote_file_properties(url: str) -> tuple[str, int]:
|
|
|
81
86
|
return file_name, content_length
|
|
82
87
|
|
|
83
88
|
|
|
84
|
-
def _file_content_length_matches(
|
|
89
|
+
def _file_content_length_matches(
|
|
90
|
+
url: str, file_path: Path, request_headers: dict[str, str] | None = None
|
|
91
|
+
) -> bool:
|
|
85
92
|
"""Check if the local file's content length matches the expected remote
|
|
86
93
|
file's content length.
|
|
87
94
|
|
|
@@ -95,13 +102,15 @@ def _file_content_length_matches(url: str, file_path: Path) -> bool:
|
|
|
95
102
|
Args:
|
|
96
103
|
url: The URL of the remote file.
|
|
97
104
|
file_path: The local path to the file being compared.
|
|
105
|
+
request_headers: A dictionary containing additional headers to be included in
|
|
106
|
+
the HTTP request.
|
|
98
107
|
|
|
99
108
|
Returns:
|
|
100
109
|
bool: `True` if the local file's content length matches the remote file's
|
|
101
110
|
content length, `False` otherwise.
|
|
102
111
|
"""
|
|
103
112
|
local_file_content_length = file_path.stat().st_size
|
|
104
|
-
remote_file_content_length = _get_remote_file_properties(url)[1]
|
|
113
|
+
remote_file_content_length = _get_remote_file_properties(url, request_headers)[1]
|
|
105
114
|
|
|
106
115
|
return local_file_content_length == remote_file_content_length
|
|
107
116
|
|
|
@@ -111,6 +120,7 @@ def download_file(
|
|
|
111
120
|
target_dir: str | Path,
|
|
112
121
|
*,
|
|
113
122
|
force: bool = False,
|
|
123
|
+
request_headers: dict[str, str] | None = None,
|
|
114
124
|
) -> Path:
|
|
115
125
|
"""Downloads a file from the specified URL to the target directory.
|
|
116
126
|
|
|
@@ -134,6 +144,9 @@ def download_file(
|
|
|
134
144
|
force: If `True`, the file is downloaded even if it already exists locally and
|
|
135
145
|
its content length matches the expected content length from the remote file.
|
|
136
146
|
Defaults to `False`.
|
|
147
|
+
request_headers: A dictionary containing additional headers to be included in
|
|
148
|
+
the HTTP request. Defaults to `None`.
|
|
149
|
+
|
|
137
150
|
|
|
138
151
|
Returns:
|
|
139
152
|
A Path object representing the full path to the downloaded file.
|
|
@@ -142,7 +155,11 @@ def download_file(
|
|
|
142
155
|
ValueError: If the provided `file_name` contains a forward slash ('/').
|
|
143
156
|
DownloadError: If an error occurs during the download process.
|
|
144
157
|
"""
|
|
145
|
-
|
|
158
|
+
try:
|
|
159
|
+
file_name = _get_remote_file_properties(url, request_headers)[0]
|
|
160
|
+
except Exception as e:
|
|
161
|
+
raise DownloadError(f"Failed to get remote file properties for {url}") from e
|
|
162
|
+
|
|
146
163
|
if "/" in file_name:
|
|
147
164
|
raise ValueError(f"File name '{file_name}' cannot contain a slash.")
|
|
148
165
|
|
|
@@ -156,7 +173,7 @@ def download_file(
|
|
|
156
173
|
|
|
157
174
|
if (
|
|
158
175
|
target_path.exists()
|
|
159
|
-
and _file_content_length_matches(url, target_path)
|
|
176
|
+
and _file_content_length_matches(url, target_path, request_headers)
|
|
160
177
|
and not force
|
|
161
178
|
):
|
|
162
179
|
return target_path
|
|
@@ -170,7 +187,9 @@ def download_file(
|
|
|
170
187
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
171
188
|
|
|
172
189
|
try:
|
|
173
|
-
_download_file_python(
|
|
190
|
+
_download_file_python(
|
|
191
|
+
url=url, target_path=target_path, request_headers=request_headers
|
|
192
|
+
)
|
|
174
193
|
except Exception as e:
|
|
175
194
|
msg = f"Failed to download {url} to {target_path}"
|
|
176
195
|
|
|
@@ -181,13 +200,17 @@ def download_file(
|
|
|
181
200
|
return target_path
|
|
182
201
|
|
|
183
202
|
|
|
184
|
-
def _download_file_python(
|
|
203
|
+
def _download_file_python(
|
|
204
|
+
url: str, target_path: Path | str, request_headers: dict[str, str] | None = None
|
|
205
|
+
) -> Path:
|
|
185
206
|
"""Download a file from a given URL and save it to a specified path using a
|
|
186
207
|
Python interface.
|
|
187
208
|
|
|
188
209
|
Args:
|
|
189
210
|
url: The URL of the file to be downloaded.
|
|
190
211
|
target_path: The path where the downloaded file will be saved.
|
|
212
|
+
request_headers: A dictionary containing additional headers to be included in
|
|
213
|
+
the HTTP request. Defaults to `None`.
|
|
191
214
|
|
|
192
215
|
Returns:
|
|
193
216
|
The path where the downloaded file has been saved.
|
|
@@ -199,11 +222,14 @@ def _download_file_python(url: str, target_path: Path | str) -> Path:
|
|
|
199
222
|
try:
|
|
200
223
|
file_path = temp_file.name
|
|
201
224
|
|
|
202
|
-
for
|
|
225
|
+
for progress, total_size in _stream_url_data_to_file(
|
|
226
|
+
url, temp_file.name, request_headers=request_headers
|
|
227
|
+
):
|
|
203
228
|
if total_size:
|
|
204
229
|
progress_msg = f"Downloading {url} ... {progress:.2%}"
|
|
205
230
|
else:
|
|
206
231
|
progress_msg = f"Downloading {url} ... {progress:.2f} MB"
|
|
232
|
+
|
|
207
233
|
print(progress_msg, end="\r\n")
|
|
208
234
|
|
|
209
235
|
# Move the file when the file is downloaded completely. Since the
|
|
@@ -219,7 +245,12 @@ def _download_file_python(url: str, target_path: Path | str) -> Path:
|
|
|
219
245
|
return Path(target_path)
|
|
220
246
|
|
|
221
247
|
|
|
222
|
-
def _stream_url_data_to_file(
|
|
248
|
+
def _stream_url_data_to_file(
|
|
249
|
+
url: str,
|
|
250
|
+
file_path: str,
|
|
251
|
+
chunk_size_in_mb: int = 64,
|
|
252
|
+
request_headers: dict[str, str] | None = None,
|
|
253
|
+
):
|
|
223
254
|
"""Download data from a URL and stream it to a file.
|
|
224
255
|
|
|
225
256
|
Note:
|
|
@@ -233,6 +264,8 @@ def _stream_url_data_to_file(url: str, file_path: str, chunk_size_in_mb: int = 6
|
|
|
233
264
|
file_path: The path to the file where the downloaded data will be saved.
|
|
234
265
|
chunk_size_in_mb: The size of each download chunk in megabytes.
|
|
235
266
|
Defaults to 64.
|
|
267
|
+
request_headers: A dictionary containing additional headers to be included in
|
|
268
|
+
the HTTP request. Defaults to `None`.
|
|
236
269
|
|
|
237
270
|
Yields:
|
|
238
271
|
A tuple containing two elements:
|
|
@@ -244,7 +277,8 @@ def _stream_url_data_to_file(url: str, file_path: str, chunk_size_in_mb: int = 6
|
|
|
244
277
|
"""
|
|
245
278
|
ONE_MB = 1024**2
|
|
246
279
|
|
|
247
|
-
|
|
280
|
+
headers = {**TEMP_HEADERS, **(request_headers or {})}
|
|
281
|
+
request = Request(url, headers=headers)
|
|
248
282
|
|
|
249
283
|
received_size = 0
|
|
250
284
|
total_size = 0
|
|
@@ -267,7 +301,9 @@ def _stream_url_data_to_file(url: str, file_path: str, chunk_size_in_mb: int = 6
|
|
|
267
301
|
raise DownloadError("Received less data than expected from the server.")
|
|
268
302
|
|
|
269
303
|
|
|
270
|
-
def download_model_weights(
|
|
304
|
+
def download_model_weights(
|
|
305
|
+
url: str, force: bool = False, request_headers: dict[str, str] | None = None
|
|
306
|
+
) -> Path:
|
|
271
307
|
"""Downloads model weights from the specified URL and saves them to a
|
|
272
308
|
predefined directory.
|
|
273
309
|
|
|
@@ -284,6 +320,8 @@ def download_model_weights(url: str, force: bool = False):
|
|
|
284
320
|
force: If `True`, the model weights are downloaded even if they already exist
|
|
285
321
|
locally and their content length matches the expected content length from
|
|
286
322
|
the remote file. Defaults to `False`.
|
|
323
|
+
request_headers: A dictionary containing additional headers to be included in
|
|
324
|
+
the HTTP request. Defaults to `None`.
|
|
287
325
|
|
|
288
326
|
Returns:
|
|
289
327
|
A Path object representing the full path to the downloaded model weights.
|
|
@@ -303,6 +341,7 @@ def download_model_weights(url: str, force: bool = False):
|
|
|
303
341
|
url,
|
|
304
342
|
target_dir=weights_dir,
|
|
305
343
|
force=force,
|
|
344
|
+
request_headers=request_headers,
|
|
306
345
|
)
|
|
307
346
|
|
|
308
347
|
|
|
@@ -48,8 +48,6 @@ def addition_app(input: Input) -> Output:
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
nomad_addition_app = addition_app.on(_scheduler="nomad")
|
|
51
|
-
kubernetes_addition_app = addition_app.on(_scheduler="kubernetes")
|
|
52
|
-
|
|
53
51
|
|
|
54
52
|
@fal.function(
|
|
55
53
|
keep_alive=300,
|
|
@@ -203,20 +201,6 @@ def test_nomad_app():
|
|
|
203
201
|
yield f"{user_id}/{app_revision}"
|
|
204
202
|
|
|
205
203
|
|
|
206
|
-
@pytest.fixture(scope="module")
|
|
207
|
-
def test_kubernetes_app():
|
|
208
|
-
# Create a temporary app, register it, and return the ID of it.
|
|
209
|
-
|
|
210
|
-
from fal.cli.deploy import _get_user_id
|
|
211
|
-
|
|
212
|
-
app_revision = kubernetes_addition_app.host.register(
|
|
213
|
-
func=nomad_addition_app.func,
|
|
214
|
-
options=kubernetes_addition_app.options,
|
|
215
|
-
)
|
|
216
|
-
user_id = _get_user_id()
|
|
217
|
-
yield f"{user_id}/{app_revision}"
|
|
218
|
-
|
|
219
|
-
|
|
220
204
|
@pytest.fixture(scope="module")
|
|
221
205
|
def test_fastapi_app():
|
|
222
206
|
# Create a temporary app, register it, and return the ID of it.
|
|
@@ -426,7 +410,7 @@ def test_app_update_app(aliased_app: tuple[str, str]):
|
|
|
426
410
|
assert res.max_multiplexing == new_max_multiplexing
|
|
427
411
|
|
|
428
412
|
with host._connection as client:
|
|
429
|
-
new_max_concurrency = new_max_concurrency
|
|
413
|
+
new_max_concurrency = new_max_concurrency - 1
|
|
430
414
|
res = client.update_application(
|
|
431
415
|
application_name=app_alias,
|
|
432
416
|
max_concurrency=new_max_concurrency,
|
|
@@ -67,6 +67,40 @@ def test_function_pipelining(isolated_client):
|
|
|
67
67
|
assert calling_function(regular_function()) == 84
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def test_function_exception(isolated_client):
|
|
71
|
+
class FooException(Exception):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
class BarException(Exception):
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
class MyFuncException(Exception):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def foo():
|
|
81
|
+
raise FooException("foo")
|
|
82
|
+
|
|
83
|
+
def bar():
|
|
84
|
+
try:
|
|
85
|
+
foo()
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
raise BarException("bar") from exc
|
|
88
|
+
|
|
89
|
+
@isolated_client()
|
|
90
|
+
def myfunc():
|
|
91
|
+
try:
|
|
92
|
+
bar()
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
raise MyFuncException("myfunc") from exc
|
|
95
|
+
|
|
96
|
+
with pytest.raises(MyFuncException) as excinfo:
|
|
97
|
+
myfunc()
|
|
98
|
+
|
|
99
|
+
bar_exc = excinfo.value.__cause__
|
|
100
|
+
assert isinstance(bar_exc, BarException)
|
|
101
|
+
assert isinstance(bar_exc.__cause__, FooException)
|
|
102
|
+
|
|
103
|
+
|
|
70
104
|
@pytest.mark.xfail(reason="See https://github.com/fal-ai/fal/issues/169")
|
|
71
105
|
def test_function_calling_other_function(isolated_client):
|
|
72
106
|
try:
|
|
@@ -341,22 +375,6 @@ def test_futures(isolated_client):
|
|
|
341
375
|
assert future_4.result() == 8
|
|
342
376
|
|
|
343
377
|
|
|
344
|
-
def test_conda_environment(isolated_client):
|
|
345
|
-
@isolated_client(
|
|
346
|
-
"conda",
|
|
347
|
-
packages=["pyjokes=0.6.0"],
|
|
348
|
-
machine_type="L",
|
|
349
|
-
# conda is only supported on Kubernetes
|
|
350
|
-
_scheduler="kubernetes",
|
|
351
|
-
)
|
|
352
|
-
def regular_function():
|
|
353
|
-
import pyjokes
|
|
354
|
-
|
|
355
|
-
return pyjokes.__version__
|
|
356
|
-
|
|
357
|
-
assert regular_function() == "0.6.0"
|
|
358
|
-
|
|
359
|
-
|
|
360
378
|
@pytest.mark.xfail(reason="Nomad does not support conda")
|
|
361
379
|
def test_conda_environment_on_nomad(isolated_client):
|
|
362
380
|
@isolated_client(
|
|
@@ -374,6 +392,7 @@ def test_conda_environment_on_nomad(isolated_client):
|
|
|
374
392
|
assert regular_function() == "0.6.0"
|
|
375
393
|
|
|
376
394
|
|
|
395
|
+
@pytest.mark.xfail(reason="Broken on nomad")
|
|
377
396
|
def test_cached_function(isolated_client, capsys, monkeypatch):
|
|
378
397
|
import inspect
|
|
379
398
|
import time
|
|
@@ -410,7 +429,6 @@ def test_cached_function(isolated_client, capsys, monkeypatch):
|
|
|
410
429
|
"virtualenv",
|
|
411
430
|
keep_alive=30,
|
|
412
431
|
requirements=["pyjokes "],
|
|
413
|
-
_scheduler="kubernetes",
|
|
414
432
|
)
|
|
415
433
|
def regular_function(n):
|
|
416
434
|
if get_pipe() == "pipe":
|
|
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
|
{fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.0.1 → fal-1.0.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents_type_0.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
|