Skip to content

Commit 1d0b541

Browse files
Parallelize file uploads/deletes
1 parent 13484ae commit 1d0b541

1 file changed

Lines changed: 40 additions & 35 deletions

File tree

routers/files.py

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import logging
23
import mimetypes
34
from typing import Literal
@@ -66,51 +67,54 @@ async def upload_file(
6667
error_messages: list[str] = []
6768
uploaded_files: list[dict[str, str | None]] = []
6869

70+
# 1. Read all file contents first (must happen before UploadFile objects close)
71+
file_payloads: list[tuple[str, bytes]] = []
6972
for file in files:
70-
file_content = None
7173
try:
72-
# 1. Read the file content first
7374
file_content = await file.read()
7475
if not file.filename:
7576
raise ValueError("File has no filename")
7677
if not file_content:
7778
raise ValueError("File content is empty")
79+
file_payloads.append((file.filename, file_content))
80+
except ValueError as ve:
81+
logger.error(f"File validation error for {file.filename}: {ve}")
82+
error_messages.append(f"{file.filename}: {ve}")
7883

79-
# 2. Upload the file content to OpenAI
84+
# 2. Upload all files to OpenAI and add to vector store in parallel
85+
async def process_file(filename: str, content: bytes) -> dict[str, str | None] | None:
86+
try:
8087
openai_file = await client.files.create(
81-
file=(file.filename, file_content),
88+
file=(filename, content),
8289
purpose=purpose
8390
)
84-
85-
# 3. Add the uploaded file to the vector store
8691
vs_file = await client.vector_stores.files.create(
8792
vector_store_id=vector_store_id,
8893
file_id=openai_file.id
8994
)
90-
logger.info(f"File {file.filename} uploaded to OpenAI and added to vector store.")
95+
logger.info(f"File {filename} uploaded to OpenAI and added to vector store.")
9196

92-
# Track the uploaded file so we can include it in the response
93-
# even if the list API hasn't caught up yet
94-
uploaded_files.append({
95-
"id": openai_file.id,
96-
"filename": file.filename,
97-
"status": vs_file.status,
98-
"last_error": vs_file.last_error.message if vs_file.last_error else None
99-
})
100-
101-
# 4. Store the file locally using the same content
10297
try:
103-
store_file(file.filename, file_content)
98+
store_file(filename, content)
10499
except Exception as e:
105-
logger.error(f"Error storing file {file.filename} locally: {e}")
106-
error_messages.append(f"Error storing {file.filename} locally")
100+
logger.error(f"Error storing file {filename} locally: {e}")
101+
error_messages.append(f"Error storing {filename} locally")
107102

108-
except ValueError as ve:
109-
logger.error(f"File validation error for {file.filename}: {ve}")
110-
error_messages.append(f"{file.filename}: {ve}")
103+
return {
104+
"id": openai_file.id,
105+
"filename": filename,
106+
"status": vs_file.status,
107+
"last_error": vs_file.last_error.message if vs_file.last_error else None
108+
}
111109
except Exception as e:
112-
logger.error(f"Error uploading file {file.filename}: {e}")
113-
error_messages.append(f"Error uploading {file.filename}")
110+
logger.error(f"Error uploading file {filename}: {e}")
111+
error_messages.append(f"Error uploading {filename}")
112+
return None
113+
114+
results = await asyncio.gather(
115+
*(process_file(fn, content) for fn, content in file_payloads)
116+
)
117+
uploaded_files = [r for r in results if r is not None]
114118

115119
# Fetch the updated list of files and render the partial
116120
file_list: list[dict[str, str | None]] = []
@@ -175,12 +179,11 @@ async def delete_all_files(
175179
break
176180
after = page.last_id
177181

178-
# Delete each file
179-
for vs_file in all_vs_files:
180-
# Retrieve filename for local deletion
182+
# Delete each file in parallel
183+
async def delete_one(vs_file_id: str) -> None:
181184
file_to_delete_name = None
182185
try:
183-
retrieved_file = await client.files.retrieve(vs_file.id)
186+
retrieved_file = await client.files.retrieve(vs_file_id)
184187
if retrieved_file and retrieved_file.filename:
185188
file_to_delete_name = retrieved_file.filename
186189
except Exception:
@@ -194,18 +197,20 @@ async def delete_all_files(
194197

195198
try:
196199
deleted = await client.vector_stores.files.delete(
197-
vector_store_id=vector_store_id, file_id=vs_file.id
200+
vector_store_id=vector_store_id, file_id=vs_file_id
198201
)
199202
if deleted.deleted:
200203
try:
201-
await client.files.delete(file_id=vs_file.id)
204+
await client.files.delete(file_id=vs_file_id)
202205
except Exception as file_err:
203-
logger.warning(f"Removed {vs_file.id} from vector store but failed to delete file object: {file_err}")
206+
logger.warning(f"Removed {vs_file_id} from vector store but failed to delete file object: {file_err}")
204207
else:
205-
error_messages.append(f"Failed to remove {vs_file.id} from vector store")
208+
error_messages.append(f"Failed to remove {vs_file_id} from vector store")
206209
except Exception as del_err:
207-
logger.error(f"Error deleting file {vs_file.id}: {del_err}")
208-
error_messages.append(f"Error deleting {vs_file.id}")
210+
logger.error(f"Error deleting file {vs_file_id}: {del_err}")
211+
error_messages.append(f"Error deleting {vs_file_id}")
212+
213+
await asyncio.gather(*(delete_one(vs_file.id) for vs_file in all_vs_files))
209214

210215
except Exception as e:
211216
logger.error(f"Error during delete all: {e}")

0 commit comments

Comments
 (0)