140 lines
4.6 KiB
Python
140 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Generate iran_cities_seed.sql from goldAli cities API.
|
||
|
||
Usage:
|
||
python generate_cities_sql.py
|
||
python generate_cities_sql.py --source /path/to/cities.json
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import sys
|
||
import urllib.request
|
||
from pathlib import Path
|
||
|
||
SQL_DIR = Path(__file__).resolve().parent
|
||
DEFAULT_SOURCE = "https://goldaliii.com/server/api/city/all"
|
||
OUTPUT = SQL_DIR / "iran_cities_seed.sql"
|
||
|
||
ARABIC_TO_PERSIAN = str.maketrans(
|
||
{"ي": "ی", "ك": "ک", "ة": "ه", "ۀ": "ه", "ؤ": "و", "إ": "ا", "أ": "ا"}
|
||
)
|
||
|
||
TITLE_FIXES = {
|
||
"چهارمحال وبختیاری": "چهارمحال و بختیاری",
|
||
"سیستان وبلوچستان": "سیستان و بلوچستان",
|
||
"کهگیلویه وبویراحمد": "کهگیلویه و بویراحمد",
|
||
}
|
||
|
||
|
||
def normalize_title(title: str) -> str:
|
||
title = title.translate(ARABIC_TO_PERSIAN)
|
||
return TITLE_FIXES.get(title, title)
|
||
|
||
|
||
def sql_str(value: str | None) -> str:
|
||
if value is None:
|
||
return "NULL"
|
||
return "'" + str(value).replace("\\", "\\\\").replace("'", "''") + "'"
|
||
|
||
|
||
def sql_bool(value: bool | None) -> str:
|
||
return "1" if value else "0"
|
||
|
||
|
||
def load_rows(source: str) -> list[dict]:
|
||
if source.startswith("http://") or source.startswith("https://"):
|
||
with urllib.request.urlopen(source, timeout=60) as resp:
|
||
data = json.load(resp)
|
||
else:
|
||
data = json.loads(Path(source).read_text(encoding="utf-8"))
|
||
|
||
return [
|
||
row
|
||
for row in data
|
||
if row.get("id") != 1600 and row.get("title") != "تست"
|
||
]
|
||
|
||
|
||
def generate_sql(rows: list[dict]) -> str:
|
||
provinces = sorted(
|
||
[row for row in rows if row.get("parent_id") is None], key=lambda r: r["id"]
|
||
)
|
||
others = sorted(
|
||
[row for row in rows if row.get("parent_id") is not None], key=lambda r: r["id"]
|
||
)
|
||
ordered = provinces + others
|
||
|
||
lines = [
|
||
"-- =============================================================================",
|
||
"-- استانها + شهرها + زیرمجموعهها (جدول cities)",
|
||
"--",
|
||
"-- ساختار مطابق City model:",
|
||
"-- level=1 → استان (parent_id = NULL, has_children = 1)",
|
||
"-- level=2 → شهر/شهرستان (parent_id = id استان)",
|
||
"-- level=3 → محله/بخش (parent_id = id شهر level=2)",
|
||
"--",
|
||
f"-- تعداد: {len(ordered)} رکورد (31 استان + {len(ordered) - 31} زیرمجموعه)",
|
||
"-- =============================================================================",
|
||
"",
|
||
"START TRANSACTION;",
|
||
"",
|
||
"SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;",
|
||
"",
|
||
"CREATE TABLE IF NOT EXISTS `cities` (",
|
||
" `id` bigint unsigned NOT NULL AUTO_INCREMENT,",
|
||
" `title` varchar(255) NOT NULL,",
|
||
" `code` varchar(50) DEFAULT NULL,",
|
||
" `parent_id` bigint unsigned DEFAULT NULL,",
|
||
" `has_children` tinyint(1) NOT NULL DEFAULT 0,",
|
||
" `level` tinyint unsigned DEFAULT NULL,",
|
||
" PRIMARY KEY (`id`),",
|
||
" KEY `cities_parent_id_index` (`parent_id`)",
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;",
|
||
"",
|
||
"ALTER TABLE `cities` MODIFY COLUMN `code` varchar(50) DEFAULT NULL;",
|
||
"",
|
||
"DELETE FROM `cities`;",
|
||
"",
|
||
]
|
||
|
||
batch_size = 50
|
||
for i in range(0, len(ordered), batch_size):
|
||
batch = ordered[i : i + batch_size]
|
||
lines.append(
|
||
"INSERT INTO `cities` (`id`, `title`, `code`, `parent_id`, `has_children`, `level`) VALUES"
|
||
)
|
||
values = []
|
||
for row in batch:
|
||
parent_id = row.get("parent_id")
|
||
level = row.get("level")
|
||
values.append(
|
||
f" ({row['id']}, {sql_str(normalize_title(row['title']))}, "
|
||
f"{sql_str(row.get('code'))}, "
|
||
f"{parent_id if parent_id is not None else 'NULL'}, "
|
||
f"{sql_bool(row.get('has_children'))}, "
|
||
f"{level if level is not None else 'NULL'})"
|
||
)
|
||
lines.append(",\n".join(values) + ";")
|
||
lines.append("")
|
||
|
||
max_id = max(row["id"] for row in ordered)
|
||
lines.extend([f"ALTER TABLE `cities` AUTO_INCREMENT = {max_id + 1};", "", "COMMIT;", ""])
|
||
return "\n".join(lines)
|
||
|
||
|
||
def main() -> None:
|
||
source = DEFAULT_SOURCE
|
||
if len(sys.argv) > 1 and sys.argv[1] == "--source" and len(sys.argv) > 2:
|
||
source = sys.argv[2]
|
||
|
||
rows = load_rows(source)
|
||
OUTPUT.write_text(generate_sql(rows), encoding="utf-8")
|
||
print(f"Wrote {OUTPUT} ({len(rows)} rows)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|