FastUI icon indicating copy to clipboard operation
FastUI copied to clipboard

UplaodFile is already closed when received by post function

Open guyernest opened this issue 1 year ago • 1 comments

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.

guyernest avatar Jan 06 '24 21:01 guyernest

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

gradient-ascent-ai-lab avatar Feb 04 '24 21:02 gradient-ascent-ai-lab