Asynkron bilde- og dokumenttransformasjon
Behandle miniatyrbilder, skalering og formatkonvertering i bakgrunnsarbeidere for å holde ventetiden for forespørsler lav.
Asynkron bilde- og dokumenttransformasjon er en gratis leksjon i Bootcamp i backendutvikling med FastAPI på CoddyKit. Dette er leksjon 4 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Bootcamp i backendutvikling med FastAPI, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Bootcamp i backendutvikling med FastAPI inneholder totalt 4 leksjoner.
Hvorfor flytte mediebehandlingen
Å endre størrelsen på et bilde eller konvertere en PDF kan ta fra flere hundre millisekunder til flere sekunder. Hvis du gjør dette arbeidet inne i forespørselshåndtereren, må klienten vente, og worker-prosessen din blir blokkert.
Mønsteret for FastAPI-tjenester på B2-nivå er:
- Ta imot opplastingen og lagre originalen raskt
- Returner
202 Acceptedmed en jobb-ID - Lag miniatyrbilder, endre størrelse og konverter format i en bakgrunnsarbeider
Dette holder ventetiden for forespørsler lav og gjør det mulig å skalere tungt CPU-arbeid uavhengig.
BackgroundTasks kontra en ekte kø
FastAPI leveres med BackgroundTasks, som kjører en funksjon etter at svaret er sendt, men fortsatt i den samme prosessen. Det passer fint til billige og raske oppfølgingsoppgaver (sende en e-post, skrive en logg).
For CPU-tunge medietransformasjoner er dette feil verktøy: Det konkurrerer med hendelsesløkken og avsluttes hvis prosessen starter på nytt. Foretrekk en dedikert oppgavekø (Celery, RQ, Dramatiq eller arq) støttet av Redis, slik at arbeidet overlever utrullinger og kan skaleres horisontalt.
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def log_upload(filename: str) -> None:
# cheap follow-up work only
print(f"received {filename}")
@app.post("/upload")
async def upload(background: BackgroundTasks):
background.add_task(log_upload, "photo.png")
return {"status": "accepted"}Ta imot raskt, behandle senere
Endepunktet bør gjøre minst mulig: validere filen, strømme den til lagring, opprette en jobboppføring og legge en oppgave i kø. Legg merke til at vi leser opplastingen i deler, slik at en stor fil aldri lastes helt inn i minnet.
await file.read(chunk)unngår store plutselige minnetopper- Vi returnerer en
job_idsom klienten kan spørre - Selve transformasjonen skjer i
process_image.delay(...)
import uuid, aiofiles
from fastapi import FastAPI, UploadFile, status
app = FastAPI()
@app.post("/images", status_code=status.HTTP_202_ACCEPTED)
async def create_image(file: UploadFile):
job_id = str(uuid.uuid4())
dest = f"/data/originals/{job_id}_{file.filename}"
async with aiofiles.open(dest, "wb") as out:
while chunk := await file.read(1024 * 1024):
await out.write(chunk)
process_image.delay(job_id, dest) # enqueue
return {"job_id": job_id, "status": "queued"}Generere miniatyrbilder med Pillow
Pillows Image.thumbnail() endrer størrelsen på stedet samtidig som sideforholdet bevares, og bildet skaleres aldri opp. Dette er riktig grunnfunksjon for miniatyrbilder fordi resultatet får plass innenfor boksen du angir.
Bruk Image.LANCZOS-resampling for skarp nedskalering, og kall img.convert("RGB") før du lagrer som JPEG, slik at bilder med alfakanaler (PNG) ikke får koderen til å krasje.
from PIL import Image
def make_thumbnail(src: str, dst: str, box=(256, 256)) -> None:
with Image.open(src) as img:
img = img.convert("RGB")
img.thumbnail(box, Image.LANCZOS)
img.save(dst, "JPEG", quality=85, optimize=True)
if __name__ == "__main__":
print("thumbnail helper ready")En Celery-workeroppgave
Hver transformasjon blir en Celery-oppgave. Oppgaven er en vanlig funksjon dekorert med @app.task; køen håndterer nye forsøk, kvitteringer og samtidighet.
- Generer flere størrelser i én oppgave for å fordele kostnaden ved bildeavkodingen
- Oppdater jobbstatusen når du er ferdig, slik at API-et kan rapportere fremdrift
- Angi
autoretry_forslik at midlertidige I/O-feil prøves på nytt automatisk
from celery import Celery
from PIL import Image
celery_app = Celery("media", broker="redis://localhost:6379/0")
SIZES = {"thumb": (256, 256), "medium": (1024, 1024)}
@celery_app.task(autoretry_for=(OSError,), retry_backoff=True, max_retries=3)
def process_image(job_id: str, src: str) -> dict:
outputs = {}
with Image.open(src) as base:
base = base.convert("RGB")
for name, box in SIZES.items():
img = base.copy()
img.thumbnail(box, Image.LANCZOS)
dst = f"/data/derived/{job_id}_{name}.jpg"
img.save(dst, "JPEG", quality=85, optimize=True)
outputs[name] = dst
return {"job_id": job_id, "outputs": outputs}Formatkonvertering: PNG og WebP
Ved å levere WebP i stedet for JPEG/PNG reduseres nyttelastens størrelse med 25–35 % med tilsvarende kvalitet. Det reduserer båndbreddebruken og gjør at sider lastes raskere.
Pillow konverterer ganske enkelt ved at du velger utdataformatet i save(). Behold også en kopi i originalformatet, siden enkelte eldre klienter ikke kan dekode WebP. Eksempelet nedenfor lager både en JPEG- og en WebP-fil fra én avkoding.
from PIL import Image
def to_jpeg_and_webp(src: str, stem: str) -> dict:
with Image.open(src) as img:
rgb = img.convert("RGB")
jpeg_path = f"{stem}.jpg"
webp_path = f"{stem}.webp"
rgb.save(jpeg_path, "JPEG", quality=85, optimize=True)
rgb.save(webp_path, "WEBP", quality=80, method=6)
return {"jpeg": jpeg_path, "webp": webp_path}
if __name__ == "__main__":
print(to_jpeg_and_webp.__name__)Følge jobbstatus
Klienter må vite når de avledede filene er klare. Lagre en liten statusoppføring (i Redis eller databasen) med job_id som nøkkel, og eksponer et endepunkt for forespørsler.
Livssyklustilstander er vanligvis queued -> processing -> done eller failed. Workeren oppdaterer oppføringen når oppgaven starter og avsluttes; API-et leser den bare.
import json, redis
r = redis.Redis()
def set_status(job_id: str, state: str, **extra) -> None:
payload = {"state": state, **extra}
r.set(f"job:{job_id}", json.dumps(payload), ex=86400)
def get_status(job_id: str) -> dict | None:
raw = r.get(f"job:{job_id}")
return json.loads(raw) if raw else NoneEndepunkt for forespørsler og resultatadresser
Statusendepunktet returnerer den nåværende tilstanden og, når jobben er ferdig, adressene til de genererte ressursene. Returner 404 for en ukjent jobb og 200 med tilstanden ellers.
En vanlig forbedring er å returnere en forhåndssignert S3-adresse for hver avledede fil, slik at klienten laster ned direkte fra objektlagringen i stedet for via API-et.
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/images/{job_id}")
async def image_status(job_id: str):
status = get_status(job_id)
if status is None:
raise HTTPException(status_code=404, detail="job not found")
return {"job_id": job_id, **status}Holde hendelsesløkken ublokkert
Selv utenfor en worker må du noen ganger kalle et blokkerende bibliotek (Pillow, et PDF-verktøy) fra et asynkront endepunkt. Hvis du kaller det direkte, blokkerer du hendelsesløkken og stopper alle samtidige forespørsler.
Flytt det til en trådpool med asyncio.to_thread (eller Starlettes run_in_threadpool). For CPU-bundne batchjobber på tvers av kjerner unngår en ProcessPoolExecutor GIL. Det kjørbare eksempelet viser mønsteret for å flytte arbeid til en tråd.
import asyncio, time
def blocking_resize(n: int) -> int:
time.sleep(0.1) # stand-in for Pillow work
return n * n
async def handle(n: int) -> int:
# runs blocking_resize in a worker thread, loop stays free
return await asyncio.to_thread(blocking_resize, n)
async def main() -> None:
results = await asyncio.gather(*(handle(i) for i in range(5)))
print(results)
if __name__ == "__main__":
asyncio.run(main())Konvertere dokumenter til PDF/bilder
Dokumenttransformasjoner (DOCX til PDF, PDF-side til PNG-miniatyrbilde) starter vanligvis eksterne verktøy som LibreOffice (soffice --headless) eller pdftoppm gjennom skallet. Disse er tunge og langsomme, så de hører hjemme i en worker-oppgave, aldri i forespørselsbanen.
Kjør dem alltid med en tidsavbruddsgrense og fang opp feil, fordi eksterne konverterere kan henge ved ugyldige inndata.
import subprocess
def docx_to_pdf(src: str, out_dir: str) -> str:
subprocess.run(
["soffice", "--headless", "--convert-to", "pdf",
"--outdir", out_dir, src],
check=True, timeout=120,
)
return out_dir
@celery_app.task(autoretry_for=(subprocess.TimeoutExpired,), max_retries=2)
def convert_document(job_id: str, src: str) -> dict:
out = docx_to_pdf(src, "/data/derived")
set_status(job_id, "done", out_dir=out)
return {"job_id": job_id, "out_dir": out}Validering, begrensninger og opprydding
Ikke-klarerte medier utgjør en sikkerhetsrisiko. Beskytt behandlingsforløpet før tungt arbeid kjøres:
- Bekreft typen basert på innholdet (for eksempel
Image.open().verify()eller magiske byte), ikke bare filendelsen - Begrens dimensjonene for å avverge bilder som utnytter dekompresjonsbomber; angi
Image.MAX_IMAGE_PIXELS - Håndhev størrelsesgrenser mens opplastingen strømmes
- Rydd opp i originaler og avledede filer ved feil eller etter en TTL
Avvis ugyldige inndata tidlig, slik at en ondsinnet fil aldri når workeren.
from PIL import Image, UnidentifiedImageError
Image.MAX_IMAGE_PIXELS = 50_000_000 # guard against decompression bombs
def is_safe_image(path: str) -> bool:
try:
with Image.open(path) as img:
img.verify() # checks integrity without full decode
return True
except (UnidentifiedImageError, OSError):
return FalseKunnskapssjekk
Test forståelsen din av hvor tungt mediearbeid hører hjemme.
Oppsummering
Du har lært hvordan du holder medietunge FastAPI-endepunkter raske:
- Ta imot raskt, behandle senere: strøm opplastingen til lagring, returner
202med en jobb-ID og legg arbeidet i kø - Bruk en ekte kø (Celery/RQ/arq + Redis) for CPU-tunge transformasjoner; reserver
BackgroundTasksfor billige oppfølgingsoppgaver - Transformer med Pillow:
thumbnail()for endring av størrelse med bevart sideforhold,convert("RGB")før JPEG og WebP for mindre nyttelaster - Dokumentkonvertering starter verktøy som LibreOffice gjennom skallet, med en tidsavbruddsgrense, og alltid i en worker
- Blokker aldri løkken: flytt enkeltstående blokkerende kall med
asyncio.to_thread - Sikre inndata: bekreft typen, begrens pikselantallet, begrens størrelsen og rydd opp i avledede filer
Lær deg Bootcamp i backendutvikling med FastAPI med en AI-veileder – gratis
Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.
- Kurs
- 21
- Leksjoner
- 84
Ofte stilte spørsmål
Er leksjonen «Asynkron bilde- og dokumenttransformasjon» gratis?
Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Bootcamp i backendutvikling med FastAPI, inkludert «Asynkron bilde- og dokumenttransformasjon», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Bootcamp i backendutvikling med FastAPI inneholder totalt 4 leksjoner.
Hva lærer jeg i «Asynkron bilde- og dokumenttransformasjon»?
Behandle miniatyrbilder, skalering og formatkonvertering i bakgrunnsarbeidere for å holde ventetiden for forespørsler lav. Du øver på Bootcamp i backendutvikling med FastAPI med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.
Trenger jeg erfaring for å begynne med Bootcamp i backendutvikling med FastAPI?
Ingen tidligere erfaring er nødvendig. Bootcamp i backendutvikling med FastAPI på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 4 av 4.
Hvor lang tid tar leksjonen «Asynkron bilde- og dokumenttransformasjon»?
De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.
Kan jeg skrive og kjøre kode i denne Bootcamp i backendutvikling med FastAPI-leksjonen?
Ja. Alle Bootcamp i backendutvikling med FastAPI-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.
Alle leksjonene i dette kurset
- Multipart-opplastinger og innholdsvalidering
- Strømmende svar og range-forespørsler
- Avlasting av lagring til S3-kompatible bøtter
- Asynkron bilde- og dokumenttransformasjon