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.

Files changed (68) hide show
  1. {fal-0.12.2 → fal-0.12.3}/PKG-INFO +4 -7
  2. {fal-0.12.2 → fal-0.12.3}/pyproject.toml +4 -7
  3. {fal-0.12.2 → fal-0.12.3}/setup.py +4 -7
  4. {fal-0.12.2 → fal-0.12.3}/src/fal/__init__.py +11 -2
  5. {fal-0.12.2 → fal-0.12.3}/src/fal/api.py +130 -50
  6. {fal-0.12.2 → fal-0.12.3}/src/fal/app.py +81 -134
  7. {fal-0.12.2 → fal-0.12.3}/src/fal/apps.py +24 -6
  8. {fal-0.12.2 → fal-0.12.3}/src/fal/auth/__init__.py +1 -1
  9. {fal-0.12.2 → fal-0.12.3}/src/fal/auth/auth0.py +31 -22
  10. {fal-0.12.2 → fal-0.12.3}/src/fal/cli.py +5 -3
  11. fal-0.12.3/src/fal/env.py +3 -0
  12. {fal-0.12.2 → fal-0.12.3}/src/fal/logging/__init__.py +0 -2
  13. {fal-0.12.2 → fal-0.12.3}/src/fal/logging/trace.py +8 -1
  14. {fal-0.12.2 → fal-0.12.3}/src/fal/sdk.py +33 -6
  15. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/__init__.py +16 -0
  16. fal-0.12.2/src/fal/env.py +0 -7
  17. fal-0.12.2/src/fal/logging/datadog.py +0 -78
  18. {fal-0.12.2 → fal-0.12.3}/README.md +0 -0
  19. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  20. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  21. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  22. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  23. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  24. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  25. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  26. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  27. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  28. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  29. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  30. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  31. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  32. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  33. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  34. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  35. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  36. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  37. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  38. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  39. {fal-0.12.2 → fal-0.12.3}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  40. {fal-0.12.2 → fal-0.12.3}/src/fal/_serialization.py +0 -0
  41. {fal-0.12.2 → fal-0.12.3}/src/fal/auth/local.py +0 -0
  42. {fal-0.12.2 → fal-0.12.3}/src/fal/console/__init__.py +0 -0
  43. {fal-0.12.2 → fal-0.12.3}/src/fal/console/icons.py +0 -0
  44. {fal-0.12.2 → fal-0.12.3}/src/fal/console/ux.py +0 -0
  45. {fal-0.12.2 → fal-0.12.3}/src/fal/exceptions/__init__.py +0 -0
  46. {fal-0.12.2 → fal-0.12.3}/src/fal/exceptions/_base.py +0 -0
  47. {fal-0.12.2 → fal-0.12.3}/src/fal/exceptions/auth.py +0 -0
  48. {fal-0.12.2 → fal-0.12.3}/src/fal/exceptions/handlers.py +0 -0
  49. {fal-0.12.2 → fal-0.12.3}/src/fal/flags.py +0 -0
  50. {fal-0.12.2 → fal-0.12.3}/src/fal/logging/isolate.py +0 -0
  51. {fal-0.12.2 → fal-0.12.3}/src/fal/logging/style.py +0 -0
  52. {fal-0.12.2 → fal-0.12.3}/src/fal/logging/user.py +0 -0
  53. {fal-0.12.2 → fal-0.12.3}/src/fal/py.typed +0 -0
  54. {fal-0.12.2 → fal-0.12.3}/src/fal/rest_client.py +0 -0
  55. {fal-0.12.2 → fal-0.12.3}/src/fal/sync.py +0 -0
  56. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/exceptions.py +0 -0
  57. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/__init__.py +0 -0
  58. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/file.py +0 -0
  59. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/providers/fal.py +0 -0
  60. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/providers/gcp.py +0 -0
  61. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/providers/r2.py +0 -0
  62. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/file/types.py +0 -0
  63. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/image/__init__.py +0 -0
  64. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/image/image.py +0 -0
  65. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/mainify.py +0 -0
  66. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/optimize.py +0 -0
  67. {fal-0.12.2 → fal-0.12.3}/src/fal/toolkit/utils/__init__.py +0 -0
  68. {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.2
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,<0.25.0)
20
+ Requires-Dist: httpx (>=0.15.4)
24
21
  Requires-Dist: importlib-metadata (>=4.4) ; python_version < "3.10"
25
- Requires-Dist: isolate-proto (>=0.3.1,<0.4.0)
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.2"
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 = "^0.3.1"
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,<0.25.0"
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,<0.25.0',
46
- 'isolate-proto>=0.3.1,<0.4.0',
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.2',
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(["fastapi==0.99.1", "uvicorn"])
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 ServeWrapper:
734
- _func: Callable
735
-
736
- def __init__(self, func: Callable):
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
- app = self.build_app()
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
- obj[f"x-fal-order-{key}"] = list(obj[key].keys())
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
- if "allOf" in schema:
801
- for sub_schema in schema["allOf"]:
802
- order_schema_object(sub_schema)
803
- if "properties" in schema:
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") or {}:
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]