From f6b450c922205cabaee45a13ee426d51d7a0c4fb Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 28 Sep 2026 09:19:48 -0400 Subject: [PATCH] =?UTF-8?q?docs(practice):=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D1=80=D0=B5=D0=BD=D1=8C=20?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ы, XCom, TaskGroup и алертинг в Airflow.md | 2 +- README.md | 2 +- airflow-docker/README.md | 21 ++++---- airflow-docker/dag-specifications.md | 2 +- .../2026-09-28-practice-improvement-plan.md | 54 +++++++++---------- docs/validation/2026-09-28-practice.md | 2 +- ...ucational-tasks.md => educational-tasks.md | 4 +- 7 files changed, 43 insertions(+), 44 deletions(-) rename airflow-docker/educational-tasks.md => educational-tasks.md (98%) diff --git a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md index 45f6356..5cd97b1 100644 --- a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md +++ b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md @@ -283,7 +283,7 @@ def notify_success(context): Не добавляйте ради уведомления единственную завершающую задачу с `trigger_rule="all_done"`: если она успешна, Airflow может признать весь запуск успешным, несмотря на отказ внутри графа. Callback не меняет граф и итог обработки. Ошибка отправки означает проблему уведомления; она не превращает успешно обработанные данные в неуспешные и не скрывает исходный отказ. Подробности определения результата - в [описании DAG Run](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/dag-run.html#dag-run-status). -Проверка: сначала воспроизведите окончательный отказ, затем успешный запуск. Сверьте текст каждого письма с состоянием соответствующего DAG Run. Упражнения находятся в [блоке ошибок](airflow-docker/educational-tasks.md#errors) и [задании на почту](airflow-docker/educational-tasks.md#resources). +Проверка: сначала воспроизведите окончательный отказ, затем успешный запуск. Сверьте текст каждого письма с состоянием соответствующего DAG Run. Упражнения находятся в [блоке ошибок](educational-tasks.md#errors) и [задании на почту](educational-tasks.md#resources). ## Практическое применение: улучшенный DAG diff --git a/README.md b/README.md index 3c7f871..df810e8 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ ## Практический стенд -В [`airflow-docker`](airflow-docker/) находятся Airflow 2.9.2, учебная PostgreSQL и девять готовых DAG. [Инструкция запуска](airflow-docker/README.md) поможет поднять стенд, а [маршрут практики](airflow-docker/educational-tasks.md) связывает восемь блоков заданий с главами учебника. +В [`airflow-docker`](airflow-docker/) находятся Airflow 2.9.2, учебная PostgreSQL и девять готовых DAG. [Инструкция запуска](airflow-docker/README.md) поможет поднять стенд, а [маршрут практики](educational-tasks.md) связывает восемь блоков заданий с главами учебника. Здесь вы запускаете готовый DAG, меняете одну часть и проверяете результат в графе, логах или данных. Самостоятельные процессы обработки данных относятся к курсовым работам дальше по [программе](https://de.dementev.space/). diff --git a/airflow-docker/README.md b/airflow-docker/README.md index e616b9b..ef84db7 100644 --- a/airflow-docker/README.md +++ b/airflow-docker/README.md @@ -32,20 +32,20 @@ docker compose logs airflow-init ## Прохождение практики -Откройте [задания](educational-tasks.md). Их восемь блоков проходят последовательно; перед каждым указаны нужные главы [учебника](../README.md), исходные файлы и настройки. +Откройте [задания](../educational-tasks.md). Их восемь блоков проходят последовательно; перед каждым указаны нужные главы [учебника](../README.md), исходные файлы и настройки. Основные задания внутри блока сохраняют изменения предыдущих шагов. Дополнительные находятся в конце блока и помечены "По желанию"; их можно пропустить. Для завершения практики достаточно основных заданий и проверки их результатов. | Блок | DAG | |---|---| -| [Первый DAG](educational-tasks.md#first-dag) | `hello_world_dag` | -| [SQL и подключения](educational-tasks.md#sql) | `sql_basic_dag` | -| [Работа с файлами](educational-tasks.md#files) | `file_operations_dag` | -| [Загрузка CSV и DQ](educational-tasks.md#load-dq) | `csv_to_postgres`, `csv_to_postgres_dq` | -| [Разбор ETL](educational-tasks.md#etl) | `data_processing_dag` | -| [Ветвление](educational-tasks.md#branching) | `branching_dag` | -| [Ошибки и повторы](educational-tasks.md#errors) | `error_handling_dag` | -| [Пулы, XCom и TaskGroup](educational-tasks.md#resources) | `resource_management_dag` | +| [Первый DAG](../educational-tasks.md#first-dag) | `hello_world_dag` | +| [SQL и подключения](../educational-tasks.md#sql) | `sql_basic_dag` | +| [Работа с файлами](../educational-tasks.md#files) | `file_operations_dag` | +| [Загрузка CSV и DQ](../educational-tasks.md#load-dq) | `csv_to_postgres`, `csv_to_postgres_dq` | +| [Разбор ETL](../educational-tasks.md#etl) | `data_processing_dag` | +| [Ветвление](../educational-tasks.md#branching) | `branching_dag` | +| [Ошибки и повторы](../educational-tasks.md#errors) | `error_handling_dag` | +| [Пулы, XCom и TaskGroup](../educational-tasks.md#resources) | `resource_management_dag` | Описание поведения исходных примеров находится в [справочнике DAG](dag-specifications.md). Почта нужна только для отдельного задания по желанию; SMTP в стенде не настроен. @@ -59,8 +59,7 @@ airflow-docker/ ├── dags/ # Девять учебных DAG ├── data/input/ # Входные CSV и данные упражнений ├── data/output/ # Результаты задач -├── init-postgres.sql # Начальная подготовка учебной БД -└── educational-tasks.md # Маршрут и условия заданий +└── init-postgres.sql # Начальная подготовка учебной БД ``` `dags/` и `data/` смонтированы в контейнеры как `/opt/airflow/dags/` и `/opt/airflow/data/`. Правки DAG подхватываются планировщиком; перед новым запуском дождитесь обновления вкладки Code. Резервные копии Python-файлов храните вне `dags/`, чтобы не создать два определения одного `dag_id`. diff --git a/airflow-docker/dag-specifications.md b/airflow-docker/dag-specifications.md index e6f917c..127a732 100644 --- a/airflow-docker/dag-specifications.md +++ b/airflow-docker/dag-specifications.md @@ -1,6 +1,6 @@ # Что делают исходные DAG -Это справочник примеров для Airflow 2.9.2. Порядок прохождения, изменения кода и условия завершения находятся в [практических заданиях](educational-tasks.md). +Это справочник примеров для Airflow 2.9.2. Порядок прохождения, изменения кода и условия завершения находятся в [практических заданиях](../educational-tasks.md). | DAG и файл в `dags/` | Подготовка | Поведение и результат | |---|---|---| diff --git a/docs/specs/2026-09-28-practice-improvement-plan.md b/docs/specs/2026-09-28-practice-improvement-plan.md index f249d24..aa860f4 100644 --- a/docs/specs/2026-09-28-practice-improvement-plan.md +++ b/docs/specs/2026-09-28-practice-improvement-plan.md @@ -2,7 +2,7 @@ Дата: 2026-09-28. Подход согласован с автором курса. Реализация завершена. Результаты и границы проверки сохранены в [протоколе](../validation/2026-09-28-practice.md). -Этот документ сохраняет решения, порядок доработки и результаты проверки. Текущие задания находятся в [educational-tasks.md](../../airflow-docker/educational-tasks.md), запуск стенда описан в [README](../../airflow-docker/README.md). +Этот документ сохраняет решения, порядок доработки и результаты проверки. Текущие задания находятся в [educational-tasks.md](../../educational-tasks.md), запуск стенда описан в [README](../../airflow-docker/README.md). ## Цель и границы @@ -48,7 +48,7 @@ ## Как оформлять задания -Сохраняется один основной документ для студентов - `educational-tasks.md`. Не нужно дублировать инструкции в отдельном сборнике дополнительных упражнений. +Задания находятся в корне репозитория рядом с главами учебника: `educational-tasks.md`. В `airflow-docker/` остаются инструкция запуска, стенд и примеры DAG. Основные и дополнительные упражнения сохраняются в одном документе. В начале блока: связь с главами учебника, необходимые исходные условия и короткий запуск готового примера. У задания: @@ -84,31 +84,31 @@ | Исходное задание | Возможность | Вход и зависимости | Решение и причина | Новое место | Наблюдаемый результат | |---|---|---|---|---|---| -| hello_world / 1 | PythonOperator, зависимости | Исходный DAG | Основное: небольшое изменение графа | [1.1](../../airflow-docker/educational-tasks.md#first-dag) | Новая задача и ее сообщение видны в UI | -| hello_world / 2 | Расписание, ограничения запусков | После 1.1 | Разделить: расписание основное, конкуренция по желанию | [1.3, 1.A](../../airflow-docker/educational-tasks.md#first-dag) | Видно расписание; второй запуск ожидает при лимите 1 | -| hello_world / 3 | BashOperator | После 1.1 | Основное: другой оператор и файл | [1.2](../../airflow-docker/educational-tasks.md#first-dag) | Файл содержит ожидаемый текст | -| sql_basic / 1 | SQL в задаче, схема таблицы | Исходный SQL-DAG и training | Разделить: UPDATE основное, схема по желанию | [2.2, 2.A](../../airflow-docker/educational-tasks.md#sql) | Возраст изменен; дополнительная колонка заполнена | -| sql_basic / 2 | PostgresHook и отчет | После 2.2, таблица с тремя строками | По желанию: дополнительная обработка | [2.B](../../airflow-docker/educational-tasks.md#sql) | Файл: 3 студента, средний возраст 23.33 после UPDATE | -| sql_basic / 3 | Connections и диагностика | Учебная БД, собственный Conn Id | Основное; вместо неопределенного обработчика - найти и исправить ошибку подключения | [2.1](../../airflow-docker/educational-tasks.md#sql) | Ошибка видна в логах, после исправления запуск успешен | -| file_operations / 1 | Генерация, валидация | Исходный файловый DAG | Разделить: простая трансформация основная, новые поля по желанию | [3.1, 3.A](../../airflow-docker/educational-tasks.md#files) | 100 обработанных строк; поля и фильтр проверены отдельно | -| file_operations / 2 | Отчет по отделам | Требует 3.A | По желанию; график исключен, поскольку требует отдельной зависимости и не знакомит с новой возможностью Airflow | [3.B](../../airflow-docker/educational-tasks.md#files) | Сумма численности отделов равна числу строк входа | -| file_operations / 3 | Параллельность, DQ, retries | Требует 3.A | По желанию; параллельность также видна в основном ETL-примере | [3.C](../../airflow-docker/educational-tasks.md#files) | Раздельные файлы покрывают все отделы, сборка читает их | -| csv_to_postgres / 1 | Проведение поля через загрузку | После основной загрузки и DQ | По желанию: согласовать все четыре SQL/CSV-участка и проверку схемы | [4.A](../../airflow-docker/educational-tasks.md#load-dq) | status заполнен; DQ проходит; исходная схема восстановлена | -| csv_to_postgres / 2 | Специализированный оператор | Базовая схема orders | По желанию: сравнение двух реализаций уже знакомого действия | [4.B](../../airflow-docker/educational-tasks.md#load-dq) | После замены оператора загрузка и DQ успешны | -| csv_to_postgres_dq / 1 | Новая проверка, ошибка данных | Основная загрузка orders | Основное: положительный и отрицательный сценарии | [4.2](../../airflow-docker/educational-tasks.md#load-dq) | Отрицательная сумма вызывает failed; восстановление возвращает success | -| csv_to_postgres_dq / 2 | Trigger Rules и итог DAG Run | После 4.2 | По желанию: запуск всех проверок и общая сводка | [4.C](../../airflow-docker/educational-tasks.md#load-dq) | Все проверки завершаются, итог сохраняет ошибку | -| data_processing / 1 | Несколько источников | Исходный ETL, свои CSV | По желанию; основной разбор графа добавлен перед расширениями | [5.1, 5.A](../../airflow-docker/educational-tasks.md#etl) | После JOIN остаются 6 заказов и выручка 2105 | -| data_processing / 2 | Отчет pandas | После основного запуска, products не нужен | По желанию | [5.B](../../airflow-docker/educational-tasks.md#etl) | Топ заказов и средние сверяются с шестью исходными строками | -| data_processing / 3 | Валидация, callback, архив | После 5.1; поля продуктов необязательны | По желанию с конкретной ошибкой и восстановлением | [5.C](../../airflow-docker/educational-tasks.md#etl) | При ошибке архив не создается; после исправления совпадает с результатом | -| branching / 1 | Выбор ветки и слияние | Исходный DAG, Variable branch_format | Разделить: две управляемые ветки основные, третья по желанию | [6.1, 6.A](../../airflow-docker/educational-tasks.md#branching) | Выбранная ветка success, другая skipped, слияние success | -| branching / 2 | Работа выбранной ветки с данными | Два явно заданных входных файла | По желанию; использовать XCom текущего запуска вместо старых файлов другой ветки | [6.B](../../airflow-docker/educational-tasks.md#branching) | Оба формата дают одинаковые агрегаты в отдельных запусках | -| branching / 3 | Variables, неизвестный параметр | После 6.1 | Variable включена в основное; fallback объединен с третьей веткой | [6.1, 6.A](../../airflow-docker/educational-tasks.md#branching) | Неизвестное значение дает понятную ошибку либо review-ветку после расширения | -| error_handling / 1 | Повторы и задержка | Исходный DAG, Variable error_mode | Разделить: один управляемый повтор основной, backoff по желанию | [7.1, 7.2, 7.A](../../airflow-docker/educational-tasks.md#errors) | Две попытки; с нулем повторов - failed | -| error_handling / 2 | Типы ошибок и оповещение | После 7.2 | По желанию; локальный callback без внешней почты | [7.B](../../airflow-docker/educational-tasks.md#errors) | После исчерпания повторов есть сообщение с task_id и причиной | -| error_handling / 3 | logging | После 7.1 | Основное небольшое дополнение вместо механической замены всех print | [7.3](../../airflow-docker/educational-tasks.md#errors) | Собственные сообщения видны в логах | -| Дополнительные / 1 | Пулы и слоты | Готовый resource_management_dag, пул из 2 слотов | Основной короткий эксперимент; резервное копирование заменено имитацией чтения | [8.1](../../airflow-docker/educational-tasks.md#resources) | Задачи с весами 2 и 1 выполняются последовательно | -| Дополнительные / 2 | XCom и Jinja | После 8.1 | Основная метрика; шаблон Bash по желанию | [8.2, 8.A](../../airflow-docker/educational-tasks.md#resources) | total_rows = 50, шаблон выводит то же значение | -| Дополнительные / 3 | TaskGroup и алертинг | После 8.2; для почты нужен SMTP | Разделить: группировка основная, внешняя доставка по желанию | [8.3, 8.B](../../airflow-docker/educational-tasks.md#resources) | Группа раскрывается; XCom работает с полными task_id | +| hello_world / 1 | PythonOperator, зависимости | Исходный DAG | Основное: небольшое изменение графа | [1.1](../../educational-tasks.md#first-dag) | Новая задача и ее сообщение видны в UI | +| hello_world / 2 | Расписание, ограничения запусков | После 1.1 | Разделить: расписание основное, конкуренция по желанию | [1.3, 1.A](../../educational-tasks.md#first-dag) | Видно расписание; второй запуск ожидает при лимите 1 | +| hello_world / 3 | BashOperator | После 1.1 | Основное: другой оператор и файл | [1.2](../../educational-tasks.md#first-dag) | Файл содержит ожидаемый текст | +| sql_basic / 1 | SQL в задаче, схема таблицы | Исходный SQL-DAG и training | Разделить: UPDATE основное, схема по желанию | [2.2, 2.A](../../educational-tasks.md#sql) | Возраст изменен; дополнительная колонка заполнена | +| sql_basic / 2 | PostgresHook и отчет | После 2.2, таблица с тремя строками | По желанию: дополнительная обработка | [2.B](../../educational-tasks.md#sql) | Файл: 3 студента, средний возраст 23.33 после UPDATE | +| sql_basic / 3 | Connections и диагностика | Учебная БД, собственный Conn Id | Основное; вместо неопределенного обработчика - найти и исправить ошибку подключения | [2.1](../../educational-tasks.md#sql) | Ошибка видна в логах, после исправления запуск успешен | +| file_operations / 1 | Генерация, валидация | Исходный файловый DAG | Разделить: простая трансформация основная, новые поля по желанию | [3.1, 3.A](../../educational-tasks.md#files) | 100 обработанных строк; поля и фильтр проверены отдельно | +| file_operations / 2 | Отчет по отделам | Требует 3.A | По желанию; график исключен, поскольку требует отдельной зависимости и не знакомит с новой возможностью Airflow | [3.B](../../educational-tasks.md#files) | Сумма численности отделов равна числу строк входа | +| file_operations / 3 | Параллельность, DQ, retries | Требует 3.A | По желанию; параллельность также видна в основном ETL-примере | [3.C](../../educational-tasks.md#files) | Раздельные файлы покрывают все отделы, сборка читает их | +| csv_to_postgres / 1 | Проведение поля через загрузку | После основной загрузки и DQ | По желанию: согласовать все четыре SQL/CSV-участка и проверку схемы | [4.A](../../educational-tasks.md#load-dq) | status заполнен; DQ проходит; исходная схема восстановлена | +| csv_to_postgres / 2 | Специализированный оператор | Базовая схема orders | По желанию: сравнение двух реализаций уже знакомого действия | [4.B](../../educational-tasks.md#load-dq) | После замены оператора загрузка и DQ успешны | +| csv_to_postgres_dq / 1 | Новая проверка, ошибка данных | Основная загрузка orders | Основное: положительный и отрицательный сценарии | [4.2](../../educational-tasks.md#load-dq) | Отрицательная сумма вызывает failed; восстановление возвращает success | +| csv_to_postgres_dq / 2 | Trigger Rules и итог DAG Run | После 4.2 | По желанию: запуск всех проверок и общая сводка | [4.C](../../educational-tasks.md#load-dq) | Все проверки завершаются, итог сохраняет ошибку | +| data_processing / 1 | Несколько источников | Исходный ETL, свои CSV | По желанию; основной разбор графа добавлен перед расширениями | [5.1, 5.A](../../educational-tasks.md#etl) | После JOIN остаются 6 заказов и выручка 2105 | +| data_processing / 2 | Отчет pandas | После основного запуска, products не нужен | По желанию | [5.B](../../educational-tasks.md#etl) | Топ заказов и средние сверяются с шестью исходными строками | +| data_processing / 3 | Валидация, callback, архив | После 5.1; поля продуктов необязательны | По желанию с конкретной ошибкой и восстановлением | [5.C](../../educational-tasks.md#etl) | При ошибке архив не создается; после исправления совпадает с результатом | +| branching / 1 | Выбор ветки и слияние | Исходный DAG, Variable branch_format | Разделить: две управляемые ветки основные, третья по желанию | [6.1, 6.A](../../educational-tasks.md#branching) | Выбранная ветка success, другая skipped, слияние success | +| branching / 2 | Работа выбранной ветки с данными | Два явно заданных входных файла | По желанию; использовать XCom текущего запуска вместо старых файлов другой ветки | [6.B](../../educational-tasks.md#branching) | Оба формата дают одинаковые агрегаты в отдельных запусках | +| branching / 3 | Variables, неизвестный параметр | После 6.1 | Variable включена в основное; fallback объединен с третьей веткой | [6.1, 6.A](../../educational-tasks.md#branching) | Неизвестное значение дает понятную ошибку либо review-ветку после расширения | +| error_handling / 1 | Повторы и задержка | Исходный DAG, Variable error_mode | Разделить: один управляемый повтор основной, backoff по желанию | [7.1, 7.2, 7.A](../../educational-tasks.md#errors) | Две попытки; с нулем повторов - failed | +| error_handling / 2 | Типы ошибок и оповещение | После 7.2 | По желанию; локальный callback без внешней почты | [7.B](../../educational-tasks.md#errors) | После исчерпания повторов есть сообщение с task_id и причиной | +| error_handling / 3 | logging | После 7.1 | Основное небольшое дополнение вместо механической замены всех print | [7.3](../../educational-tasks.md#errors) | Собственные сообщения видны в логах | +| Дополнительные / 1 | Пулы и слоты | Готовый resource_management_dag, пул из 2 слотов | Основной короткий эксперимент; резервное копирование заменено имитацией чтения | [8.1](../../educational-tasks.md#resources) | Задачи с весами 2 и 1 выполняются последовательно | +| Дополнительные / 2 | XCom и Jinja | После 8.1 | Основная метрика; шаблон Bash по желанию | [8.2, 8.A](../../educational-tasks.md#resources) | total_rows = 50, шаблон выводит то же значение | +| Дополнительные / 3 | TaskGroup и алертинг | После 8.2; для почты нужен SMTP | Разделить: группировка основная, внешняя доставка по желанию | [8.3, 8.B](../../educational-tasks.md#resources) | Группа раскрывается; XCom работает с полными task_id | Основной маршрут сохраняет изменения внутри блоков. Общие данные есть только в паре загрузка/DQ; изменение схемы вынесено после основных опытов, с обратным ALTER и возвратом списков колонок. Задания 3.B и 3.C явно зависят от дополнительного 3.A, но дальнейшая основная практика от этой ветки не зависит. Variables возвращаются к исходным значениям перед переходом дальше. diff --git a/docs/validation/2026-09-28-practice.md b/docs/validation/2026-09-28-practice.md index 0a96978..1327230 100644 --- a/docs/validation/2026-09-28-practice.md +++ b/docs/validation/2026-09-28-practice.md @@ -1,6 +1,6 @@ # Проверка обновленной практики -Дата: 2026-09-28. Код примеров: коммит `cf1b770`; задания и навигация: `d8dfe83`. Проверка выполнена авторским прогоном по [заданиям](../../airflow-docker/educational-tasks.md). Это проверка воспроизводимости инструкций; время и трудность прохождения на студентах не измерялись. +Дата: 2026-09-28. Код примеров: коммит `cf1b770`; задания и навигация: `d8dfe83`. Проверка выполнена авторским прогоном по [заданиям](../../educational-tasks.md). Это проверка воспроизводимости инструкций; время и трудность прохождения на студентах не измерялись. ## Окружение diff --git a/airflow-docker/educational-tasks.md b/educational-tasks.md similarity index 98% rename from airflow-docker/educational-tasks.md rename to educational-tasks.md index 78f0c69..b7c9437 100644 --- a/airflow-docker/educational-tasks.md +++ b/educational-tasks.md @@ -19,7 +19,7 @@ | [7. Ошибки и повторы](#errors) | `error_handling_dag.py` | Попытки, обработчики, итог запуска | Главы 6 и 9 | | [8. Пулы и группировка](#resources) | `resource_management_dag.py` | Слоты, XCom, TaskGroup | Глава 9 | -Главы доступны в [оглавлении учебника](../README.md). Инструкция запуска и параметры учебной БД находятся в [README стенда](README.md). +Главы доступны в [оглавлении учебника](README.md). Инструкция запуска и параметры учебной БД находятся в [README стенда](airflow-docker/README.md), файлы примеров - в [airflow-docker/dags/](airflow-docker/dags/). Откройте http://localhost:8080, войдите как `admin` / `admin`. Для выбранного DAG включите переключатель и нажмите Trigger DAG. Дождитесь завершения запуска, затем откройте его граф и логи задач. После правки Python-файла дождитесь обновления вкладки Code; новый запуск должен использовать новый код. Если DAG исчез или перестал обновляться, проверьте Import Errors и логи scheduler. @@ -420,7 +420,7 @@ ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'NEW'; По желанию. Сложность: высокая. Требуется 7.B: вернитесь к `error_handling_dag.py` с callback отказа. Нужен отдельно настроенный SMTP и адрес, на который вы можете отправить тестовое письмо. В стандартном стенде SMTP не настроен; отсутствие письма здесь не означает ошибку основного маршрута. -Используйте пример из [раздела об алертинге](../09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#система-оповещений-алертинг). Добавьте отправку письма в callback отказа. Укажите DAG, задачу, идентификатор запуска и причину ошибки. Для успешного DAG Run задайте отдельный `on_success_callback` у `DAG(...)`. +Используйте пример из [раздела об алертинге](09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#система-оповещений-алертинг). Добавьте отправку письма в callback отказа. Укажите DAG, задачу, идентификатор запуска и причину ошибки. Для успешного DAG Run задайте отдельный `on_success_callback` у `DAG(...)`. **Что должно получиться:** после постоянной ошибки приходит сообщение об отказе, после успешного запуска - об успехе. Уведомление не меняет результат обработки данных. Запуск с отказом сохраняет `failed`; ошибку доставки ищите в логах callback. Возвращение к основной практике не требует настройки SMTP: отключите почтовый callback и верните исходный режим ошибок.