File size: 3,097 Bytes
41c9648 55ca1bd aa93eef e5d586b aa93eef 41c9648 55ca1bd aa93eef e5d586b aa93eef 55ca1bd aa93eef 55ca1bd aa93eef 55ca1bd aa93eef 41c9648 aa93eef 41c9648 aa93eef 41c9648 aa93eef 55ca1bd aa93eef 41c9648 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | import csv
import logging
import os
import tempfile
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from model import extract_contact
from vcf import generate_vcf, VCF_DIR
CSV_FIELDS = ["name", "company", "designation", "phone", "email", "website", "address"]
CSV_PATH = os.path.join(VCF_DIR, "contacts.csv")
def append_csv(data):
# ponytail: append-only log of every extraction; write header once on first row
new_file = not os.path.exists(CSV_PATH)
with open(CSV_PATH, "a", newline="") as f:
w = csv.DictWriter(f, fieldnames=CSV_FIELDS, extrasaction="ignore")
if new_file:
w.writeheader()
w.writerow({k: data.get(k, "") for k in CSV_FIELDS})
return os.path.basename(CSV_PATH)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s: %(message)s",
)
log = logging.getLogger("cardextractor")
app = FastAPI(title="Business Card Extractor", version="1.0")
# ponytail: allow-all CORS; tighten to your frontend origin if this ever leaves a demo
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
def home():
return {"message": "Business Card API", "endpoint": "/extract"}
@app.post("/extract")
async def extract(file: UploadFile = File(...)):
image_path = None # ponytail: defined up front so finally never UnboundLocalErrors
try:
suffix = os.path.splitext(file.filename)[1]
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp:
body = await file.read()
temp.write(body)
image_path = temp.name
log.info("received %s (%d bytes) -> %s", file.filename, len(body), image_path)
data = extract_contact(image_path)
log.info("extracted: %s", data)
vcf_path = generate_vcf(data)
filename = os.path.basename(vcf_path)
csv_name = append_csv(data)
log.info("wrote vcf: %s, appended csv: %s", filename, csv_name)
return {
"success": True,
"contact": data,
"vcf": filename,
"csv": csv_name,
"download": f"/download/{filename}",
"csv_download": f"/download/{csv_name}",
}
except Exception as e:
# ponytail: exc_info dumps the full traceback to the container logs
log.exception("extract failed for %s", file.filename)
raise HTTPException(500, f"{type(e).__name__}: {e}")
finally:
if image_path and os.path.exists(image_path):
os.remove(image_path)
@app.get("/download/{filename}")
def download(filename: str):
# ponytail: basename strips any ../ path traversal from the URL
path = os.path.join(VCF_DIR, os.path.basename(filename))
if not os.path.exists(path):
raise HTTPException(404, "file not found")
media = "text/csv" if path.endswith(".csv") else "text/vcard"
return FileResponse(path, filename=filename, media_type=media)
|