fal 1.1.1__tar.gz → 1.2.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.1.1 → fal-1.2.1}/PKG-INFO +1 -1
- {fal-1.1.1 → fal-1.2.1}/fal.egg-info/PKG-INFO +1 -1
- {fal-1.1.1 → fal-1.2.1}/src/fal/_fal_version.py +2 -2
- {fal-1.1.1 → fal-1.2.1}/src/fal/_serialization.py +3 -4
- {fal-1.1.1 → fal-1.2.1}/src/fal/apps.py +1 -2
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/apps.py +0 -1
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/debug.py +1 -5
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/deploy.py +8 -7
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/keys.py +4 -1
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/run.py +1 -4
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/secrets.py +3 -5
- {fal-1.1.1 → fal-1.2.1}/src/fal/exceptions/_base.py +1 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/logging/isolate.py +20 -1
- {fal-1.1.1 → fal-1.2.1}/src/fal/sdk.py +1 -1
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/file/file.py +1 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/file/providers/fal.py +3 -1
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/file/providers/gcp.py +6 -1
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/file/providers/r2.py +6 -1
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/image/image.py +6 -7
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/utils/download_utils.py +48 -39
- {fal-1.1.1 → fal-1.2.1}/src/fal/utils.py +0 -1
- {fal-1.1.1 → fal-1.2.1}/tests/cli/test_apps.py +11 -6
- {fal-1.1.1 → fal-1.2.1}/tests/cli/test_keys.py +4 -2
- {fal-1.1.1 → fal-1.2.1}/tests/integration_test.py +0 -1
- {fal-1.1.1 → fal-1.2.1}/tests/test_stability.py +16 -7
- {fal-1.1.1 → fal-1.2.1}/tests/toolkit/file_test.py +1 -39
- {fal-1.1.1 → fal-1.2.1}/.gitignore +0 -0
- {fal-1.1.1 → fal-1.2.1}/README.md +0 -0
- {fal-1.1.1 → fal-1.2.1}/fal.egg-info/SOURCES.txt +0 -0
- {fal-1.1.1 → fal-1.2.1}/fal.egg-info/dependency_links.txt +0 -0
- {fal-1.1.1 → fal-1.2.1}/fal.egg-info/entry_points.txt +0 -0
- {fal-1.1.1 → fal-1.2.1}/fal.egg-info/requires.txt +0 -0
- {fal-1.1.1 → fal-1.2.1}/fal.egg-info/top_level.txt +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/README.md +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/pyproject.toml +0 -0
- {fal-1.1.1 → fal-1.2.1}/openapi_rest.config.yaml +0 -0
- {fal-1.1.1 → fal-1.2.1}/pyproject.toml +0 -0
- {fal-1.1.1 → fal-1.2.1}/setup.cfg +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/__main__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/_version.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/api.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/app.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/auth/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/auth/auth0.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/auth/local.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/auth.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/doctor.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/main.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/cli/parser.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/console/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/console/icons.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/console/ux.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/container.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/exceptions/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/exceptions/auth.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/flags.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/logging/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/logging/style.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/logging/trace.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/logging/user.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/py.typed +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/rest_client.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/sync.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/exceptions.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/file/types.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/optimize.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/src/fal/workflows.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/cli/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/cli/test_auth.py +1 -1
- {fal-1.1.1 → fal-1.2.1}/tests/cli/test_deploy.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/cli/test_run.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/cli/test_secrets.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/conftest.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/mainify_package/__init__.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/mainify_package/impl.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/mainify_package/utils.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/mainify_target.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/test_apps.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tests/toolkit/image_test.py +0 -0
- {fal-1.1.1 → fal-1.2.1}/tools/demo_script.py +0 -0
{fal-1.1.1 → fal-1.2.1}/PKG-INFO
RENAMED
|
@@ -186,8 +186,8 @@ def _patch_rlock() -> None:
|
|
|
186
186
|
|
|
187
187
|
def pickle_rlock(obj: RLockType) -> tuple[Callable, tuple]:
|
|
188
188
|
r = obj.__repr__()
|
|
189
|
-
count = int(r.split(
|
|
190
|
-
owner = int(r.split(
|
|
189
|
+
count = int(r.split("count=")[1].split()[0].rstrip(">"))
|
|
190
|
+
owner = int(r.split("owner=")[1].split()[0])
|
|
191
191
|
|
|
192
192
|
return create_rlock, (count, owner)
|
|
193
193
|
|
|
@@ -209,7 +209,7 @@ def _patch_console_thread_locals() -> None:
|
|
|
209
209
|
"buffer": obj.buffer,
|
|
210
210
|
"buffer_index": obj.buffer_index,
|
|
211
211
|
}
|
|
212
|
-
return create_locals, (kwargs,
|
|
212
|
+
return create_locals, (kwargs,)
|
|
213
213
|
|
|
214
214
|
_register(ConsoleThreadLocals, pickle_locals)
|
|
215
215
|
|
|
@@ -231,4 +231,3 @@ def patch_pickle() -> None:
|
|
|
231
231
|
_patch_exceptions()
|
|
232
232
|
|
|
233
233
|
_register_pickle_by_value("fal")
|
|
234
|
-
|
|
@@ -47,11 +47,7 @@ def debugtools(args):
|
|
|
47
47
|
def get_debug_parser():
|
|
48
48
|
parser = FalParser(add_help=False)
|
|
49
49
|
group = parser.add_argument_group(title="Debug")
|
|
50
|
-
group.add_argument(
|
|
51
|
-
"--debug",
|
|
52
|
-
action="store_true",
|
|
53
|
-
help="Show verbose errors."
|
|
54
|
-
)
|
|
50
|
+
group.add_argument("--debug", action="store_true", help="Show verbose errors.")
|
|
55
51
|
group.add_argument(
|
|
56
52
|
"--pdb",
|
|
57
53
|
action="store_true",
|
|
@@ -69,9 +69,7 @@ def _deploy(args):
|
|
|
69
69
|
# Try to find a python file in the current directory
|
|
70
70
|
options = list(Path(".").glob("*.py"))
|
|
71
71
|
if len(options) == 0:
|
|
72
|
-
raise FalServerlessError(
|
|
73
|
-
"No python files found in the current directory"
|
|
74
|
-
)
|
|
72
|
+
raise FalServerlessError("No python files found in the current directory")
|
|
75
73
|
elif len(options) > 1:
|
|
76
74
|
raise FalServerlessError(
|
|
77
75
|
"Multiple python files found in the current directory. "
|
|
@@ -107,8 +105,12 @@ def _deploy(args):
|
|
|
107
105
|
"Registered a new revision for function "
|
|
108
106
|
f"'{app_name}' (revision='{app_id}')."
|
|
109
107
|
)
|
|
110
|
-
args.console.print(
|
|
111
|
-
|
|
108
|
+
args.console.print(
|
|
109
|
+
f"Playground: https://fal.ai/models/{user.username}/{app_name}"
|
|
110
|
+
)
|
|
111
|
+
args.console.print(
|
|
112
|
+
f"Endpoint: https://{gateway_host}/{user.username}/{app_name}"
|
|
113
|
+
)
|
|
112
114
|
|
|
113
115
|
|
|
114
116
|
def add_parser(main_subparsers, parents):
|
|
@@ -139,8 +141,7 @@ def add_parser(main_subparsers, parents):
|
|
|
139
141
|
nargs="?",
|
|
140
142
|
action=RefAction,
|
|
141
143
|
help=(
|
|
142
|
-
"Application reference. "
|
|
143
|
-
"For example: `myfile.py::MyApp`, `myfile.py`."
|
|
144
|
+
"Application reference. " "For example: `myfile.py::MyApp`, `myfile.py`."
|
|
144
145
|
),
|
|
145
146
|
)
|
|
146
147
|
parser.add_argument(
|
|
@@ -56,7 +56,10 @@ def _list(args):
|
|
|
56
56
|
keys = connection.list_user_keys()
|
|
57
57
|
for key in keys:
|
|
58
58
|
table.add_row(
|
|
59
|
-
key.key_id,
|
|
59
|
+
key.key_id,
|
|
60
|
+
str(key.created_at),
|
|
61
|
+
str(key.scope.value),
|
|
62
|
+
key.alias,
|
|
60
63
|
)
|
|
61
64
|
|
|
62
65
|
args.console.print(table)
|
|
@@ -14,10 +14,7 @@ def _run(args):
|
|
|
14
14
|
|
|
15
15
|
def add_parser(main_subparsers, parents):
|
|
16
16
|
run_help = "Run fal function."
|
|
17
|
-
epilog =
|
|
18
|
-
"Examples:\n"
|
|
19
|
-
" fal run path/to/myfile.py::myfunc"
|
|
20
|
-
)
|
|
17
|
+
epilog = "Examples:\n" " fal run path/to/myfile.py::myfunc"
|
|
21
18
|
parser = main_subparsers.add_parser(
|
|
22
19
|
"run",
|
|
23
20
|
description=run_help,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
1
|
from .parser import DictAction, FalClientParser
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
def _set(args):
|
|
6
5
|
from fal.sdk import FalServerlessClient
|
|
6
|
+
|
|
7
7
|
client = FalServerlessClient(args.host)
|
|
8
8
|
with client.connect() as connection:
|
|
9
9
|
for name, value in args.secrets.items():
|
|
@@ -12,10 +12,7 @@ def _set(args):
|
|
|
12
12
|
|
|
13
13
|
def _add_set_parser(subparsers, parents):
|
|
14
14
|
set_help = "Set a secret."
|
|
15
|
-
epilog =
|
|
16
|
-
"Examples:\n"
|
|
17
|
-
" fal secrets set HF_TOKEN=hf_***"
|
|
18
|
-
)
|
|
15
|
+
epilog = "Examples:\n" " fal secrets set HF_TOKEN=hf_***"
|
|
19
16
|
|
|
20
17
|
parser = subparsers.add_parser(
|
|
21
18
|
"set",
|
|
@@ -64,6 +61,7 @@ def _add_list_parser(subparsers, parents):
|
|
|
64
61
|
|
|
65
62
|
def _unset(args):
|
|
66
63
|
from fal.sdk import FalServerlessClient
|
|
64
|
+
|
|
67
65
|
client = FalServerlessClient(args.host)
|
|
68
66
|
with client.connect() as connection:
|
|
69
67
|
connection.delete_secret(args.secret)
|
|
@@ -13,16 +13,35 @@ _renderer = ConsoleRenderer(level_styles=LEVEL_STYLES)
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class IsolateLogPrinter:
|
|
16
|
-
|
|
17
16
|
debug: bool
|
|
18
17
|
|
|
19
18
|
def __init__(self, debug: bool = False) -> None:
|
|
20
19
|
self.debug = debug
|
|
20
|
+
self._current_source: LogSource | None = None
|
|
21
|
+
|
|
22
|
+
def _maybe_print_header(self, source: LogSource):
|
|
23
|
+
from fal.console import console
|
|
24
|
+
|
|
25
|
+
if source == self._current_source:
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
msg = {
|
|
29
|
+
LogSource.BUILDER: "Building the environment",
|
|
30
|
+
LogSource.BRIDGE: "Unpacking user code",
|
|
31
|
+
LogSource.USER: "Running",
|
|
32
|
+
}.get(source)
|
|
33
|
+
|
|
34
|
+
if msg:
|
|
35
|
+
console.print(f"==> {msg}", style="bold green")
|
|
36
|
+
|
|
37
|
+
self._current_source = source
|
|
21
38
|
|
|
22
39
|
def print(self, log: Log):
|
|
23
40
|
if log.level < LogLevel.INFO and not self.debug:
|
|
24
41
|
return
|
|
25
42
|
|
|
43
|
+
self._maybe_print_header(log.source)
|
|
44
|
+
|
|
26
45
|
if log.source == LogSource.USER:
|
|
27
46
|
stream = sys.stderr if log.level == LogLevel.STDERR else sys.stdout
|
|
28
47
|
print(log.message, file=stream)
|
|
@@ -277,7 +277,7 @@ class KeyScope(enum.Enum):
|
|
|
277
277
|
|
|
278
278
|
@from_grpc.register(isolate_proto.ApplicationInfo)
|
|
279
279
|
def _from_grpc_application_info(
|
|
280
|
-
message: isolate_proto.ApplicationInfo
|
|
280
|
+
message: isolate_proto.ApplicationInfo,
|
|
281
281
|
) -> ApplicationInfo:
|
|
282
282
|
return ApplicationInfo(
|
|
283
283
|
application_id=message.application_id,
|
|
@@ -41,7 +41,9 @@ class FalFileRepositoryBase(FileRepository):
|
|
|
41
41
|
|
|
42
42
|
grpc_host = os.environ.get("FAL_HOST", "api.alpha.fal.ai")
|
|
43
43
|
rest_host = grpc_host.replace("api", "rest", 1)
|
|
44
|
-
storage_url =
|
|
44
|
+
storage_url = (
|
|
45
|
+
f"https://{rest_host}/storage/upload/initiate?storage_type={storage_type}"
|
|
46
|
+
)
|
|
45
47
|
|
|
46
48
|
try:
|
|
47
49
|
req = Request(
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import datetime
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
+
import posixpath
|
|
7
|
+
import uuid
|
|
6
8
|
from dataclasses import dataclass
|
|
7
9
|
|
|
8
10
|
from fal.toolkit.file.types import FileData, FileRepository
|
|
@@ -49,7 +51,10 @@ class GoogleStorageRepository(FileRepository):
|
|
|
49
51
|
return self._bucket
|
|
50
52
|
|
|
51
53
|
def save(self, data: FileData) -> str:
|
|
52
|
-
destination_path =
|
|
54
|
+
destination_path = posixpath.join(
|
|
55
|
+
self.folder,
|
|
56
|
+
f"{uuid.uuid4().hex}_{data.file_name}",
|
|
57
|
+
)
|
|
53
58
|
|
|
54
59
|
gcp_blob = self.bucket.blob(destination_path)
|
|
55
60
|
gcp_blob.upload_from_string(data.data, content_type=data.content_type)
|
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import posixpath
|
|
6
|
+
import uuid
|
|
5
7
|
from dataclasses import dataclass
|
|
6
8
|
from io import BytesIO
|
|
7
9
|
|
|
@@ -66,7 +68,10 @@ class R2Repository(FileRepository):
|
|
|
66
68
|
return self._bucket
|
|
67
69
|
|
|
68
70
|
def save(self, data: FileData) -> str:
|
|
69
|
-
destination_path =
|
|
71
|
+
destination_path = posixpath.join(
|
|
72
|
+
self.key,
|
|
73
|
+
f"{uuid.uuid4().hex}_{data.file_name}",
|
|
74
|
+
)
|
|
70
75
|
|
|
71
76
|
s3_object = self.bucket.Object(destination_path)
|
|
72
77
|
s3_object.upload_fileobj(
|
|
@@ -44,6 +44,7 @@ IMAGE_SIZE_PRESETS: dict[ImageSizePreset, ImageSize] = {
|
|
|
44
44
|
|
|
45
45
|
ImageSizeInput = Union[ImageSize, ImageSizePreset]
|
|
46
46
|
|
|
47
|
+
|
|
47
48
|
def get_image_size(source: ImageSizeInput) -> ImageSize:
|
|
48
49
|
if isinstance(source, ImageSize):
|
|
49
50
|
return source
|
|
@@ -62,7 +63,8 @@ class Image(File):
|
|
|
62
63
|
"""
|
|
63
64
|
|
|
64
65
|
width: Optional[int] = Field(
|
|
65
|
-
None,
|
|
66
|
+
None,
|
|
67
|
+
description="The width of the image in pixels.",
|
|
66
68
|
examples=[1024],
|
|
67
69
|
)
|
|
68
70
|
height: Optional[int] = Field(
|
|
@@ -84,8 +86,8 @@ class Image(File):
|
|
|
84
86
|
file_name=file_name,
|
|
85
87
|
repository=repository,
|
|
86
88
|
)
|
|
87
|
-
obj.width=size.width if size else None
|
|
88
|
-
obj.height=size.height if size else None
|
|
89
|
+
obj.width = size.width if size else None
|
|
90
|
+
obj.height = size.height if size else None
|
|
89
91
|
return obj
|
|
90
92
|
|
|
91
93
|
@classmethod
|
|
@@ -120,9 +122,7 @@ class Image(File):
|
|
|
120
122
|
from PIL import Image as PILImage
|
|
121
123
|
from PIL import ImageOps
|
|
122
124
|
except ImportError:
|
|
123
|
-
raise ImportError(
|
|
124
|
-
"The PIL package is required to use Image.to_pil()."
|
|
125
|
-
)
|
|
125
|
+
raise ImportError("The PIL package is required to use Image.to_pil().")
|
|
126
126
|
|
|
127
127
|
# Stream the image data from url to a temp file and convert it to a PIL image
|
|
128
128
|
with NamedTemporaryFile() as temp_file:
|
|
@@ -134,4 +134,3 @@ class Image(File):
|
|
|
134
134
|
img = ImageOps.exif_transpose(img)
|
|
135
135
|
|
|
136
136
|
return img
|
|
137
|
-
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import hashlib
|
|
4
|
+
import os
|
|
4
5
|
import shutil
|
|
5
6
|
import subprocess
|
|
6
7
|
import sys
|
|
7
8
|
from pathlib import Path, PurePath
|
|
8
|
-
from tempfile import TemporaryDirectory
|
|
9
|
+
from tempfile import NamedTemporaryFile, TemporaryDirectory
|
|
9
10
|
from urllib.parse import urlparse
|
|
10
11
|
from urllib.request import Request, urlopen
|
|
11
12
|
|
|
@@ -215,10 +216,14 @@ def _download_file_python(
|
|
|
215
216
|
Returns:
|
|
216
217
|
The path where the downloaded file has been saved.
|
|
217
218
|
"""
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
with
|
|
219
|
+
basename = os.path.basename(target_path)
|
|
220
|
+
# NOTE: using the same directory to avoid potential copies across temp fs and target
|
|
221
|
+
# fs, and also to be able to atomically rename a downloaded file into place.
|
|
222
|
+
with NamedTemporaryFile(
|
|
223
|
+
delete=False,
|
|
224
|
+
dir=os.path.dirname(target_path),
|
|
225
|
+
prefix=f"{basename}.tmp",
|
|
226
|
+
) as temp_file:
|
|
222
227
|
try:
|
|
223
228
|
file_path = temp_file.name
|
|
224
229
|
|
|
@@ -232,13 +237,14 @@ def _download_file_python(
|
|
|
232
237
|
|
|
233
238
|
print(progress_msg, end="\r\n")
|
|
234
239
|
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
|
|
240
|
+
# NOTE: Atomically renaming the file into place when the file is downloaded
|
|
241
|
+
# completely.
|
|
242
|
+
#
|
|
243
|
+
# Since the file used is temporary, in a case of an interruption, the
|
|
244
|
+
# downloaded content will be lost. So, it is safe to redownload the file in
|
|
245
|
+
# such cases.
|
|
246
|
+
os.rename(file_path, target_path)
|
|
239
247
|
|
|
240
|
-
except Exception as error:
|
|
241
|
-
raise error
|
|
242
248
|
finally:
|
|
243
249
|
Path(temp_file.name).unlink(missing_ok=True)
|
|
244
250
|
|
|
@@ -403,35 +409,38 @@ def clone_repository(
|
|
|
403
409
|
print(f"Removing the existing repository: {local_repo_path} ")
|
|
404
410
|
shutil.rmtree(local_repo_path)
|
|
405
411
|
|
|
406
|
-
#
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
"
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
subprocess.check_call(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
412
|
+
# NOTE: using the target_dir to be able to avoid potential copies across temp fs
|
|
413
|
+
# and target fs, and also to be able to atomically rename repo_name dir into place
|
|
414
|
+
# when we are done setting it up.
|
|
415
|
+
os.makedirs(target_dir, exist_ok=True) # type: ignore[arg-type]
|
|
416
|
+
with TemporaryDirectory(
|
|
417
|
+
dir=target_dir,
|
|
418
|
+
suffix=f"{local_repo_path.name}.tmp",
|
|
419
|
+
) as temp_dir:
|
|
420
|
+
try:
|
|
421
|
+
print(f"Cloning the repository '{https_url}' .")
|
|
422
|
+
|
|
423
|
+
# Clone with disabling the logs and advices for detached HEAD state.
|
|
424
|
+
clone_command = [
|
|
425
|
+
"git",
|
|
426
|
+
"clone",
|
|
427
|
+
"--recursive",
|
|
428
|
+
https_url,
|
|
429
|
+
temp_dir,
|
|
430
|
+
]
|
|
431
|
+
subprocess.check_call(clone_command)
|
|
432
|
+
|
|
433
|
+
if commit_hash:
|
|
434
|
+
checkout_command = ["git", "checkout", commit_hash]
|
|
435
|
+
subprocess.check_call(checkout_command, cwd=temp_dir)
|
|
436
|
+
|
|
437
|
+
# NOTE: Atomically renaming the repository directory into place when the
|
|
438
|
+
# clone and checkout are done.
|
|
439
|
+
os.rename(temp_dir, local_repo_path)
|
|
433
440
|
|
|
434
|
-
|
|
441
|
+
except Exception as error:
|
|
442
|
+
print(f"{error}\nFailed to clone repository '{https_url}' .")
|
|
443
|
+
raise error
|
|
435
444
|
|
|
436
445
|
if include_to_path:
|
|
437
446
|
__add_local_path_to_sys_path(local_repo_path)
|
|
@@ -31,11 +31,17 @@ def test_set_rev():
|
|
|
31
31
|
def test_scale():
|
|
32
32
|
args = parse_args(
|
|
33
33
|
[
|
|
34
|
-
"apps",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"--
|
|
38
|
-
"
|
|
34
|
+
"apps",
|
|
35
|
+
"scale",
|
|
36
|
+
"myapp",
|
|
37
|
+
"--keep-alive",
|
|
38
|
+
"123",
|
|
39
|
+
"--max-multiplexing",
|
|
40
|
+
"321",
|
|
41
|
+
"--min-concurrency",
|
|
42
|
+
"7",
|
|
43
|
+
"--max-concurrency",
|
|
44
|
+
"10",
|
|
39
45
|
]
|
|
40
46
|
)
|
|
41
47
|
assert args.func == _scale
|
|
@@ -61,4 +67,3 @@ def test_delete_rev():
|
|
|
61
67
|
args = parse_args(["apps", "delete-rev", "myrev"])
|
|
62
68
|
assert args.func == _delete_rev
|
|
63
69
|
assert args.app_rev == "myrev"
|
|
64
|
-
|
|
@@ -556,7 +556,6 @@ def test_fal_compressed_file(isolated_client):
|
|
|
556
556
|
|
|
557
557
|
|
|
558
558
|
def test_fal_cdn(isolated_client):
|
|
559
|
-
|
|
560
559
|
@isolated_client(requirements=[f"pydantic=={pydantic_version}"])
|
|
561
560
|
def upload_to_fal_cdn() -> FalImage:
|
|
562
561
|
return FalImage.from_bytes(b"0", "jpeg", repository="cdn")
|
|
@@ -56,7 +56,10 @@ def test_regular_function_on_nomad(isolated_client):
|
|
|
56
56
|
|
|
57
57
|
assert mult(5, 2) == 10
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
@pytest.mark.xfail(
|
|
61
|
+
reason="The support needs to be deployed. See https://github.com/fal-ai/isolate-cloud/pull/1809"
|
|
62
|
+
)
|
|
60
63
|
def test_regular_function_in_a_container(isolated_client):
|
|
61
64
|
@isolated_client("container")
|
|
62
65
|
def regular_function():
|
|
@@ -70,7 +73,10 @@ def test_regular_function_in_a_container(isolated_client):
|
|
|
70
73
|
|
|
71
74
|
assert mult(5, 2) == 10
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
|
|
77
|
+
@pytest.mark.xfail(
|
|
78
|
+
reason="The support needs to be deployed. See https://github.com/fal-ai/isolate-cloud/pull/1809"
|
|
79
|
+
)
|
|
74
80
|
def test_regular_function_in_a_container_with_custom_image(isolated_client):
|
|
75
81
|
@isolated_client(
|
|
76
82
|
"container",
|
|
@@ -557,11 +563,14 @@ def test_worker_env_vars(isolated_client):
|
|
|
557
563
|
|
|
558
564
|
|
|
559
565
|
@pytest.mark.parametrize(
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
566
|
+
"repo_type, url_prefix",
|
|
567
|
+
[
|
|
568
|
+
(
|
|
569
|
+
"fal",
|
|
570
|
+
"https://storage.googleapis.com/isolate-dev-smiling-shark_toolkit_bucket/",
|
|
571
|
+
),
|
|
572
|
+
("fal_v2", "https://v2.fal.media/files"),
|
|
573
|
+
],
|
|
565
574
|
)
|
|
566
575
|
def test_fal_storage(isolated_client, repo_type, url_prefix):
|
|
567
576
|
file = File.from_bytes(b"Hello fal storage from local", repository=repo_type)
|
|
@@ -4,8 +4,7 @@ import os
|
|
|
4
4
|
from base64 import b64encode
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
-
from fal.toolkit.file.file import
|
|
8
|
-
from fal.toolkit.file.types import FileRepository, RepositoryId
|
|
7
|
+
from fal.toolkit.file.file import File, GoogleStorageRepository
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
def test_binary_content_matches():
|
|
@@ -65,40 +64,3 @@ def test_gcp_storage_if_available():
|
|
|
65
64
|
assert file.url.startswith(
|
|
66
65
|
"https://storage.googleapis.com/fal_registry_image_results/"
|
|
67
66
|
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@pytest.mark.xfail(reason="fal cdn is temporarily broken")
|
|
71
|
-
@pytest.mark.parametrize(
|
|
72
|
-
"repo",
|
|
73
|
-
BUILT_IN_REPOSITORIES.keys(),
|
|
74
|
-
)
|
|
75
|
-
def test_uniqueness_of_file_name(repo: RepositoryId | FileRepository):
|
|
76
|
-
if repo == "in_memory":
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
if repo == "gcp_storage":
|
|
80
|
-
gcp_sa_json = os.environ.get("GCLOUD_SA_JSON")
|
|
81
|
-
if gcp_sa_json is None:
|
|
82
|
-
pytest.skip(reason="GCLOUD_SA_JSON environment variable is not set")
|
|
83
|
-
repo = GoogleStorageRepository(bucket_name="fal_registry_image_results")
|
|
84
|
-
|
|
85
|
-
if repo == "r2":
|
|
86
|
-
r2_account_json = os.environ.get("R2_CREDS_JSON")
|
|
87
|
-
if r2_account_json is None:
|
|
88
|
-
pytest.skip(reason="R2_CREDS_JSON environment variable is not set")
|
|
89
|
-
|
|
90
|
-
if repo == "fal":
|
|
91
|
-
fal_key = os.environ.get("FAL_KEY")
|
|
92
|
-
if fal_key is None:
|
|
93
|
-
pytest.skip(reason="FAL_KEY environment variable is not set")
|
|
94
|
-
|
|
95
|
-
file = File.from_bytes(b"print('Hello!')", repository=repo, file_name="hello.py")
|
|
96
|
-
|
|
97
|
-
host_and_path = file.url.split("?")[0]
|
|
98
|
-
last_path = host_and_path.split("/")[-1]
|
|
99
|
-
assert (
|
|
100
|
-
last_path.endswith(".py")
|
|
101
|
-
), f"The file name {last_path} should end with the same extension"
|
|
102
|
-
assert (
|
|
103
|
-
len(last_path) > 10
|
|
104
|
-
), f"There should be a long enough random string in the file name {last_path}"
|
|
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.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/api/workflows/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
|
|
File without changes
|
|
File without changes
|
{fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py
RENAMED
|
File without changes
|
{fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.1.1 → fal-1.2.1}/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.1.1 → fal-1.2.1}/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.1.1 → fal-1.2.1}/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.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.1.1 → fal-1.2.1}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py
RENAMED
|
File without changes
|
|
File without changes
|
{fal-1.1.1 → fal-1.2.1}/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
|
|
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
|
|
@@ -6,6 +6,7 @@ def test_login():
|
|
|
6
6
|
args = parse_args(["auth", "login"])
|
|
7
7
|
assert args.func == _login
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
def test_logout():
|
|
10
11
|
args = parse_args(["auth", "logout"])
|
|
11
12
|
assert args.func == _logout
|
|
@@ -14,4 +15,3 @@ def test_logout():
|
|
|
14
15
|
def test_whoami():
|
|
15
16
|
args = parse_args(["auth", "whoami"])
|
|
16
17
|
assert args.func == _whoami
|
|
17
|
-
|
|
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
|