Skip to content

Commit a305260

Browse files
author
Кирилл Горелов
committed
Add insert_tools project
0 parents  commit a305260

24 files changed

Lines changed: 964 additions & 0 deletions

insert_tools/.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Byte-compiled / cache
2+
__pycache__/
3+
*.py[cod]
4+
*.pyo
5+
*.pyd
6+
*.pyc
7+
*.so
8+
9+
# Logs
10+
*.log
11+
insert.log
12+
13+
# Virtual environment
14+
.env
15+
.env.*
16+
.venv/
17+
venv/
18+
19+
# Test & coverage
20+
htmlcov/
21+
.tox/
22+
.coverage
23+
coverage.xml
24+
pytest_cache/
25+
26+
# Packaging
27+
dist/
28+
build/
29+
*.egg-info/
30+
31+
# Docker volumes / data
32+
clickhouse_data/
33+
34+
# IDE/editor configs
35+
*.idea/
36+
.vscode/
37+
*.swp
38+
39+
# System files
40+
.DS_Store
41+
Thumbs.db

insert_tools/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Your Name
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

insert_tools/Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.PHONY: up down logs test format lint typecheck check
2+
3+
# Docker
4+
up:
5+
docker-compose up -d
6+
7+
down:
8+
docker-compose down
9+
10+
logs:
11+
docker-compose logs -f clickhouse
12+
13+
# Тесты
14+
test:
15+
pytest -v --tb=short tests/
16+
17+
# Линтеры и типизация
18+
lint:
19+
flake8 clickhouse_insert
20+
21+
format:
22+
black clickhouse_insert
23+
24+
typecheck:
25+
mypy clickhouse_insert
26+
27+
# Быстрая проверка всего
28+
check: lint typecheck test

