sources.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. from __future__ import annotations
  2. from dataclasses import dataclass, replace
  3. from typing import Any, Callable
  4. from app.core.common.timezone_utils import now_china_naive
  5. from app.core.data_research.errors import IngestionSourceInvalid
  6. @dataclass(frozen=True)
  7. class IngestionSourceRecord:
  8. uid: str
  9. source_type: str
  10. name: str
  11. config: dict[str, Any]
  12. permission_scope: dict[str, Any]
  13. status: str
  14. created_by: str | None
  15. created_at: Any = None
  16. updated_at: Any = None
  17. class DatabaseSourceRegistrationService:
  18. SUPPORTED_DATABASES = frozenset({"postgresql", "mysql"})
  19. def __init__(
  20. self,
  21. repository,
  22. *,
  23. definition_resolver: Callable[[str], Any],
  24. clock: Callable[[], Any] = now_china_naive,
  25. commit: Callable[[], Any] = lambda: None,
  26. rollback: Callable[[], Any] = lambda: None,
  27. ):
  28. self.repository = repository
  29. self.definition_resolver = definition_resolver
  30. self.clock = clock
  31. self.commit = commit
  32. self.rollback = rollback
  33. def ensure(self, data_source_uid, actor_uid):
  34. uid = str(data_source_uid or "").strip()
  35. definition = self.definition_resolver(uid) if uid else None
  36. if definition is None:
  37. raise IngestionSourceInvalid("data source was not found")
  38. if not bool(definition.status):
  39. raise IngestionSourceInvalid("data source is disabled")
  40. database_type = str(definition.database_type or "").strip().lower()
  41. if database_type not in self.SUPPORTED_DATABASES:
  42. raise IngestionSourceInvalid(
  43. f"database type {database_type or 'unknown'} is not supported"
  44. )
  45. now = self.clock()
  46. existing = self.repository.get(uid)
  47. if existing is not None and existing.source_type != "database":
  48. raise IngestionSourceInvalid(
  49. "data source UID is already used by another source type"
  50. )
  51. config = {
  52. "database_type": database_type,
  53. "database": str(definition.database or ""),
  54. "schema": str(definition.schema or "") or None,
  55. }
  56. name = str(
  57. definition.name_zh or definition.name_en or uid
  58. ).strip()
  59. if existing is None:
  60. record = IngestionSourceRecord(
  61. uid=uid,
  62. source_type="database",
  63. name=name,
  64. config=config,
  65. permission_scope={},
  66. status="active",
  67. created_by=actor_uid,
  68. created_at=now,
  69. updated_at=now,
  70. )
  71. created = True
  72. else:
  73. record = replace(
  74. existing,
  75. name=name,
  76. config=config,
  77. status="active",
  78. updated_at=now,
  79. )
  80. created = False
  81. try:
  82. saved = self.repository.save(record)
  83. self.commit()
  84. return saved, created
  85. except Exception:
  86. self.rollback()
  87. raise