fal 0.11.3__py3-none-any.whl → 0.11.4__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 CHANGED
@@ -6,6 +6,7 @@ from fal import apps
6
6
  from fal.api import FalServerlessHost, LocalHost, cached
7
7
  from fal.api import function
8
8
  from fal.api import function as isolated
9
+ from fal.app import App, endpoint, wrap_app
9
10
  from fal.sdk import FalServerlessKeyCredentials
10
11
  from fal.sync import sync_dir
11
12
 
@@ -15,35 +16,8 @@ serverless = FalServerlessHost()
15
16
  # DEPRECATED - use serverless instead
16
17
  cloud = FalServerlessHost()
17
18
 
18
- DBT_FAL_IMPORT_NOTICE = """
19
- The dbt tool `fal` and `dbt-fal` adapter have been merged into a single tool.
20
- Please import from the `fal.dbt` module instead.
21
- Running `pip install dbt-fal` will install the new tool and the adapter alongside.
22
- Then import from the `fal.dbt` module like
23
-
24
- from fal.dbt import {name}
25
-
26
- """
27
-
28
-
29
- # Avoid printing on non-direct imports
30
- def __getattr__(name: str):
31
- if name in (
32
- "NodeStatus",
33
- "FalDbt",
34
- "DbtModel",
35
- "DbtSource",
36
- "DbtTest",
37
- "DbtGenericTest",
38
- "DbtSingularTest",
39
- "Context",
40
- "CurrentModel",
41
- ):
42
- raise ImportError(DBT_FAL_IMPORT_NOTICE.format(name=name))
43
-
44
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
45
-
46
19
 
20
+ # NOTE: This makes `import fal.dbt` import the `dbt-fal` module and `import fal` import the `fal` module
47
21
  # NOTE: taken from dbt-core: https://github.com/dbt-labs/dbt-core/blob/ac539fd5cf325cfb5315339077d03399d575f570/core/dbt/adapters/__init__.py#L1-L7
48
22
  # N.B.
49
23
  # This will add to the package’s __path__ all subdirectories of directories on sys.path named after the package which effectively combines both modules into a single namespace (dbt.adapters)
