FastUI
FastUI copied to clipboard
UplaodFile is already closed when received by post function
I'm trying to upload a file from a form with FastUI and I'm consistently getting I/O operation on closed file.
error.
The code is an adaptation of the demo:
class UploadForm(BaseModel):
text_file: Annotated[UploadFile, FormFile(accept='text/plain')] = Field(
description='Upload a text file'
)
@router.get('/content/{kind}', response_model=FastUI, response_model_exclude_none=True)
def form_content(kind: FormKind):
match kind:
...
case 'text_file':
return [
c.Heading(text='Text File', level=2),
c.Paragraph(text='Upload the text file.'),
c.ModelForm(model=UploadForm, submit_url='/api/text_file_upload'),
]
case _:
raise ValueError(f'Invalid kind {kind!r}')
@router.post('/text_file_upload', response_model=FastUI, response_model_exclude_none=True)
async def text_file_form_post(form: Annotated[UploadForm, fastui_form(UploadForm)]):
# print(form)
file: UploadFile = form.text_file
print(file.filename)
print(file.file.closed)
contents = await file.file.read()
The print message at the end prints the name of the file that I choose correctly and then True
for the file to be closed.
Everything I try to read the contents of the file gives I/O operation on closed file.
error.
I ran into this as well, I found that yielding inside the request.form()
context instead of returning after it in the fastui_form
function fixes it:
def fastui_form(model: _t.Type[FormModel]) -> fastapi_params.Depends:
async def run_fastui_form(request: fastapi.Request):
async with request.form() as form_data:
model_data = unflatten(form_data)
try:
yield model.model_validate(model_data)
except pydantic.ValidationError as e:
raise fastapi.HTTPException(
status_code=422,
detail={'form': e.errors(include_input=False, include_url=False, include_context=False)},
)
return fastapi.Depends(run_fastui_form)
I've made a PR for it, until it's merged or otherwise fixed one could use a monkeypatch like this as a workaround:
import fastapi
import fastui.forms
import pydantic
from fastapi import params as fastapi_params
def patched_fastui_form(model: type[fastui.forms.FormModel]) -> fastapi_params.Depends:
async def run_fastui_form(request: fastapi.Request):
async with request.form() as form_data:
model_data = fastui.forms.unflatten(form_data)
try:
yield model.model_validate(model_data)
except pydantic.ValidationError as e:
raise fastapi.HTTPException(
status_code=422,
detail={'form': e.errors(include_input=False, include_url=False, include_context=False)},
)
return fastapi.Depends(run_fastui_form)
fastui.forms.fastui_form = patched_fastui_form