fal 1.2.1__py3-none-any.whl → 1.2.3__py3-none-any.whl
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/_fal_version.py +2 -2
- fal/api.py +3 -1
- fal/app.py +3 -2
- fal/cli/apps.py +4 -2
- fal/cli/create.py +26 -0
- fal/cli/deploy.py +3 -3
- fal/cli/main.py +2 -2
- fal/cli/run.py +1 -1
- fal/toolkit/image/__init__.py +71 -0
- fal/toolkit/image/nsfw_filter/__init__.py +11 -0
- fal/toolkit/image/nsfw_filter/env.py +9 -0
- fal/toolkit/image/nsfw_filter/inference.py +77 -0
- fal/toolkit/image/nsfw_filter/model.py +18 -0
- fal/toolkit/image/nsfw_filter/requirements.txt +4 -0
- fal/toolkit/image/safety_checker.py +107 -0
- fal/utils.py +8 -4
- {fal-1.2.1.dist-info → fal-1.2.3.dist-info}/METADATA +2 -1
- {fal-1.2.1.dist-info → fal-1.2.3.dist-info}/RECORD +21 -14
- {fal-1.2.1.dist-info → fal-1.2.3.dist-info}/WHEEL +1 -1
- {fal-1.2.1.dist-info → fal-1.2.3.dist-info}/entry_points.txt +0 -0
- {fal-1.2.1.dist-info → fal-1.2.3.dist-info}/top_level.txt +0 -0
fal/_fal_version.py
CHANGED
fal/api.py
CHANGED
|
@@ -1048,7 +1048,9 @@ class BaseServable:
|
|
|
1048
1048
|
from uvicorn import Config
|
|
1049
1049
|
|
|
1050
1050
|
app = self._build_app()
|
|
1051
|
-
server = Server(
|
|
1051
|
+
server = Server(
|
|
1052
|
+
config=Config(app, host="0.0.0.0", port=8080, timeout_keep_alive=300)
|
|
1053
|
+
)
|
|
1052
1054
|
metrics_app = FastAPI()
|
|
1053
1055
|
metrics_app.add_route("/metrics", handle_metrics)
|
|
1054
1056
|
metrics_server = Server(config=Config(metrics_app, host="0.0.0.0", port=9090))
|
fal/app.py
CHANGED
|
@@ -7,7 +7,7 @@ import re
|
|
|
7
7
|
import time
|
|
8
8
|
import typing
|
|
9
9
|
from contextlib import asynccontextmanager, contextmanager
|
|
10
|
-
from typing import Any, Callable, ClassVar, TypeVar
|
|
10
|
+
from typing import Any, Callable, ClassVar, Literal, TypeVar
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
13
|
from fastapi import FastAPI
|
|
@@ -152,12 +152,13 @@ class App(fal.api.BaseServable):
|
|
|
152
152
|
"keep_alive": 60,
|
|
153
153
|
}
|
|
154
154
|
app_name: ClassVar[str]
|
|
155
|
+
app_auth: ClassVar[Literal["private", "public", "shared"]] = "private"
|
|
155
156
|
|
|
156
157
|
def __init_subclass__(cls, **kwargs):
|
|
157
158
|
app_name = kwargs.pop("name", None) or _to_fal_app_name(cls.__name__)
|
|
158
159
|
parent_settings = getattr(cls, "host_kwargs", {})
|
|
159
160
|
cls.host_kwargs = {**parent_settings, **kwargs}
|
|
160
|
-
cls.app_name = app_name
|
|
161
|
+
cls.app_name = getattr(cls, "app_name", app_name)
|
|
161
162
|
|
|
162
163
|
if cls.__init__ is not App.__init__:
|
|
163
164
|
raise ValueError(
|
fal/cli/apps.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
5
|
from .parser import FalClientParser
|
|
@@ -6,7 +8,7 @@ if TYPE_CHECKING:
|
|
|
6
8
|
from fal.sdk import AliasInfo, ApplicationInfo
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
def _apps_table(apps: list[
|
|
11
|
+
def _apps_table(apps: list[AliasInfo]):
|
|
10
12
|
from rich.table import Table
|
|
11
13
|
|
|
12
14
|
table = Table()
|
|
@@ -56,7 +58,7 @@ def _add_list_parser(subparsers, parents):
|
|
|
56
58
|
parser.set_defaults(func=_list)
|
|
57
59
|
|
|
58
60
|
|
|
59
|
-
def _app_rev_table(revs: list[
|
|
61
|
+
def _app_rev_table(revs: list[ApplicationInfo]):
|
|
60
62
|
from rich.table import Table
|
|
61
63
|
|
|
62
64
|
table = Table()
|
fal/cli/create.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
PROJECT_TYPES = ["app"]
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _create_project(project_type: str):
|
|
5
|
+
from cookiecutter.main import cookiecutter
|
|
6
|
+
|
|
7
|
+
cookiecutter("https://github.com/fal-ai/cookiecutter-fal.git")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def add_parser(main_subparsers, parents):
|
|
11
|
+
apps_help = "Create fal applications."
|
|
12
|
+
parser = main_subparsers.add_parser(
|
|
13
|
+
"create",
|
|
14
|
+
description=apps_help,
|
|
15
|
+
help=apps_help,
|
|
16
|
+
parents=parents,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
metavar="project_type",
|
|
21
|
+
choices=PROJECT_TYPES,
|
|
22
|
+
help="Type of project to create.",
|
|
23
|
+
dest="project_type",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
parser.set_defaults(func=_create_project)
|
fal/cli/deploy.py
CHANGED
|
@@ -81,17 +81,18 @@ def _deploy(args):
|
|
|
81
81
|
|
|
82
82
|
user = _get_user()
|
|
83
83
|
host = FalServerlessHost(args.host)
|
|
84
|
-
isolated_function, app_name = load_function_from(
|
|
84
|
+
isolated_function, app_name, app_auth = load_function_from(
|
|
85
85
|
host,
|
|
86
86
|
file_path,
|
|
87
87
|
func_name,
|
|
88
88
|
)
|
|
89
89
|
app_name = args.app_name or app_name
|
|
90
|
+
app_auth = args.auth or app_auth or "private"
|
|
90
91
|
app_id = host.register(
|
|
91
92
|
func=isolated_function.func,
|
|
92
93
|
options=isolated_function.options,
|
|
93
94
|
application_name=app_name,
|
|
94
|
-
application_auth_mode=
|
|
95
|
+
application_auth_mode=app_auth,
|
|
95
96
|
metadata=isolated_function.options.host.get("metadata", {}),
|
|
96
97
|
)
|
|
97
98
|
|
|
@@ -151,7 +152,6 @@ def add_parser(main_subparsers, parents):
|
|
|
151
152
|
parser.add_argument(
|
|
152
153
|
"--auth",
|
|
153
154
|
type=valid_auth_option,
|
|
154
|
-
default="private",
|
|
155
155
|
help="Application authentication mode (private, public).",
|
|
156
156
|
)
|
|
157
157
|
parser.set_defaults(func=_deploy)
|
fal/cli/main.py
CHANGED
|
@@ -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, deploy, doctor, keys, run, secrets
|
|
9
|
+
from . import apps, auth, create, deploy, doctor, keys, 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]:
|
|
34
|
+
for cmd in [auth, apps, deploy, run, keys, secrets, doctor, create]:
|
|
35
35
|
cmd.add_parser(subparsers, parents)
|
|
36
36
|
|
|
37
37
|
return parser
|
fal/cli/run.py
CHANGED
|
@@ -6,7 +6,7 @@ def _run(args):
|
|
|
6
6
|
from fal.utils import load_function_from
|
|
7
7
|
|
|
8
8
|
host = FalServerlessHost(args.host)
|
|
9
|
-
isolated_function, _ = load_function_from(host, *args.func_ref)
|
|
9
|
+
isolated_function, _, _ = load_function_from(host, *args.func_ref)
|
|
10
10
|
# let our exc handlers handle UserFunctionException
|
|
11
11
|
isolated_function.reraise = False
|
|
12
12
|
isolated_function()
|
fal/toolkit/image/__init__.py
CHANGED
|
@@ -1,3 +1,74 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
from urllib.request import Request, urlopen
|
|
6
|
+
|
|
3
7
|
from .image import * # noqa: F403
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from PIL.Image import Image as PILImage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def filter_by(
|
|
14
|
+
has_nsfw_concepts: list[bool],
|
|
15
|
+
images: list[PILImage],
|
|
16
|
+
) -> list[PILImage]:
|
|
17
|
+
from PIL import Image as PILImage
|
|
18
|
+
|
|
19
|
+
return [
|
|
20
|
+
(
|
|
21
|
+
PILImage.new("RGB", (image.width, image.height), (0, 0, 0))
|
|
22
|
+
if has_nsfw
|
|
23
|
+
else image
|
|
24
|
+
)
|
|
25
|
+
for image, has_nsfw in zip(images, has_nsfw_concepts)
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def preprocess_image(image_pil, convert_to_rgb=True, fix_orientation=True):
|
|
30
|
+
from PIL import ImageOps, ImageSequence
|
|
31
|
+
|
|
32
|
+
# For MPO (multi picture object) format images, we only need the first image
|
|
33
|
+
images = []
|
|
34
|
+
for image in ImageSequence.Iterator(image_pil):
|
|
35
|
+
img = image
|
|
36
|
+
|
|
37
|
+
if convert_to_rgb:
|
|
38
|
+
img = img.convert("RGB")
|
|
39
|
+
|
|
40
|
+
if fix_orientation:
|
|
41
|
+
img = ImageOps.exif_transpose(img)
|
|
42
|
+
|
|
43
|
+
images.append(img)
|
|
44
|
+
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
return images[0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@lru_cache(maxsize=64)
|
|
51
|
+
def read_image_from_url(
|
|
52
|
+
url: str, convert_to_rgb: bool = True, fix_orientation: bool = True
|
|
53
|
+
):
|
|
54
|
+
from fastapi import HTTPException
|
|
55
|
+
from PIL import Image
|
|
56
|
+
|
|
57
|
+
TEMP_HEADERS = {
|
|
58
|
+
"User-Agent": (
|
|
59
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) "
|
|
60
|
+
"Gecko/20100101 Firefox/21.0"
|
|
61
|
+
),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
request = Request(url, headers=TEMP_HEADERS)
|
|
66
|
+
response = urlopen(request)
|
|
67
|
+
image_pil = Image.open(response)
|
|
68
|
+
except Exception:
|
|
69
|
+
import traceback
|
|
70
|
+
|
|
71
|
+
traceback.print_exc()
|
|
72
|
+
raise HTTPException(422, f"Could not load image from url: {url}")
|
|
73
|
+
|
|
74
|
+
return preprocess_image(image_pil, convert_to_rgb, fix_orientation)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
import fal
|
|
4
|
+
from fal.toolkit.image import read_image_from_url
|
|
5
|
+
|
|
6
|
+
from .env import get_requirements
|
|
7
|
+
from .model import get_model
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NSFWImageDetectionInput(BaseModel):
|
|
11
|
+
image_url: str = Field(
|
|
12
|
+
description="Input image url.",
|
|
13
|
+
examples=[
|
|
14
|
+
"https://storage.googleapis.com/falserverless/model_tests/remove_background/elephant.jpg",
|
|
15
|
+
],
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NSFWImageDetectionOutput(BaseModel):
|
|
20
|
+
nsfw_probability: float = Field(
|
|
21
|
+
description="The probability of the image being NSFW.",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_nsfw_content(pil_image: object):
|
|
26
|
+
import torch
|
|
27
|
+
|
|
28
|
+
model, processor = get_model()
|
|
29
|
+
|
|
30
|
+
with torch.no_grad():
|
|
31
|
+
inputs = processor(images=pil_image, return_tensors="pt")
|
|
32
|
+
outputs = model(**inputs)
|
|
33
|
+
logits = outputs.logits.squeeze() # Remove batch dimension to simplify indexing
|
|
34
|
+
|
|
35
|
+
# Apply softmax to convert logits to probabilities
|
|
36
|
+
probabilities = torch.softmax(logits, dim=0)
|
|
37
|
+
|
|
38
|
+
nsfw_class_index = model.config.label2id.get(
|
|
39
|
+
"nsfw", None
|
|
40
|
+
) # Replace "NSFW" with the exact class name if different
|
|
41
|
+
|
|
42
|
+
# Validate that NSFW class index is found
|
|
43
|
+
if nsfw_class_index is not None:
|
|
44
|
+
nsfw_probability = probabilities[int(nsfw_class_index)].item()
|
|
45
|
+
return nsfw_probability
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError("NSFW class not found in model output.")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_nsfw_estimation(
|
|
51
|
+
input: NSFWImageDetectionInput,
|
|
52
|
+
) -> NSFWImageDetectionOutput:
|
|
53
|
+
img = read_image_from_url(input.image_url)
|
|
54
|
+
nsfw_probability = check_nsfw_content(img)
|
|
55
|
+
|
|
56
|
+
return NSFWImageDetectionOutput(nsfw_probability=nsfw_probability)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@fal.function(
|
|
60
|
+
requirements=get_requirements(),
|
|
61
|
+
machine_type="GPU-A6000",
|
|
62
|
+
serve=True,
|
|
63
|
+
)
|
|
64
|
+
def run_nsfw_estimation_on_fal(
|
|
65
|
+
input: NSFWImageDetectionInput,
|
|
66
|
+
) -> NSFWImageDetectionOutput:
|
|
67
|
+
return run_nsfw_estimation(input)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
local = run_nsfw_estimation_on_fal.on(serve=False)
|
|
72
|
+
result = local(
|
|
73
|
+
NSFWImageDetectionInput(
|
|
74
|
+
image_url="https://storage.googleapis.com/falserverless/model_tests/remove_background/elephant.jpg",
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
print(result)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fal
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@fal.cached
|
|
5
|
+
def get_model():
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from transformers import AutoModelForImageClassification, ViTImageProcessor
|
|
9
|
+
|
|
10
|
+
os.environ["TRANSFORMERS_CACHE"] = "/data/models"
|
|
11
|
+
os.environ["HF_HOME"] = "/data/models"
|
|
12
|
+
|
|
13
|
+
model = AutoModelForImageClassification.from_pretrained(
|
|
14
|
+
"Falconsai/nsfw_image_detection"
|
|
15
|
+
)
|
|
16
|
+
processor = ViTImageProcessor.from_pretrained("Falconsai/nsfw_image_detection")
|
|
17
|
+
|
|
18
|
+
return model, processor
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import fal
|
|
4
|
+
|
|
5
|
+
from . import filter_by
|
|
6
|
+
from .nsfw_filter.model import get_model
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@fal.cached
|
|
10
|
+
def load_safety_checker():
|
|
11
|
+
import torch
|
|
12
|
+
from diffusers.pipelines.stable_diffusion.safety_checker import (
|
|
13
|
+
StableDiffusionSafetyChecker,
|
|
14
|
+
)
|
|
15
|
+
from transformers import AutoFeatureExtractor
|
|
16
|
+
|
|
17
|
+
feature_extractor = AutoFeatureExtractor.from_pretrained(
|
|
18
|
+
"CompVis/stable-diffusion-safety-checker",
|
|
19
|
+
torch_dtype="float16",
|
|
20
|
+
)
|
|
21
|
+
safety_checker = StableDiffusionSafetyChecker.from_pretrained(
|
|
22
|
+
"CompVis/stable-diffusion-safety-checker",
|
|
23
|
+
torch_dtype=torch.float16,
|
|
24
|
+
).to("cuda")
|
|
25
|
+
|
|
26
|
+
return feature_extractor, safety_checker
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run_safety_checker(
|
|
30
|
+
pil_images: list[object],
|
|
31
|
+
) -> list[bool]:
|
|
32
|
+
import numpy as np
|
|
33
|
+
import torch
|
|
34
|
+
|
|
35
|
+
feature_extractor, safety_checker = load_safety_checker()
|
|
36
|
+
|
|
37
|
+
safety_checker_input = feature_extractor(pil_images, return_tensors="pt").to("cuda")
|
|
38
|
+
|
|
39
|
+
np_image = [np.array(val) for val in pil_images]
|
|
40
|
+
|
|
41
|
+
_, has_nsfw_concept = safety_checker(
|
|
42
|
+
images=np_image,
|
|
43
|
+
clip_input=safety_checker_input.pixel_values.to(torch.float16),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return has_nsfw_concept
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_safety_checker_v2(pil_images: list, nsfw_threshold: float = 0.5) -> list[bool]:
|
|
50
|
+
import torch
|
|
51
|
+
|
|
52
|
+
model, processor = get_model()
|
|
53
|
+
|
|
54
|
+
has_nsfw_concept = []
|
|
55
|
+
|
|
56
|
+
with torch.no_grad():
|
|
57
|
+
for pil_image in pil_images:
|
|
58
|
+
inputs = processor(
|
|
59
|
+
images=pil_image.convert("RGB"),
|
|
60
|
+
return_tensors="pt",
|
|
61
|
+
)
|
|
62
|
+
outputs = model(**inputs)
|
|
63
|
+
logits = (
|
|
64
|
+
outputs.logits.squeeze()
|
|
65
|
+
) # Remove batch dimension to simplify indexing
|
|
66
|
+
|
|
67
|
+
# Apply softmax to convert logits to probabilities
|
|
68
|
+
probabilities = torch.softmax(logits, dim=0)
|
|
69
|
+
|
|
70
|
+
nsfw_class_index = model.config.label2id.get(
|
|
71
|
+
"nsfw", None
|
|
72
|
+
) # Replace "NSFW" with the exact class name if different
|
|
73
|
+
|
|
74
|
+
# Validate that NSFW class index is found
|
|
75
|
+
if nsfw_class_index is not None:
|
|
76
|
+
nsfw_probability = probabilities[int(nsfw_class_index)].item()
|
|
77
|
+
print("NSFW probability:", nsfw_probability)
|
|
78
|
+
has_nsfw_concept.append(nsfw_probability > nsfw_threshold)
|
|
79
|
+
else:
|
|
80
|
+
raise ValueError("NSFW class not found in model output.")
|
|
81
|
+
|
|
82
|
+
return has_nsfw_concept
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def postprocess_images(
|
|
86
|
+
pil_images: list[object],
|
|
87
|
+
enable_safety_checker: bool = True,
|
|
88
|
+
safety_checker_version: int = 2,
|
|
89
|
+
) -> dict[str, Any]:
|
|
90
|
+
outputs: dict[str, list[Any]] = {
|
|
91
|
+
"images": pil_images,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if enable_safety_checker:
|
|
95
|
+
safety_checker_fn = (
|
|
96
|
+
run_safety_checker_v2 if safety_checker_version == 2 else run_safety_checker
|
|
97
|
+
)
|
|
98
|
+
outputs["has_nsfw_concepts"] = safety_checker_fn(pil_images) # type: ignore
|
|
99
|
+
else:
|
|
100
|
+
outputs["has_nsfw_concepts"] = [False] * len(pil_images)
|
|
101
|
+
|
|
102
|
+
outputs["images"] = filter_by(
|
|
103
|
+
outputs["has_nsfw_concepts"],
|
|
104
|
+
outputs["images"],
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return outputs
|
fal/utils.py
CHANGED
|
@@ -10,13 +10,13 @@ def load_function_from(
|
|
|
10
10
|
host: FalServerlessHost,
|
|
11
11
|
file_path: str,
|
|
12
12
|
function_name: str | None = None,
|
|
13
|
-
) -> tuple[IsolatedFunction, str | None]:
|
|
13
|
+
) -> tuple[IsolatedFunction, str | None, str | None]:
|
|
14
14
|
import runpy
|
|
15
15
|
|
|
16
16
|
module = runpy.run_path(file_path)
|
|
17
17
|
if function_name is None:
|
|
18
18
|
fal_objects = {
|
|
19
|
-
|
|
19
|
+
obj_name: obj
|
|
20
20
|
for obj_name, obj in module.items()
|
|
21
21
|
if isinstance(obj, type)
|
|
22
22
|
and issubclass(obj, fal.App)
|
|
@@ -30,9 +30,12 @@ def load_function_from(
|
|
|
30
30
|
"Please specify the name of the app."
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
[(
|
|
33
|
+
[(function_name, obj)] = fal_objects.items()
|
|
34
|
+
app_name = obj.app_name
|
|
35
|
+
app_auth = obj.app_auth
|
|
34
36
|
else:
|
|
35
37
|
app_name = None
|
|
38
|
+
app_auth = None
|
|
36
39
|
|
|
37
40
|
if function_name not in module:
|
|
38
41
|
raise FalServerlessError(f"Function '{function_name}' not found in module")
|
|
@@ -44,10 +47,11 @@ def load_function_from(
|
|
|
44
47
|
target = module[function_name]
|
|
45
48
|
if isinstance(target, type) and issubclass(target, App):
|
|
46
49
|
app_name = target.app_name
|
|
50
|
+
app_auth = target.app_auth
|
|
47
51
|
target = wrap_app(target, host=host)
|
|
48
52
|
|
|
49
53
|
if not isinstance(target, IsolatedFunction):
|
|
50
54
|
raise FalServerlessError(
|
|
51
55
|
f"Function '{function_name}' is not a fal.function or a fal.App"
|
|
52
56
|
)
|
|
53
|
-
return target, app_name
|
|
57
|
+
return target, app_name, app_auth
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
5
|
Author: Features & Labels <support@fal.ai>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -34,6 +34,7 @@ Requires-Dist: websockets <13,>=12.0
|
|
|
34
34
|
Requires-Dist: pillow <11,>=10.2.0
|
|
35
35
|
Requires-Dist: pyjwt[crypto] <3,>=2.8.0
|
|
36
36
|
Requires-Dist: uvicorn <1,>=0.29.0
|
|
37
|
+
Requires-Dist: cookiecutter
|
|
37
38
|
Requires-Dist: importlib-metadata >=4.4 ; python_version < "3.10"
|
|
38
39
|
Provides-Extra: dev
|
|
39
40
|
Requires-Dist: fal[test] ; extra == 'dev'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
fal/__init__.py,sha256=wXs1G0gSc7ZK60-bHe-B2m0l_sA6TrFk4BxY0tMoLe8,784
|
|
2
2
|
fal/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
|
3
|
-
fal/_fal_version.py,sha256=
|
|
3
|
+
fal/_fal_version.py,sha256=VUBVzEBuW57s195P22MF-rLGH7GWx3G_z5nV-l_8MBE,411
|
|
4
4
|
fal/_serialization.py,sha256=rD2YiSa8iuzCaZohZwN_MPEB-PpSKbWRDeaIDpTEjyY,7653
|
|
5
5
|
fal/_version.py,sha256=EBGqrknaf1WygENX-H4fBefLvHryvJBBGtVJetaB0NY,266
|
|
6
|
-
fal/api.py,sha256=
|
|
7
|
-
fal/app.py,sha256=
|
|
6
|
+
fal/api.py,sha256=LAPl5Hf6ZWzEjv4lFUtsisWgrnXH_qNUHdJrEHT_A5Y,40602
|
|
7
|
+
fal/app.py,sha256=9HJGu_64ArtW8W91BC0U4Etr2gA31LXaHgR6HzoOops,15903
|
|
8
8
|
fal/apps.py,sha256=FrKmaAUo8U9vE_fcva0GQvk4sCrzaTEr62lGtu3Ld5M,6825
|
|
9
9
|
fal/container.py,sha256=V7riyyq8AZGwEX9QaqRQDZyDN_bUKeRKV1OOZArXjL0,622
|
|
10
10
|
fal/flags.py,sha256=oWN_eidSUOcE9wdPK_77si3A1fpgOC0UEERPsvNLIMc,842
|
|
@@ -12,21 +12,22 @@ fal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
12
12
|
fal/rest_client.py,sha256=kGBGmuyHfX1lR910EoKCYPjsyU8MdXawT_cW2q8Sajc,568
|
|
13
13
|
fal/sdk.py,sha256=wA58DYnSK1vdsBi8Or9Z8kvMMEyBNfeZYk_xulSfTWE,20078
|
|
14
14
|
fal/sync.py,sha256=ZuIJA2-hTPNANG9B_NNJZUsO68EIdTH0dc9MzeVE2VU,4340
|
|
15
|
-
fal/utils.py,sha256=
|
|
15
|
+
fal/utils.py,sha256=4-V6iGSRd3kG_-UP6OdZp_-EhAkl3zectFlFKkCsS0Q,1884
|
|
16
16
|
fal/workflows.py,sha256=jx3tGy2R7cN6lLvOzT6lhhlcjmiq64iZls2smVrmQj0,14657
|
|
17
17
|
fal/auth/__init__.py,sha256=r8iA2-5ih7-Fik3gEC4HEWNFbGoxpYnXpZu1icPIoS0,3561
|
|
18
18
|
fal/auth/auth0.py,sha256=rSG1mgH-QGyKfzd7XyAaj1AYsWt-ho8Y_LZ-FUVWzh4,5421
|
|
19
19
|
fal/auth/local.py,sha256=sndkM6vKpeVny6NHTacVlTbiIFqaksOmw0Viqs_RN1U,1790
|
|
20
20
|
fal/cli/__init__.py,sha256=padK4o0BFqq61kxAA1qQ0jYr2SuhA2mf90B3AaRkmJA,37
|
|
21
|
-
fal/cli/apps.py,sha256
|
|
21
|
+
fal/cli/apps.py,sha256=-DDp-Gvxz5kHho5YjAhbri8vOny_9cftAI_wP2KR5nU,8175
|
|
22
22
|
fal/cli/auth.py,sha256=--MhfHGwxmtHbRkGioyn1prKn_U-pBzbz0G_QeZou-U,1352
|
|
23
|
+
fal/cli/create.py,sha256=a8WDq-nJLFTeoIXqpb5cr7GR7YR9ZZrQCawNm34KXXE,627
|
|
23
24
|
fal/cli/debug.py,sha256=u_urnyFzSlNnrq93zz_GXE9FX4VyVxDoamJJyrZpFI0,1312
|
|
24
|
-
fal/cli/deploy.py,sha256=
|
|
25
|
+
fal/cli/deploy.py,sha256=1e4OERVGtfwgM0VEFjlCLpNyuOl1BiLI-dx8u-71PVs,4817
|
|
25
26
|
fal/cli/doctor.py,sha256=U4ne9LX5gQwNblsYQ27XdO8AYDgbYjTO39EtxhwexRM,983
|
|
26
27
|
fal/cli/keys.py,sha256=trDpA3LJu9S27qE_K8Hr6fKLK4vwVzbxUHq8TFrV4pw,3157
|
|
27
|
-
fal/cli/main.py,sha256=
|
|
28
|
+
fal/cli/main.py,sha256=_Wh_DQc02qwh-ZN7v41lZm0lDR1WseViXVOcqUlyWLg,2009
|
|
28
29
|
fal/cli/parser.py,sha256=r1hd5e8Jq6yzDZw8-S0On1EjJbjRtHMuVuHC6MlvUj4,2835
|
|
29
|
-
fal/cli/run.py,sha256=
|
|
30
|
+
fal/cli/run.py,sha256=8wHNDruIr8i21JwbfFzS389C-y0jktM5zN5iDnJHsvA,873
|
|
30
31
|
fal/cli/secrets.py,sha256=740msFm7d41HruudlcfqUXlFl53N-WmChsQP9B9M9Po,2572
|
|
31
32
|
fal/console/__init__.py,sha256=ernZ4bzvvliQh5SmrEqQ7lA5eVcbw6Ra2jalKtA7dxg,132
|
|
32
33
|
fal/console/icons.py,sha256=De9MfFaSkO2Lqfne13n3PrYfTXJVIzYZVqYn5BWsdrA,108
|
|
@@ -48,8 +49,14 @@ fal/toolkit/file/types.py,sha256=bJCeV5NPcpJYJoglailiRgFsuNAfcextYA8Et5-XUag,106
|
|
|
48
49
|
fal/toolkit/file/providers/fal.py,sha256=65-BkK9jhGBwYI_OjhHJsL2DthyKxBBRrqXPI_ZN4-k,4115
|
|
49
50
|
fal/toolkit/file/providers/gcp.py,sha256=pUVH2qNcnO_VrDQQU8MmfYOQZMGaKQIqE4yGnYdQhAc,2003
|
|
50
51
|
fal/toolkit/file/providers/r2.py,sha256=WxmOHF5WxHt6tKMcFjWj7ZWO8a1EXysO9lfYv_tB3MI,2627
|
|
51
|
-
fal/toolkit/image/__init__.py,sha256=
|
|
52
|
+
fal/toolkit/image/__init__.py,sha256=aLcU8HzD7HyOxx-C-Bbx9kYCMHdBhy9tR98FSVJ6gSA,1830
|
|
52
53
|
fal/toolkit/image/image.py,sha256=UDIHgkxae8LzmCvWBM9GayMnK8c0JMMfsrVlLnW5rto,4234
|
|
54
|
+
fal/toolkit/image/safety_checker.py,sha256=S7ow-HuoVxC6ixHWWcBrAUm2dIlgq3sTAIull6xIbAg,3105
|
|
55
|
+
fal/toolkit/image/nsfw_filter/__init__.py,sha256=0d9D51EhcnJg8cZLYJjgvQJDZT74CfQu6mpvinRYRpA,216
|
|
56
|
+
fal/toolkit/image/nsfw_filter/env.py,sha256=iAP2Q3vzIl--DD8nr8o3o0goAwhExN2v0feYE0nIQjs,212
|
|
57
|
+
fal/toolkit/image/nsfw_filter/inference.py,sha256=BhIPF_zxRLetThQYxDDF0sdx9VRwvu74M5ye6Povi40,2167
|
|
58
|
+
fal/toolkit/image/nsfw_filter/model.py,sha256=63mu8D15z_IosoRUagRLGHy6VbLqFmrG-yZqnu2vVm4,457
|
|
59
|
+
fal/toolkit/image/nsfw_filter/requirements.txt,sha256=3Pmrd0Ny6QAeBqUNHCgffRyfaCARAPJcfSCX5cRYpbM,37
|
|
53
60
|
fal/toolkit/utils/__init__.py,sha256=CrmM9DyCz5-SmcTzRSm5RaLgxy3kf0ZsSEN9uhnX2Xo,97
|
|
54
61
|
fal/toolkit/utils/download_utils.py,sha256=9WMpn0mFIhkFelQpPj5KG-pC7RMyyOzGHbNRDSyz07o,17664
|
|
55
62
|
openapi_fal_rest/__init__.py,sha256=ziculmF_i6trw63LzZGFX-6W3Lwq9mCR8_UpkpvpaHI,152
|
|
@@ -115,8 +122,8 @@ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRi
|
|
|
115
122
|
openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
|
|
116
123
|
openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
|
|
117
124
|
openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
|
|
118
|
-
fal-1.2.
|
|
119
|
-
fal-1.2.
|
|
120
|
-
fal-1.2.
|
|
121
|
-
fal-1.2.
|
|
122
|
-
fal-1.2.
|
|
125
|
+
fal-1.2.3.dist-info/METADATA,sha256=tUsGGaLLvqfYpEePodE0fuc5nm_XSoYE7AWTrci2nEY,3805
|
|
126
|
+
fal-1.2.3.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
|
127
|
+
fal-1.2.3.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
|
|
128
|
+
fal-1.2.3.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
|
|
129
|
+
fal-1.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|