Почему мой курсор закрывается в моей функции генератора, когда я передаю его 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()
.