pattern_pg.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. """M9C:只读直连 pattern PG(open_aigc),判需求是否含分类树终端元素(叶子)。
  2. 叶子 = pattern_mining_category 中某 id 没有任何行 parent_id=该 id(同 execution)。
  3. Gate 1:需求 itemset_items 的 category_id 里至少有一个叶子 → 放行;否则不做。
  4. """
  5. from __future__ import annotations
  6. from typing import Any
  7. from content_agent.errors import ContentAgentError, ErrorCode
  8. from content_agent.integrations.crawapi_http import _env, _load_env_file
  9. _LEAF_SQL = (
  10. "SELECT 1 FROM pattern_mining_category c "
  11. "WHERE c.execution_id = %s AND c.id = ANY(%s) "
  12. "AND NOT EXISTS (SELECT 1 FROM pattern_mining_category ch "
  13. "WHERE ch.execution_id = c.execution_id AND ch.parent_id = c.id) LIMIT 1"
  14. )
  15. class PatternPgClient:
  16. def __init__(
  17. self,
  18. *,
  19. host: str,
  20. port: int,
  21. user: str,
  22. password: str,
  23. database: str,
  24. timeout_seconds: float = 10.0,
  25. ) -> None:
  26. self.host = host
  27. self.port = port
  28. self.user = user
  29. self.password = password
  30. self.database = database
  31. self.timeout_seconds = timeout_seconds
  32. @classmethod
  33. def from_env(cls, env_path: str = ".env") -> "PatternPgClient":
  34. env = _load_env_file(env_path)
  35. return cls(
  36. host=_env("OPEN_AIGC_PG_HOST", env, required=True),
  37. port=int(_env("OPEN_AIGC_PG_PORT", env, default="5432")),
  38. user=_env("OPEN_AIGC_PG_USER", env, required=True),
  39. password=_env("OPEN_AIGC_PG_PASSWORD", env, required=True),
  40. database=_env("OPEN_AIGC_PG_DB_NAME", env, default="open_aigc"),
  41. )
  42. def has_terminal_element(self, execution_id: int, category_ids: list[int]) -> bool:
  43. ids = [int(c) for c in category_ids if c is not None]
  44. if not ids:
  45. return True # 无可判定 category → 不阻断(交给后续判定)
  46. import pg8000.dbapi # 惰性导入:mock 测试用 fake client,不触发依赖
  47. try:
  48. conn = pg8000.dbapi.connect(
  49. host=self.host,
  50. port=self.port,
  51. user=self.user,
  52. password=self.password,
  53. database=self.database,
  54. ssl_context=None, # 服务器拒绝 SSL,必须明确关闭
  55. timeout=self.timeout_seconds,
  56. )
  57. except Exception as exc: # PG 不可达:明确报错,不静默放过
  58. raise ContentAgentError(
  59. ErrorCode.DB_CONNECTION_FAILED,
  60. "pattern pg unreachable for gate1 terminal-element check",
  61. {"exception_type": type(exc).__name__},
  62. ) from exc
  63. try:
  64. cur = conn.cursor()
  65. cur.execute(_LEAF_SQL, (int(execution_id), ids))
  66. return cur.fetchone() is not None
  67. finally:
  68. conn.close()