fal 1.3.3__py3-none-any.whl → 1.7.2__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/toolkit/file/file.py CHANGED
@@ -8,6 +8,7 @@ from urllib.parse import urlparse
8
8
  from zipfile import ZipFile
9
9
 
10
10
  import pydantic
11
+ from fastapi import Request
11
12
 
12
13
  # https://github.com/pydantic/pydantic/pull/2573
13
14
  if not hasattr(pydantic, "__version__") or pydantic.__version__.startswith("1."):
@@ -21,9 +22,11 @@ else:
21
22
  from pydantic import BaseModel, Field
22
23
 
23
24
  from fal.toolkit.file.providers.fal import (
25
+ LIFECYCLE_PREFERENCE,
24
26
  FalCDNFileRepository,
25
27
  FalFileRepository,
26
28
  FalFileRepositoryV2,
29
+ FalFileRepositoryV3,
27
30
  InMemoryRepository,
28
31
  )
29
32
  from fal.toolkit.file.providers.gcp import GoogleStorageRepository
@@ -36,6 +39,7 @@ FileRepositoryFactory = Callable[[], FileRepository]
36
39
  BUILT_IN_REPOSITORIES: dict[RepositoryId, FileRepositoryFactory] = {
37
40
  "fal": lambda: FalFileRepository(),
38
41
  "fal_v2": lambda: FalFileRepositoryV2(),
42
+ "fal_v3": lambda: FalFileRepositoryV3(),
39
43
  "in_memory": lambda: InMemoryRepository(),
40
44
  "gcp_storage": lambda: GoogleStorageRepository(),
41
45
  "r2": lambda: R2Repository(),
@@ -43,7 +47,10 @@ BUILT_IN_REPOSITORIES: dict[RepositoryId, FileRepositoryFactory] = {
43
47
  }
44
48
 
45
49
 
46
- def get_builtin_repository(id: RepositoryId) -> FileRepository:
50
+ def get_builtin_repository(id: RepositoryId | FileRepository) -> FileRepository:
51
+ if isinstance(id, FileRepository):
52
+ return id
53
+
47
54
  if id not in BUILT_IN_REPOSITORIES.keys():
48
55
  raise ValueError(f'"{id}" is not a valid built-in file repository')
49
56
  return BUILT_IN_REPOSITORIES[id]()
@@ -51,7 +58,9 @@ def get_builtin_repository(id: RepositoryId) -> FileRepository:
51
58
 
52
59
  get_builtin_repository.__module__ = "__main__"
53
60
 
54
- DEFAULT_REPOSITORY: FileRepository | RepositoryId = "fal"
61
+ DEFAULT_REPOSITORY: FileRepository | RepositoryId = "fal_v3"
62
+ FALLBACK_REPOSITORY: FileRepository | RepositoryId = "cdn"
63
+ OBJECT_LIFECYCLE_PREFERENCE_KEY = "x-fal-object-lifecycle-preference"
55
64
 
56
65
 
57
66
  class File(BaseModel):
@@ -116,7 +125,8 @@ class File(BaseModel):
116
125
  url=url,
117
126
  content_type=None,
118
127
  file_name=None,
119
- repository=DEFAULT_REPOSITORY,
128
+ file_size=None,
129
+ file_data=None,
120
130
  )
121
131
 
122
132
  @classmethod
@@ -126,17 +136,38 @@ class File(BaseModel):
126
136
  content_type: Optional[str] = None,
127
137
  file_name: Optional[str] = None,
128
138
  repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
139
+ fallback_repository: Optional[
140
+ FileRepository | RepositoryId
141
+ ] = FALLBACK_REPOSITORY,
142
+ request: Optional[Request] = None,
143
+ save_kwargs: Optional[dict] = None,
144
+ fallback_save_kwargs: Optional[dict] = None,
129
145
  ) -> File:
130
- repo = (
131
- repository
132
- if isinstance(repository, FileRepository)
133
- else get_builtin_repository(repository)
134
- )
146
+ repo = get_builtin_repository(repository)
147
+
148
+ save_kwargs = save_kwargs or {}
149
+ fallback_save_kwargs = fallback_save_kwargs or {}
135
150
 
