<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: sqlalchemy</title>
<link>http://test.leftjoin.ru/tags/sqlalchemy/</link>
<description></description>
<generator>E2 (v3365; Aegea)</generator>

<item>
<title>Транзакции в SQLAlchemy</title>
<guid isPermaLink="false">94</guid>
<link>http://test.leftjoin.ru/all/tranzakcii-v-sqlalchemy/</link>
<comments>http://test.leftjoin.ru/all/tranzakcii-v-sqlalchemy/</comments>
<description>
&lt;p&gt;Транзакция — последовательность действий, связанных с базой данных. Их основная польза заключается в том, что при возникновении какой-то ошибки или достижении других нужных условий всю транзакцию можно отменить, и все изменения, примененные к базе данных, будут отменены. Сегодня мы напишем небольшой скрипт, который при помощи транзакций SQLAlchemy пишет информацию о подписчиках сообщества в базу данных MySQL, а при возникновении ошибки отменяет текущую транзакцию.&lt;/p&gt;
&lt;h2&gt;Сбор информации об участниках через VK API&lt;/h2&gt;
&lt;p&gt;Для начала напишем пару маленьких функций — первая будет возвращать число подписчиков сообщества, а вторая — отправлять запрос и формировать датафрейм с информацией о подписчиках сообщества.&lt;/p&gt;
&lt;p class="note"&gt;Подробнее о том, как получить токен, можно прочитать в материале &lt;a href="http://test.leftjoin.ru/all/get-data-from-vk/" class="nu"&gt;«&lt;u&gt;Собираем данные по рекламным кампаниям ВКонтакте&lt;/u&gt;»&lt;/a&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;from sqlalchemy import create_engine
import pandas as pd
import requests
import time

token = '42hj2ehd3djdournf48fjurhf9r9o2eurnf48fjurhf9r9734'
group_id = 'leftjoin'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Чтобы узнать число подписчиков достаточно отправить метод groups.getMembers с любыми параметрами — в ответе всегда возвращается количество в поле count.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_subs_count(group_id):
    count = requests.get('https://api.vk.com/method/groups.getMembers', params={
        'access_token':token,
        'v':5.103,
        'group_id':group_id
    }).json()['response']['count']
    return count&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для примера будем брать имена, id, фамилии подписчиков, некоторую расширенную информацию и получать только по 10 подписчиков за раз, чтобы рассмотреть работу транзакций детально — каждые 10 подписчиков будут вставляться одной транзакцией. Введём дополнительное поле offset, чтобы знать, в какой итерации добавлены строки.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_subs_info(group_id, offset):
    response = requests.get('https://api.vk.com/method/groups.getMembers', params={
        'access_token':token,
        'v':5.103,
        'group_id':group_id,
        'offset':offset,
        'count':10,
        'fields':'sex, has_mobile, relation, can_post'
    }).json()['response']['items']
    df = pd.DataFrame(response)
    df['offset'] = offset
    return df&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Транзакции&lt;/h2&gt;
&lt;p&gt;Наконец, можем подсоединиться к базе данных при помощи SQLAlchemy:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;engine = create_engine('mysql+mysqlconnector://' +
                           'root' + ':' + '' + '@' +
                           'localhost' + '/' +
                           'transaction', echo=False)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;У транзакций всегда должно быть начало — begin, и конец — commit. В случае, если произошла какая-то ошибка, можно сделать откат — rollback. Сперва получаем число подписчиков сообщество, и в каждой итерации цикла при помощи контекстного менеджера with ... as создаём новое подключение. Сразу после объявляем начало транзакции по этому подключению и с обработчиком исключений пробуем получить информацию о десяти подписчиках через функцию get_subs_info. Вставляем полученный датафрейм в таблицу методом to_sql и завершаем транзакцию при помощи метода commit(). В случае, если возникла какая-то ошибка — печатаем её на экран и отменяем транзакцию.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;offset = 0
subs_count = get_subs_count(group_id)
while offset &amp;lt; subs_count:
    with engine.connect() as conn:
        transaction = conn.begin()
        try:
            df = get_subs_info(group_id, offset)
            df.to_sql('subscribers', con=conn, if_exists='append', index=False)
            transaction.commit()
        except Exception as E:
            print(E)
            transaction.rollback()
    time.sleep(1)
    offset += 10&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Чтобы протестировать работу транзакций слегка обновим последний блок кода — добавим вызов ошибки ValueError после вставки данных в базу, если текущий offset равен 10.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;offset = 0
subs_count = get_subs_count(group_id)
while offset &amp;lt; subs_count:
    with engine.connect() as conn:
        transaction = conn.begin()
        try:
            df = get_subs_info(group_id, offset)
            df.to_sql('subscribers', con=conn, if_exists='append', index=False)
            if offset == 10:
                raise(ValueError)
            transaction.commit()
        except Exception as E:
            print(E)
            transaction.rollback()
    time.sleep(1)
    offset += 10&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Как и планировалось, данные за итерацию с offset = 10 не занесены в таблицу. Несмотря на то, что ошибка возникла уже после добавления новых данных, транзакция была прервана методом rollback() и завершение транзакции было отменено.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/1-23.png" width="759" height="562" alt="" /&gt;
&lt;/div&gt;
</description>
<pubDate>Fri, 12 Feb 2021 11:10:22 +0300</pubDate>
</item>


</channel>
</rss>