fal 0.12.1__py3-none-any.whl → 0.12.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/__init__.py +12 -3
- fal/_serialization.py +18 -0
- fal/api.py +140 -59
- fal/app.py +309 -86
- fal/apps.py +92 -8
- fal/auth/__init__.py +20 -1
- fal/auth/auth0.py +32 -22
- fal/cli.py +34 -52
- fal/env.py +0 -4
- fal/exceptions/handlers.py +3 -2
- fal/flags.py +5 -0
- fal/logging/__init__.py +0 -2
- fal/logging/trace.py +8 -1
- fal/logging/user.py +2 -1
- fal/rest_client.py +2 -2
- fal/sdk.py +46 -31
- fal/sync.py +3 -3
- fal/toolkit/__init__.py +18 -1
- fal/toolkit/file/file.py +98 -11
- fal/toolkit/file/providers/fal.py +43 -2
- fal/toolkit/file/types.py +1 -1
- fal/toolkit/image/image.py +26 -4
- fal/toolkit/optimize.py +50 -0
- fal/toolkit/utils/download_utils.py +59 -13
- {fal-0.12.1.dist-info → fal-0.12.3.dist-info}/METADATA +7 -7
- fal-0.12.3.dist-info/RECORD +66 -0
- openapi_fal_rest/models/__init__.py +2 -70
- openapi_fal_rest/models/customer_details.py +26 -0
- openapi_fal_rest/models/lock_reason.py +16 -0
- fal/logging/datadog.py +0 -77
- fal-0.12.1.dist-info/RECORD +0 -147
- openapi_fal_rest/api/admin/get_invoice_users.py +0 -142
- openapi_fal_rest/api/admin/get_usage_per_user.py +0 -199
- openapi_fal_rest/api/admin/handle_user_lock.py +0 -191
- openapi_fal_rest/api/admin/set_billing_type.py +0 -186
- openapi_fal_rest/api/applications/get_status_applications_app_user_id_app_alias_or_id_status_get.py +0 -179
- openapi_fal_rest/api/billing/delete_payment_method.py +0 -162
- openapi_fal_rest/api/billing/get_checkout_page.py +0 -198
- openapi_fal_rest/api/billing/get_setup_intent_key.py +0 -141
- openapi_fal_rest/api/billing/get_user_invoices.py +0 -152
- openapi_fal_rest/api/billing/get_user_payment_methods.py +0 -152
- openapi_fal_rest/api/billing/get_user_price.py +0 -186
- openapi_fal_rest/api/billing/get_user_spending.py +0 -192
- openapi_fal_rest/api/billing/handle_stripe_webhook.py +0 -173
- openapi_fal_rest/api/billing/upcoming_invoice.py +0 -143
- openapi_fal_rest/api/billing/update_customer_budget.py +0 -183
- openapi_fal_rest/api/files/delete.py +0 -162
- openapi_fal_rest/api/files/download.py +0 -162
- openapi_fal_rest/api/files/file_exists.py +0 -183
- openapi_fal_rest/api/files/list_directory.py +0 -173
- openapi_fal_rest/api/files/list_root.py +0 -152
- openapi_fal_rest/api/files/upload_from_url.py +0 -179
- openapi_fal_rest/api/health/__init__.py +0 -0
- openapi_fal_rest/api/health/check.py +0 -136
- openapi_fal_rest/api/keys/__init__.py +0 -0
- openapi_fal_rest/api/keys/create_key.py +0 -188
- openapi_fal_rest/api/keys/delete_key.py +0 -162
- openapi_fal_rest/api/keys/list_keys.py +0 -152
- openapi_fal_rest/api/logs/__init__.py +0 -0
- openapi_fal_rest/api/logs/list_since.py +0 -224
- openapi_fal_rest/api/requests/__init__.py +0 -0
- openapi_fal_rest/api/requests/requests.py +0 -247
- openapi_fal_rest/api/storage/__init__.py +0 -0
- openapi_fal_rest/api/storage/get_file_link.py +0 -200
- openapi_fal_rest/api/storage/initiate_upload.py +0 -172
- openapi_fal_rest/api/storage/upload_file.py +0 -172
- openapi_fal_rest/api/tokens/__init__.py +0 -0
- openapi_fal_rest/api/tokens/create_token.py +0 -166
- openapi_fal_rest/api/usage/__init__.py +0 -0
- openapi_fal_rest/api/usage/get_custom_usage_per_machine.py +0 -203
- openapi_fal_rest/api/usage/get_gateway_request_stats.py +0 -247
- openapi_fal_rest/api/usage/get_gateway_request_stats_by_time.py +0 -236
- openapi_fal_rest/api/usage/get_gateway_stats_for_yesterday.py +0 -152
- openapi_fal_rest/api/usage/get_shared_usage_per_app.py +0 -203
- openapi_fal_rest/api/usage/get_usage_records.py +0 -253
- openapi_fal_rest/api/usage/per_machine_usage.py +0 -218
- openapi_fal_rest/api/usage/per_machine_usage_details.py +0 -173
- openapi_fal_rest/api/users/__init__.py +0 -0
- openapi_fal_rest/api/users/handle_user_registration.py +0 -228
- openapi_fal_rest/models/billing_type.py +0 -9
- openapi_fal_rest/models/body_create_token.py +0 -68
- openapi_fal_rest/models/body_upload_file.py +0 -75
- openapi_fal_rest/models/file_spec.py +0 -110
- openapi_fal_rest/models/gateway_stats_by_time.py +0 -115
- openapi_fal_rest/models/gateway_usage_stats.py +0 -147
- openapi_fal_rest/models/get_gateway_request_stats_by_time_response_get_gateway_request_stats_by_time.py +0 -70
- openapi_fal_rest/models/grouped_usage_detail.py +0 -85
- openapi_fal_rest/models/handle_stripe_webhook_response_handle_stripe_webhook.py +0 -43
- openapi_fal_rest/models/initiate_upload_info.py +0 -64
- openapi_fal_rest/models/invoice.py +0 -129
- openapi_fal_rest/models/invoice_item.py +0 -85
- openapi_fal_rest/models/key_scope.py +0 -9
- openapi_fal_rest/models/log_entry.py +0 -104
- openapi_fal_rest/models/log_entry_labels.py +0 -43
- openapi_fal_rest/models/new_user_key.py +0 -64
- openapi_fal_rest/models/payment_method.py +0 -96
- openapi_fal_rest/models/per_app_usage_detail.py +0 -88
- openapi_fal_rest/models/persisted_usage_record.py +0 -118
- openapi_fal_rest/models/persisted_usage_record_meta.py +0 -43
- openapi_fal_rest/models/presigned_upload_url.py +0 -64
- openapi_fal_rest/models/request_io.py +0 -112
- openapi_fal_rest/models/request_io_json_input.py +0 -43
- openapi_fal_rest/models/request_io_json_output.py +0 -43
- openapi_fal_rest/models/run_type.py +0 -9
- openapi_fal_rest/models/stats_timeframe.py +0 -12
- openapi_fal_rest/models/status.py +0 -82
- openapi_fal_rest/models/status_health.py +0 -10
- openapi_fal_rest/models/uploaded_file_result.py +0 -64
- openapi_fal_rest/models/url_file_upload.py +0 -57
- openapi_fal_rest/models/usage_per_machine_type.py +0 -115
- openapi_fal_rest/models/usage_per_user.py +0 -71
- openapi_fal_rest/models/usage_run_detail.py +0 -73
- openapi_fal_rest/models/user_key_info.py +0 -84
- /openapi_fal_rest/api/admin/__init__.py → /fal/py.typed +0 -0
- {fal-0.12.1.dist-info → fal-0.12.3.dist-info}/WHEEL +0 -0
- {fal-0.12.1.dist-info → fal-0.12.3.dist-info}/entry_points.txt +0 -0
fal/__init__.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
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
|
|
9
|
-
from fal.app import App, endpoint, wrap_app
|
|
7
|
+
from fal.app import App, endpoint, realtime, wrap_app
|
|
10
8
|
from fal.sdk import FalServerlessKeyCredentials
|
|
11
9
|
from fal.sync import sync_dir
|
|
12
10
|
|
|
@@ -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
|
fal/_serialization.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from functools import wraps
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
import dill
|
|
6
7
|
from dill import _dill
|
|
8
|
+
|
|
7
9
|
from fal.toolkit import mainify
|
|
8
10
|
|
|
9
11
|
# each @fal.function gets added to this set so that we can
|
|
@@ -49,12 +51,28 @@ def by_value_locator(obj, pickler=None, og_locator=_dill._locate_function):
|
|
|
49
51
|
_dill._locate_function = by_value_locator
|
|
50
52
|
|
|
51
53
|
|
|
54
|
+
def include_packages_from_path(raw_path: str):
|
|
55
|
+
path = Path(raw_path).resolve()
|
|
56
|
+
parent = path
|
|
57
|
+
while (parent.parent / "__init__.py").exists():
|
|
58
|
+
parent = parent.parent
|
|
59
|
+
|
|
60
|
+
if parent != path:
|
|
61
|
+
_PACKAGES.add(parent.name)
|
|
62
|
+
|
|
63
|
+
|
|
52
64
|
def add_serialization_listeners_for(obj):
|
|
53
65
|
module_name = getattr(obj, "__module__", None)
|
|
54
66
|
if not module_name:
|
|
55
67
|
return None
|
|
56
68
|
|
|
57
69
|
_MODULES.add(module_name)
|
|
70
|
+
if module_name == "__main__":
|
|
71
|
+
# When the module is __main__, we need to recursively go up the
|
|
72
|
+
# tree to locate the actual package name.
|
|
73
|
+
import __main__
|
|
74
|
+
|
|
75
|
+
include_packages_from_path(__main__.__file__)
|
|
58
76
|
|
|
59
77
|
if "." in module_name:
|
|
60
78
|
package_name, *_ = module_name.partition(".")
|
fal/api.py
CHANGED
|
@@ -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,
|
|
@@ -23,10 +24,18 @@ from typing import (
|
|
|
23
24
|
|
|
24
25
|
import dill
|
|
25
26
|
import dill.detect
|
|
26
|
-
import fal.flags as flags
|
|
27
27
|
import grpc
|
|
28
28
|
import isolate
|
|
29
29
|
import yaml
|
|
30
|
+
from fastapi import FastAPI
|
|
31
|
+
from isolate.backends.common import active_python
|
|
32
|
+
from isolate.backends.settings import DEFAULT_SETTINGS
|
|
33
|
+
from isolate.connections import PythonIPC
|
|
34
|
+
from packaging.requirements import Requirement
|
|
35
|
+
from packaging.utils import canonicalize_name
|
|
36
|
+
from typing_extensions import Concatenate, ParamSpec
|
|
37
|
+
|
|
38
|
+
import fal.flags as flags
|
|
30
39
|
from fal._serialization import add_serialization_listeners_for, patch_dill
|
|
31
40
|
from fal.logging.isolate import IsolateLogPrinter
|
|
32
41
|
from fal.sdk import (
|
|
@@ -38,16 +47,10 @@ from fal.sdk import (
|
|
|
38
47
|
FalServerlessConnection,
|
|
39
48
|
HostedRunState,
|
|
40
49
|
MachineRequirements,
|
|
41
|
-
|
|
50
|
+
get_agent_credentials,
|
|
42
51
|
get_default_credentials,
|
|
43
52
|
)
|
|
44
53
|
from fal.toolkit import mainify
|
|
45
|
-
from isolate.backends.common import active_python
|
|
46
|
-
from isolate.backends.settings import DEFAULT_SETTINGS
|
|
47
|
-
from isolate.connections import PythonIPC
|
|
48
|
-
from packaging.requirements import Requirement
|
|
49
|
-
from packaging.utils import canonicalize_name
|
|
50
|
-
from typing_extensions import Concatenate, ParamSpec
|
|
51
54
|
|
|
52
55
|
ArgsT = ParamSpec("ArgsT")
|
|
53
56
|
ReturnT = TypeVar("ReturnT", covariant=True)
|
|
@@ -55,6 +58,8 @@ ReturnT = TypeVar("ReturnT", covariant=True)
|
|
|
55
58
|
BasicConfig = Dict[str, Any]
|
|
56
59
|
_UNSET = object()
|
|
57
60
|
|
|
61
|
+
SERVE_REQUIREMENTS = ["fastapi==0.99.1", "uvicorn"]
|
|
62
|
+
|
|
58
63
|
|
|
59
64
|
@dataclass
|
|
60
65
|
class FalServerlessError(Exception):
|
|
@@ -109,7 +114,7 @@ class Host(Generic[ArgsT, ReturnT]):
|
|
|
109
114
|
options.environment[key] = value
|
|
110
115
|
|
|
111
116
|
if options.gateway.get("serve"):
|
|
112
|
-
options.add_requirements(
|
|
117
|
+
options.add_requirements(SERVE_REQUIREMENTS)
|
|
113
118
|
|
|
114
119
|
return options
|
|
115
120
|
|
|
@@ -328,7 +333,7 @@ class FalServerlessHost(Host):
|
|
|
328
333
|
|
|
329
334
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
330
335
|
self.__dict__.update(state)
|
|
331
|
-
self.credentials =
|
|
336
|
+
self.credentials = get_agent_credentials(self.credentials)
|
|
332
337
|
|
|
333
338
|
@property
|
|
334
339
|
def _connection(self) -> FalServerlessConnection:
|
|
@@ -729,53 +734,17 @@ def function( # type: ignore
|
|
|
729
734
|
|
|
730
735
|
|
|
731
736
|
@mainify
|
|
732
|
-
class
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
self._func = func
|
|
737
|
-
|
|
738
|
-
def build_app(self):
|
|
739
|
-
from fastapi import FastAPI
|
|
740
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
741
|
-
|
|
742
|
-
_app = FastAPI()
|
|
743
|
-
|
|
744
|
-
_app.add_middleware(
|
|
745
|
-
CORSMiddleware,
|
|
746
|
-
allow_credentials=True,
|
|
747
|
-
allow_headers=("*"),
|
|
748
|
-
allow_methods=("*"),
|
|
749
|
-
allow_origins=("*"),
|
|
750
|
-
)
|
|
751
|
-
|
|
752
|
-
_app.add_api_route(
|
|
753
|
-
"/",
|
|
754
|
-
self._func, # type: ignore
|
|
755
|
-
name=self._func.__name__,
|
|
756
|
-
methods=["POST"],
|
|
757
|
-
)
|
|
758
|
-
|
|
759
|
-
return _app
|
|
760
|
-
|
|
761
|
-
def __call__(self, *args, **kwargs) -> None:
|
|
762
|
-
if len(args) != 0 or len(kwargs) != 0:
|
|
763
|
-
print(
|
|
764
|
-
f"[warning] {self._func.__name__} function is served with no arguments."
|
|
765
|
-
)
|
|
766
|
-
|
|
767
|
-
from uvicorn import run
|
|
768
|
-
|
|
769
|
-
app = self.build_app()
|
|
770
|
-
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
|
+
"""
|
|
771
741
|
|
|
772
742
|
def openapi(self) -> dict[str, Any]:
|
|
773
743
|
"""
|
|
774
744
|
Build the OpenAPI specification for the served function.
|
|
775
745
|
Attach needed metadata for a better integration to fal.
|
|
776
746
|
"""
|
|
777
|
-
|
|
778
|
-
spec = app.openapi()
|
|
747
|
+
spec = super().openapi()
|
|
779
748
|
self._mark_order_openapi(spec)
|
|
780
749
|
return spec
|
|
781
750
|
|
|
@@ -787,7 +756,8 @@ class ServeWrapper:
|
|
|
787
756
|
"""
|
|
788
757
|
|
|
789
758
|
def mark_order(obj: dict[str, Any], key: str):
|
|
790
|
-
|
|
759
|
+
if key in obj:
|
|
760
|
+
obj[f"x-fal-order-{key}"] = list(obj[key].keys())
|
|
791
761
|
|
|
792
762
|
mark_order(spec, "paths")
|
|
793
763
|
|
|
@@ -796,18 +766,129 @@ class ServeWrapper:
|
|
|
796
766
|
Mark the order of properties in the schema object.
|
|
797
767
|
They can have 'allOf', 'properties' or '$ref' key.
|
|
798
768
|
"""
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
mark_order(schema, "properties")
|
|
769
|
+
for sub_schema in schema.get("allOf", []):
|
|
770
|
+
order_schema_object(sub_schema)
|
|
771
|
+
|
|
772
|
+
mark_order(schema, "properties")
|
|
804
773
|
|
|
805
|
-
for key in spec.get("components", {}).get("schemas"
|
|
774
|
+
for key in spec.get("components", {}).get("schemas", {}):
|
|
806
775
|
order_schema_object(spec["components"]["schemas"][key])
|
|
807
776
|
|
|
808
777
|
return spec
|
|
809
778
|
|
|
810
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
|
+
|
|
811
892
|
@dataclass
|
|
812
893
|
class IsolatedFunction(Generic[ArgsT, ReturnT]):
|
|
813
894
|
host: Host[ArgsT, ReturnT]
|