fal 0.12.2__tar.gz → 0.12.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-0.12.2 → fal-0.12.3}/PKG-INFO +4 -7
- {fal-0.12.2 → fal-0.12.3}/pyproject.toml +4 -7
- {fal-0.12.2 → fal-0.12.3}/setup.py +4 -7
- {fal-0.12.2 → fal-0.12.3}/src/fal/__init__.py +11 -2
- {fal-0.12.2 → fal-0.12.3}/src/fal/api.py +130 -50
- {fal-0.12.2 → fal-0.12.3}/src/fal/app.py +81 -134
- {fal-0.12.2 → fal-0.12.3}/src/fal/apps.py +24 -6
- {fal-0.12.2 → fal-0.12.3}/src/fal/auth/__init__.py +1 -1
- {fal-0.12.2 → fal-0.12.3}/src/fal/auth/auth0.py +31 -22
- {fal-0.12.2 → fal-0.12.3}/src/fal/cli.py +5 -3
- fal-0.12.3/src/fal/env.py +3 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/logging/__init__.py +0 -2
- {fal-0.12.2 → fal-0.12.3}/src/fal/logging/trace.py +8 -1
- {fal-0.12.2 → fal-0.12.3}/src/fal/sdk.py +33 -6
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/__init__.py +16 -0
- fal-0.12.2/src/fal/env.py +0 -7
- fal-0.12.2/src/fal/logging/datadog.py +0 -78
- {fal-0.12.2 → fal-0.12.3}/README.md +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
- {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/_serialization.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/auth/local.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/console/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/console/icons.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/console/ux.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/exceptions/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/exceptions/_base.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/exceptions/auth.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/exceptions/handlers.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/flags.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/logging/isolate.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/logging/style.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/logging/user.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/py.typed +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/rest_client.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/sync.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/exceptions.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/file.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/providers/fal.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/providers/gcp.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/providers/r2.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/types.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/image/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/image/image.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/mainify.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/optimize.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/utils/__init__.py +0 -0
- {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/utils/download_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.3
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
5
|
Author: Features & Labels
|
|
6
6
|
Author-email: hello@fal.ai
|
|
@@ -11,18 +11,15 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Requires-Dist: attrs (>=21.3.0)
|
|
14
|
-
Requires-Dist: auth0-python (>=4.1.0,<5.0.0)
|
|
15
|
-
Requires-Dist: boto3 (>=1.33.8,<2.0.0)
|
|
16
14
|
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
17
15
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
18
|
-
Requires-Dist: datadog-api-client (==2.12.0)
|
|
19
16
|
Requires-Dist: dill (==0.3.7)
|
|
20
17
|
Requires-Dist: fastapi (==0.99.1)
|
|
21
18
|
Requires-Dist: grpc-interceptor (>=0.15.0,<0.16.0)
|
|
22
19
|
Requires-Dist: grpcio (>=1.50.0,<2.0.0)
|
|
23
|
-
Requires-Dist: httpx (>=0.15.4
|
|
20
|
+
Requires-Dist: httpx (>=0.15.4)
|
|
24
21
|
Requires-Dist: importlib-metadata (>=4.4) ; python_version < "3.10"
|
|
25
|
-
Requires-Dist: isolate-proto (
|
|
22
|
+
Requires-Dist: isolate-proto (==0.3.3)
|
|
26
23
|
Requires-Dist: isolate[build] (>=0.12.3,<1.0)
|
|
27
24
|
Requires-Dist: msgpack (>=1.0.7,<2.0.0)
|
|
28
25
|
Requires-Dist: opentelemetry-api (>=1.15.0,<2.0.0)
|
|
@@ -32,8 +29,8 @@ Requires-Dist: pathspec (>=0.11.1,<0.12.0)
|
|
|
32
29
|
Requires-Dist: pillow (>=10.2.0,<11.0.0)
|
|
33
30
|
Requires-Dist: portalocker (>=2.7.0,<3.0.0)
|
|
34
31
|
Requires-Dist: pydantic (<2.0)
|
|
32
|
+
Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
|
|
35
33
|
Requires-Dist: python-dateutil (>=2.8.0,<3.0.0)
|
|
36
|
-
Requires-Dist: requests (>=2.28.1,<3.0.0)
|
|
37
34
|
Requires-Dist: rich (>=13.3.2,<14.0.0)
|
|
38
35
|
Requires-Dist: structlog (>=22.3.0,<23.0.0)
|
|
39
36
|
Requires-Dist: types-python-dateutil (>=2.8.0,<3.0.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "fal"
|
|
3
|
-
version = "0.12.
|
|
3
|
+
version = "0.12.3"
|
|
4
4
|
description = "fal is an easy-to-use Serverless Python Framework"
|
|
5
5
|
authors = ["Features & Labels <hello@fal.ai>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -12,15 +12,12 @@ packages = [
|
|
|
12
12
|
[tool.poetry.dependencies]
|
|
13
13
|
python = "^3.8"
|
|
14
14
|
isolate = {version = ">=0.12.3, <1.0", extras = ["build"]}
|
|
15
|
-
isolate-proto = "
|
|
16
|
-
auth0-python = "^4.1.0"
|
|
17
|
-
requests = "^2.28.1"
|
|
15
|
+
isolate-proto = "0.3.3"
|
|
18
16
|
grpcio = "^1.50.0"
|
|
19
17
|
dill = "0.3.7"
|
|
20
18
|
typing-extensions = "^4.7.1"
|
|
21
19
|
click = "^8.1.3"
|
|
22
20
|
structlog = "^22.3.0"
|
|
23
|
-
datadog-api-client = "2.12.0"
|
|
24
21
|
opentelemetry-api = "^1.15.0"
|
|
25
22
|
opentelemetry-sdk = "^1.15.0"
|
|
26
23
|
grpc-interceptor = "^0.15.0"
|
|
@@ -35,17 +32,17 @@ pydantic = "<2.0"
|
|
|
35
32
|
fastapi = "0.99.1"
|
|
36
33
|
|
|
37
34
|
# rest-api-client dependencies
|
|
38
|
-
httpx = ">=0.15.4
|
|
35
|
+
httpx = ">=0.15.4"
|
|
39
36
|
attrs = ">=21.3.0"
|
|
40
37
|
python-dateutil = "^2.8.0"
|
|
41
38
|
types-python-dateutil = "^2.8.0"
|
|
42
39
|
|
|
43
40
|
# For 3.9 and earlier, importlib-metadata's newer versions are included in the standard library.
|
|
44
41
|
importlib-metadata = { version = ">=4.4", python = "<3.10" }
|
|
45
|
-
boto3 = "^1.33.8"
|
|
46
42
|
msgpack = "^1.0.7"
|
|
47
43
|
websockets = "^12.0"
|
|
48
44
|
pillow = "^10.2.0"
|
|
45
|
+
pyjwt = "^2.8.0"
|
|
49
46
|
|
|
50
47
|
[tool.poetry.group.dev.dependencies]
|
|
51
48
|
openapi-python-client = "^0.14.1"
|
|
@@ -33,17 +33,14 @@ package_data = \
|
|
|
33
33
|
|
|
34
34
|
install_requires = \
|
|
35
35
|
['attrs>=21.3.0',
|
|
36
|
-
'auth0-python>=4.1.0,<5.0.0',
|
|
37
|
-
'boto3>=1.33.8,<2.0.0',
|
|
38
36
|
'click>=8.1.3,<9.0.0',
|
|
39
37
|
'colorama>=0.4.6,<0.5.0',
|
|
40
|
-
'datadog-api-client==2.12.0',
|
|
41
38
|
'dill==0.3.7',
|
|
42
39
|
'fastapi==0.99.1',
|
|
43
40
|
'grpc-interceptor>=0.15.0,<0.16.0',
|
|
44
41
|
'grpcio>=1.50.0,<2.0.0',
|
|
45
|
-
'httpx>=0.15.4
|
|
46
|
-
'isolate-proto
|
|
42
|
+
'httpx>=0.15.4',
|
|
43
|
+
'isolate-proto==0.3.3',
|
|
47
44
|
'isolate[build]>=0.12.3,<1.0',
|
|
48
45
|
'msgpack>=1.0.7,<2.0.0',
|
|
49
46
|
'opentelemetry-api>=1.15.0,<2.0.0',
|
|
@@ -53,8 +50,8 @@ install_requires = \
|
|
|
53
50
|
'pillow>=10.2.0,<11.0.0',
|
|
54
51
|
'portalocker>=2.7.0,<3.0.0',
|
|
55
52
|
'pydantic<2.0',
|
|
53
|
+
'pyjwt>=2.8.0,<3.0.0',
|
|
56
54
|
'python-dateutil>=2.8.0,<3.0.0',
|
|
57
|
-
'requests>=2.28.1,<3.0.0',
|
|
58
55
|
'rich>=13.3.2,<14.0.0',
|
|
59
56
|
'structlog>=22.3.0,<23.0.0',
|
|
60
57
|
'types-python-dateutil>=2.8.0,<3.0.0',
|
|
@@ -69,7 +66,7 @@ entry_points = \
|
|
|
69
66
|
|
|
70
67
|
setup_kwargs = {
|
|
71
68
|
'name': 'fal',
|
|
72
|
-
'version': '0.12.
|
|
69
|
+
'version': '0.12.3',
|
|
73
70
|
'description': 'fal is an easy-to-use Serverless Python Framework',
|
|
74
71
|
'long_description': '# fal\n\nfal is a serverless Python runtime that lets you run and scale code in the cloud with no infra management.\n\nWith fal, you can build pipelines, serve ML models and scale them up to many users. You scale down to 0 when you don\'t use any resources.\n\n## Quickstart\n\nFirst, you need to install the `fal` package. You can do so using pip:\n```shell\npip install fal\n```\n\nThen you need to authenticate:\n```shell\nfal auth login\n```\n\nYou can also use fal keys that you can get from [our dashboard](https://fal.ai/dashboard/keys).\n\nNow can use the fal package in your Python scripts as follows:\n\n```py\nimport fal\n\n@fal.function(\n "virtualenv",\n requirements=["pyjokes"],\n)\ndef tell_joke() -> str:\n import pyjokes\n\n joke = pyjokes.get_joke()\n return joke\n\nprint("Joke from the clouds: ", tell_joke())\n```\n\nA new virtual environment will be created by fal in the cloud and the set of requirements that we passed will be installed as soon as this function is called. From that point on, our code will be executed as if it were running locally, and the joke prepared by the pyjokes library will be returned.\n\n## Next steps\n\nIf you would like to find out more about the capabilities of fal, check out to the [docs](https://fal.ai/docs). You can learn more about persistent storage, function caches and deploying your functions as API endpoints.\n',
|
|
75
72
|
'author': 'Features & Labels',
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from fal import apps
|
|
4
|
-
|
|
5
|
-
# TODO: DEPRECATED - use function instead
|
|
6
4
|
from fal.api import FalServerlessHost, LocalHost, cached
|
|
7
5
|
from fal.api import function
|
|
8
6
|
from fal.api import function as isolated
|
|
@@ -16,6 +14,17 @@ serverless = FalServerlessHost()
|
|
|
16
14
|
# DEPRECATED - use serverless instead
|
|
17
15
|
cloud = FalServerlessHost()
|
|
18
16
|
|
|
17
|
+
__all__ = [
|
|
18
|
+
"function",
|
|
19
|
+
"cached",
|
|
20
|
+
"App",
|
|
21
|
+
"endpoint",
|
|
22
|
+
"realtime",
|
|
23
|
+
# "wrap_app",
|
|
24
|
+
"FalServerlessKeyCredentials",
|
|
25
|
+
"sync_dir",
|
|
26
|
+
]
|
|
27
|
+
|
|
19
28
|
|
|
20
29
|
# NOTE: This makes `import fal.dbt` import the `dbt-fal` module and `import fal` import the `fal` module
|
|
21
30
|
# NOTE: taken from dbt-core: https://github.com/dbt-labs/dbt-core/blob/ac539fd5cf325cfb5315339077d03399d575f570/core/dbt/adapters/__init__.py#L1-L7
|
|
@@ -4,7 +4,7 @@ import inspect
|
|
|
4
4
|
import sys
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from concurrent.futures import ThreadPoolExecutor
|
|
7
|
-
from contextlib import suppress
|
|
7
|
+
from contextlib import asynccontextmanager, suppress
|
|
8
8
|
from dataclasses import dataclass, field, replace
|
|
9
9
|
from functools import partial, wraps
|
|
10
10
|
from os import PathLike
|
|
@@ -16,6 +16,7 @@ from typing import (
|
|
|
16
16
|
Generic,
|
|
17
17
|
Iterator,
|
|
18
18
|
Literal,
|
|
19
|
+
NamedTuple,
|
|
19
20
|
TypeVar,
|
|
20
21
|
cast,
|
|
21
22
|
overload,
|
|
@@ -26,6 +27,7 @@ import dill.detect
|
|
|
26
27
|
import grpc
|
|
27
28
|
import isolate
|
|
28
29
|
import yaml
|
|
30
|
+
from fastapi import FastAPI
|
|
29
31
|
from isolate.backends.common import active_python
|
|
30
32
|
from isolate.backends.settings import DEFAULT_SETTINGS
|
|
31
33
|
from isolate.connections import PythonIPC
|
|
@@ -56,6 +58,8 @@ ReturnT = TypeVar("ReturnT", covariant=True)
|
|
|
56
58
|
BasicConfig = Dict[str, Any]
|
|
57
59
|
_UNSET = object()
|
|
58
60
|
|
|
61
|
+
SERVE_REQUIREMENTS = ["fastapi==0.99.1", "uvicorn"]
|
|
62
|
+
|
|
59
63
|
|
|
60
64
|
@dataclass
|
|
61
65
|
class FalServerlessError(Exception):
|
|
@@ -110,7 +114,7 @@ class Host(Generic[ArgsT, ReturnT]):
|
|
|
110
114
|
options.environment[key] = value
|
|
111
115
|
|
|
112
116
|
if options.gateway.get("serve"):
|
|
113
|
-
options.add_requirements(
|
|
117
|
+
options.add_requirements(SERVE_REQUIREMENTS)
|
|
114
118
|
|
|
115
119
|
return options
|
|
116
120
|
|
|
@@ -730,53 +734,17 @@ def function( # type: ignore
|
|
|
730
734
|
|
|
731
735
|
|
|
732
736
|
@mainify
|
|
733
|
-
class
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
self._func = func
|
|
738
|
-
|
|
739
|
-
def build_app(self):
|
|
740
|
-
from fastapi import FastAPI
|
|
741
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
742
|
-
|
|
743
|
-
_app = FastAPI()
|
|
744
|
-
|
|
745
|
-
_app.add_middleware(
|
|
746
|
-
CORSMiddleware,
|
|
747
|
-
allow_credentials=True,
|
|
748
|
-
allow_headers=("*"),
|
|
749
|
-
allow_methods=("*"),
|
|
750
|
-
allow_origins=("*"),
|
|
751
|
-
)
|
|
752
|
-
|
|
753
|
-
_app.add_api_route(
|
|
754
|
-
"/",
|
|
755
|
-
self._func, # type: ignore
|
|
756
|
-
name=self._func.__name__,
|
|
757
|
-
methods=["POST"],
|
|
758
|
-
)
|
|
759
|
-
|
|
760
|
-
return _app
|
|
761
|
-
|
|
762
|
-
def __call__(self, *args, **kwargs) -> None:
|
|
763
|
-
if len(args) != 0 or len(kwargs) != 0:
|
|
764
|
-
print(
|
|
765
|
-
f"[warning] {self._func.__name__} function is served with no arguments."
|
|
766
|
-
)
|
|
767
|
-
|
|
768
|
-
from uvicorn import run
|
|
769
|
-
|
|
770
|
-
app = self.build_app()
|
|
771
|
-
run(app, host="0.0.0.0", port=8080)
|
|
737
|
+
class FalFastAPI(FastAPI):
|
|
738
|
+
"""
|
|
739
|
+
A subclass of FastAPI that adds some fal-specific functionality.
|
|
740
|
+
"""
|
|
772
741
|
|
|
773
742
|
def openapi(self) -> dict[str, Any]:
|
|
774
743
|
"""
|
|
775
744
|
Build the OpenAPI specification for the served function.
|
|
776
745
|
Attach needed metadata for a better integration to fal.
|
|
777
746
|
"""
|
|
778
|
-
|
|
779
|
-
spec = app.openapi()
|
|
747
|
+
spec = super().openapi()
|
|
780
748
|
self._mark_order_openapi(spec)
|
|
781
749
|
return spec
|
|
782
750
|
|
|
@@ -788,7 +756,8 @@ class ServeWrapper:
|
|
|
788
756
|
"""
|
|
789
757
|
|
|
790
758
|
def mark_order(obj: dict[str, Any], key: str):
|
|
791
|
-
|
|
759
|
+
if key in obj:
|
|
760
|
+
obj[f"x-fal-order-{key}"] = list(obj[key].keys())
|
|
792
761
|
|
|
793
762
|
mark_order(spec, "paths")
|
|
794
763
|
|
|
@@ -797,18 +766,129 @@ class ServeWrapper:
|
|
|
797
766
|
Mark the order of properties in the schema object.
|
|
798
767
|
They can have 'allOf', 'properties' or '$ref' key.
|
|
799
768
|
"""
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
mark_order(schema, "properties")
|
|
769
|
+
for sub_schema in schema.get("allOf", []):
|
|
770
|
+
order_schema_object(sub_schema)
|
|
771
|
+
|
|
772
|
+
mark_order(schema, "properties")
|
|
805
773
|
|
|
806
|
-
for key in spec.get("components", {}).get("schemas"
|
|
774
|
+
for key in spec.get("components", {}).get("schemas", {}):
|
|
807
775
|
order_schema_object(spec["components"]["schemas"][key])
|
|
808
776
|
|
|
809
777
|
return spec
|
|
810
778
|
|
|
811
779
|
|
|
780
|
+
@mainify
|
|
781
|
+
class RouteSignature(NamedTuple):
|
|
782
|
+
path: str
|
|
783
|
+
is_websocket: bool = False
|
|
784
|
+
input_modal: type | None = None
|
|
785
|
+
buffering: int | None = None
|
|
786
|
+
session_timeout: float | None = None
|
|
787
|
+
max_batch_size: int = 1
|
|
788
|
+
emit_timings: bool = False
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
@mainify
|
|
792
|
+
class BaseServable:
|
|
793
|
+
def collect_routes(self) -> dict[RouteSignature, Callable[..., Any]]:
|
|
794
|
+
raise NotImplementedError
|
|
795
|
+
|
|
796
|
+
def _add_extra_middlewares(self, app: FastAPI):
|
|
797
|
+
"""
|
|
798
|
+
For subclasses to add extra middlewares to the app.
|
|
799
|
+
"""
|
|
800
|
+
pass
|
|
801
|
+
|
|
802
|
+
@asynccontextmanager
|
|
803
|
+
async def lifespan(self, app: FastAPI):
|
|
804
|
+
yield
|
|
805
|
+
|
|
806
|
+
def _build_app(self) -> FastAPI:
|
|
807
|
+
from fastapi import HTTPException, Request
|
|
808
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
809
|
+
from fastapi.responses import JSONResponse
|
|
810
|
+
|
|
811
|
+
_app = FalFastAPI(lifespan=self.lifespan)
|
|
812
|
+
|
|
813
|
+
_app.add_middleware(
|
|
814
|
+
CORSMiddleware,
|
|
815
|
+
allow_credentials=True,
|
|
816
|
+
allow_headers=("*"),
|
|
817
|
+
allow_methods=("*"),
|
|
818
|
+
allow_origins=("*"),
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
self._add_extra_middlewares(_app)
|
|
822
|
+
|
|
823
|
+
@_app.exception_handler(404)
|
|
824
|
+
async def not_found_exception_handler(request: Request, exc: HTTPException):
|
|
825
|
+
# Rewrite the message to include the path that was not found.
|
|
826
|
+
# This is supposed to make it easier to understand to the user
|
|
827
|
+
# that the error comes from the app and not our platform.
|
|
828
|
+
if exc.detail == "Not Found":
|
|
829
|
+
return JSONResponse(
|
|
830
|
+
{"detail": f"Path {request.url.path} not found"}, 404
|
|
831
|
+
)
|
|
832
|
+
else:
|
|
833
|
+
# If it's not a generic 404, just return the original message.
|
|
834
|
+
return JSONResponse({"detail": exc.detail}, 404)
|
|
835
|
+
|
|
836
|
+
routes = self.collect_routes()
|
|
837
|
+
if not routes:
|
|
838
|
+
raise ValueError("An application must have at least one route!")
|
|
839
|
+
|
|
840
|
+
for signature, endpoint in routes.items():
|
|
841
|
+
if signature.is_websocket:
|
|
842
|
+
_app.add_api_websocket_route(
|
|
843
|
+
signature.path,
|
|
844
|
+
endpoint,
|
|
845
|
+
name=endpoint.__name__,
|
|
846
|
+
)
|
|
847
|
+
else:
|
|
848
|
+
_app.add_api_route(
|
|
849
|
+
signature.path,
|
|
850
|
+
endpoint,
|
|
851
|
+
name=endpoint.__name__,
|
|
852
|
+
methods=["POST"],
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
return _app
|
|
856
|
+
|
|
857
|
+
def openapi(self) -> dict[str, Any]:
|
|
858
|
+
"""
|
|
859
|
+
Build the OpenAPI specification for the served function.
|
|
860
|
+
Attach needed metadata for a better integration to fal.
|
|
861
|
+
"""
|
|
862
|
+
return self._build_app().openapi()
|
|
863
|
+
|
|
864
|
+
def serve(self) -> None:
|
|
865
|
+
import uvicorn
|
|
866
|
+
|
|
867
|
+
app = self._build_app()
|
|
868
|
+
uvicorn.run(app, host="0.0.0.0", port=8080)
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
@mainify
|
|
872
|
+
class ServeWrapper(BaseServable):
|
|
873
|
+
_func: Callable
|
|
874
|
+
|
|
875
|
+
def __init__(self, func: Callable):
|
|
876
|
+
self._func = func
|
|
877
|
+
|
|
878
|
+
def collect_routes(self) -> dict[RouteSignature, Callable[..., Any]]:
|
|
879
|
+
return {
|
|
880
|
+
RouteSignature("/"): self._func,
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
def __call__(self, *args, **kwargs) -> None:
|
|
884
|
+
if len(args) != 0 or len(kwargs) != 0:
|
|
885
|
+
print(
|
|
886
|
+
f"[warning] {self._func.__name__} function is served with no arguments."
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
self.serve()
|
|
890
|
+
|
|
891
|
+
|
|
812
892
|
@dataclass
|
|
813
893
|
class IsolatedFunction(Generic[ArgsT, ReturnT]):
|
|
814
894
|
host: Host[ArgsT, ReturnT]
|