try_upload
def try_upload(path, mime_type, upload_name):
with open(path, "rb") as f:
return requests.post(
f"{WEBUI}/api/v1/files/",
headers=HDR_FILE,
files={"file": (upload_name, f, mime_type)},
timeout=60,
)
path : 업로드할 로컬 파일의 절대 경로
mime_type : 서버에 전달할 콘텐츠 타입
upload_name : 서버/웹UI에 표시될 파일명
with open(path, "rb") as f
- 바이너리 읽기 모드로 file handle f 열기
- text로 열게 되면 개행 및 인코딩의 변형 가능성이 있음
- 이미지, 엑셀 등의 비정형 데이터를 안전하게 전송해야 함
- with 블록을 벗어나면 파일은 자동으로 닫힘
requests.post(...)
- f"{WEBUI}/api/v1/files/" : .env에서 읽은 WEBUI_URL을 바탕으로 한 파일 업로드 앤드포인트
- headers=HDR_FILE : Authorization(Barer 토큰)만 있음, content-Type 직접 지정 X
- files={"file": (upload_name, f, mime_type)} : requests의 멀티파트 업로드 규약
# 공통 헤더(서버가 HTML을 주지 않도록 Accept 추가)
HDR_JSON = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json", # 요청 방식 = json
"Accept": "application/json", # 응답 방식 = json
}
HDR_FILE = {"Authorization": f"Bearer {API_TOKEN}"}
body 파트에 있는 file은 멀티파트로 생성되기 때문에
header가 json 형태일지라도 body 타입과 불일치하게 되어
HDR_JSON을 사용하면 파싱에 실패하게 됨
Authorization만 주고 Content-Type을 비워두면
requests가 멀티파트에 맞는 Content-Type: multipart/form-data; boundary=...를 자동으로 삽입
poll_file_indexed
def poll_file_indexed(file_id: str, max_wait_sec=30, interval_sec=2):
"""간단 폴링으로 색인/추출 완료 추정"""
deadline = time.time() + max_wait_sec
last_ok = False
while time.time() < deadline:
try:
r = requests.get(f"{WEBUI}/api/v1/files/{file_id}", headers=HDR_JSON, timeout=15)
if r.status_code == 200:
j = r.json()
if isinstance(j, dict) and j.get("data"):
return True
last_ok = True
except Exception:
pass
time.sleep(interval_sec)
return last_ok
max_wait_sec=30 : 폴링 최대 30초 동안 시도하는 목표 시간
interval_sec=2 : 각 시도 사이 대기 간격 2초
deadline = time.time() + max_wait_sec : 목표 종료 시각(UNIX 타임)
last_ok = False : 도중에 한 번이라도 200 OK를 받았는지 기억하는 플래그
최대 30초 동안 GET /api/v1/files/{file_id} 경로로 서버에 요청을 반복
서버에서 200 OK를 주고, 응답 JSON 안에 data가 채워져 있으면 last_ok = True 반환
data가 비어 있으면 2초 뒤에 다시 요청
30초가 지나면 머춤