-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
streaming uploaded file not working #2578
Comments
Does your issue happen with the exact application you have in the snippet? Or that's the endpoint where you verify this behavior in the whole application? |
The issue occurs in the exact application that I copy-paste above (and also in my whole application of course!) |
I don't have any clue. It works here. Could you try to change the
|
same behavior with Do you have enough network latency to reproduce the problem? If I run the server on the same machine as the client, I can observe the issue with curl by adding
|
Maybe I missed it, but I feel like I'm missing the actual problem you are experiencing. My understanding is that the issue you are facing is that it doesn't appear to be consuming the uploaded file as a stream? But you are able to receive the file in fastapi as uploaded, right? I didn't fully understand how the stream got consumed in this case, so I attempted to trace it through. The stream seems to be be consumed by the time it gets to your path operators by the form parser, which is called prior to any of the code in your path operator. Specifically, the body is processed as a form here which then calls the Starlette form function here which then calls the form parser, which in this case it's a multipart form, so it uses the MultiPartParser, in which it consumes the stream in this for loop by iterating over chunks of the stream. Because it's an upload file, it creates an UploadFile object, and then adds content to the (in memory) file as it consumes the stream. If it overflows the memory buffer (which it would with a large file), it writes the data out to a tempfile I believe. If you are looking to iterate over the chunks of the stream directly in your route for some reason, I guess you would have to override this behavior of starlette's form parser. You could do that in a couple ways I suppose, one of which is that you could create a custom request and route, documented here. In that case, you'd replace the form method with whatever you'd like to do with the file. I'm curious, why do you need the file as a stream inside your route in the first place? It seems as though starlette/fastapi have handled this upload stream and so all you should have to do is |
I my application, the user can upload a large file, for example To provide feedback to the user (a progress bar), the FastAPI function also stores in a global dict the number of bytes in has read, associated to an ID that the client knows and can use to GET this number of bytes, so it can update its progress bar, for example each 100ms. But with the issue, the FastAPI function is called when all the file as been transferred, so the progress shows 0%, and some minutes later, it shows 100%... The upload progress could also by managed by nginx (which I use). But I would be more comfortable to do it with FastAPI. Also, the issue would make FastAPI to consume 70M of memory, while maybe 1M or less would be necessary, if bytes are written to the disk as they are received, then replaced by the next bytes. (ok, as long as writting to the disk is faster than the network latency...). I would really expect an Thanks for tracing the code! I'll have a look on it. (that's a "side project", so I may not reply quickly during the day...) EDIT: my first post is maybe not clear about what the issue is, I'll try to update it in the day! |
@falkben I've read your answer again, you are right, the body = await request.form() is blocking. I'll try to make a custom request and route, as you suggest to solve my issue. If it works and is useful, I could then make a PR for the code, or at least to add a note in the doc that |
Yea, perhaps the docs could be more explicit that the file received in the path operator is not a stream. |
I understand it better now, the file can not but a stream, as the I'll look at |
So I think I have to choose between monkey-patching MultiPartParser.on_part_data and other callbacks (another issue on customize the class, for example) or manage the big files upload using nginx. I'll experiment with both... |
Actually, I'll just use XMLHttpRequest: progress event in the client and keep the file not streamed in the FastAPI function : not enough advantage to code it (Custom Request and APIRoute class using directly python-multipart should be a great solution if needed). I close the issue, thanks! |
Yes was thinking this problem would be better solved on the client... Glad you found a solution! |
I was searching for some information about whether one could process a large file in FastAPI, part by part, while it's being beamed over the network. Am I understanding correctly that the conversation above was specifically about this? Thanks! The specific use-case I had in mind was starting to validate a (potentially large) file being received before the upload finishes. |
@antonwnk yes, this conversion was about this! My understanding is that by default, he whole file that is received by the FastAPI application is buffered: The file is parsed by I closed the issue because for my use case, I managed it on the client side (monitor the file upload progress). If I had to process an uploaded file in a FastAPI application while it's being beamed over the network, I was come to the conclusion to make special case for this particular |
Thank you very much for the summary! This issue is really a treasure trove of information. |
I think this was actually supported via Starlette requests #58 (comment) This does indeed start to print as soon as I send the large file with @app.post("/")
async def stream_file(request: Request):
chunks = 0
logger.info(f"Starting to stream")
async for _ in request.stream():
chunks += 1
logger.info(f"got chunk {chunks}") |
Thanks for the help here everyone! 👏 🙇 Thanks for reporting back and closing the issue @dfroger 👍
|
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Hi,
I'm deploying my application on a OVH VPS and I'm having issue with streaming file that I upload.
I can reproduce the issue on a minimal example where the streaming works when using
uvicorn
only, but fails when usinguvicorn
+fastapi
.Here is the
uvicorn
only code:I run it with:
The client command to upload a file, from my laptop is:
I get the following output:
Here is the
fastAPI
code:I run it with:
The client command is:
And the output is, after a moment (about 26 seconds I guess):
So the upload file seems to be buffered somewhere, and then pass to the
upload_file
function.Both code run on the same machine (Debian 10), with Python 3.8.6 (installed with pyenv).
My virtualenv contains:
I'll add more infos if I think to, and will continue debugging...
Thanks!
The text was updated successfully, but these errors were encountered: