Files
2026-06-06 13:59:49 +03:30

140 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()