feat: add import templates

This commit is contained in:
2026-06-23 18:59:42 +08:00
parent 7efef8ae64
commit 6a17c9a336
18 changed files with 1331 additions and 10 deletions

View File

@@ -5,12 +5,20 @@ import sqlite3
from pathlib import Path
from typing import Iterable, Mapping, Union
from .import_templates import ImportTemplateError, load_tabular_rows, validate_import_rows
PROJECT_ROOT = Path(__file__).resolve().parents[2]
DEFAULT_DB_PATH = PROJECT_ROOT / "data" / "shanghai_housing.sqlite"
SCHEMA_PATH = Path(__file__).with_name("schema.sql")
SAMPLE_DIR = PROJECT_ROOT / "data" / "sample"
SQLITE_IMPORT_TABLES = {
"area_monthly_metrics": "area_monthly_metrics",
"neighborhood_monthly_metrics": "neighborhood_monthly_metrics",
"policy_events": "policy_events",
}
def connect(db_path: Union[Path, str] = DEFAULT_DB_PATH) -> sqlite3.Connection:
conn = sqlite3.connect(str(db_path))
@@ -73,6 +81,44 @@ def load_csv(conn: sqlite3.Connection, table: str, path: Path) -> int:
return len(rows)
def import_template_file(
db_path: Union[Path, str],
template_name: str,
file_path: Path,
) -> int:
table = SQLITE_IMPORT_TABLES.get(template_name)
if table is None:
supported = ", ".join(sorted(SQLITE_IMPORT_TABLES))
raise ImportTemplateError(
f"template '{template_name}' cannot be imported into SQLite; "
f"supported templates: {supported}"
)
rows = load_tabular_rows(file_path)
result = validate_import_rows(template_name, rows)
if not result.valid:
first_error = result.errors[0]
raise ImportTemplateError(
f"validation failed at row {first_error.row}, "
f"column {first_error.column}: {first_error.message}"
)
if not rows:
return 0
init_db(db_path)
with connect(db_path) as conn:
columns = list(rows[0].keys())
placeholders = ", ".join(["?"] * len(columns))
column_sql = ", ".join(columns)
update_sql = ", ".join([f"{column}=excluded.{column}" for column in columns])
sql = (
f"INSERT INTO {table} ({column_sql}) VALUES ({placeholders}) "
f"ON CONFLICT DO UPDATE SET {update_sql}"
)
conn.executemany(sql, [tuple(row[column] for column in columns) for row in rows])
return len(rows)
def fetch_area_metrics(conn: sqlite3.Connection, month: str) -> list[sqlite3.Row]:
return conn.execute(
"""