Ошибка - "попытка прочитать закрытый файл"

Всем доброго времени суток. В данном коде присутствует работа с изображением в переменной data идет создание даты для пост запроса, файлы можно только один раз читать я это понял, но я не понял, почему ошибка не ушла, когда я в коде создал еще 1 дату?

async def _upload_file_to_vk(self, upload_url, data):
    async with self.manager.session.post(upload_url, data=data) as resp:
        if resp.status != 200:
            return None

        upload_result_text = await resp.text()

        if not upload_result_text:
            return None

        try:
            upload_result = json.loads(upload_result_text)

            if "error" in upload_result:
                raise RuntimeError

        except RuntimeError:
            return None

        return upload_result
async def upload_photo(self, file, **kwargs):

    if kwargs.get("peer_id") is None:
        peer_id = self.peer_id

    else:
        peer_id = kwargs.get("peer_id")

    upload_data = await self.manager.request(
        "photos.getMessagesUploadServer", peer_id=peer_id
    )

    if upload_data.error:
        return None

    upload_url = upload_data.response["upload_url"]

    data = aiohttp.FormData()
    data.add_field("photo", file, filename="image.png")

    upload_result = await self._upload_file_to_vk(upload_url, data)

    if not upload_result:
        return None

    attachments = await self.manager.request(
        "photos.saveMessagesPhoto", **upload_result
    )

    #Если произошла ошибка пробуем загрузить тот же самый аттач без peer_id
    if attachments.error:
        upload_data = await self.manager.request(
        "photos.getMessagesUploadServer", peer_id=None
        )

        if upload_data.error:
            return None

        upload_url = upload_data.response["upload_url"]

        data = aiohttp.FormData()
        data.add_field("photo", file, filename="image.png")

        upload_result = await self._upload_file_to_vk(upload_url, data)

        if not upload_result:
            return None

        attachments = await self.manager.request(
            "photos.saveMessagesPhoto", **upload_result
        )

        if attachments.error:
            return None

    return self.manager.create_attachment(
        attachments.response[0], "photo"
    )


f = io.BytesIO()
img2.save(f, format='png')
f.seek(0)
at = await env.upload_photo(f)
f.close()

Получаю ошибку:

File "plugins/content/certificate.py", line 50, in certificate 
at = await env.upload_photo(f) 
File "/root/grizzly/kutana/manager/vk/environment.py", line 146, in upload_photo 
upload_result = await self._upload_file_to_vk(upload_url, data) 
File "/root/grizzly/kutana/manager/vk/environment.py", line 13, in _upload_file_to_vk 
async with self.manager.session.post(upload_url, data=data) as resp: 
File "/usr/local/lib/python3.6/dist-packages/aiohttp/client.py", line 1005, in __aenter__ 
self._resp = await self._coro 
File "/usr/local/lib/python3.6/dist-packages/aiohttp/client.py", line 466, in _request 
ssl=ssl, proxy_headers=proxy_headers, traces=traces) 
File "/usr/local/lib/python3.6/dist-packages/aiohttp/client_reqrep.py", line 294, in __init__ 
self.update_body_from_data(data) 
File "/usr/local/lib/python3.6/dist-packages/aiohttp/client_reqrep.py", line 479, in update_body_from_data 
body = body() 
File "/usr/local/lib/python3.6/dist-packages/aiohttp/formdata.py", line 148, in __call__ 
return self._gen_form_data() 
File "/usr/local/lib/python3.6/dist-packages/aiohttp/formdata.py", line 142, in _gen_form_data 
self._writer.append_payload(part) 
File "/usr/local/lib/python3.6/dist-packages/aiohttp/multipart.py", line 798, in append_payload 
size = payload.size 
File "/usr/local/lib/python3.6/dist-packages/aiohttp/payload.py", line 360, in size 
position = self._value.tell() 
ValueError: I/O operation on closed file.

Не понимаю, что я делаю не так?


Ответы (2 шт):

Автор решения: DGDays

Вы не так поняли. Файлы можно читать неограниченное кол-во раз, но после каждого прочтения файла следует его закрывать, т.к. нередко Python начинает кричать неблагим матом и выводить кракозябры. Также возможно что вы не открыли нужный файл (самое первое обращение, например при работе с txt: t = open('myfile.txt')), либо прописали неправильное название. Вполне возможно что он пытается обратиться к файлу в совершенно другой директории (отличной от расположения вашего файла), если найду пример правильного обращения к файлу, то вставлю сюда.


Вам надо прописать:

import os #В начале файла
data = aiohttp.FormData()
data.add_field("photo", file, filename=os.path.dirname(os.path.abspath(__file__))+"/имя_файла.расширение") #Таким образом файл будет искаться в одной директории с кодом.

Также вам нужно f.close() использовать в другом месте, после вызова файла в двух местах внутри функции. Так вам не придётся закрывать файл вручную.

→ Ссылка
Автор решения: insolor

По результатам обсуждения в комментариях пришли к выводу, что при POST запросе отправляемый файловый объект закрывается. Поэтому такой вариант: пишем изображение в объект BytesIO, но в upload_photo передаем не этот же объект, а данные из него (получаем их с помощью метода getvalue). Внутри upload_photo перед каждой отправкой данных заново создаем объект BytesIO.

async def upload_photo(self, file_data: bytes, **kwargs):

    # ...

    data = aiohttp.FormData()
    data.add_field("photo", io.ByteIO(file_data), filename="image.png")

    upload_result = await self._upload_file_to_vk(upload_url, data)

    if not upload_result:
        return None

    attachments = await self.manager.request(
        "photos.saveMessagesPhoto", **upload_result
    )

    #Если произошла ошибка пробуем загрузить тот же самый аттач без peer_id
    if attachments.error:
        # ...

        data = aiohttp.FormData()
        data.add_field("photo", io.BytesIO(file_data), filename="image.png")

        upload_result = await self._upload_file_to_vk(upload_url, data)

        # ...


f = io.BytesIO()
img2.save(f, format='png')
at = await env.upload_photo(f.getvalue())

Также можно просто прикреплять вложение как байты, а не как файловый объект:

data.add_field("photo", file_data, filename="image.png")
→ Ссылка