Результат прогона линтера

This commit is contained in:
2025-10-18 23:56:37 +03:00
parent b2f40e4dd2
commit f1e01274de
5 changed files with 91 additions and 72 deletions
+34 -27
View File
@@ -8,10 +8,11 @@ from pathlib import Path
from typing import List
import pandas as pd
from airflow import DAG
from airflow.operators.python import PythonOperator
from helpers.greenplum import get_gp_conn
from airflow import DAG
CSV_DIR = Path(os.getenv("CSV_DIR", "/opt/airflow/data"))
CSV_ROWS = int(os.getenv("CSV_ROWS", "1000"))
@@ -43,29 +44,27 @@ def _generate_csv(rows: int, csv_dir: Path) -> str:
base_order_id = int(datetime.utcnow().timestamp() * 1_000)
# Создаём DataFrame с использованием pandas методов
df = pd.DataFrame({
# Уникальные order_id начиная с базового значения
"order_id": pd.Series(range(base_order_id, base_order_id + rows), dtype="int64"),
# Временные метки с интервалом в 1 секунду в обратном порядке
"order_ts": pd.date_range(
end=datetime.utcnow(),
periods=rows,
freq="1S"
).sort_values(ascending=False),
# Случайные customer_id от 1 до 1000
"customer_id": pd.Series(
random.choices(range(1, 1001), k=rows),
dtype="int64"
),
# Случайные суммы от 10 до 500 с округлением до 2 знаков
"amount": pd.Series(
[round(random.uniform(10, 500), 2) for _ in range(rows)],
dtype="float64"
)
})
df = pd.DataFrame(
{
# Уникальные order_id начиная с базового значения
"order_id": pd.Series(
range(base_order_id, base_order_id + rows), dtype="int64"
),
# Временные метки с интервалом в 1 секунду в обратном порядке
"order_ts": pd.date_range(
end=datetime.utcnow(), periods=rows, freq="1S"
).sort_values(ascending=False),
# Случайные customer_id от 1 до 1000
"customer_id": pd.Series(
random.choices(range(1, 1001), k=rows), dtype="int64"
),
# Случайные суммы от 10 до 500 с округлением до 2 знаков
"amount": pd.Series(
[round(random.uniform(10, 500), 2) for _ in range(rows)],
dtype="float64",
),
}
)
# Сохраняем CSV без индекса
df.to_csv(csv_path, index=False)
@@ -77,7 +76,9 @@ def _preview_csv(csv_path: str, sample_rows: int = 5) -> None:
"""Отображает предпросмотр CSV через pandas (head и describe)."""
df = pd.read_csv(csv_path)
df["order_ts"] = pd.to_datetime(df["order_ts"], errors="coerce")
logging.info("Первые %s строк:\n%s", sample_rows, df.head(sample_rows).to_string(index=False))
logging.info(
"Первые %s строк:\n%s", sample_rows, df.head(sample_rows).to_string(index=False)
)
numeric_summary = df.describe(include="number")
logging.info("Числовая статистика:\n%s", numeric_summary.to_string())
if df["order_ts"].notna().any():
@@ -94,8 +95,14 @@ def _load_csv(csv_path: str) -> None:
if not csv_file.exists():
raise FileNotFoundError(f"CSV не найден: {csv_file}")
with get_gp_conn() as conn, conn.cursor() as cur, csv_file.open("r", encoding="utf-8") as f:
cur.execute("CREATE TEMP TABLE tmp_orders (LIKE public.orders INCLUDING DEFAULTS) ON COMMIT DROP;")
with (
get_gp_conn() as conn,
conn.cursor() as cur,
csv_file.open("r", encoding="utf-8") as f,
):
cur.execute(
"CREATE TEMP TABLE tmp_orders (LIKE public.orders INCLUDING DEFAULTS) ON COMMIT DROP;"
)
cur.copy_expert(
"COPY tmp_orders (order_id, order_ts, customer_id, amount) FROM STDIN WITH CSV HEADER",
f,
+5 -8
View File
@@ -3,16 +3,13 @@ from __future__ import annotations
import logging
from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.python import PythonOperator
from helpers.greenplum import (assert_orders_have_rows,
assert_orders_no_duplicates,
assert_orders_schema,
assert_orders_table_exists, get_gp_conn)
from helpers.greenplum import (
assert_orders_have_rows,
assert_orders_no_duplicates,
assert_orders_schema,
assert_orders_table_exists,
get_gp_conn,
)
from airflow import DAG
def _run_check(check_callable):
+20 -6
View File
@@ -9,7 +9,11 @@ import psycopg2
# Настройки для подключения к Greenplum. По умолчанию используем Airflow Connection,
# но при проблемах можно переключиться на ENV-подключение, установив GP_USE_AIRFLOW_CONN=false.
GP_CONN_ID = os.getenv("GP_CONN_ID", "greenplum_conn")
GP_USE_AIRFLOW_CONN = os.getenv("GP_USE_AIRFLOW_CONN", "true").lower() in ("1", "true", "yes")
GP_USE_AIRFLOW_CONN = os.getenv("GP_USE_AIRFLOW_CONN", "true").lower() in (
"1",
"true",
"yes",
)
# Ожидаемая схема таблицы orders для проверки качества данных
EXPECTED_ORDERS_SCHEMA: List[Tuple[str, str]] = [
@@ -52,7 +56,9 @@ def get_gp_conn():
"host": os.getenv("GP_HOST", "greenplum"),
"port": int(os.getenv("GP_PORT", "5432")),
}
logging.info("🔗 Подключение к Greenplum: %s:%s", conn_params["host"], conn_params["port"])
logging.info(
"🔗 Подключение к Greenplum: %s:%s", conn_params["host"], conn_params["port"]
)
return psycopg2.connect(**conn_params)
@@ -76,7 +82,9 @@ def assert_orders_table_exists(conn) -> None:
"""
)
if cur.fetchone() is None:
raise ValueError("❌ Таблица public.orders не найдена; запусти DAG csv_to_greenplum.")
raise ValueError(
"❌ Таблица public.orders не найдена; запусти DAG csv_to_greenplum."
)
logging.info("✅ Таблица public.orders существует")
@@ -118,7 +126,9 @@ def assert_orders_schema(conn) -> None:
logging.info("📊 Ожидаемая схема: %s", EXPECTED_ORDERS_SCHEMA)
if list(schema) != EXPECTED_ORDERS_SCHEMA:
raise ValueError(f"❌ Неожиданная схема orders: {schema}. Ожидали {EXPECTED_ORDERS_SCHEMA}.")
raise ValueError(
f"❌ Неожиданная схема orders: {schema}. Ожидали {EXPECTED_ORDERS_SCHEMA}."
)
logging.info("✅ Схема таблицы orders соответствует ожиданиям")
@@ -152,7 +162,9 @@ def assert_orders_have_rows(conn) -> None:
logging.info("📈 Количество строк в orders: %s", row_count)
if row_count <= 0:
raise ValueError("❌ Таблица public.orders пустая — запусти DAG csv_to_greenplum перед проверкой.")
raise ValueError(
"❌ Таблица public.orders пустая — запусти DAG csv_to_greenplum перед проверкой."
)
logging.info("✅ Таблица orders содержит данные (%s строк)", row_count)
@@ -195,5 +207,7 @@ def assert_orders_no_duplicates(conn) -> None:
logging.info("📊 Найдено дубликатов: %s", duplicates)
if duplicates:
raise ValueError(f"❌ Обнаружены дубли по order_id ({duplicates} шт.) — проверь загрузку данных.")
raise ValueError(
f"❌ Обнаружены дубли по order_id ({duplicates} шт.) — проверь загрузку данных."
)
logging.info("✅ Дубликаты не обнаружены")
-1
View File
@@ -6,7 +6,6 @@ from pathlib import Path
from types import ModuleType
from typing import Type
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.append(str(PROJECT_ROOT))
+3 -1
View File
@@ -14,7 +14,9 @@ def _airflow_available() -> bool:
return hasattr(af, "DAG")
pytestmark = pytest.mark.skipif(not _airflow_available(), reason="Airflow is not installed for DAG smoke tests")
pytestmark = pytest.mark.skipif(
not _airflow_available(), reason="Airflow is not installed for DAG smoke tests"
)
def _load_dag(module_name: str):