fal 1.3.3__py3-none-any.whl → 1.7.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.

@@ -6,8 +6,10 @@ import os
6
6
  import posixpath
7
7
  import uuid
8
8
  from dataclasses import dataclass
9
+ from typing import Optional
9
10
 
10
11
  from fal.toolkit.file.types import FileData, FileRepository
12
+ from fal.toolkit.utils.retry import retry
11
13
 
12
14
  DEFAULT_URL_TIMEOUT = 60 * 15 # 15 minutes
13
15
 
@@ -50,7 +52,12 @@ class GoogleStorageRepository(FileRepository):
50
52
 
51
53
  return self._bucket
52
54
 
53
- def save(self, data: FileData) -> str:
55
+ @retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
56
+ def save(
57
+ self,
58
+ data: FileData,
59
+ object_lifecycle_preference: Optional[dict[str, str]] = None,
60
+ ) -> str:
54
61
  destination_path = posixpath.join(
55
62
  self.folder,
56
63
  f"{uuid.uuid4().hex}_{data.file_name}",
@@ -6,8 +6,10 @@ import posixpath
6
6
  import uuid
7
7
  from dataclasses import dataclass
8
8
  from io import BytesIO
9
+ from typing import Optional
9
10
 
10
11
  from fal.toolkit.file.types import FileData, FileRepository
12
+ from fal.toolkit.utils.retry import retry
11
13
 
12
14
  DEFAULT_URL_TIMEOUT = 60 * 15 # 15 minutes
13
15
 
@@ -67,7 +69,12 @@ class R2Repository(FileRepository):
67
69
 
68
70
  return self._bucket
69
71
 
70
- def save(self, data: FileData) -> str:
72
+ @retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
73
+ def save(
74
+ self,
75
+ data: FileData,
76
+ object_lifecycle_preference: Optional[dict[str, str]] = None,
77
+ ) -> str:
71
78
  destination_path = posixpath.join(
72
79
  self.key,
73
80
  f"{uuid.uuid4().hex}_{data.file_name}",
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import posixpath
5
+ import uuid
6
+ from dataclasses import dataclass
7
+ from io import BytesIO
8
+ from typing import Optional
9
+
10
+ from fal.toolkit.file.types import FileData, FileRepository
11
+ from fal.toolkit.utils.retry import retry
12
+
13
+ DEFAULT_URL_TIMEOUT = 60 * 15 # 15 minutes
14
+
15
+
16
+ @dataclass
17
+ class S3Repository(FileRepository):
18
+ bucket_name: str = "fal_file_storage"
19
+ url_expiration: int = DEFAULT_URL_TIMEOUT
20
+ aws_access_key_id: str | None = None
21
+ aws_secret_access_key: str | None = None
22
+
23
+ _s3_client = None
24
+
25
+ def __post_init__(self):
26
+ try:
27
+ import boto3
28
+ from botocore.client import Config
29
+ except ImportError:
30
+ raise Exception("boto3 is not installed")
31
+
32
+ if self.aws_access_key_id is None:
33
+ self.aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID")
34
+ if self.aws_access_key_id is None:
35
+ raise Exception("AWS_ACCESS_KEY_ID environment variable is not set")
36
+
37
+ if self.aws_secret_access_key is None:
38
+ self.aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
39
+ if self.aws_secret_access_key is None:
40
+ raise Exception("AWS_SECRET_ACCESS_KEY environment variable is not set")
41
+
42
+ self._s3_client = boto3.client(
43
+ "s3",
44
+ aws_access_key_id=self.aws_access_key_id,
45
+ aws_secret_access_key=self.aws_secret_access_key,
46
+ config=Config(signature_version="s3v4"),
47
+ )
48
+
49
+ @property
50
+ def storage_client(self):
51
+ if self._s3_client is None:
52
+ raise Exception("S3 client is not initialized")
53
+
54
+ return self._s3_client
55
+
56
+ @retry(max_retries=3, base_delay=1, backoff_type="exponential", jitter=True)
57
+ def save(
58
+ self,
59
+ data: FileData,
60
+ object_lifecycle_preference: Optional[dict[str, str]] = None,
61
+ key: Optional[str] = None,
62
+ ) -> str:
63
+ destination_path = posixpath.join(
64
+ key or "",
65
+ f"{uuid.uuid4().hex}_{data.file_name}",
66
+ )
67
+
68
+ self.storage_client.upload_fileobj(
69
+ BytesIO(data.data),
70
+ self.bucket_name,
71
+ destination_path,
72
+ ExtraArgs={"ContentType": data.content_type},
73
+ )
74
+
75
+ public_url = self.storage_client.generate_presigned_url(
76
+ ClientMethod="get_object",
77
+ Params={"Bucket": self.bucket_name, "Key": destination_path},
78
+ ExpiresIn=self.url_expiration,
79
+ )
80
+ return public_url
fal/toolkit/file/types.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from mimetypes import guess_extension, guess_type
5
5
  from pathlib import Path
6
- from typing import Literal
6
+ from typing import Literal, Optional
7
7
  from uuid import uuid4
8
8
 
9
9
 
@@ -29,12 +29,18 @@ class FileData:
29
29
  self.file_name = file_name
30
30
 
31
31
 
32
- RepositoryId = Literal["fal", "fal_v2", "in_memory", "gcp_storage", "r2", "cdn"]
32
+ RepositoryId = Literal[
33
+ "fal", "fal_v2", "fal_v3", "in_memory", "gcp_storage", "r2", "cdn"
34
+ ]
33
35
 
34
36
 
35
37
  @dataclass
36
38
  class FileRepository:
37
- def save(self, data: FileData) -> str:
39
+ def save(
40
+ self,
41
+ data: FileData,
42
+ object_lifecycle_preference: Optional[dict[str, str]] = None,
43
+ ) -> str:
38
44
  raise NotImplementedError()
39
45
 
40
46
  def save_file(
@@ -45,6 +51,7 @@ class FileRepository:
45
51
  multipart_threshold: int | None = None,
46
52
  multipart_chunk_size: int | None = None,
47
53
  multipart_max_concurrency: int | None = None,
54
+ object_lifecycle_preference: Optional[dict[str, str]] = None,
48
55
  ) -> tuple[str, FileData | None]:
49
56
  if multipart:
50
57
  raise NotImplementedError()
@@ -52,4 +59,4 @@ class FileRepository:
52
59
  with open(file_path, "rb") as fobj:
53
60
  data = FileData(fobj.read(), content_type, Path(file_path).name)
54
61
 
55
- return self.save(data), data
62
+ return self.save(data, object_lifecycle_preference), data
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import urllib.request
3
4
  from functools import lru_cache
4
5
  from typing import TYPE_CHECKING
5
- from urllib.request import Request, urlopen
6
6
 
7
7
  from .image import * # noqa: F403
8
8
 
@@ -62,8 +62,8 @@ def read_image_from_url(
62
62
  }
63
63
 
64
64
  try:
65
- request = Request(url, headers=TEMP_HEADERS)
66
- response = urlopen(request)
65
+ request = urllib.request.Request(url, headers=TEMP_HEADERS)
66
+ response = urllib.request.urlopen(request)
67
67
  image_pil = Image.open(response)
68
68
  except Exception:
69
69
  import traceback
@@ -4,9 +4,10 @@ import io
4
4
  from tempfile import NamedTemporaryFile
5
5
  from typing import TYPE_CHECKING, Literal, Optional, Union
6
6
 
7
+ from fastapi import Request
7
8
  from pydantic import BaseModel, Field
8
9
 
9
- from fal.toolkit.file.file import DEFAULT_REPOSITORY, File
10
+ from fal.toolkit.file.file import DEFAULT_REPOSITORY, FALLBACK_REPOSITORY, File
10
11
  from fal.toolkit.file.types import FileRepository, RepositoryId
11
12
  from fal.toolkit.utils.download_utils import _download_file_python
12
13
 
@@ -79,12 +80,18 @@ class Image(File):
79
80
  size: ImageSize | None = None,
80
81
  file_name: str | None = None,
81
82
  repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
83
+ fallback_repository: Optional[
84
+ FileRepository | RepositoryId
85
+ ] = FALLBACK_REPOSITORY,
86
+ request: Optional[Request] = None,
82
87
  ) -> Image:
83
88
  obj = super().from_bytes(
84
89
  data,
85
90
  content_type=f"image/{format}",
86
91
  file_name=file_name,
87
92
  repository=repository,
93
+ fallback_repository=fallback_repository,
94
+ request=request,
88
95
  )
89
96
  obj.width = size.width if size else None
90
97
  obj.height = size.height if size else None
@@ -97,6 +104,10 @@ class Image(File):
97
104
  format: ImageFormat | None = None,
98
105
  file_name: str | None = None,
99
106
  repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
107
+ fallback_repository: Optional[
108
+ FileRepository | RepositoryId
109
+ ] = FALLBACK_REPOSITORY,
110
+ request: Optional[Request] = None,
100
111
  ) -> Image:
