feat: Textual TUI + реструктуризація пакету olap_tool/ - #5
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Об'єднує ClickHouseSink з sinks.py та всю допоміжну логіку з clickhouse_export.py в один модуль olap_tool/sinks/clickhouse.py. Імпортує AnalyticsSink та sanitize_df з .base, ClickHouseConfig з ..config. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…it__.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Старі плоскі файли замінені пакетом sinks/. Імпорт у import_xlsx_to_clickhouse.py оновлено на olap_tool.sinks.clickhouse. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Переміщено плоскі модулі в тематичні субпакети: - core/: config, cli, runner, periods, profiles, scheduler, compression, progress, utils - connection/: connection, auth, security, prompt - data/: queries, exporter Оновлено крос-пакетні імпорти та sinks/*.py (..config → ..core.config, ..utils → ..core.utils). Оновлено olap_tool/__init__.py і кореневий olap.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Замінює два окремі скрипти (import_xlsx_to_clickhouse.py, import_xlsx_to_duckdb.py) єдиним scripts/import_xlsx.py з аргументом --target ch|duck|pg. ClickHouse використовує thread-local sinks, DuckDB і PostgreSQL — один спільний sink на весь запуск. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ити olap.py - Додано textual>=0.70.0 до requirements.txt (виправлено також злиплий рядок rich+pyarrow) - TUIStream у utils.py: перехоплює stdout, очищує ANSI, пише у RichLog потокобезпечно - olap_tool/tui/: OlapApp + MainMenuScreen + OlapExportScreen + XlsxImportScreen - olap.py: без аргументів → TUI, з аргументами → CLI (sys.exit(main())) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TUIStream.flush() тепер дренує _buf замість no-op, щоб не втрачати останній рядок - PostgreSQLSink у scripts/import_xlsx.py переведено на thread-local патерн (_pg_local/_get_pg_sink/_process_pg), аналогічно ClickHouse; оновлено коментарі - _list_profiles() прив'язано до Path(__file__) замість CWD, щоб profiles/ знаходились незалежно від робочої директорії Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ля reconfigure, assert для cfg narrowing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Claude finished @starychenko's task in 4m 36s —— View job Огляд PR #5 — feat: Textual TUI + реструктуризація пакету olap_tool/
Вердикт: ❌ Request ChangesPR реструктурує пакет коректно і TUI виглядає логічно, але є один критичний баг у thread-local ініціалізації sink-ів у [BLOCKER] Thread-local CH/PG sink-и ніколи не викликають
|
- BLOCKER: thread-local CH/PG sinks тепер викликають setup() при створенні (зберігаємо _ch_setup_df / _pg_setup_df під час ініціалізації) - asyncio.get_event_loop() → get_running_loop() в TUI screens - query_one() виноситься на головний потік, передається як аргумент - DuckDBSink._refresh_schema(): try/except з інформативним RuntimeError - _duck_value() видалено (мертвий код — вставка через Parquet, не VALUES) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @starychenko's task in 3m 8s —— View job Повторний огляд PR #5 — всі виправлення перевірено
Вердикт: ✅ ApproveВсі 5 проблем з попереднього ревʼю усунуто. Один залишковий edge-case задокументовано нижче — не є blocker-ом. Статус виправлень
[IMPORTANT] Залишковий edge-case: перший файл порожній → thread-local CH sink ініціалізується без
|
Шукаємо перший непорожній файл для ініціалізації sink замість завжди брати files[0]. Без цього thread-local CH/PG sinks не отримували б setup_df і _client залишався None → тихий AttributeError у воркер-потоці. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @starychenko's task in 1m 5s —— View job Claude finished @starychenko's task —— View job Перевірка edge-case PR #5 — перший XLSX порожній
Вердикт: ✅ ApproveВиправлення edge-case коректне та повне. Аналіз виправлення (
|
| Сценарій | Поведінка |
|---|---|
| Перший файл порожній, решта — ні | init_file_idx вказує на перший непорожній; _ch_setup_df встановлюється коректно ✓ |
| Перші N файлів порожні | Те ж саме — цикл знаходить перший непорожній ✓ |
| Всі файли порожні | _ch_setup_df = None, але воркери повертають достроково (рядок 181–182: if df.empty: return 0, True, ...), тому _get_ch_sink() ніколи не викликається → без краша ✓ |
[NIT] Файл, знайдений під час init-циклу, буде прочитаний вдруге у воркер-потоці. Для типового сценарію (мало порожніх файлів) це незначно, але варто знати при великій кількості порожніх файлів на початку.
Загальний статус всіх виправлень
| Проблема | Статус |
|---|---|
[BLOCKER] Thread-local CH/PG sink без setup() → AttributeError |
✅ Виправлено |
[IMPORTANT] asyncio.get_event_loop() deprecated |
✅ Виправлено |
[IMPORTANT] query_one() з executor thread |
✅ Виправлено |
[IMPORTANT] DuckDBSink._refresh_schema() без захисту |
✅ Виправлено |
[NIT] _duck_value() — мертвий код |
✅ Видалено |
| [IMPORTANT] Edge-case: перший XLSX порожній | ✅ Виправлено |
|
Claude finished @starychenko's task in 1m 9s —— View job Claude finished @starychenko's task —— Перевірка нового коміту PR #5
Вердикт: ✅ ApproveНовий коміт Аналіз коміту
# Було:
def _get_ch_sink(cfg_kwargs: dict) -> "ClickHouseSink":
def _get_pg_sink(cfg_kwargs: dict) -> "PostgreSQLSink":
# Стало:
def _get_ch_sink(cfg_kwargs: dict):
def _get_pg_sink(cfg_kwargs: dict):Виправлення коректне. Загальний статус всіх виправлень PR #5
PR готовий до мержу. |
Textual Select очікує (label, value), а не (value, label). Некоректний порядок призводив до InvalidSelectValueError при запуску. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @starychenko's task in 55s —— View job Claude finished @starychenko's task —— View job Перевірка нового коміту PR #5
Вердикт: ✅ ApproveНовий коміт Аналіз коміту
# Було (неправильно — Textual отримував value там де очікував label):
FORMAT_OPTIONS = [
("xlsx", "XLSX"), # (value, label) ← InvalidSelectValueError
...
]
# Стало (правильно):
FORMAT_OPTIONS = [
("XLSX", "xlsx"), # (label, value) ← відповідає Textual API
...
]Виправлення коректне — Textual Охоплені всі три константи ( Загальний статус всіх виправлень PR #5
PR готовий до мержу. |
…ls, alongside connection authentication and prompting utilities.
|
Claude finished @starychenko's task in 3m 13s —— View job Огляд нового коміту PR #5
Вердикт: ❌ Request ChangesКоміт [IMPORTANT]
|
| Проблема | Статус |
|---|---|
[BLOCKER] Thread-local CH/PG sink без setup() |
✅ Виправлено |
[IMPORTANT] asyncio.get_event_loop() deprecated |
✅ Виправлено |
[IMPORTANT] query_one() з executor thread |
✅ Виправлено |
[IMPORTANT] DuckDBSink._refresh_schema() без захисту |
✅ Виправлено |
[NIT] _duck_value() — мертвий код |
✅ Видалено |
| [IMPORTANT] Edge-case: перший XLSX порожній | ✅ Виправлено |
| [NIT] Невалідні return type annotations | ✅ Виправлено |
[BLOCKER] InvalidSelectValueError у TUI Select |
✅ Виправлено |
[IMPORTANT] event.wait() без таймауту → зависання |
❌ Потребує виправлення |
[IMPORTANT] Дубльована TUI-логіка у auth.py |
❌ Потребує виправлення |
…CSV/XLSX streaming, connection management, and progress display.
|
Claude finished @starychenko's task in 3m 23s —— View job Claude finished @starychenko's task in 3m 13s —— View job Огляд коміту
|
| Проблема | Статус |
|---|---|
[BLOCKER] Thread-local CH/PG sink без setup() |
✅ Виправлено |
[IMPORTANT] asyncio.get_event_loop() deprecated |
✅ Виправлено |
[IMPORTANT] query_one() з executor thread |
✅ Виправлено |
[IMPORTANT] DuckDBSink._refresh_schema() без захисту |
✅ Виправлено |
[NIT] _duck_value() — мертвий код |
✅ Видалено |
| [IMPORTANT] Edge-case: перший XLSX порожній | ✅ Виправлено |
| [NIT] Невалідні return type annotations | ✅ Виправлено |
[BLOCKER] InvalidSelectValueError у TUI Select |
✅ Виправлено |
[NIT] Enter у формі CredentialsDialog не підтверджує |
✅ Виправлено |
[IMPORTANT] event.wait() без таймауту → зависання |
❌ Не виправлено |
[IMPORTANT] Дубльована TUI-логіка у auth.py |
❌ Не виправлено |
[IMPORTANT] XlsxStreamWriter не закривається при винятку |
❌ Нова проблема |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… для quoting у exporter.py
… руйнує XmlReader стан
|
Claude finished @starychenko's task in 2m 13s —— View job Огляд нових комітів PR #5
Вердикт: ❌ Request ChangesКоміти Статус виправлень з попереднього ревʼю
[IMPORTANT]
|
| Проблема | Статус |
|---|---|
[BLOCKER] Thread-local CH/PG sink без setup() |
✅ Виправлено |
[IMPORTANT] asyncio.get_event_loop() deprecated |
✅ Усунуто (Textual видалено) |
[IMPORTANT] query_one() з executor thread |
✅ Усунуто (Textual видалено) |
[IMPORTANT] DuckDBSink._refresh_schema() без захисту |
✅ Виправлено |
[NIT] _duck_value() — мертвий код |
✅ Видалено |
| [IMPORTANT] Edge-case: перший XLSX порожній | ✅ Виправлено |
| [NIT] Невалідні return type annotations | ✅ Виправлено |
[BLOCKER] InvalidSelectValueError у TUI Select |
✅ Усунуто (Textual видалено) |
[NIT] Enter у формі CredentialsDialog не підтверджує |
✅ Усунуто (Textual видалено) |
[IMPORTANT] event.wait() без таймауту → зависання |
✅ Усунуто (Textual видалено) |
[IMPORTANT] Дубльована TUI-логіка у auth.py |
✅ Усунуто (Textual видалено) |
[IMPORTANT] XlsxStreamWriter не закривається при винятку |
❌ Не виправлено |
Summary
olap_tool/розбито на підпакетиcore/,connection/,data/,sinks/— чіткі межі відповідальності, оновлені всі відносні імпортиpython olap.pyбез аргументів запускає інтерактивне меню з екранами для OLAP-експорту та XLSX-імпорту; прогрес виводиться прямо у TUI черезTUIStreamimport_xlsx_to_clickhouse.py+import_xlsx_to_duckdb.py→scripts/import_xlsx.py --target ch|duck|pg; ClickHouse та PostgreSQL — thread-local sinksChanges
Нові файли
olap_tool/sinks/base.py—AnalyticsSinkABC +sanitize_df()olap_tool/sinks/clickhouse.py— поглинаєclickhouse_export.pyolap_tool/sinks/duckdb.py,postgresql.pyolap_tool/core/,olap_tool/connection/,olap_tool/data/— переміщені модуліolap_tool/tui/app.py+screens/main_menu.py,olap_export.py,xlsx_import.pyscripts/import_xlsx.pyВидалені файли
olap_tool/sinks.py,olap_tool/clickhouse_export.py(поглинуті)import_xlsx_to_clickhouse.py,import_xlsx_to_duckdb.py(замінені)olap_tool/*.py(переміщені в підпакети)Test Plan
python -c "from olap_tool import main; from olap_tool.tui.app import OlapApp; print('OK')"— всі імпортиpython olap.py --help— CLI режим без змінpython olap.py— запускається Textual TUI (q для виходу)python scripts/import_xlsx.py --help— виводить usage з--target ch|duck|pg🤖 Generated with Claude Code