""" CV Upload API Client. A client for submitting job applications with CV uploads. """ import os from dataclasses import dataclass from typing import Optional, BinaryIO import requests def _clean_base_url(url: str) -> str: """Normalize base URL to avoid issues from quoted env vars.""" cleaned = url.strip().strip("\"'") if cleaned.endswith("/"): cleaned = cleaned[:-1] return cleaned @dataclass class SubmitResponse: """Response from a CV submission.""" success: bool message: str candidate_name: str = "" email: str = "" cv_file_path: str = "" already_exists: bool = False class CVUploadClient: """ Client for the CV Upload API. Usage: client = CVUploadClient() # Submit application with open("my_cv.pdf", "rb") as f: response = client.submit( full_name="Ada Lovelace", email="ada@example.com", phone="+49 123 456789", cv_file=f, filename="my_cv.pdf" ) if response.success: print(f"Application submitted: {response.message}") elif response.already_exists: print("You already applied!") else: print(f"Error: {response.message}") """ def __init__(self, base_url: Optional[str] = None, session_id: Optional[str] = None): """ Initialize the CV Upload client. Args: base_url: API base URL. Defaults to CV_UPLOAD_API_URL env var or http://localhost:8080/api/v1/cv """ raw = base_url or os.getenv( "CV_UPLOAD_API_URL", "http://localhost:8080/api/v1/cv" ) self.base_url = _clean_base_url(raw) self.session_id = (session_id or os.getenv("SESSION_ID") or "").strip().strip("\"'") def _headers(self) -> dict: headers = {} if self.session_id: headers["X-Session-Id"] = self.session_id return headers def submit( self, full_name: str, email: str, cv_file: BinaryIO, filename: str, phone: str = "", timeout: int = 120 ) -> SubmitResponse: """ Submit a job application with CV. Args: full_name: Candidate's full name email: Candidate's email address cv_file: File-like object containing the CV (PDF or DOCX) filename: Original filename of the CV phone: Optional phone number timeout: Request timeout in seconds Returns: SubmitResponse with success status and details Raises: requests.exceptions.RequestException: On connection errors ValueError: On API errors """ files = { "cv_file": (filename, cv_file, "application/octet-stream") } data = { "full_name": full_name, "email": email, "phone": phone, } response = requests.post( f"{self.base_url}/submit", files=files, data=data, headers=self._headers(), timeout=timeout ) if response.status_code == 400: error = response.json().get("detail", "Invalid request") raise ValueError(f"Validation error: {error}") if response.status_code == 500: error = response.json().get("detail", "Server error") raise ValueError(f"Server error: {error}") if response.status_code != 200: raise ValueError(f"Unexpected status: {response.status_code}") data = response.json() return SubmitResponse( success=data["success"], message=data["message"], candidate_name=data.get("candidate_name", ""), email=data.get("email", ""), cv_file_path=data.get("cv_file_path", ""), already_exists=data.get("already_exists", False), ) def health(self) -> bool: """ Check if the API is healthy. Returns: True if healthy, False otherwise """ try: response = requests.get(f"{self.base_url}/health", timeout=5, headers=self._headers()) return response.status_code == 200 except requests.exceptions.RequestException: return False