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()
|