odps_writer.py 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """脚本主驱需求写入 ODPS 需求池表。"""
  2. from __future__ import annotations
  3. import re
  4. from typing import Any
  5. from app.aliyun_odps.client import get_odps_client
  6. from app.gap_script_demand.exceptions import GapScriptDemandError
  7. IDENTIFIER_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$")
  8. def _safe_identifier(name: str) -> str:
  9. value = name.strip()
  10. if not IDENTIFIER_RE.match(value):
  11. raise GapScriptDemandError(f"invalid sql identifier: {name}")
  12. return value
  13. def _escape_sql_string(value: str) -> str:
  14. return value.replace("'", "''")
  15. def build_odps_row(
  16. *,
  17. strategy: str,
  18. demand_id: str,
  19. demand_name: str,
  20. weight: float = 1.0,
  21. demand_type: str = "特征点",
  22. ) -> dict[str, Any]:
  23. return {
  24. "strategy": strategy,
  25. "demand_id": demand_id,
  26. "demand_name": demand_name,
  27. "weight": weight,
  28. "type": demand_type,
  29. "video_count": 0,
  30. "extend": "",
  31. }
  32. def _build_row_select(row: dict[str, Any]) -> str:
  33. strategy = _escape_sql_string(str(row["strategy"]))
  34. demand_id = _escape_sql_string(str(row["demand_id"]))
  35. demand_name = _escape_sql_string(str(row["demand_name"]))
  36. weight = float(row.get("weight") or 1.0)
  37. demand_type = _escape_sql_string(str(row.get("type") or "特征点"))
  38. extend = _escape_sql_string(str(row.get("extend") or ""))
  39. return f"""
  40. SELECT
  41. '{strategy}' AS strategy,
  42. '{demand_id}' AS demand_id,
  43. '{demand_name}' AS demand_name,
  44. {weight} AS weight,
  45. '{demand_type}' AS type,
  46. CAST(0 AS BIGINT) AS video_count,
  47. array() AS video_list,
  48. '{extend}' AS extend
  49. """
  50. def sync_gap_script_demands_to_odps(
  51. *,
  52. rows: list[dict[str, Any]],
  53. partition_dt: str,
  54. target_table: str,
  55. ) -> dict[str, Any]:
  56. """将脚本主驱需求追加写入 ODPS 分区(不覆盖分区已有数据)。"""
  57. partition_value = partition_dt.strip()
  58. if not partition_value:
  59. raise GapScriptDemandError("partition_dt is required")
  60. if not rows:
  61. return {
  62. "target_table": target_table,
  63. "partition_dt": partition_value,
  64. "written_count": 0,
  65. }
  66. table_name = _safe_identifier(target_table)
  67. odps_client = get_odps_client()
  68. select_sql = " UNION ALL ".join(_build_row_select(row) for row in rows)
  69. sql = f"""
  70. INSERT INTO TABLE {table_name} PARTITION (dt='{_escape_sql_string(partition_value)}')
  71. {select_sql}
  72. """
  73. instance = odps_client.execute_sql(sql)
  74. instance.wait_for_success()
  75. return {
  76. "target_table": table_name,
  77. "partition_dt": partition_value,
  78. "written_count": len(rows),
  79. }