insert_tools/README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# 🚀 Insert Tools
2+
3+
**Проблема:**
4+
5+
Вы сталкивались с проблемами при вставке данных в базы данных? Постоянные ошибки соответствия схем, неправильные типы данных, потеря времени на ручные проверки и риск случайного повреждения данных — всё это знакомо каждому, кто регулярно работает с большими ETL-процессами и базами данных.
6+
7+
**Решение:**
8+
9+
Insert Tools — это мощный и гибко настраиваемый инструмент, специально разработанный для безопасной и быстрой вставки данных в различные базы данных, начиная с ClickHouse. Он автоматически проверяет соответствие схем по названиям столбцов, выполняет приведение типов данных и позволяет проводить предварительную проверку в режиме dry-run, полностью исключая ошибки перед реальной вставкой данных. Особенно полезен инструмент для крупных ETL-процессов, где важно, чтобы обновления схемы целевой таблицы не приводили к падениям системы.
10+
11+
## 🔥 Почему это стоит попробовать:
12+
13+
-**Безопасность данных:** Автоматическая проверка схемы данных по именам столбцов перед вставкой.
14+
- ⚙️ **Автоприведение типов:** Легко преобразует данные к нужному типу без вашего вмешательства.
15+
- 🚧 **Dry-run режим:** Тестируйте вставку без рисков.
16+
- 🐳 **Docker-ready:** Простое развертывание и интеграционные тесты в Docker.
17+
- 🔧 **Гибкая настройка:** Полностью контролируемый процесс вставки под ваши задачи.
18+
- 🔥 **Экономия времени:** Забудьте о ручных проверках и ускорьте ваш рабочий процесс.
19+
20+
## 🎯 Ключевые возможности:
21+
22+
- 🖥️ Удобный CLI и Python API.
23+
- 🛡️ Поддержка строгого режима (strict mode) для полного контроля.
24+
- 📌 Детализированные логи и расширенный вывод для диагностики.
25+
- 🔄 Поддержка интеграции с CI/CD процессами.
26+
27+
## 📦 Быстрая установка:
28+
29+
```bash
30+
pip install insert-tools
31+
```
32+
33+
или для разработки:
34+
35+
```bash
36+
pip install -e .[dev]
37+
```
38+
39+
## 🚀 Запуск и примеры:
40+
41+
Использование через CLI:
42+
43+
```bash
44+
insert-tools \
45+
--host localhost \
46+
--port 8123 \
47+
--user default \
48+
--password admin123 \
49+
--database default \
50+
--target_table my_table \
51+
--select_sql "SELECT * FROM source_table" \
52+
--allow_type_cast \
53+
--strict \
54+
--dry-run \
55+
--verbose
56+
```
57+
58+
Использование через Python:
59+
60+
```python
61+
from insert_tools.runner import InsertConfig, run_insert
62+
63+
config = InsertConfig(
64+
host="localhost",
65+
database="default",
66+
target_table="my_table",
67+
select_sql="SELECT * FROM source_table",
68+
user="default",
69+
password="admin123",
70+
allow_type_cast=True,
71+
strict_column_match=True
72+
)
73+
74+
run_insert(config)
75+
```
76+
77+
## 🧪 Тестирование и интеграция:
78+
79+
```bash
80+
pytest -v --cov=insert_tools tests/
81+
```
82+
83+
Поддержка интеграционных тестов через Docker (см. `docker-compose.yml`).
84+
85+
## 📈 Планы на развитие:
86+
87+
В ближайших планах — поддержка дополнительных популярных баз данных, что сделает Insert Tools универсальным решением для управления ETL-процессами в вашей организации.
88+
89+
## 🤝 Вклад и поддержка проекта:
90+
91+
Ваши идеи, баг-репорты и pull-реквесты всегда приветствуются! Присоединяйтесь к сообществу и помогите сделать инструмент лучше.
92+
93+
> С Insert Tools вставка данных становится простой, быстрой и безопасной. Экономьте время и нервы уже сегодня!
94+
95+
---
96+
97+
**Готовы начать? Установите инструмент прямо сейчас и почувствуйте разницу!**
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
jobs:
9+
build-and-publish:
10+
name: Build and publish to PyPI
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repo
15+
uses: actions/checkout@v3
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: '3.10'
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install build twine
26+
27+
- name: Build package
28+
run: python -m build
29+
30+
- name: Publish to PyPI
31+
env:
32+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
33+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
34+
run: |
35+
twine upload dist/*
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
name: Python CI
3+
4+
on:
5+
push:
6+
branches: [ main ]
7+
pull_request:
8+
branches: [ main ]
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v3
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: '3.10'
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install .[dev]
26+
pip install pytest clickhouse-connect pydantic python-dotenv pyyaml
27+
28+
- name: Run tests
29+
run: |
30+
pytest tests/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from .runner import InsertConfig, run_insert, SchemaMismatchError
2+
3+
__all__ = [
4+
"InsertConfig",
5+
"run_insert",
6+
"SchemaMismatchError",
7+
]
8+
9+
__version__ = "0.1.0"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
import re
3+
from typing import Dict, Tuple
4+
5+
def rewrite_select_with_cast(select_sql: str,
6+
source_schema: Dict[str, Tuple[str, bool]],
7+
target_schema: Dict[str, Tuple[str, bool]]) -> str:
8+
"""
9+
Переписывает SELECT-запрос с добавлением CAST(...) там, где типы отличаются и безопасное приведение допустимо.
10+
"""
11+
columns = []
12+
for col, (src_type, _) in source_schema.items():
13+
tgt_type, _ = target_schema.get(col, (src_type, False))
14+
if src_type != tgt_type:
15+
columns.append(f"CAST({col} AS {tgt_type}) AS {col}")
16+
else:
17+
columns.append(col)
18+
19+
# Пытаемся заменить только список колонок в SELECT (не FROM, не WHERE)
20+
match = re.match(r"\s*SELECT\s+(.+?)\s+FROM\s+(.+)", select_sql, re.IGNORECASE | re.DOTALL)
21+
if not match:
22+
raise ValueError("Не удалось разобрать SELECT-запрос")
23+
24+
col_part, from_part = match.groups()
25+
return f"SELECT {', '.join(columns)} FROM {from_part.strip()}"
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import argparse
2+
import logging
3+
from clickhouse_insert.runner import InsertConfig, run_insert, SchemaMismatchError
4+
5+
def main():
6+
# Создаем парсер командной строки
7+
parser = argparse.ArgumentParser(description="ClickHouse Insert CLI Tool")
8+
9+
from clickhouse_insert import __version__
10+
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
11+
parser.add_argument("--host", type=str, help="Host of the ClickHouse server", required=False)
12+
parser.add_argument("--port", type=int, default=8123, help="Port of the ClickHouse server", required=False)
13+
parser.add_argument("--user", type=str, help="User for ClickHouse authentication", required=False)
14+
parser.add_argument("--password", type=str, help="Password for ClickHouse authentication", required=False)
15+
parser.add_argument("--database", type=str, help="Target database in ClickHouse", required=False)
16+
parser.add_argument("--target_table", type=str, help="Target table in ClickHouse", required=False)
17+
parser.add_argument("--select_sql", type=str, help="SELECT query to fetch data", required=False)
18+
parser.add_argument("--allow_type_cast", action="store_true", help="Allow type casting if needed", required=False)
19+
20+
# Убираем обязательный параметр config для использования параметров CLI напрямую
21+
# parser.add_argument("--config", required=True, help="Path to config file (.json/.yaml/.env)")
22+
23+
parser.add_argument("--dry-run", action="store_true", help="Только проверить схему, не вставлять данные")
24+
parser.add_argument("--strict", action="store_true", help="Ошибка при наличии лишних колонок в SELECT")
25+
parser.add_argument("--verbose", action="store_true", help="Подробный лог (DEBUG)")
26+
parser.add_argument("--insert-columns", type=str, help="Список колонок для INSERT через запятую (в порядке)")
27+
28+
# Получаем аргументы
29+
args = parser.parse_args()
30+
31+
# Включаем подробные логи, если нужно
32+
if args.verbose:
33+
logging.getLogger().setLevel(logging.DEBUG)
34+
print("[VERBOSE] Включён режим DEBUG")
35+
36+
try:
37+
# Формируем словарь конфигурации
38+
config_params = {
39+
"host": args.host,
40+
"port": args.port,
41+
"user": args.user,
42+
"password": args.password,
43+
"database": args.database,
44+
"target_table": args.target_table,
45+
"select_sql": args.select_sql,
46+
"allow_type_cast": args.allow_type_cast
47+
}
48+
49+
# Пропускаем пустые параметры
50+
config_params = {key: value for key, value in config_params.items() if value is not None}
51+
52+
# Создаем объект конфигурации
53+
config = InsertConfig(**config_params)
54+
55+
# Обрабатываем dry-run
56+
if args.dry_run:
57+
print("[DRY-RUN] Проверка схемы без вставки")
58+
config._dry_run = True
59+
60+
# Включаем строгую проверку колонок, если требуется
61+
if args.strict:
62+
print("[STRICT] Проверка: лишние поля вызовут ошибку")
63+
config.strict_column_match = True
64+
65+
# Обрабатываем аргумент insert-columns
66+
if args.insert_columns:
67+
config.insert_columns = [col.strip() for col in args.insert_columns.split(",")]
68+
print(f"[COLUMNS] Вставка только в колонки: {config.insert_columns}")
69+
70+
# Выполняем вставку данных
71+
run_insert(config)
72+
73+
if args.verbose or args.dry_run:
74+
logging.debug(f"Итоговый SQL: {config.select_sql}")
75+
76+
except SchemaMismatchError as e:
77+
print(e)
78+
exit(1)
79+
except Exception as e:
80+
print(f"Произошла ошибка: {e}")
81+
exit(2)
82+
83+
if __name__ == "__main__":
84+
main()

0 commit comments

Comments
 (0)