Think of a file object as a page and a pencil. That pencil can point to any position on that page. When you open a file, the pen points to the beginning of the page (position 0), and the contents of the page are either empty (if it's a file open for output, or a new BytesIO
object), or contain the contents of the file (if it's a file open for input, or a BytesIO
object with preloaded content).
What happens when you write
to or read
from the file?
- When you
write
something to the file, you simply start from the position where your pencil is resting right now, and write on the page from there. Assuming it's a new page, you start from the upper left corner (position 0), and go from there. When you're done writing, your pencil rests at the point where you stopped writing.
- When you
read
from a file, you simply read from the page, starting from the point where your pencil is resting right now, and helping yourself by moving the pencil with every byte you read. You can read up to the point where the data on the page ends, at which point no further reading can be done (since the pencil points to the end of the data, and no additional data is present beyond that point).
So, what happens in the first scenario you described?
- You create a new
BytesIO
object (i.e., you get a shiny new page with a pencil pointing to the beginning).
- You write your data to the file (i.e., you use the pencil to write that data to the page, and when you're done, the pencil points to the end of that data).
- You attempt to read that data (not you directly, but the implementation of
send_file
) - but that attempt fails, since the pencil is at the end of the page, and there's nothing left to read.
However, in your second scenario you added a couple of changes:
file.seek(0)
moves the pencil to the beginning of the page, so the following read
operation will succeed.
- Instead of passing
file
to send_file
, you're passing BytesIO(file.read())
. First, you read the file with file.read()
- that succeeds, thanks to the file.seek(0)
above. Next, you take the data that you just read and pass it to the BytesIO
constructor - so it generates a new page, with that content preloaded on the page, with the pencil still pointing to its beginning (so read
ing will succeed).
In fact, of the above 2 changes, only the first one is required - the 2nd change is redundant and, in fact, reduces performance since the data is read and written twice. The correct approach would be:
with BytesIO() as file:
data.write(file, encoding='windows-1251')
file.seek(0)
return send_file(file, attachment_filename='output.xml', as_attachment=True)