HTML Diff
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>