Merge pull request 'refactor(make): корень — про стенд, генератор — за своей дверью' (#77) from refactor/65-make-two-doors into main
Reviewed-on: #77
This commit was merged in pull request #77.
This commit is contained in:
@@ -77,7 +77,10 @@ Airflow) и названия из кода. Если для понятия ес
|
|||||||
|
|
||||||
## Код и данные
|
## Код и данные
|
||||||
|
|
||||||
- Python — только через `uv`; проверка и формат — `ruff` (`make lint`).
|
- Python — только через `uv`; проверка и формат — `ruff`. Цель `make lint`
|
||||||
|
есть за двумя дверями и охватывает разное: в корне — код стенда, в
|
||||||
|
`generator/` — код генератора. Правишь даги или Superset — зови корневую,
|
||||||
|
правишь генератор — зови из `generator/`.
|
||||||
- Изменения держать минимальными и в границах задания.
|
- Изменения держать минимальными и в границах задания.
|
||||||
- Куда класть новую проверку, что утверждает каждая цель `make` и почему смоук
|
- Куда класть новую проверку, что утверждает каждая цель `make` и почему смоук
|
||||||
обязан оставаться быстрым — [`docs/architecture/testing.md`](docs/architecture/testing.md).
|
обязан оставаться быстрым — [`docs/architecture/testing.md`](docs/architecture/testing.md).
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ GENERATOR_DAY ?= 0
|
|||||||
GENERATOR_LIMIT ?=
|
GENERATOR_LIMIT ?=
|
||||||
GENERATOR_SPEED ?=
|
GENERATOR_SPEED ?=
|
||||||
|
|
||||||
.PHONY: up down clean ps logs generate-batch generate-live config-test lint typecheck test docs inventory smoke check-clickhouse check-services
|
# Цели корня — про стенд; проверки генератора — в `generator/Makefile`.
|
||||||
|
.PHONY: up down clean ps logs smoke check-clickhouse check-services config-test lint generate-batch generate-live
|
||||||
|
|
||||||
|
# --- Жизнь стенда ---
|
||||||
|
|
||||||
# Второй шаг: `--wait` дожидается служб, а приём событий асинхронный — довод
|
# Второй шаг: `--wait` дожидается служб, а приём событий асинхронный — довод
|
||||||
# целиком в шапке скрипта.
|
# целиком в шапке скрипта.
|
||||||
@@ -23,33 +26,7 @@ ps:
|
|||||||
logs:
|
logs:
|
||||||
$(COMPOSE) logs --follow
|
$(COMPOSE) logs --follow
|
||||||
|
|
||||||
generate-batch:
|
# --- Проверки, которым нужен поднятый стенд ---
|
||||||
$(COMPOSE) --profile generator run --rm generator batch --day "$(GENERATOR_DAY)" \
|
|
||||||
$(if $(GENERATOR_LIMIT),--limit "$(GENERATOR_LIMIT)")
|
|
||||||
|
|
||||||
generate-live:
|
|
||||||
$(COMPOSE) --profile generator run --rm generator live --day "$(GENERATOR_DAY)" \
|
|
||||||
$(if $(GENERATOR_SPEED),--speed "$(GENERATOR_SPEED)")
|
|
||||||
|
|
||||||
config-test:
|
|
||||||
COMPOSE_BIN="$(COMPOSE)" ./scripts/config-test.sh
|
|
||||||
|
|
||||||
lint:
|
|
||||||
cd generator && uv run ruff check && uv run ruff format --check
|
|
||||||
|
|
||||||
typecheck:
|
|
||||||
cd generator && uv run ty check
|
|
||||||
|
|
||||||
test:
|
|
||||||
cd generator && uv run pytest
|
|
||||||
|
|
||||||
docs:
|
|
||||||
cd generator && uv run python -m clickstream_generator.schema_doc \
|
|
||||||
../docs/formats/clickstream-event.md
|
|
||||||
|
|
||||||
inventory:
|
|
||||||
cd generator && uv run python -m clickstream_generator.inventory \
|
|
||||||
../data/world-inventory.json
|
|
||||||
|
|
||||||
smoke:
|
smoke:
|
||||||
COMPOSE_BIN="$(COMPOSE)" ./scripts/stand-smoke.sh
|
COMPOSE_BIN="$(COMPOSE)" ./scripts/stand-smoke.sh
|
||||||
@@ -59,3 +36,26 @@ check-clickhouse:
|
|||||||
|
|
||||||
check-services:
|
check-services:
|
||||||
COMPOSE_BIN="$(COMPOSE)" ./scripts/stand-services.sh
|
COMPOSE_BIN="$(COMPOSE)" ./scripts/stand-services.sh
|
||||||
|
|
||||||
|
# --- Проверки, которым стенд не нужен ---
|
||||||
|
|
||||||
|
config-test:
|
||||||
|
COMPOSE_BIN="$(COMPOSE)" ./scripts/config-test.sh
|
||||||
|
|
||||||
|
# Пути названы вслух: без них ruff из корня прошёлся бы и по генератору, а у
|
||||||
|
# того своя дверь и свой конфиг. Версия закреплена, потому что лока в корне
|
||||||
|
# нет, а форматтер между версиями меняет вывод — иначе проверка однажды
|
||||||
|
# покраснела бы сама, без единой правки в репозитории.
|
||||||
|
lint:
|
||||||
|
uvx ruff@0.16.1 check dags infra/superset
|
||||||
|
uvx ruff@0.16.1 format --check dags infra/superset
|
||||||
|
|
||||||
|
# --- Наполнение миром ---
|
||||||
|
|
||||||
|
generate-batch:
|
||||||
|
$(COMPOSE) --profile generator run --rm generator batch --day "$(GENERATOR_DAY)" \
|
||||||
|
$(if $(GENERATOR_LIMIT),--limit "$(GENERATOR_LIMIT)")
|
||||||
|
|
||||||
|
generate-live:
|
||||||
|
$(COMPOSE) --profile generator run --rm generator live --day "$(GENERATOR_DAY)" \
|
||||||
|
$(if $(GENERATOR_SPEED),--speed "$(GENERATOR_SPEED)")
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ Prometheus, Grafana и общая база Postgres для метаданных.
|
|||||||
`grep`, `sed`, `tail`, `sleep` и `timeout`. `jq` нужен и самому `make up`: им
|
`grep`, `sed`, `tail`, `sleep` и `timeout`. `jq` нужен и самому `make up`: им
|
||||||
читается опись мира, по которой он ждёт заливки. По умолчанию должны быть свободны
|
читается опись мира, по которой он ждёт заливки. По умолчанию должны быть свободны
|
||||||
порты `23000`, `28080`, `28088`, `28123`, `28124`, `29000`, `29001`, `29090`
|
порты `23000`, `28080`, `28088`, `28123`, `28124`, `29000`, `29001`, `29090`
|
||||||
и `29092`. Проверкам без стенда — `make config-test`, `make lint`,
|
и `29092`. Проверкам без стенда — `make config-test` и `make lint` в корне,
|
||||||
`make typecheck`, `make test` — и сборкам `make docs` и `make inventory` нужен
|
всем целям генератора в `generator/` — нужен `uv`.
|
||||||
`uv`.
|
|
||||||
|
|
||||||
Сначала скопируйте настройки стенда:
|
Сначала скопируйте настройки стенда:
|
||||||
|
|
||||||
@@ -140,11 +139,11 @@ ClickHouse, пока мир не доедет. Повторный `make up` за
|
|||||||
событий и хеш его байтов. Опись отвечает на единственный вопрос: тот ли это
|
событий и хеш его байтов. Опись отвечает на единственный вопрос: тот ли это
|
||||||
мир, что был вчера.
|
мир, что был вчера.
|
||||||
|
|
||||||
Спрашивают её двое. `make test` сверяет опись с тем, что собирается из кода
|
Спрашивают её двое. `make test` в `generator/` сверяет опись с тем, что
|
||||||
сегодня: правка генератора меняет мир, и опись надо пересобрать —
|
собирается из кода сегодня: правка генератора меняет мир, и опись надо
|
||||||
`make inventory`. `make check-clickhouse` сверяет с описью то, что доехало до
|
пересобрать — `make inventory` там же. `make check-clickhouse` в корне сверяет
|
||||||
`ods.event`, подневно. Хеш дня можно пересчитать и руками — это обычный
|
с описью то, что доехало до `ods.event`, подневно. Хеш дня можно пересчитать и
|
||||||
`sha256sum` файла, который пишет файловый приёмник:
|
руками — это обычный `sha256sum` файла, который пишет файловый приёмник:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run --project generator python -m clickstream_generator batch \
|
uv run --project generator python -m clickstream_generator batch \
|
||||||
@@ -326,6 +325,6 @@ Superset закреплён на 6.1.0; драйвер `clickhouse-connect`, ф
|
|||||||
- [docs/research/](docs/research/) — исследования; сейчас это формат
|
- [docs/research/](docs/research/) — исследования; сейчас это формат
|
||||||
кликстрима Яндекса, по которому строится модель события.
|
кликстрима Яндекса, по которому строится модель события.
|
||||||
- [docs/formats/](docs/formats/) — описания форматов источников: по ним
|
- [docs/formats/](docs/formats/) — описания форматов источников: по ним
|
||||||
пишется сторона хранилища. Собираются из кода командой `make docs`, руками
|
пишется сторона хранилища. Собираются из кода командой `make docs` в
|
||||||
не правятся.
|
`generator/`, руками не правятся.
|
||||||
- [AGENTS.md](AGENTS.md) — контракт работы в репозитории.
|
- [AGENTS.md](AGENTS.md) — контракт работы в репозитории.
|
||||||
|
|||||||
@@ -75,8 +75,7 @@ def _table_engines(client, source: str) -> list[tuple[str, str]]:
|
|||||||
|
|
||||||
def _drop_tables(client) -> None:
|
def _drop_tables(client) -> None:
|
||||||
client.command(
|
client.command(
|
||||||
f"DROP TABLE IF EXISTS default.{DISTRIBUTED_TABLE} "
|
f"DROP TABLE IF EXISTS default.{DISTRIBUTED_TABLE} ON CLUSTER {CLUSTER} SYNC"
|
||||||
f"ON CLUSTER {CLUSTER} SYNC"
|
|
||||||
)
|
)
|
||||||
client.command(
|
client.command(
|
||||||
f"DROP TABLE IF EXISTS default.{LOCAL_TABLE} ON CLUSTER {CLUSTER} SYNC"
|
f"DROP TABLE IF EXISTS default.{LOCAL_TABLE} ON CLUSTER {CLUSTER} SYNC"
|
||||||
@@ -97,7 +96,7 @@ def _assert_tables_absent(client) -> None:
|
|||||||
@dag(
|
@dag(
|
||||||
dag_id="test_clickhouse",
|
dag_id="test_clickhouse",
|
||||||
schedule=None,
|
schedule=None,
|
||||||
start_date=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
|
start_date=datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC),
|
||||||
catchup=False,
|
catchup=False,
|
||||||
tags=["проверка"],
|
tags=["проверка"],
|
||||||
doc_md=__doc__,
|
doc_md=__doc__,
|
||||||
@@ -178,9 +177,7 @@ def test_clickhouse():
|
|||||||
"""
|
"""
|
||||||
).result_rows
|
).result_rows
|
||||||
if len(node_2_rows) != 1:
|
if len(node_2_rows) != 1:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"не удалось определить имя ноды 2: {node_2_rows}")
|
||||||
f"не удалось определить имя ноды 2: {node_2_rows}"
|
|
||||||
)
|
|
||||||
distributed_rows = client.query(
|
distributed_rows = client.query(
|
||||||
f"""
|
f"""
|
||||||
SELECT _shard_num, hostName(), marker
|
SELECT _shard_num, hostName(), marker
|
||||||
|
|||||||
+2
-4
@@ -77,7 +77,7 @@ class RecordAddress(NamedTuple):
|
|||||||
@dag(
|
@dag(
|
||||||
dag_id="test_kafka",
|
dag_id="test_kafka",
|
||||||
schedule=None,
|
schedule=None,
|
||||||
start_date=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
|
start_date=datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC),
|
||||||
catchup=False,
|
catchup=False,
|
||||||
tags=["проверка"],
|
tags=["проверка"],
|
||||||
doc_md=__doc__,
|
doc_md=__doc__,
|
||||||
@@ -100,9 +100,7 @@ def test_kafka():
|
|||||||
if error is not None:
|
if error is not None:
|
||||||
delivery_errors.append(str(error))
|
delivery_errors.append(str(error))
|
||||||
else:
|
else:
|
||||||
addresses.append(
|
addresses.append(RecordAddress(message.partition(), message.offset()))
|
||||||
RecordAddress(message.partition(), message.offset())
|
|
||||||
)
|
|
||||||
|
|
||||||
producer = Producer(PRODUCER_CONFIG)
|
producer = Producer(PRODUCER_CONFIG)
|
||||||
# produce() не пишет, а ставит сообщение в очередь: о судьбе записи
|
# produce() не пишет, а ставит сообщение в очередь: о судьбе записи
|
||||||
|
|||||||
@@ -29,19 +29,47 @@
|
|||||||
|
|
||||||
## Карта целей
|
## Карта целей
|
||||||
|
|
||||||
Стенд нужен трём целям из семи. Цена — замер, см. «Что проверено»: `make test`,
|
Цели живут за двумя дверями. В корне — цели стенда; в `generator/` — цели
|
||||||
`make smoke`, `make check-clickhouse` и `make check-services` перемерены
|
генератора: он отдельный пакет со своим `pyproject.toml`, локом и образом, и
|
||||||
7 августа 2026 года на приёмке этапа 2; остальные три стоят с замера 6 августа.
|
спрашивают его отдельно.
|
||||||
|
|
||||||
| Цель | Что утверждает | Стенд | Цена |
|
Стенд нужен трём целям из восьми. Цена — замер, см. «Что проверено»:
|
||||||
|---|---|---|---|
|
`make test`, `make smoke`, `make check-clickhouse` и `make check-services`
|
||||||
| `make lint` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
перемерены 7 августа 2026 года на приёмке этапа 2, корневой `make lint` —
|
||||||
| `make typecheck` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
9 августа при его появлении; остальные стоят с замера 6 августа.
|
||||||
| `make test` | Генератор делает то, что обещает; схема события остаётся объявленным контрактом, а собранные из кода [описание выгрузки](../formats/clickstream-event.md) и [опись мира](../../data/world-inventory.json) — свежими | не нужен | 71 с |
|
|
||||||
| `make config-test` | Compose разбирается, файлы DAG синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
| Цель | Откуда | Что утверждает | Стенд | Цена |
|
||||||
| `make smoke` | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 9 с |
|
|---|---|---|---|---|
|
||||||
| `make check-clickhouse` | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, счёт событий стартового мира против описи | нужен | 8 с |
|
| `make config-test` | корень | Compose разбирается, файлы DAG синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
||||||
| `make check-services` | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 59 с |
|
| `make lint` | корень | Код стенда — `dags/` и `infra/superset/` — отформатирован и проходит ruff | не нужен | 0,4 с |
|
||||||
|
| `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 9 с |
|
||||||
|
| `make check-clickhouse` | корень | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, счёт событий стартового мира против описи | нужен | 8 с |
|
||||||
|
| `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 59 с |
|
||||||
|
| `make lint` | `generator/` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
||||||
|
| `make typecheck` | `generator/` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
||||||
|
| `make test` | `generator/` | Генератор делает то, что обещает; схема события остаётся объявленным контрактом, а собранные из кода [описание выгрузки](../formats/clickstream-event.md) и [опись мира](../../data/world-inventory.json) — свежими | не нужен | 71 с |
|
||||||
|
|
||||||
|
**Строк с именем `make lint` две, и это не опечатка.** Одноимённые цели за
|
||||||
|
разными дверями охватывают разное: корневая не видит ни одного файла
|
||||||
|
генератора, генераторная — ни одного файла стенда. Человек в корне видит
|
||||||
|
зелёное и может решить, что зелёный весь репозиторий.
|
||||||
|
|
||||||
|
Две тонкости, которых не видно из таблицы:
|
||||||
|
|
||||||
|
- **Версия ruff у корневой цели закреплена в самом вызове** (`uvx ruff@0.16.1`)
|
||||||
|
и равна той, что в `generator/uv.lock`. Равенство держится руками: обновили
|
||||||
|
лок — поправьте и вызов, сверять их некому.
|
||||||
|
- **Охват держат аргументы вызова, а не устройство дерева.** Python,
|
||||||
|
положенный вне двух названных в таблице путей, не проверит ни одна из дверей.
|
||||||
|
|
||||||
|
**Корневого `typecheck` нет, и это решение, а не пропуск.** Проверке типов мало
|
||||||
|
прочитать файл: чтобы понять `from airflow.sdk import dag`, ей нужен
|
||||||
|
установленный Airflow, а он живёт в образе, не на машине — 132 пакета ради двух
|
||||||
|
файлов пробников (замер 9 августа 2026 года, `uv pip compile`). Цена решения
|
||||||
|
называется честно: две настоящие находки `ty` в `dags/test_kafka.py` постоянного
|
||||||
|
сторожа не получают. Вопрос вернётся на этапе 5, когда придут настоящие даги:
|
||||||
|
тогда окружение окупится, и условие возврата — именно это, а не «стало
|
||||||
|
неудобно».
|
||||||
|
|
||||||
Цена самого `make up` — 2 м 50 с с нуля (`make clean` перед ним — ещё 23 с) и
|
Цена самого `make up` — 2 м 50 с с нуля (`make clean` перед ним — ещё 23 с) и
|
||||||
1 м 5 с на живом стенде. В цену целей она не входит, но записана здесь по той
|
1 м 5 с на живом стенде. В цену целей она не входит, но записана здесь по той
|
||||||
@@ -74,8 +102,9 @@ ClickHouse отвечает сразу.
|
|||||||
| PR или задача | плюс цели, которых правка касалась: DAG-и, Superset или Kafka — `check-services`; DDL, кластер или данные — `check-clickhouse` |
|
| PR или задача | плюс цели, которых правка касалась: DAG-и, Superset или Kafka — `check-services`; DDL, кластер или данные — `check-clickhouse` |
|
||||||
| Приёмка этапа | `make up` с нуля и все три цели на стенде |
|
| Приёмка этапа | `make up` с нуля и все три цели на стенде |
|
||||||
|
|
||||||
Правки генератора добавляют к этому `make lint`, `make typecheck` и
|
Правки генератора добавляют к этому `make lint`, `make typecheck` и `make test`
|
||||||
`make test`: стенд им не нужен, а `make test` из них самая дорогая.
|
из каталога `generator/`: стенд им не нужен, а `make test` из них самая
|
||||||
|
дорогая. Правки дагов и Superset — корневой `make lint`.
|
||||||
|
|
||||||
Тринадцать секунд из её семидесяти одной — пересборка восьми модельных дней
|
Тринадцать секунд из её семидесяти одной — пересборка восьми модельных дней
|
||||||
для сверки с описью мира. Дешевле хеши не сравнить: чтобы узнать, тот ли
|
для сверки с описью мира. Дешевле хеши не сравнить: чтобы узнать, тот ли
|
||||||
@@ -192,6 +221,15 @@ Kafka → STG → ODS, и живёт он в `make check-clickhouse`.
|
|||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
|
**Замер 9 августа 2026 года при появлении корневого `lint` (#65).** Три прогона
|
||||||
|
подряд — 0,39, 0,36 и 0,39 секунды на трёх файлах стенда; в таблице 0,4 с. На
|
||||||
|
машине с непрогретым кешем первый прогон дороже: `uvx` сначала скачивает ruff.
|
||||||
|
|
||||||
|
Тогда же снято, что двери не перекрываются: `ruff check --show-files` из корня
|
||||||
|
перечисляет ровно три файла стенда, из `generator/` — только файлы генератора.
|
||||||
|
Корневой конфиг правила генератору не подменяет: конфиг ищется вверх по дереву
|
||||||
|
от файла, и ближний выигрывает.
|
||||||
|
|
||||||
**Перезамер 7 августа 2026 года при исполнении #42.** Подъём стенда с нуля
|
**Перезамер 7 августа 2026 года при исполнении #42.** Подъём стенда с нуля
|
||||||
(`make clean && make up`) — 2 м 50 с, из них 22,5 с занимает сама заливка:
|
(`make clean && make up`) — 2 м 50 с, из них 22,5 с занимает сама заливка:
|
||||||
18,7 с генерация восьми дней и 3,8 с доставка 401 185 сообщений в Kafka.
|
18,7 с генерация восьми дней и 3,8 с доставка 401 185 сообщений в Kafka.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Документ собран из контракта схемы генератора
|
Документ собран из контракта схемы генератора
|
||||||
(`generator/src/clickstream_generator/schema.py`). Руками не править —
|
(`generator/src/clickstream_generator/schema.py`). Руками не править —
|
||||||
пересобрать: `make docs`.
|
пересобрать: `make docs` из каталога `generator/`.
|
||||||
|
|
||||||
Одно событие — одна строка: хит по образцу облачной выгрузки Яндекс Метрики.
|
Одно событие — одна строка: хит по образцу облачной выгрузки Яндекс Метрики.
|
||||||
Многозначное лежит в параллельных массивах, плюс одно сырое JSON-поле
|
Многозначное лежит в параллельных массивах, плюс одно сырое JSON-поле
|
||||||
|
|||||||
@@ -903,7 +903,8 @@
|
|||||||
`KubernetesPodOperator`. Побочно это единственный вариант, при котором
|
`KubernetesPodOperator`. Побочно это единственный вариант, при котором
|
||||||
«генератор — отдельная и заменяемая сущность» перестаёт быть словами: его
|
«генератор — отдельная и заменяемая сущность» перестаёт быть словами: его
|
||||||
зависимости не смешиваются с окружением Airflow. Хостовый `uv` остаётся для
|
зависимости не смешиваются с окружением Airflow. Хостовый `uv` остаётся для
|
||||||
`make test`, `make lint` и `make typecheck`. Цена названа и принимается:
|
целей генератора — `make test`, `make lint` и `make typecheck` из каталога
|
||||||
|
`generator/`. Цена названа и принимается:
|
||||||
чтобы даг запускал контейнер, в Airflow пробрасывается сокет докера, а это
|
чтобы даг запускал контейнер, в Airflow пробрасывается сокет докера, а это
|
||||||
доступ, равный root на хосте; на учебном стенде размен допустим, но
|
доступ, равный root на хосте; на учебном стенде размен допустим, но
|
||||||
записывается уроком — в бою так не делают. Отклонено: *генератор в образе
|
записывается уроком — в бою так не делают. Отклонено: *генератор в образе
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.PHONY: lint typecheck test docs inventory
|
||||||
|
|
||||||
|
# --- Проверки ---
|
||||||
|
|
||||||
|
lint:
|
||||||
|
uv run ruff check
|
||||||
|
uv run ruff format --check
|
||||||
|
|
||||||
|
typecheck:
|
||||||
|
uv run ty check
|
||||||
|
|
||||||
|
test:
|
||||||
|
uv run pytest
|
||||||
|
|
||||||
|
# --- Сборка из кода ---
|
||||||
|
|
||||||
|
docs:
|
||||||
|
uv run python -m clickstream_generator.schema_doc \
|
||||||
|
../docs/formats/clickstream-event.md
|
||||||
|
|
||||||
|
inventory:
|
||||||
|
uv run python -m clickstream_generator.inventory \
|
||||||
|
../data/world-inventory.json
|
||||||
+7
-2
@@ -92,12 +92,17 @@ D0 живёт предыстория, поэтому любой день соб
|
|||||||
|
|
||||||
Проиграть день — [быстрый старт](../README.md#как-позвать-генератор) и `--help`.
|
Проиграть день — [быстрый старт](../README.md#как-позвать-генератор) и `--help`.
|
||||||
|
|
||||||
Из корня репозитория:
|
Из этого каталога — цели генератора живут здесь, а не в корне:
|
||||||
|
|
||||||
- `make test` — тесты генератора;
|
- `make test` — тесты генератора;
|
||||||
- `make lint` — ruff: проверка и формат;
|
- `make lint` — ruff: проверка и формат;
|
||||||
- `make typecheck` — ty: проверка типов;
|
- `make typecheck` — ty: проверка типов;
|
||||||
- `make docs` — пересобрать описание выгрузки.
|
- `make docs` — пересобрать описание выгрузки;
|
||||||
|
- `make inventory` — пересобрать опись мира.
|
||||||
|
|
||||||
|
В корне репозитория `make lint` тоже есть, но он про код стенда — даги и
|
||||||
|
Superset. Одноимённые цели за разными дверями охватывают разное:
|
||||||
|
[карта проверок](../docs/architecture/testing.md).
|
||||||
|
|
||||||
Python и зависимости — через `uv`, версии закреплены в `uv.lock`: на этом
|
Python и зависимости — через `uv`, версии закреплены в `uv.lock`: на этом
|
||||||
держится обещание побайтовой воспроизводимости (спека, раздел 2).
|
держится обещание побайтовой воспроизводимости (спека, раздел 2).
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
пересчитывается он и обычным `sha256sum` по сыгранному в файл дню (как
|
пересчитывается он и обычным `sha256sum` по сыгранному в файл дню (как
|
||||||
именно — в README репозитория).
|
именно — в README репозитория).
|
||||||
|
|
||||||
Собирается опись из корня репозитория целью `make inventory`, а свежесть её
|
Собирается опись из каталога генератора целью `make inventory`, а свежесть её
|
||||||
сторожит тест — как и у «описания выгрузки».
|
сторожит тест — как и у «описания выгрузки».
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
содержание берётся из контракта (`schema`), правится только там; свежесть
|
содержание берётся из контракта (`schema`), правится только там; свежесть
|
||||||
документа сторожит тест.
|
документа сторожит тест.
|
||||||
|
|
||||||
Запуск — из корня репозитория целью `make docs`.
|
Запуск — из каталога генератора целью `make docs`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -18,7 +18,7 @@ PREAMBLE = """# Описание выгрузки: событие кликстр
|
|||||||
|
|
||||||
Документ собран из контракта схемы генератора
|
Документ собран из контракта схемы генератора
|
||||||
(`generator/src/clickstream_generator/schema.py`). Руками не править —
|
(`generator/src/clickstream_generator/schema.py`). Руками не править —
|
||||||
пересобрать: `make docs`.
|
пересобрать: `make docs` из каталога `generator/`.
|
||||||
|
|
||||||
Одно событие — одна строка: хит по образцу облачной выгрузки Яндекс Метрики.
|
Одно событие — одна строка: хит по образцу облачной выгрузки Яндекс Метрики.
|
||||||
Многозначное лежит в параллельных массивах, плюс одно сырое JSON-поле
|
Многозначное лежит в параллельных массивах, плюс одно сырое JSON-поле
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Настройка ruff для кода стенда — дагов и Superset. У генератора своя,
|
||||||
|
# в `generator/pyproject.toml`: конфиг ruff ищется вверх по дереву от файла,
|
||||||
|
# поэтому ближний к генератору выигрывает и этот его не подменяет.
|
||||||
|
#
|
||||||
|
# Отдельный файл, а не `pyproject.toml`: пакета в корне нет и заводить его
|
||||||
|
# ради настройки линтера незачем.
|
||||||
|
|
||||||
|
# На каком Python побежит код, ruff сам не знает: у генератора он берёт ответ
|
||||||
|
# из `requires-python`, а у стенда спрашивать некого — даги и Superset живут
|
||||||
|
# внутри образов. Стоит версия дагов: их читают и правят, и идиомы им нужны
|
||||||
|
# те, на которых они работают. Замер 9 августа 2026 года:
|
||||||
|
# `apache/airflow:3.3.0` несёт Python 3.13.14, `apache/superset:6.1.0` — 3.10.20.
|
||||||
|
# Настройке Superset от этого пока ни холодно ни жарко: модернизировать в ней
|
||||||
|
# нечего. Предложит ruff что-то не по её возрасту — заведём исключение тогда,
|
||||||
|
# с настоящим поводом.
|
||||||
|
target-version = "py313"
|
||||||
|
|
||||||
|
[lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B"]
|
||||||
Reference in New Issue
Block a user