fal/api.py CHANGED
@@ -552,6 +552,7 @@ def function(
552
552
  exposed_port: int | None = None,
553
553
  max_concurrency: int | None = None,
554
554
  # FalServerlessHost options
555
+ metadata: dict[str, Any] | None = None,
555
556
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
556
557
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
557
558
  max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
@@ -576,6 +577,7 @@ def function(
576
577
  exposed_port: int | None = None,
577
578
  max_concurrency: int | None = None,
578
579
  # FalServerlessHost options
580
+ metadata: dict[str, Any] | None = None,
579
581
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
580
582
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
581
583
  max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
@@ -652,6 +654,7 @@ def function(
652
654
  exposed_port: int | None = None,
653
655
  max_concurrency: int | None = None,
654
656
  # FalServerlessHost options
657
+ metadata: dict[str, Any] | None = None,
655
658
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
656
659
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
657
660
  max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
@@ -681,6 +684,7 @@ def function(
681
684
  exposed_port: int | None = None,
682
685
  max_concurrency: int | None = None,
683
686
  # FalServerlessHost options
687
+ metadata: dict[str, Any] | None = None,
684
688
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
685
689
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
686
690
  max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
@@ -788,7 +792,7 @@ class ServeWrapper:
788
792
  if "properties" in schema:
789
793
  mark_order(schema, "properties")
790
794
 
791
- for key in spec["components"].get("schemas") or {}:
795
+ for key in spec.get("components", {}).get("schemas") or {}:
792
796
  order_schema_object(spec["components"]["schemas"][key])
793
797
 
794
798
  return spec
fal/app.py ADDED
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import os
5
+ import fal.api
6
+ from fal.toolkit import mainify
7
+ from fastapi import FastAPI
8
+ from typing import Any, NamedTuple, Callable, TypeVar, ClassVar
9
+ from fal.logging import get_logger
10
+
11
+ EndpointT = TypeVar("EndpointT", bound=Callable[..., Any])
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
16
+ def initialize_and_serve():
17
+ app = cls()
18
+ app.serve()
19
+
20
+ try:
21
+ app = cls(_allow_init=True)
22
+ metadata = app.openapi()
23
+ except Exception as exc:
24
+ logger.warning("Failed to build OpenAPI specification for %s", cls.__name__)
25
+ metadata = {}
26
+
27
+ wrapper = fal.api.function(
28
+ "virtualenv",
29
+ requirements=cls.requirements,
30
+ machine_type=cls.machine_type,
31
+ **cls.host_kwargs,
32
+ **kwargs,
33
+ metadata=metadata,
34
+ serve=True,
35
+ )
36
+ return wrapper(initialize_and_serve).on(
37
+ serve=False,
38
+ exposed_port=8080,
39
+ )
40
+
41
+
42
+ @mainify
43
+ class RouteSignature(NamedTuple):
44
+ path: str
45
+
46
+
47
+ @mainify
48
+ class App:
49
+ requirements: ClassVar[list[str]] = []
50
+ machine_type: ClassVar[str] = "S"
51
+ host_kwargs: ClassVar[dict[str, Any]] = {}
52
+
53
+ def __init_subclass__(cls, **kwargs):
54
+ cls.host_kwargs = kwargs
55
+
56
+ if cls.__init__ is not App.__init__:
57
+ raise ValueError(
58
+ "App classes should not override __init__ directly. "
59
+ "Use setup() instead."
60
+ )
61
+
62
+ def __init__(self, *, _allow_init: bool = False):
63
+ if not _allow_init and not os.getenv("IS_ISOLATE_AGENT"):
64
+ raise NotImplementedError(
65
+ "Running apps through SDK is not implemented yet."
66
+ )
67
+
68
+ def setup(self):
69
+ """Setup the application before serving."""
70
+
71
+ def serve(self) -> None:
72
+ import uvicorn
73
+
74
+ app = self._build_app()
75
+ self.setup()
76
+ uvicorn.run(app, host="0.0.0.0", port=8080)
77
+
78
+ def _build_app(self) -> FastAPI:
79
+ from fastapi import FastAPI
80
+ from fastapi.middleware.cors import CORSMiddleware
81
+
82
+ _app = FastAPI()
83
+
84
+ _app.add_middleware(
85
+ CORSMiddleware,
86
+ allow_credentials=True,
87
+ allow_headers=("*"),
88
+ allow_methods=("*"),
89
+ allow_origins=("*"),
90
+ )
91
+
92
+ routes: dict[RouteSignature, Callable[..., Any]] = {
93
+ signature: endpoint
94
+ for _, endpoint in inspect.getmembers(self, inspect.ismethod)
95
+ if (signature := getattr(endpoint, "route_signature", None))
96
+ }
97
+ if not routes:
98
+ raise ValueError("An application must have at least one route!")
99
+
100
+ for signature, endpoint in routes.items():
101
+ _app.add_api_route(
102
+ signature.path,
103
+ endpoint,
104
+ name=endpoint.__name__,
105
+ methods=["POST"],
106
+ )
107
+
108
+ return _app
109
+
110
+ def openapi(self) -> dict[str, Any]:
111
+ """
112
+ Build the OpenAPI specification for the served function.
113
+ Attach needed metadata for a better integration to fal.
114
+ """
115
+ app = self._build_app()
116
+ spec = app.openapi()
117
+ self._mark_order_openapi(spec)
118
+ return spec
119
+
120
+ def _mark_order_openapi(self, spec: dict[str, Any]):
121
+ """
122
+ Add x-fal-order-* keys to the OpenAPI specification to help the rendering of UI.
123
+
124
+ NOTE: We rely on the fact that fastapi and Python dicts keep the order of properties.
125
+ """
126
+
127
+ def mark_order(obj: dict[str, Any], key: str):
128
+ obj[f"x-fal-order-{key}"] = list(obj[key].keys())
129
+
130
+ mark_order(spec, "paths")
131
+
132
+ def order_schema_object(schema: dict[str, Any]):
133
+ """
134
+ Mark the order of properties in the schema object.
135
+ They can have 'allOf', 'properties' or '$ref' key.
136
+ """
137
+ if "allOf" in schema:
138
+ for sub_schema in schema["allOf"]:
139
+ order_schema_object(sub_schema)
140
+ if "properties" in schema:
141
+ mark_order(schema, "properties")
142
+
143
+ for key in spec["components"].get("schemas") or {}:
144
+ order_schema_object(spec["components"]["schemas"][key])
145
+
146
+ return spec
147
+
148
+
149
+ @mainify
150
+ def endpoint(path: str) -> Callable[[EndpointT], EndpointT]:
151
+ """Designate the decorated function as an application endpoint."""
152
+
153
+ def marker_fn(callable: EndpointT) -> EndpointT:
154
+ if hasattr(callable, "route_signature"):
155
+ raise ValueError(
156
+ f"Can't set multiple routes for the same function: {callable.__name__}"
157
+ )
158
+
159
+ callable.route_signature = RouteSignature(path=path) # type: ignore
160
+ return callable
161
+
162
+ return marker_fn
fal/cli.py CHANGED
@@ -9,7 +9,7 @@ from uuid import uuid4
9
9
 
10
10
  import click
11
11
  import fal.auth as auth
12
- import grpc
12
+ import fal
13
13
  from fal import api, sdk
14
14
  from fal.console import console
15
15
  from fal.exceptions import ApplicationExceptionHandler
@@ -145,15 +145,6 @@ def auth_cli():
145
145
  @auth_cli.command(name="login")
146
146
  def auth_login():
147
147
  auth.login()
148
- try:
149
- client = sdk.FalServerlessClient(f"{DEFAULT_HOST}:{DEFAULT_PORT}")
150
- with client.connect() as connection:
151
- connection.list_aliases()
152
- except grpc.RpcError as e:
153
- if "Insufficient permissions" in e.details():
154
- console.print(e.details())
155
- else:
156
- raise e
157
148
 
158
149
 
159
150
  @auth_cli.command(name="logout")
@@ -244,6 +235,28 @@ def function_cli(ctx, host: str, port: str):
244
235
  ctx.obj = api.FalServerlessHost(f"{host}:{port}")
245
236
 
246
237
 
238
+ def load_function_from(
239
+ host: api.FalServerlessHost,
240
+ file_path: str,
241
+ function_name: str,
242
+ ) -> api.IsolatedFunction:
243
+ import runpy
244
+
245
+ module = runpy.run_path(file_path)
246
+ if function_name not in module:
247
+ raise api.FalServerlessError(f"Function '{function_name}' not found in module")
248
+
249
+ target = module[function_name]
250
+ if isinstance(target, type) and issubclass(target, fal.App):
251
+ target = fal.wrap_app(target, host=host)
252
+
253
+ if not isinstance(target, api.IsolatedFunction):
254
+ raise api.FalServerlessError(
255
+ f"Function '{function_name}' is not a fal.function or a fal.App"
256
+ )
257
+ return target
258
+
259
+
247
260
  @function_cli.command("serve")
248
261
  @click.option("--alias", default=None)
249
262
  @click.option(
@@ -262,15 +275,9 @@ def register_application(
262
275
  alias: str | None,
263
276
  auth_mode: ALIAS_AUTH_TYPE,
264
277
  ):
265
- import runpy
266
-
267
278
  user_id = _get_user_id()
268
279
 
269
- module = runpy.run_path(file_path)
270
- if function_name not in module:
271
- raise api.FalServerlessError(f"Function '{function_name}' not found in module")
272
-
273
- isolated_function: api.IsolatedFunction = module[function_name]
280
+ isolated_function = load_function_from(host, file_path, function_name)
274
281
  gateway_options = isolated_function.options.gateway
275
282
  if "serve" not in gateway_options and "exposed_port" not in gateway_options:
276
283
  raise api.FalServerlessError(
@@ -289,7 +296,7 @@ def register_application(
289
296
  options=isolated_function.options,
290
297
  application_name=alias,
291
298
  application_auth_mode=auth_mode,
292
- metadata={},
299
+ metadata=isolated_function.options.host.get("metadata", {}),
293
300
  )
294
301
 
295
302
  if id:
@@ -307,6 +314,15 @@ def register_application(
307
314
  console.print(f"URL: https://{user_id}-{id}.{gateway_host}")
308
315
 
309
316
 
317
+ @function_cli.command("run")
318
+ @click.argument("file_path", required=True)
319
+ @click.argument("function_name", required=True)
320
+ @click.pass_obj
321
+ def run(host: api.FalServerlessHost, file_path: str, function_name: str):
322
+ isolated_function = load_function_from(host, file_path, function_name)
323
+ isolated_function()
324
+
325
+
310
326
  @function_cli.command("logs")
311
327
  @click.option("--lines", default=100)
312
328
  @click.option("--url", default=None)
@@ -520,30 +536,6 @@ def remove_http_and_port_from_url(url):
520
536
  return url
521
537
 
522
538
 
523
- # dbt-fal commands to be errored out
524
- DBT_FAL_COMMAND_NOTICE = """
525
- The dbt tool `fal` and `dbt-fal` adapter have been merged into a single tool.
526
- Please use the new `dbt-fal` command line tool instead.
527
- Running `pip install dbt-fal` will install the new tool and the adapter alongside.
528
- Then run your command like
529
-
530
- dbt-fal <command>
531
-
532
- """
533
-
534
-
535
- @cli.command("run", context_settings={"ignore_unknown_options": True})
536
- @click.argument("any", nargs=-1, type=click.UNPROCESSED)
537
- def dbt_run(any):
538
- raise click.BadArgumentUsage(DBT_FAL_COMMAND_NOTICE)
539
-
540
-
541
- @cli.command("flow", context_settings={"ignore_unknown_options": True})
542
- @click.argument("any", nargs=-1, type=click.UNPROCESSED)
543
- def dbt_flow(any):
544
- raise click.BadArgumentUsage(DBT_FAL_COMMAND_NOTICE)
545
-
546
-
547
539
  def _get_user_id() -> str:
548
540
  try:
549
541
  user_details_response = get_user_details.sync_detailed(
fal/exceptions/_base.py CHANGED
@@ -14,4 +14,4 @@ class FalServerlessException(Exception):
14
14
  super().__init__(message)
15
15
 
16
16
  def __str__(self) -> str:
17
- return self.message
17
+ return self.message + (f"\nHint: {self.hint}" if self.hint else "")
fal/exceptions/auth.py CHANGED
@@ -9,5 +9,5 @@ class UnauthenticatedException(FalServerlessException):
9
9
  def __init__(self) -> None:
10
10
  super().__init__(
11
11
  message="You must be authenticated.",
12
- hint="Login via `fal auth login`",
12
+ hint="Login via `fal auth login` or make sure to setup fal keys correctly.",
13
13
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 0.11.3
3
+ Version: 0.11.4
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels
6
6
  Author-email: hello@fal.ai
@@ -52,7 +52,7 @@ openapi_fal_rest/api/usage/per_machine_usage.py,sha256=MRMxYP1A-88hFPnEEI0TfCujr
52
52
  openapi_fal_rest/api/usage/per_machine_usage_details.py,sha256=1trqk3c3NDfUaQvsu-qmeorflPbrSdHuB9CTNpgvM_Q,4779
53
53
  openapi_fal_rest/client.py,sha256=G6BpJg9j7-JsrAUGddYwkzeWRYickBjPdcVgXoPzxuE,2817
54
54
  openapi_fal_rest/errors.py,sha256=8mXSxdfSGzxT82srdhYbR0fHfgenxJXaUtMkaGgb6iU,470
55
- openapi_fal_rest/models/__init__.py,sha256=6CLCG50sd_eBvivvI4GUSaApsnJM3vJQ6W8C4Eo-jO8,2931
55
+ openapi_fal_rest/models/__init__.py,sha256=obF7FJiQflc4e5UzkiOCQUfqdLvCFD6zSvE2I2YcJkg,2940
56
56
  openapi_fal_rest/models/app_metadata_response_app_metadata.py,sha256=swJMfWvbjlMF8dmv-KEqcR9If0UjsRogwj9UqBBlkpc,1251
57
57
  openapi_fal_rest/models/body_create_token.py,sha256=GBiwnz4US7VqD7Y6uM4Vy4P89aymRo9etU5WJ1NNl68,1902
58
58
  openapi_fal_rest/models/body_upload_file.py,sha256=QD2KgOE-YYQi3ktU94OMKua2V0NLkxLa01YdVLnty9g,1900
@@ -62,7 +62,7 @@ openapi_fal_rest/models/file_spec.py,sha256=cd56WUz3VbeBv-1s6YRn7blPQxGyH0SBVlAl
62
62
  openapi_fal_rest/models/gateway_stats_by_time.py,sha256=XcX8C9lh0vn3yEm94umaPVTZKXFUNt3afF1BErHD4mc,3572
63
63
  openapi_fal_rest/models/gateway_usage_stats.py,sha256=OlBVAzkNwy-rtP4-KRfnGHF8eV6K3a1QCJJImhjHSoA,4989
64
64
  openapi_fal_rest/models/get_gateway_request_stats_by_time_response_get_gateway_request_stats_by_time.py,sha256=qIf5EVyDLG9YjxC4BWNZm3DHTiNwiq5s4_wN1nUlnKw,2590
65
- openapi_fal_rest/models/grouped_usage_detail.py,sha256=S2YP_SF64mN1lvuD-jScAWKj8CQbuZUN9eBPG0QeDTo,2358
65
+ openapi_fal_rest/models/grouped_usage_detail.py,sha256=nKZX50iuqsV2Gey_xwzO5qMzRlqbIzOaN9VC6toF5cc,2388
66
66
  openapi_fal_rest/models/handle_stripe_webhook_response_handle_stripe_webhook.py,sha256=Nsw1G8WDub678G2Rl_AcOLZc6uzhQCJTSox3UFLIbq8,1340
67
67
  openapi_fal_rest/models/hash_check.py,sha256=T9R7n4EdadCxbFUZvresZZFPYwDfyJMZVNxY6wIJEE8,1352
68
68
  openapi_fal_rest/models/http_validation_error.py,sha256=2nhqlv8RX2qp6VR7hb8-SKtzJWXSZ0J95ThW9J4agJo,2131
@@ -93,21 +93,22 @@ openapi_fal_rest/models/user_key_info.py,sha256=4JJ3Bc5YOX--aJgk3PrqPI-TQMXBi8ge
93
93
  openapi_fal_rest/models/validation_error.py,sha256=I6tB-HbEOmE0ua27erDX5PX5YUynENv_dgPN3SrwTrQ,2091
94
94
  openapi_fal_rest/py.typed,sha256=8ZJUsxZiuOy1oJeVhsTWQhTG_6pTVHVXk5hJL79ebTk,25
95
95
  openapi_fal_rest/types.py,sha256=4xaUIOliefW-5jz_p-JT2LO7-V0wKWaniHGtjPBQfvQ,993
96
- fal/__init__.py,sha256=WE0JWoOFjzLVQPnj4tXRPgH1y5fZW0GLXPGEyORgAFg,1677
96
+ fal/__init__.py,sha256=oBfj8CzfWo-SYWRSEXsJDxCJXPc0ta3t96f1mjgO55o,1075
97
97
  fal/_serialization.py,sha256=GD3iXh-noBr0S33eLRaaj9dupo0OclTiyYPiYIMHBuU,3695
98
- fal/api.py,sha256=CZm-h1b1CmGTPmecHAPvL8_PZdoxhz-ykufm927Pmv4,29613
98
+ fal/api.py,sha256=mqDZ3C88t6JXWCXszV5QBgMVMAg4N5x46wpjZLQi2ys,29797
99
+ fal/app.py,sha256=v2K1-7piOL2poXPy5DBQqSuC4fjlbrSnNqhMBzFWooE,4761
99
100
  fal/apps.py,sha256=j8c4mR-kpRKnZ6wfcuvdgrjuAse7NGD9QR_ZRYan3m4,4121
100
101
  fal/auth/__init__.py,sha256=IzXoOgHaWkZvE2O0JUC9CL18whq8HILFW4oTrcrCJno,2547
101
102
  fal/auth/auth0.py,sha256=qFzCy-n9ptctIu3uj4zbx62WVdjjVw-tQSZEYga9IKY,5302
102
103
  fal/auth/local.py,sha256=lZqp4j32l2xFpY8zYvLoIHHyJrNAJDcm5MxgsLpY_pw,1786
103
- fal/cli.py,sha256=D0csuZOGATUUxwiwuoHy8IVfF_9zcquJMgBhyozjB8s,17134
104
+ fal/cli.py,sha256=bbpVDvrMR68T69nvSBSPjbJ7g_PGingwk68omNIO1l0,16899
104
105
  fal/console/__init__.py,sha256=ernZ4bzvvliQh5SmrEqQ7lA5eVcbw6Ra2jalKtA7dxg,132
105
106
  fal/console/icons.py,sha256=De9MfFaSkO2Lqfne13n3PrYfTXJVIzYZVqYn5BWsdrA,108
106
107
  fal/console/ux.py,sha256=4vj1aGA3grRn-ebeMuDLR6u3YjMwUGpqtNgdTG9su5s,485
107
108
  fal/env.py,sha256=g_s2FAtY2-6zyTar_2NUmswHcab--3xozEJW4E6Y9iQ,172
108
109
  fal/exceptions/__init__.py,sha256=Q4LCSqIrJ8GFQZWH5BvWL5mDPR0HwYQuIhNvsdiOkEU,938
109
- fal/exceptions/_base.py,sha256=eh3u06n4OzaXzwxE5NaZopCfH1X8ia_QQBMou09g-_I,412
110
- fal/exceptions/auth.py,sha256=sknwlBj_tNqnVTZ0XeIv1LHsYA32DH4g3H5xnGmXAk4,342
110
+ fal/exceptions/_base.py,sha256=LeQmx-soL_-s1742WKN18VwTVjUuYP0L0BdQHPJBpM4,460
111
+ fal/exceptions/auth.py,sha256=01Ro7SyGJpwchubdHe14Cl6-Al1jUj16Sy4BvakNWf4,384
111
112
  fal/exceptions/handlers.py,sha256=i_rFyYnfN2p5HmaUGEjxmKqdoixzsMy1tkWdOcqQNL4,1413
112
113
  fal/flags.py,sha256=Ib8cXVzbJZxjSDTYwlBt1brCTD1oqZijexkqZQHRTho,757
113
114
  fal/logging/__init__.py,sha256=tXFlHBtPFydL3Wgzhq72-EzCFKrnDYvZIF0pKYGVxac,1649
@@ -132,7 +133,7 @@ fal/toolkit/image/image.py,sha256=zFCGKiGBzdlwq20ISY-olBqUDzUpQ84L5LbLepZteoo,35
132
133
  fal/toolkit/mainify.py,sha256=E7gE45nZQZoaJdSlIq0mqajcH-IjcuPBWFmKm5hvhAU,406
133
134
  fal/toolkit/utils/__init__.py,sha256=b3zVpm50Upx1saXU7RiV9r9in6-Chs4OU9KRjBv7MYI,83
134
135
  fal/toolkit/utils/download_utils.py,sha256=OlAun9phis7LJtBvVjsQC9sQ6qcZMeZ1_ebdRHm7CLw,13921
135
- fal-0.11.3.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
136
- fal-0.11.3.dist-info/entry_points.txt,sha256=nE9GBVV3PdBosudFwbIzZQUe_9lfPR6EH8K_FdDASnM,62
137
- fal-0.11.3.dist-info/METADATA,sha256=evOSg_iE5c-4cj5Bz4u02jNOWIcHSH_sXA5zxhSUJc4,2956
138
- fal-0.11.3.dist-info/RECORD,,
136
+ fal-0.11.4.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
137
+ fal-0.11.4.dist-info/entry_points.txt,sha256=nE9GBVV3PdBosudFwbIzZQUe_9lfPR6EH8K_FdDASnM,62
138
+ fal-0.11.4.dist-info/METADATA,sha256=JGab-ufAg6-rP3lg01f3Nfm4-rQoqyPrvouS7AGz8G0,2956
139
+ fal-0.11.4.dist-info/RECORD,,
@@ -12,7 +12,9 @@ from .get_gateway_request_stats_by_time_response_get_gateway_request_stats_by_ti
12
12
  GetGatewayRequestStatsByTimeResponseGetGatewayRequestStatsByTime,
13
13
  )
14
14
  from .grouped_usage_detail import GroupedUsageDetail
15
- from .handle_stripe_webhook_response_handle_stripe_webhook import HandleStripeWebhookResponseHandleStripeWebhook
15
+ from .handle_stripe_webhook_response_handle_stripe_webhook import (
16
+ HandleStripeWebhookResponseHandleStripeWebhook,
17
+ )
16
18
  from .hash_check import HashCheck
17
19
  from .http_validation_error import HTTPValidationError
18
20
  from .initiate_upload_info import InitiateUploadInfo
@@ -12,14 +12,14 @@ class GroupedUsageDetail:
12
12
  model_id (str):
13
13
  machine_type (str):
14
14
  request_count (int):
15
- avg_duration (float):
15
+ median_duration (float):
16
16
  total_duration (float):
17
17
  """
18
18
 
19
19
  model_id: str
20
20
  machine_type: str
21
21
  request_count: int
22
- avg_duration: float
22
+ median_duration: float
23
23
  total_duration: float
24
24
  additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
25
25
 
@@ -27,7 +27,7 @@ class GroupedUsageDetail:
27
27
  model_id = self.model_id
28
28
  machine_type = self.machine_type
29
29
  request_count = self.request_count
30
- avg_duration = self.avg_duration
30
+ median_duration = self.median_duration
31
31
  total_duration = self.total_duration
32
32
 
33
33
  field_dict: Dict[str, Any] = {}
@@ -37,7 +37,7 @@ class GroupedUsageDetail:
37
37
  "model_id": model_id,
38
38
  "machine_type": machine_type,
39
39
  "request_count": request_count,
40
- "avg_duration": avg_duration,
40
+ "median_duration": median_duration,
41
41
  "total_duration": total_duration,
42
42
  }
43
43
  )
@@ -53,7 +53,7 @@ class GroupedUsageDetail:
53
53
 
54
54
  request_count = d.pop("request_count")
55
55
 
56
- avg_duration = d.pop("avg_duration")
56
+ median_duration = d.pop("median_duration")
57
57
 
58
58
  total_duration = d.pop("total_duration")
59
59
 
@@ -61,7 +61,7 @@ class GroupedUsageDetail:
61
61
  model_id=model_id,
62
62
  machine_type=machine_type,
63
63
  request_count=request_count,
64
- avg_duration=avg_duration,
64
+ median_duration=median_duration,
65
65
  total_duration=total_duration,
66
66
  )
67
67
 
File without changes