0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p><strong>Внимание! Данный материал может содержать ошибки, и является лишь попыткой закрепить изученный материал через изложение. Комментарии с правками приветствуются.</strong></p>
1
<p><strong>Внимание! Данный материал может содержать ошибки, и является лишь попыткой закрепить изученный материал через изложение. Комментарии с правками приветствуются.</strong></p>
2
<p>Используем:</p>
2
<p>Используем:</p>
3
<ul><li>Python 3.9</li>
3
<ul><li>Python 3.9</li>
4
<li>Asyncio</li>
4
<li>Asyncio</li>
5
<li>Aiohttp</li>
5
<li>Aiohttp</li>
6
<li>Postgresql</li>
6
<li>Postgresql</li>
7
<li>Docker</li>
7
<li>Docker</li>
8
<li>Sqlalchemy</li>
8
<li>Sqlalchemy</li>
9
</ul><p>Задача: написать асинхронный код, который скачивает данные с сайта и загружает их в БД.</p>
9
</ul><p>Задача: написать асинхронный код, который скачивает данные с сайта и загружает их в БД.</p>
10
<p>Создаем 5 файлов:</p>
10
<p>Создаем 5 файлов:</p>
11
<ul><li>main.py - точка входа для запуска программы</li>
11
<ul><li>main.py - точка входа для запуска программы</li>
12
<li>jsonplaceholder.py - модуль, скачивающий данные с сайта</li>
12
<li>jsonplaceholder.py - модуль, скачивающий данные с сайта</li>
13
<li>models.py - ф-и для БД</li>
13
<li>models.py - ф-и для БД</li>
14
<li>docker-compose.yaml - информация для докера</li>
14
<li>docker-compose.yaml - информация для докера</li>
15
<li>postgres.env - логин, пароль, название БД</li>
15
<li>postgres.env - логин, пароль, название БД</li>
16
</ul><p><em>docker-compose.yaml</em>:</p>
16
</ul><p><em>docker-compose.yaml</em>:</p>
17
<p><em>postgres.env</em></p>
17
<p><em>postgres.env</em></p>
18
<p><em>jsonplaceholder.py</em></p>
18
<p><em>jsonplaceholder.py</em></p>
19
<ul><li>aiohttp - это "async HTTP client/server for asyncio and Python".</li>
19
<ul><li>aiohttp - это "async HTTP client/server for asyncio and Python".</li>
20
<li>asyncio - это "a library to write concurrent code using the async/await syntax".</li>
20
<li>asyncio - это "a library to write concurrent code using the async/await syntax".</li>
21
<li>async - создает корутину</li>
21
<li>async - создает корутину</li>
22
<li>await - позволяет переключить контекст, переходя к другой ф-и, если текущая занята.</li>
22
<li>await - позволяет переключить контекст, переходя к другой ф-и, если текущая занята.</li>
23
<li>async with - асинхронный менеджер контекста, который автоматически закроется после выполнения задачи (см. Менеджер контекста или with as)</li>
23
<li>async with - асинхронный менеджер контекста, который автоматически закроется после выполнения задачи (см. Менеджер контекста или with as)</li>
24
</ul><p>Корутина - cooperative routine, код, который может выполняться одновременно с другим кодом.</p>
24
</ul><p>Корутина - cooperative routine, код, который может выполняться одновременно с другим кодом.</p>
25
<p><strong>Что мы здесь делаем:</strong></p>
25
<p><strong>Что мы здесь делаем:</strong></p>
26
<ul><li><p>Импортируем библиотеки Пишем константы (по соглашению разработчиков, константы - это переменные, записанные с большой буквы, и которые НЕ следует менять!)</p>
26
<ul><li><p>Импортируем библиотеки Пишем константы (по соглашению разработчиков, константы - это переменные, записанные с большой буквы, и которые НЕ следует менять!)</p>
27
</li>
27
</li>
28
<li><p>Создаем асинхронную ф-и(корутину) fetch_json - дословно: "получить json".</p>
28
<li><p>Создаем асинхронную ф-и(корутину) fetch_json - дословно: "получить json".</p>
29
</li>
29
</li>
30
<li><p>Внутри ф-и вызываем асинхронный менеджер, в нем открываем сессию через aiohttp, и следующим шагом посылаем запрос GET по урлу session.get(url). Результат кладем в переменную response и возвращаем его, преобразовав в json (return await response.json()) - код с return читается справа налево. Вначале await переменной, затем return того, что получилось)</p>
30
<li><p>Внутри ф-и вызываем асинхронный менеджер, в нем открываем сессию через aiohttp, и следующим шагом посылаем запрос GET по урлу session.get(url). Результат кладем в переменную response и возвращаем его, преобразовав в json (return await response.json()) - код с return читается справа налево. Вначале await переменной, затем return того, что получилось)</p>
31
</li>
31
</li>
32
</ul><p><strong>Важно:</strong>данные, приходящие с сервера, не нужно мутировать! Почему? Потому что другие разработчики знают, как работает json() и ожидают json, а не мутанта:) Не усложняйте жизнь другим и себе, например, спустя месяц.</p>
32
</ul><p><strong>Важно:</strong>данные, приходящие с сервера, не нужно мутировать! Почему? Потому что другие разработчики знают, как работает json() и ожидают json, а не мутанта:) Не усложняйте жизнь другим и себе, например, спустя месяц.</p>
33
<p><strong>Итак, результат работы модуля jsonplaceholder.py - возврат данных в виде json</strong></p>
33
<p><strong>Итак, результат работы модуля jsonplaceholder.py - возврат данных в виде json</strong></p>
34
<h3>Импортируем модуль даты и времени</h3>
34
<h3>Импортируем модуль даты и времени</h3>
35
<p>from datetime import datetime</p>
35
<p>from datetime import datetime</p>
36
<h3>Импортируем логгер (типа дебаггера)</h3>
36
<h3>Импортируем логгер (типа дебаггера)</h3>
37
<h3>Импортируем ORM для работы с БД</h3>
37
<h3>Импортируем ORM для работы с БД</h3>
38
<h3>Импортируем асинхронные методы sqlalchemy для работы с БД</h3>
38
<h3>Импортируем асинхронные методы sqlalchemy для работы с БД</h3>
39
<p>from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession</p>
39
<p>from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession</p>
40
<h3>Импортируем метод работы с бд, фабрику сессий и связи</h3>
40
<h3>Импортируем метод работы с бд, фабрику сессий и связи</h3>
41
<h3>Создаем движок</h3>
41
<h3>Создаем движок</h3>
42
<h4>Создаем метод описания БД (Создаем базовый класс для декларативных определений классов.)</h4>
42
<h4>Создаем метод описания БД (Создаем базовый класс для декларативных определений классов.)</h4>
43
<h3>Создаем сессию (Фабрика sessionmaker генерирует новые объекты Session при вызове)</h3>
43
<h3>Создаем сессию (Фабрика sessionmaker генерирует новые объекты Session при вызове)</h3>
44
<p>Session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)</p>
44
<p>Session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)</p>
45
<h3>Запускаем докер, вызвав команду в консоли используя create_subprocess_shell(cmd)</h3>
45
<h3>Запускаем докер, вызвав команду в консоли используя create_subprocess_shell(cmd)</h3>
46
<p>cmd = 'docker compose up -d' async def create_pg_docker(cmd): result = await asyncio.create_subprocess_shell(cmd) await result.communicate() logger.info('____pg docker rdy')</p>
46
<p>cmd = 'docker compose up -d' async def create_pg_docker(cmd): result = await asyncio.create_subprocess_shell(cmd) await result.communicate() logger.info('____pg docker rdy')</p>
47
<h3>Делаем DROP TABLE, CREATE TABLE в БД</h3>
47
<h3>Делаем DROP TABLE, CREATE TABLE в БД</h3>
48
<h3>Cоздаем ф-ю, которая скачивает с сайта юзеров и сохраняет их в БД</h3>
48
<h3>Cоздаем ф-ю, которая скачивает с сайта юзеров и сохраняет их в БД</h3>
49
<h3>Cоздаем ф-ю, которая скачивает с сайта посты и сохраняет их в БД</h3>
49
<h3>Cоздаем ф-ю, которая скачивает с сайта посты и сохраняет их в БД</h3>
50
<h3>Создаем модель таблицы User и Post</h3>
50
<h3>Создаем модель таблицы User и Post</h3>
51
<p><strong>Результат работы модуля models.py:</strong></p>
51
<p><strong>Результат работы модуля models.py:</strong></p>
52
<ol><li>Подключаемся к БД в докере</li>
52
<ol><li>Подключаемся к БД в докере</li>
53
<li>Дропаем и создаем заново таблицы (чтобы небыло конфликта с существующими записями)</li>
53
<li>Дропаем и создаем заново таблицы (чтобы небыло конфликта с существующими записями)</li>
54
<li>Сохраняем данные юзеров и постов в БД</li>
54
<li>Сохраняем данные юзеров и постов в БД</li>
55
</ol><p>main.py:</p>
55
</ol><p>main.py:</p>
56
<p>Что здесь делаем:</p>
56
<p>Что здесь делаем:</p>
57
<p>Создаем асинхронный main() и запускаем его в синхронном main(). Внутри async_main() последовательно запускаем асинхронные ф-и:</p>
57
<p>Создаем асинхронный main() и запускаем его в синхронном main(). Внутри async_main() последовательно запускаем асинхронные ф-и:</p>
58
<ol><li>Запуск БД в докере</li>
58
<ol><li>Запуск БД в докере</li>
59
<li>Дроп и создание пустых таблиц</li>
59
<li>Дроп и создание пустых таблиц</li>
60
<li>Получение данных юзеров и постов с сайта</li>
60
<li>Получение данных юзеров и постов с сайта</li>
61
<li>Сохранение юзеров в бд</li>
61
<li>Сохранение юзеров в бд</li>
62
<li>Сохранение постов в бд</li>
62
<li>Сохранение постов в бд</li>
63
<li>Закрытие подключения к бд (происходит неявно, автоматически при завершении работы менеджера контекста)</li>
63
<li>Закрытие подключения к бд (происходит неявно, автоматически при завершении работы менеджера контекста)</li>
64
</ol><ul><li>asyncio.gather - метод, который вернет то, что в него было положено, в том же порядке. (deprecated in python 3.10!!!)</li>
64
</ol><ul><li>asyncio.gather - метод, который вернет то, что в него было положено, в том же порядке. (deprecated in python 3.10!!!)</li>
65
<li>asyncio.run - метод, создающий event loop для асинхронных ф-ий.</li>
65
<li>asyncio.run - метод, создающий event loop для асинхронных ф-ий.</li>
66
</ul><p><strong>Результат работы main.py:</strong>В БД, в таблице User появляется 10 пользователей, в таблице Post - 100 записей, связанных отношением many to one к юзерам, написавших их.</p>
66
</ul><p><strong>Результат работы main.py:</strong>В БД, в таблице User появляется 10 пользователей, в таблице Post - 100 записей, связанных отношением many to one к юзерам, написавших их.</p>
67
<p>P.S. Если будете копировать код - используйте IDE для его запуска т.к. где-то может стоять 4 пробела, а где-то табуляция. Но лучше не копировать, а руками перепечатать, так лучше запомнится и поймется.</p>
67
<p>P.S. Если будете копировать код - используйте IDE для его запуска т.к. где-то может стоять 4 пробела, а где-то табуляция. Но лучше не копировать, а руками перепечатать, так лучше запомнится и поймется.</p>