Рефакторинг рендера [этап 4/6]: группировка без deepcopy и мутаций, фикс naive/aware краша #31
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Часть эпика «Рефакторинг пайплайна рендера». Спека:
plans/2026-07-05-render-pipeline-refactor.md(v5), раздел «Этап 4». Зависимость: этап 3 (#30). Независим от этапа 5 — можно параллельно. Line-refs действительны дляf9550d8.Цель
Убрать
copy.deepcopyвсех сообщений на каждый запрос фида (rss_generator.py:43) и мутациюmedia_group_id: тайм-кластеризация становится чистой функцией, возвращающей маппингmsg.id → effective_group_id. Попутно чинится ЖИВОЙ прод-баг: kurigram-даты naive-local, фолбэки — aware UTC → один None-date пост в фиде с реальными датами = TypeError → 500 при ЛЮБОМtime_based_merge(сорт-ключ групп, rss_generator.py:141).Пункты реестра §3
media_group_idпродолжает действовать). Было: naive+None (прод) — краш; полностью-None и aware+None (только aware-моки тестов) — выживали и кластеризовали None-хвост по порядку вставки. Все эти поведения заменяются, новое закрепляется тестами как сознательное.+infв сортировке ГРУПП — детерминированно переживают[:limit]-срез как новейшие; в ИТОГОВОМ выводе позиция не меняется — финальная сортировка постов (фолбэк0.0, branch:319–320) ставит их в конец фида.Новая функция
Правки
_create_messages_groups(messages, group_ids=None):effective = (group_ids or {}).get(message.id, message.media_group_id); sort-ключ групп — timestamp-based, фолбэк None-date группfloat('inf'); финальная сортировка постов в_render_messages_groups(фолбэк0.0) НЕ меняется._render_pipelineприtime_based_merge: маппинг + пред-сортировка входа date-ASC тем же timestamp-ключом (фолбэк+inf— None-date в конце). Стабильный sorted на fetch-order входе воспроизводит порядок старогоmessages_sorted, включая ties (старый sorted работал на deepcopy того же списка, мутации — после сортировки).sorted()не мутирует вход — deepcopy не нужен._create_time_based_media_groupsи импортcopy; поправить модульный импорт в test_stage4_eventloop.py:34–42 (API relocation).pubDate = datetime.now()(branch:503–507) — недетерминированно, новую нормализацию не вводим; RSS-семантика закрепляется unit-тестом (entry сгенерирован, pubDate присутствует), вне golden.Тесты (
tests/test_group_ids.py, даты NAIVE)Time-кластер без id → синтетический
time_...; усыновление truthy id с backfill и перезаписью чужого id; falsy id (0,"") игнорируется; одиночка без entry; None-date: нет entry в маппинге, но собственныйmedia_group_idдействует (медиагруппа с None-датами собирается); смешанный naive+None вход НЕ падает; полностью-None вход — новое поведение закреплено явно; None-date группы переживают[:limit]-срез, в выводе — в конце фида; вход не мутирован (сравнение объектов «кэша» до/после); ties при равных датах → порядок старого кода; кластеризация через DST-fold → как старый код (naive-вычитание).DoD
copy.deepcopyотсутствует в rss_generator; pytest + golden зелёные (golden-дифф — только добавленная None-date фикстура в HTML-golden).Общие правила эпика
Ветка
refactor/render-pipelineот f9550d8; один этап = один коммит; комментарии — English; точечные правки.