136
151
  fdata = FileData(data, content_type, file_name)
137
152
 
153
+ object_lifecycle_preference = (
154
+ request_lifecycle_preference(request) or LIFECYCLE_PREFERENCE.get()
155
+ )
156
+
157
+ try:
158
+ url = repo.save(fdata, object_lifecycle_preference, **save_kwargs)
159
+ except Exception:
160
+ if not fallback_repository:
161
+ raise
162
+
163
+ fallback_repo = get_builtin_repository(fallback_repository)
164
+
165
+ url = fallback_repo.save(
166
+ fdata, object_lifecycle_preference, **fallback_save_kwargs
167
+ )
168
+
138
169
  return cls(
139
- url=repo.save(fdata),
170
+ url=url,
140
171
  content_type=fdata.content_type,
141
172
  file_name=fdata.file_name,
142
173
  file_size=len(data),
@@ -150,24 +181,49 @@ class File(BaseModel):
150
181
  content_type: Optional[str] = None,
151
182
  repository: FileRepository | RepositoryId = DEFAULT_REPOSITORY,
152
183
  multipart: bool | None = None,
184
+ fallback_repository: Optional[
185
+ FileRepository | RepositoryId
186
+ ] = FALLBACK_REPOSITORY,
187
+ request: Optional[Request] = None,
188
+ save_kwargs: Optional[dict] = None,
189
+ fallback_save_kwargs: Optional[dict] = None,
153
190
  ) -> File:
154
191
  file_path = Path(path)
155
192
  if not file_path.exists():
156
193
  raise FileNotFoundError(f"File {file_path} does not exist")
157
194
 
158
- repo = (
159
- repository
160
- if isinstance(repository, FileRepository)
161
- else get_builtin_repository(repository)
162
- )
195
+ repo = get_builtin_repository(repository)
163
196
 
164
- content_type = content_type or "application/octet-stream"
197
+ save_kwargs = save_kwargs or {}
198
+ fallback_save_kwargs = fallback_save_kwargs or {}
165
199
 
166
- url, data = repo.save_file(
167
- file_path,
168
- content_type=content_type,
169
- multipart=multipart,
200
+ content_type = content_type or "application/octet-stream"
201
+ object_lifecycle_preference = (
202
+ request_lifecycle_preference(request) or LIFECYCLE_PREFERENCE.get()
170
203
  )
204
+
205
+ try:
206
+ url, data = repo.save_file(
207
+ file_path,
208
+ content_type=content_type,
209
+ multipart=multipart,
210
+ object_lifecycle_preference=object_lifecycle_preference,
211
+ **save_kwargs,
212
+ )
213
+ except Exception:
214
+ if not fallback_repository:
215
+ raise
216
+
217
+ fallback_repo = get_builtin_repository(fallback_repository)
218
+
219
+ url, data = fallback_repo.save_file(
220
+ file_path,
221
+ content_type=content_type,
222
+ multipart=multipart,
223
+ object_lifecycle_preference=object_lifecycle_preference,
224
+ **fallback_save_kwargs,
225
+ )
226
+
171
227
  return cls(
172
228
  url=url,
173
229
  file_data=data.data if data else None,
@@ -223,3 +279,20 @@ class CompressedFile(File):
223
279
  def __del__(self):
224
280
  if self.extract_dir:
225
281
  shutil.rmtree(self.extract_dir)
282
+
283
+
284
+ def request_lifecycle_preference(request: Optional[Request]) -> dict[str, str] | None:
285
+ import json
286
+
287
+ if request is None:
288
+ return None
289
+
290
+ preference_str = request.headers.get(OBJECT_LIFECYCLE_PREFERENCE_KEY)
291
+ if preference_str is None:
292
+ return None
293
+
294
+ try:
295
+ return json.loads(preference_str)
296
+ except Exception as e:
297
+ print(f"Failed to parse object lifecycle preference: {e}")
298
+ return None