101
112
  size = ImageSize(width=pil_image.width, height=pil_image.height)
102
113
  if format is None:
@@ -110,12 +121,24 @@ class Image(File):
110
121
  # enough result quickly to utilize the underlying resources
111
122
  # efficiently.
112
123
  saving_options["compress_level"] = 1
124
+ elif format == "jpeg":
125
+ # JPEG quality is set to 95 by default, which is a good balance
126
+ # between file size and image quality.
127
+ saving_options["quality"] = 95
113
128
 
114
129
  with io.BytesIO() as f:
115
130
  pil_image.save(f, format=format, **saving_options)
116
131
  raw_image = f.getvalue()
117
132
 
118
- return cls.from_bytes(raw_image, format, size, file_name, repository)
133
+ return cls.from_bytes(
134
+ raw_image,
135
+ format,
136
+ size,
137
+ file_name,
138
+ repository,
139
+ fallback_repository=fallback_repository,
140
+ request=request,
141
+ )
119
142
 
120
143
  def to_pil(self, mode: str = "RGB") -> PILImage.Image:
121
144
  try:
fal/toolkit/types.py ADDED
@@ -0,0 +1,140 @@
1
+ import re
2
+ import tempfile
3
+ from contextlib import contextmanager
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Generator, Union
6
+
7
+ import pydantic
8
+ from pydantic.utils import update_not_none
9
+
10
+ from fal.toolkit.image import read_image_from_url
11
+ from fal.toolkit.utils.download_utils import download_file
12
+
13
+ # https://github.com/pydantic/pydantic/pull/2573
14
+ if not hasattr(pydantic, "__version__") or pydantic.__version__.startswith("1."):
15
+ IS_PYDANTIC_V2 = False
16
+ else:
17
+ IS_PYDANTIC_V2 = True
18
+
19
+ MAX_DATA_URI_LENGTH = 10 * 1024 * 1024
20
+ MAX_HTTPS_URL_LENGTH = 2048
21
+
22
+ HTTP_URL_REGEX = (
23
+ r"^https:\/\/(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?::\d{1,5})?(?:\/[^\s]*)?$"
24
+ )
25
+
26
+
27
+ class DownloadFileMixin:
28
+ @contextmanager
29
+ def as_temp_file(self) -> Generator[Path, None, None]:
30
+ with tempfile.TemporaryDirectory() as temp_dir:
31
+ yield download_file(str(self), temp_dir)
32
+
33
+
34
+ class DownloadImageMixin:
35
+ def to_pil(self):
36
+ return read_image_from_url(str(self))
37
+
38
+
39
+ class DataUri(DownloadFileMixin, str):
40
+ if IS_PYDANTIC_V2:
41
+
42
+ @classmethod
43
+ def __get_pydantic_core_schema__(cls, source_type: Any, handler) -> Any:
44
+ return {
45
+ "type": "str",
46
+ "pattern": "^data:",
47
+ "max_length": MAX_DATA_URI_LENGTH,
48
+ "strip_whitespace": True,
49
+ }
50
+
51
+ def __get_pydantic_json_schema__(cls, core_schema, handler) -> Dict[str, Any]:
52
+ json_schema = handler(core_schema)
53
+ json_schema.update(format="data-uri")
54
+ return json_schema
55
+ else:
56
+
57
+ @classmethod
58
+ def __get_validators__(cls):
59
+ yield cls.validate
60
+
61
+ @classmethod
62
+ def validate(cls, value: Any) -> "DataUri":
63
+ from pydantic.validators import str_validator
64
+
65
+ value = str_validator(value)
66
+ value = value.strip()
67
+
68
+ if not value.startswith("data:"):
69
+ raise ValueError("Data URI must start with 'data:'")
70
+
71
+ if len(value) > MAX_DATA_URI_LENGTH:
72
+ raise ValueError(
73
+ f"Data URI is too long. Max length is {MAX_DATA_URI_LENGTH} bytes."
74
+ )
75
+
76
+ return cls(value)
77
+
78
+ @classmethod
79
+ def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
80
+ update_not_none(field_schema, format="data-uri")
81
+
82
+
83
+ class HttpsUrl(DownloadFileMixin, str):
84
+ if IS_PYDANTIC_V2:
85
+
86
+ @classmethod
87
+ def __get_pydantic_core_schema__(cls, source_type: Any, handler) -> Any:
88
+ return {
89
+ "type": "str",
90
+ "pattern": HTTP_URL_REGEX,
91
+ "max_length": MAX_HTTPS_URL_LENGTH,
92
+ "strip_whitespace": True,
93
+ }
94
+
95
+ def __get_pydantic_json_schema__(cls, core_schema, handler) -> Dict[str, Any]:
96
+ json_schema = handler(core_schema)
97
+ json_schema.update(format="https-url")
98
+ return json_schema
99
+
100
+ else:
101
+
102
+ @classmethod
103
+ def __get_validators__(cls):
104
+ yield cls.validate
105
+
106
+ @classmethod
107
+ def validate(cls, value: Any) -> "HttpsUrl":
108
+ from pydantic.validators import str_validator
109
+
110
+ value = str_validator(value)
111
+ value = value.strip()
112
+
113
+ if not re.match(HTTP_URL_REGEX, value):
114
+ raise ValueError(
115
+ "URL must start with 'https://' and follow the correct format."
116
+ )
117
+
118
+ if len(value) > MAX_HTTPS_URL_LENGTH:
119
+ raise ValueError(
120
+ f"HTTPS URL is too long. Max length is "
121
+ f"{MAX_HTTPS_URL_LENGTH} characters."
122
+ )
123
+
124
+ return cls(value)
125
+
126
+ @classmethod
127
+ def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
128
+ update_not_none(field_schema, format="https-url")
129
+
130
+
131
+ class ImageHttpsUrl(DownloadImageMixin, HttpsUrl):
132
+ pass
133
+
134
+
135
+ class ImageDataUri(DownloadImageMixin, DataUri):
136
+ pass
137
+
138
+
139
+ FileInput = Union[HttpsUrl, DataUri]
140
+ ImageInput = Union[ImageHttpsUrl, ImageDataUri]
@@ -84,6 +84,9 @@ def _get_remote_file_properties(
84
84
  url_path = parsed_url.path
85
85
  file_name = Path(url_path).name or _hash_url(url)
86
86
 
87
+ # file name can still contain a forward slash if the server returns a relative path
88
+ file_name = Path(file_name).name
89
+
87
90
  return file_name, content_length
88
91
 
89
92
 
@@ -159,6 +162,7 @@ def download_file(
159
162
  try:
160
163
  file_name = _get_remote_file_properties(url, request_headers)[0]
161
164
  except Exception as e:
165
+ print(f"GOt error: {e}")
162
166
  raise DownloadError(f"Failed to get remote file properties for {url}") from e
163
167
 
164
168
  if "/" in file_name:
@@ -0,0 +1,45 @@
1
+ import functools
2
+ import random
3
+ import time
4
+ import traceback
5
+ from typing import Any, Callable, Literal
6
+
7
+ BackoffType = Literal["exponential", "fixed"]
8
+
9
+
10
+ def retry(
11
+ max_retries: int = 3,
12
+ base_delay: float = 1.0,
13
+ max_delay: float = 60.0,
14
+ backoff_type: BackoffType = "exponential",
15
+ jitter: bool = False,
16
+ ) -> Callable:
17
+ def decorator(func: Callable) -> Callable:
18
+ @functools.wraps(func)
19
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
20
+ retries = 0
21
+ while retries < max_retries:
22
+ try:
23
+ return func(*args, **kwargs)
24
+ except Exception as e:
25
+ retries += 1
26
+ print(f"Retrying {retries} of {max_retries}...")
27
+ if retries == max_retries:
28
+ print(f"Max retries reached. Raising exception: {e}")
29
+ traceback.print_exc()
30
+
31
+ raise e
32
+
33
+ if backoff_type == "exponential":
34
+ delay = min(base_delay * (2 ** (retries - 1)), max_delay)
35
+ else: # fixed
36
+ delay = min(base_delay, max_delay)
37
+
38
+ if jitter:
39
+ delay *= random.uniform(0.5, 1.5)
40
+
41
+ time.sleep(delay)
42
+
43
+ return wrapper
44
+
45
+ return decorator
fal/workflows.py CHANGED
@@ -5,7 +5,7 @@ import webbrowser
5
5
  from argparse import ArgumentParser
6
6
  from collections import Counter
7
7
  from dataclasses import dataclass, field
8
- from typing import Any, Iterator, Union, cast
8
+ from typing import Any, Dict, Iterator, List, Union, cast
9
9
 
10
10
  import graphlib
11
11
  import rich
@@ -21,8 +21,8 @@ from fal import flags
21
21
  from fal.exceptions import FalServerlessException
22
22
  from fal.rest_client import REST_CLIENT
23
23
 
24
- JSONType = Union[dict[str, Any], list[Any], str, int, float, bool, None, "Leaf"]
25
- SchemaType = dict[str, Any]
24
+ JSONType = Union[Dict[str, Any], List[Any], str, int, float, bool, None, "Leaf"]
25
+ SchemaType = Dict[str, Any]
26
26
 
27
27
  VARIABLE_PREFIX = "$"
28
28
  INPUT_VARIABLE_NAME = "input"
@@ -50,7 +50,13 @@ def parse_leaf(raw_leaf: str) -> Leaf:
50
50
  f"Invalid leaf: {raw_leaf} (must start with a reference)"
51
51
  )
52
52
 
53
- leaf: Leaf = ReferenceLeaf(reference.removeprefix(VARIABLE_PREFIX))
53
+ # remove the $ prefix
54
+ _reference = (
55
+ reference[len(VARIABLE_PREFIX) :]
56
+ if reference.startswith(VARIABLE_PREFIX)
57
+ else reference
58
+ )
59
+ leaf: Leaf = ReferenceLeaf(_reference)
54
60
  for raw_part in raw_parts:
55
61
  if raw_part.isdigit():
56
62
  leaf = IndexLeaf(leaf, int(raw_part))
@@ -1,12 +1,12 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: fal
3
- Version: 1.3.3
3
+ Version: 1.7.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
7
7
  Description-Content-Type: text/markdown
8
- Requires-Dist: isolate[build]<1.14.0,>=0.13.0
9
- Requires-Dist: isolate-proto==0.5.3
8
+ Requires-Dist: isolate[build]<0.16.0,>=0.15.0
9
+ Requires-Dist: isolate-proto<0.7.0,>=0.6.0
10
10
  Requires-Dist: grpcio==1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -19,16 +19,18 @@ Requires-Dist: grpc-interceptor<1,>=0.15.0
19
19
  Requires-Dist: colorama<1,>=0.4.6
20
20
  Requires-Dist: portalocker<3,>=2.7.0
21
21
  Requires-Dist: rich<14,>=13.3.2
22
- Requires-Dist: rich-argparse
22
+ Requires-Dist: rich_argparse
23
23
  Requires-Dist: packaging>=21.3
24
24
  Requires-Dist: pathspec<1,>=0.11.1
25
25
  Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
26
+ Requires-Dist: structlog>=22.0
26
27
  Requires-Dist: fastapi<1,>=0.99.1
27
28
  Requires-Dist: starlette-exporter>=0.21.0
28
29
  Requires-Dist: httpx>=0.15.4
29
30
  Requires-Dist: attrs>=21.3.0
30
31
  Requires-Dist: python-dateutil<3,>=2.8.0
31
32
  Requires-Dist: types-python-dateutil<3,>=2.8.0
33
+ Requires-Dist: importlib-metadata>=4.4; python_version < "3.10"
32
34
  Requires-Dist: msgpack<2,>=1.0.7
33
35
  Requires-Dist: websockets<13,>=12.0
34
36
  Requires-Dist: pillow<11,>=10.2.0
@@ -36,16 +38,19 @@ Requires-Dist: pyjwt[crypto]<3,>=2.8.0
36
38
  Requires-Dist: uvicorn<1,>=0.29.0
37
39
  Requires-Dist: cookiecutter
38
40
  Requires-Dist: tomli
39
- Requires-Dist: importlib-metadata>=4.4; python_version < "3.10"
40
- Provides-Extra: dev
41
- Requires-Dist: fal[test]; extra == "dev"
42
- Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
41
+ Provides-Extra: docs
42
+ Requires-Dist: sphinx; extra == "docs"
43
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
44
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
43
45
  Provides-Extra: test
44
46
  Requires-Dist: pytest<8; extra == "test"
45
47
  Requires-Dist: pytest-asyncio; extra == "test"
46
48
  Requires-Dist: pytest-xdist; extra == "test"
47
49
  Requires-Dist: flaky; extra == "test"
48
50
  Requires-Dist: boto3; extra == "test"
51
+ Provides-Extra: dev
52
+ Requires-Dist: fal[docs,test]; extra == "dev"
53
+ Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
49
54
 
50
55
  [![PyPI](https://img.shields.io/pypi/v/fal.svg?logo=PyPI)](https://pypi.org/project/fal)
51
56
  [![Tests](https://img.shields.io/github/actions/workflow/status/fal-ai/fal/integration_tests.yaml?label=Tests)](https://github.com/fal-ai/fal/actions)
@@ -1,35 +1,37 @@
1
1
  fal/__init__.py,sha256=wXs1G0gSc7ZK60-bHe-B2m0l_sA6TrFk4BxY0tMoLe8,784
2
2
  fal/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83
3
- fal/_fal_version.py,sha256=VriGPi1kVXIBM0YGAuhpE803XR-FNq1JvTW1Kz2us08,411
3
+ fal/_fal_version.py,sha256=Lv0gR-NbC-8DxwfmwXEmOzSq6Hgx6MH4xF1fYh_opXo,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=xOPRO8-Y-7tgab_yKkQ2Lh_n4l8av5zd7srs9PCgJ5U,42020
7
- fal/app.py,sha256=mBBwTi6IldCEN-IEeznpEwyjUydqB4HkVCu49J3Vsfw,17639
8
- fal/apps.py,sha256=lge7-HITzI20l1oXdlkAzqxdMVtXRfnACIylKRWgCNQ,7151
9
- fal/container.py,sha256=V7riyyq8AZGwEX9QaqRQDZyDN_bUKeRKV1OOZArXjL0,622
6
+ fal/api.py,sha256=u9QfJtb1nLDJu9kegKCrdvW-Cp0mfMSGTPm5X1ywoeE,43388
7
+ fal/app.py,sha256=C1dTWjit90XdTKmrwd5Aqv3SD0MA1JDZoLLtmStn2Xc,22917
8
+ fal/apps.py,sha256=RpmElElJnDYjsTRQOdNYiJwd74GEOGYA38L5O5GzNEg,11068
9
+ fal/config.py,sha256=hgI3kW4_2NoFsrYEiPss0mnDTr8_Td2z0pVgm93wi9o,600
10
+ fal/container.py,sha256=EjokKTULJ3fPUjDttjir-jmg0gqcUDe0iVzW2j5njec,634
10
11
  fal/files.py,sha256=QgfYfMKmNobMPufrAP_ga1FKcIAlSbw18Iar1-0qepo,2650
11
12
  fal/flags.py,sha256=oWN_eidSUOcE9wdPK_77si3A1fpgOC0UEERPsvNLIMc,842
12
13
  fal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
14
  fal/rest_client.py,sha256=kGBGmuyHfX1lR910EoKCYPjsyU8MdXawT_cW2q8Sajc,568
14
- fal/sdk.py,sha256=ND3nwZRQDEIC130zbTfTaP0fYpR2KEkQ10e3zvOXylQ,21391
15
+ fal/sdk.py,sha256=HjlToPJkG0Z5h_D0D2FK43i3JFKeO4r2IhCGx4B82Z8,22564
15
16
  fal/sync.py,sha256=ZuIJA2-hTPNANG9B_NNJZUsO68EIdTH0dc9MzeVE2VU,4340
16
17
  fal/utils.py,sha256=9q_QrQBlQN3nZYA1kEGRfhJWi4RjnO4H1uQswfaei9w,2146
17
- fal/workflows.py,sha256=jx3tGy2R7cN6lLvOzT6lhhlcjmiq64iZls2smVrmQj0,14657
18
- fal/auth/__init__.py,sha256=r8iA2-5ih7-Fik3gEC4HEWNFbGoxpYnXpZu1icPIoS0,3561
18
+ fal/workflows.py,sha256=Zl4f6Bs085hY40zmqScxDUyCu7zXkukDbW02iYOLTTI,14805
19
+ fal/auth/__init__.py,sha256=MXwS5zyY1SYJWEkc6s39et73Dkg3cDJg1ZwxRhXNj4c,4704
19
20
  fal/auth/auth0.py,sha256=rSG1mgH-QGyKfzd7XyAaj1AYsWt-ho8Y_LZ-FUVWzh4,5421
20
21
  fal/auth/local.py,sha256=sndkM6vKpeVny6NHTacVlTbiIFqaksOmw0Viqs_RN1U,1790
21
22
  fal/cli/__init__.py,sha256=padK4o0BFqq61kxAA1qQ0jYr2SuhA2mf90B3AaRkmJA,37
22
- fal/cli/_utils.py,sha256=DSfHZ6qna4jLffs-N4F3XbV-9ydF_OSlP2-GRounWsY,911
23
- fal/cli/apps.py,sha256=-DDp-Gvxz5kHho5YjAhbri8vOny_9cftAI_wP2KR5nU,8175
23
+ fal/cli/_utils.py,sha256=45G0LEz2bW-69MUQKPdatVE_CBC2644gC-V0qdNEsco,1252
24
+ fal/cli/apps.py,sha256=Fo4iUpd6FGTUcIp22WcssE1CaEn_BLKzK_E4JPsXhVI,8179
24
25
  fal/cli/auth.py,sha256=--MhfHGwxmtHbRkGioyn1prKn_U-pBzbz0G_QeZou-U,1352
25
26
  fal/cli/create.py,sha256=a8WDq-nJLFTeoIXqpb5cr7GR7YR9ZZrQCawNm34KXXE,627
26
27
  fal/cli/debug.py,sha256=u_urnyFzSlNnrq93zz_GXE9FX4VyVxDoamJJyrZpFI0,1312
27
- fal/cli/deploy.py,sha256=JCTQRNzbPt7Bn7lR8byJ38Ff-vQ2BQoSdzmdp9OlF3A,6790
28
+ fal/cli/deploy.py,sha256=-woTZObzntUenPFmWJwDaeCmBl3Vb7jqSkhPCIfk2SM,7581
28
29
  fal/cli/doctor.py,sha256=U4ne9LX5gQwNblsYQ27XdO8AYDgbYjTO39EtxhwexRM,983
29
30
  fal/cli/keys.py,sha256=trDpA3LJu9S27qE_K8Hr6fKLK4vwVzbxUHq8TFrV4pw,3157
30
- fal/cli/main.py,sha256=_Wh_DQc02qwh-ZN7v41lZm0lDR1WseViXVOcqUlyWLg,2009
31
+ fal/cli/main.py,sha256=O0i9wdLPxcd1u4CvXit-ufkT_UnON-baTN6v9HaHPmw,2027
31
32
  fal/cli/parser.py,sha256=edCqFWYAQSOhrxeEK9BtFRlTEUAlG2JUDjS_vhZ_nHE,2868
32
- fal/cli/run.py,sha256=uscbLBfTe8-UAbqh8h1iWuGD_G9UNNrj3k8bF5rtzy4,1201
33
+ fal/cli/run.py,sha256=nAC12Qss4Fg1XmV0qOS9RdGNLYcdoHeRgQMvbTN4P9I,1202
34
+ fal/cli/runners.py,sha256=5pXuKq7nSkf0VpnppNnvxwP8XDq0SWkc6mkfizDwWMQ,1046
33
35
  fal/cli/secrets.py,sha256=740msFm7d41HruudlcfqUXlFl53N-WmChsQP9B9M9Po,2572
34
36
  fal/console/__init__.py,sha256=ernZ4bzvvliQh5SmrEqQ7lA5eVcbw6Ra2jalKtA7dxg,132
35
37
  fal/console/icons.py,sha256=De9MfFaSkO2Lqfne13n3PrYfTXJVIzYZVqYn5BWsdrA,108
@@ -46,14 +48,16 @@ fal/logging/user.py,sha256=0Xvb8n6tSb9l_V51VDzv6SOdYEFNouV_6nF_W9e7uNQ,642
46
48
  fal/toolkit/__init__.py,sha256=sV95wiUzKoiDqF9vDgq4q-BLa2sD6IpuKSqp5kdTQNE,658
47
49
  fal/toolkit/exceptions.py,sha256=elHZ7dHCJG5zlHGSBbz-ilkZe9QUvQMomJFi8Pt91LA,198
48
50
  fal/toolkit/optimize.py,sha256=p75sovF0SmRP6zxzpIaaOmqlxvXB_xEz3XPNf59EF7w,1339
51
+ fal/toolkit/types.py,sha256=kkbOsDKj1qPGb1UARTBp7yuJ5JUuyy7XQurYUBCdti8,4064
49
52
  fal/toolkit/file/__init__.py,sha256=FbNl6wD-P0aSSTUwzHt4HujBXrbC3ABmaigPQA4hRfg,70
50
- fal/toolkit/file/file.py,sha256=qk_hj7U3cfvuWO-qF_eC_R8lzzVhudfnt1erWEa8eDQ,6578
51
- fal/toolkit/file/types.py,sha256=GymH0CJesJvsZ6wph7GqTGTuNjzvyMgLxQmBBxoKzS0,1627
52
- fal/toolkit/file/providers/fal.py,sha256=ClCWM4GI11hOjEIVv2IJZj2SdzBNO8iS1r1WaXFcF6I,10090
53
- fal/toolkit/file/providers/gcp.py,sha256=pUVH2qNcnO_VrDQQU8MmfYOQZMGaKQIqE4yGnYdQhAc,2003
54
- fal/toolkit/file/providers/r2.py,sha256=WxmOHF5WxHt6tKMcFjWj7ZWO8a1EXysO9lfYv_tB3MI,2627
55
- fal/toolkit/image/__init__.py,sha256=aLcU8HzD7HyOxx-C-Bbx9kYCMHdBhy9tR98FSVJ6gSA,1830
56
- fal/toolkit/image/image.py,sha256=UDIHgkxae8LzmCvWBM9GayMnK8c0JMMfsrVlLnW5rto,4234
53
+ fal/toolkit/file/file.py,sha256=-gccCKnarTu6Nfm_0yQ0sJM9aadB5tUNvKS1PTqxiFc,9071
54
+ fal/toolkit/file/types.py,sha256=MjZ6xAhKPv4rowLo2Vcbho0sX7AQ3lm3KFyYDcw0dL4,1845
55
+ fal/toolkit/file/providers/fal.py,sha256=X7vz0QQg4xFdglbHvOzjgL78dleFMeUzUh1xX68K-zQ,25831
56
+ fal/toolkit/file/providers/gcp.py,sha256=iQtkoYUqbmKKpC5srVOYtrruZ3reGRm5lz4kM8bshgk,2247
57
+ fal/toolkit/file/providers/r2.py,sha256=G2OHcCH2yWrVtXT4hWHEXUeEjFhbKO0koqHcd7hkczk,2871
58
+ fal/toolkit/file/providers/s3.py,sha256=CfiA6rTBFfP-empp0cB9OW2c9F5iy0Z-kGwCs5HBICU,2524
59
+ fal/toolkit/image/__init__.py,sha256=m3OatPbBhcEOYyaTu_dgToxunUKoJu4bJVCWUoN7HX4,1838
60
+ fal/toolkit/image/image.py,sha256=ZSkozciP4XxaGnvrR_mP4utqE3_QhoPN0dau9FJ2Xco,5033
57
61
  fal/toolkit/image/safety_checker.py,sha256=S7ow-HuoVxC6ixHWWcBrAUm2dIlgq3sTAIull6xIbAg,3105
58
62
  fal/toolkit/image/nsfw_filter/__init__.py,sha256=0d9D51EhcnJg8cZLYJjgvQJDZT74CfQu6mpvinRYRpA,216
59
63
  fal/toolkit/image/nsfw_filter/env.py,sha256=iAP2Q3vzIl--DD8nr8o3o0goAwhExN2v0feYE0nIQjs,212
@@ -61,7 +65,8 @@ fal/toolkit/image/nsfw_filter/inference.py,sha256=BhIPF_zxRLetThQYxDDF0sdx9VRwvu
61
65
  fal/toolkit/image/nsfw_filter/model.py,sha256=63mu8D15z_IosoRUagRLGHy6VbLqFmrG-yZqnu2vVm4,457
62
66
  fal/toolkit/image/nsfw_filter/requirements.txt,sha256=3Pmrd0Ny6QAeBqUNHCgffRyfaCARAPJcfSCX5cRYpbM,37
63
67
  fal/toolkit/utils/__init__.py,sha256=CrmM9DyCz5-SmcTzRSm5RaLgxy3kf0ZsSEN9uhnX2Xo,97
64
- fal/toolkit/utils/download_utils.py,sha256=9WMpn0mFIhkFelQpPj5KG-pC7RMyyOzGHbNRDSyz07o,17664
68
+ fal/toolkit/utils/download_utils.py,sha256=fFrKoSJPBSurrD636ncNHhJv-cS3zReIv6ltiU3tMZU,17823
69
+ fal/toolkit/utils/retry.py,sha256=mHcQvvNIpu-Hi29P1HXSZuyvolRd48dMaJToqzlG0NY,1353
65
70
  openapi_fal_rest/__init__.py,sha256=ziculmF_i6trw63LzZGFX-6W3Lwq9mCR8_UpkpvpaHI,152
66
71
  openapi_fal_rest/client.py,sha256=G6BpJg9j7-JsrAUGddYwkzeWRYickBjPdcVgXoPzxuE,2817
67
72
  openapi_fal_rest/errors.py,sha256=8mXSxdfSGzxT82srdhYbR0fHfgenxJXaUtMkaGgb6iU,470
@@ -125,8 +130,8 @@ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRi
125
130
  openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
126
131
  openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
127
132
  openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
128
- fal-1.3.3.dist-info/METADATA,sha256=49757swSphQt8uqSMj5h83T79CeFwOWi0J0iDQHIKe8,3787
129
- fal-1.3.3.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
130
- fal-1.3.3.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
131
- fal-1.3.3.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
132
- fal-1.3.3.dist-info/RECORD,,
133
+ fal-1.7.3.dist-info/METADATA,sha256=eTRggSeYlEsdzY5D68R6NkJbD65PrD-nkIRMd2MPa5Q,3996
134
+ fal-1.7.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
135
+ fal-1.7.3.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
136
+ fal-1.7.3.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
137
+ fal-1.7.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.0.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5