async def save_import(import_obj: Import, database: Database) -> Union[int, None]: """Create import and corresponding citizens and relations.""" async with database.transaction(): insert_import_query = imports.insert().values().returning( imports.c.import_id) import_id = await database.fetch_val(insert_import_query) if import_obj: max_citizens_per_insert = MAX_QUERY_ARGS // len(citizens.columns) citizens_rows = make_citizens_rows(import_obj, import_id) chunked_citizens = chunk_list(citizens_rows, max_citizens_per_insert) insert_citizens_query = citizens.insert() for chunk in chunked_citizens: await database.execute( insert_citizens_query.values(list(chunk))) max_relations_per_insert = MAX_QUERY_ARGS // len(relations.columns) relations_rows = make_relations_rows(import_obj, import_id) chunked_relations = chunk_list(relations_rows, max_relations_per_insert) insert_relations_query = relations.insert() for chunk in chunked_relations: await database.execute( insert_relations_query.values(list(chunk))) return import_id
async def create_import(db: PG, citizens: List[dict]) -> int: async with db.transaction() as conn: query = imports_table.insert().returning(imports_table.c.import_id) import_id = await conn.fetchval(query=query) citizen_rows = make_citizen_rows(import_id=import_id, citizens=citizens) relation_rows = make_relation_rows(import_id=import_id, citizens=citizens) chunked_citizen_rows = chunk_list(iterable=citizen_rows, size=MAX_CITIZENS_PER_INSERT) chunked_relation_rows = chunk_list(iterable=relation_rows, size=MAX_RELATIONS_PER_INSERT) query = citizens_table.insert() for chunk in chunked_citizen_rows: await conn.execute(query.values(list(chunk))) query = relations_table.insert() for chunk in chunked_relation_rows: await conn.execute(query.values(list(chunk))) return import_id
async def post(self): # Транзакция требуется чтобы в случае ошибки (или отключения клиента, # не дождавшегося ответа) откатить частично добавленные изменения. async with self.pg.transaction() as conn: # Создаем выгрузку query = imports_table.insert().returning(imports_table.c.import_id) import_id = await conn.fetchval(query) # Генераторы make_citizens_table_rows и make_relations_table_rows # лениво генерируют данные, готовые для вставки в таблицы citizens # и relations на основе данных отправленных клиентом. citizens = self.request['data']['citizens'] citizen_rows = self.make_citizens_table_rows(citizens, import_id) relation_rows = self.make_relations_table_rows(citizens, import_id) # Чтобы уложиться в ограничение кол-ва аргументов в запросе к # postgres, а также сэкономить память и избежать создания полной # копии данных присланных клиентом во время подготовки - используем # генератор chunk_list. # Он будет получать из генератора make_citizens_table_rows только # необходимый для 1 запроса объем данных. chunked_citizen_rows = chunk_list(citizen_rows, self.MAX_CITIZENS_PER_INSERT) chunked_relation_rows = chunk_list(relation_rows, self.MAX_RELATIONS_PER_INSERT) query = citizens_table.insert() for chunk in chunked_citizen_rows: await conn.execute(query.values(list(chunk))) query = relations_table.insert() for chunk in chunked_relation_rows: await conn.execute(query.values(list(chunk))) return Response(body={'data': {'import_id': import_id}}, status=HTTPStatus.CREATED)
def test_chunk_list(): data = tuple(map(tuple, aiomisc.chunk_list(range(10), 3))) assert data == ((0, 1, 2), (3, 4, 5), (6, 7, 8), (9, ))