{"record":{"id":"2a8ba4cbcf5d1048","repo":"huggingface/transformers","slug":"expected-file-upload-got-string","errorCode":null,"errorMessage":"Expected file upload, got string","messagePattern":"Expected file upload, got string","errorType":"http","errorClass":"HTTPException","httpStatus":422,"severity":"error","filePath":"src/transformers/cli/serving/transcription.py","lineNumber":108,"sourceCode":"                ``file`` (audio bytes), ``model`` (model ID), and optional ``stream`` flag.\n\n        Returns:\n            `JSONResponse | StreamingResponse`: Transcription result or SSE stream.\n        \"\"\"\n        from transformers.utils.import_utils import is_librosa_available, is_multipart_available\n\n        if not is_librosa_available():\n            raise ImportError(\"Missing librosa dependency for audio transcription. Install with `pip install librosa`\")\n        if not is_multipart_available():\n            raise ImportError(\n                \"Missing python-multipart dependency for file uploads. Install with `pip install python-multipart`\"\n            )\n\n        async with request.form() as form:\n            self._validate_request(set(form.keys()))\n            file_field = form[\"file\"]\n            if isinstance(file_field, str):\n                raise HTTPException(status_code=422, detail=\"Expected file upload, got string\")\n            file_bytes = await file_field.read()\n            model = form[\"model\"]\n            if not isinstance(model, str):\n                raise HTTPException(status_code=422, detail=\"Expected model name as string\")\n            stream = str(form.get(\"stream\", \"false\")).lower() == \"true\"\n\n        model_id_and_revision = self.model_manager.process_model_name(model)\n        audio_model, audio_processor = self.model_manager.load_model_and_processor(model_id_and_revision)\n        base_manager = self.generation_state.get_manager(model_id_and_revision)\n        if not isinstance(base_manager, GenerateManager):\n            raise HTTPException(status_code=400, detail=\"Audio transcription requires sequential generation (not CB)\")\n        gen_manager = base_manager\n        audio_inputs = self._prepare_audio_inputs(file_bytes, audio_processor, audio_model)\n\n        if stream:\n            return self._streaming(gen_manager, audio_model, audio_processor, audio_inputs)\n        return await self._non_streaming(gen_manager, audio_model, audio_processor, audio_inputs)\n","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/huggingface/transformers/blob/a597f974857b3d92939971296bc0deb93d33d780/src/transformers/cli/serving/transcription.py#L90-L126","documentation":"HTTP 422 from the transcription handler when the multipart part named 'file' is a plain string instead of an UploadFile. This happens when the client sends 'file' as an ordinary form field (text value) rather than as a file part with a filename, so form['file'] is a str and await file_field.read() would crash.","triggerScenarios":"Sending the audio as a form field (data={'file': '/path/to/audio.mp3'}) instead of a file part (files={'file': open(...)}); hand-rolled multipart bodies that omit the filename=\"...\" disposition, which Starlette treats as text.","commonSituations":"Using requests with data= instead of files=; passing a base64 string or a path string rather than an opened binary file; frontend FormData appending a string instead of a Blob/File.","solutions":["Send the audio as a real file part: requests.post(url, files={'file': open('a.wav','rb')}, data={'model': 'whisper'})","In JS, append a Blob/File to FormData, not a string: form.append('file', fileInput.files[0])","If building multipart manually, include filename in the Content-Disposition so the parser creates an UploadFile"],"exampleFix":"# before\nrequests.post(url, data={'file': 'audio.wav', 'model': 'whisper'})\n# after\nrequests.post(url, files={'file': open('audio.wav', 'rb')}, data={'model': 'whisper'})","handlingStrategy":"type-guard","validationCode":"path = Path('audio.wav')\nassert path.is_file(), f'{path} must be a real file'\nwith path.open('rb') as fh:\n    resp = requests.post(url, files={'file': (path.name, fh, 'audio/wav')}, data={'model': model_id})","typeGuard":"def is_upload_file(v) -> bool:\n    # a proper file part is (filename, binary stream, content type)\n    return isinstance(v, tuple) and isinstance(v[0], str) and hasattr(v[1], 'read')","tryCatchPattern":"if resp.status_code == 422 and 'Expected file upload' in resp.text:\n    raise ValueError(\"send audio via files=, not data= (form fields arrive as strings)\") from None","preventionTips":["Always open audio in binary mode and pass it through the files= argument","In JS, append File/Blob objects to FormData, never path strings"],"tags":["api","serving","file-upload","validation","http-422"],"backgroundTag":null,"analyzedSha":"a597f974857b3d92939971296bc0deb93d33d780","analyzedAt":"2026-08-14T18:24:08.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}