Почему мой курсор закрывается в моей функции генератора, когда я передаю его StreamingHttpResponse?
У меня есть потенциально большой набор запросов, который я не хочу загружать в память. Это пользовательская инструкция SQL.
django.http.StreamingHttpResponse принимает аргумент iterator (генератор). Чтобы избежать загрузки всего в память, я использую серверные курсоры Postgres и fetchmany (хотя мне еще предстоит убедиться, что это действительно работает).
Вот моя генераторная функция, которую я передаю:
def queryset_generator(cursor, chunk_size=CHUNK_SIZE):
while True:
if cursor.closed:
yield "cursor closed!"
break
rows = cursor.fetchmany(chunk_size)
if not rows:
break
yield rows
Я проверяю, закрыт ли курсор, потому что в противном случае psycopg2 жалуется, когда код впоследствии пытается получить доступ закрытый курсор.
И вот как я передаю это в моем представлении (упрощая SQL):
with connections['mydb'].cursor() as cursor:
cursor.execute("SELECT * FROM foobar;")
return StreamingHttpResponse(queryset_generator(cursor))
Что последовательно дает мне
Курсор закрыт!
Почему курсор закрыт в моей функции генератора? Если я делаю это, на мой взгляд, это работает нормально:
with connections['mydb'].cursor() as cursor:
cursor.execute("SELECT * FROM foobar;")
return StreamingHttpResponse(cursor.fetchall())
Также потенциально примечательно, что это прекрасно работает в оболочке:
cursor = connections['mydb'].cursor()
cursor.execute(...)
for x in StreamingHttpResponse(queryset_generator(cursor))._iterator:
print(x)
1 ответ:
Почему курсор закрыт в моей функции генератора?
Потому что вы выходите из контекстного менеджера с помощью
return:return StreamingHttpResponse(queryset_generator(cursor))Это завершает работу блока
with, запускает метод__exit__в контекстном менеджере, и этот метод закрывает курсор. Операторwithили менеджер контекста не могут знать, что вы только что передали ссылку на Объектcursorчему-то другому, что все еще нуждается в том, чтобы он оставался открытым.withне заботится о ссылках, только о семантическом блоке окончание.Если вам нужно держать курсор открытым до тех пор, пока экземпляр
StreamingHttpResponse()не выполнит потоковую передачу данных, вы не можете использовать контекстный менеджер вокруг оператораreturn.Передайте Курсор без использования его в контекстном менеджере и сделайте функцию
queryset_generator()ответственной за использованиеwithвместо:def queryset_generator(cursor, chunk_size=CHUNK_SIZE): with cursor: while True: if cursor.closed: yield "cursor closed!" break rows = cursor.fetchmany(chunk_size) if not rows: break yield rowsИ
Теперь курсор остается открытым до тех пор, пока не будет выполнен циклcursor = connections['mydb'].cursor() cursor.execute("SELECT * FROM foobar;") return StreamingHttpResponse(queryset_generator(cursor))whileвqueryset_generator().