store.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. # -*- coding: utf-8 -*-
  2. """
  3. Abstrakte EventStore-Klasse.
  4. Definiert die Schnittstelle für Event-Speicherung.
  5. """
  6. from __future__ import annotations
  7. from abc import ABC, abstractmethod
  8. from dataclasses import dataclass, field
  9. from datetime import datetime
  10. from typing import TYPE_CHECKING, Any, Iterator
  11. import json
  12. import uuid
  13. if TYPE_CHECKING:
  14. from trixy_core.events.event_data.base import EventData
  15. @dataclass
  16. class StoredEvent:
  17. """
  18. Ein gespeichertes Event.
  19. """
  20. id: str
  21. """Eindeutige ID."""
  22. event_name: str
  23. """Name des Events."""
  24. data: dict[str, Any]
  25. """Serialisierte Event-Daten."""
  26. timestamp: datetime
  27. """Zeitpunkt des Events."""
  28. source: str = ""
  29. """Quelle des Events."""
  30. metadata: dict[str, Any] = field(default_factory=dict)
  31. """Zusätzliche Metadaten."""
  32. sequence: int = 0
  33. """Sequenznummer für Reihenfolge."""
  34. @classmethod
  35. def from_event(
  36. cls,
  37. event_name: str,
  38. event_data: "EventData",
  39. sequence: int = 0,
  40. ) -> "StoredEvent":
  41. """
  42. Erstellt ein StoredEvent aus Event-Daten.
  43. Args:
  44. event_name: Name des Events.
  45. event_data: Die Event-Daten.
  46. sequence: Sequenznummer.
  47. Returns:
  48. StoredEvent.
  49. """
  50. # Event-Daten serialisieren
  51. data = {}
  52. for key, value in event_data.__dict__.items():
  53. if not key.startswith("_"):
  54. if isinstance(value, datetime):
  55. data[key] = value.isoformat()
  56. elif hasattr(value, "__dict__"):
  57. data[key] = str(value)
  58. else:
  59. data[key] = value
  60. return cls(
  61. id=event_data.event_id,
  62. event_name=event_name,
  63. data=data,
  64. timestamp=event_data.timestamp,
  65. source=event_data.source,
  66. metadata=dict(event_data.metadata),
  67. sequence=sequence,
  68. )
  69. def to_dict(self) -> dict[str, Any]:
  70. """Konvertiert in ein Dictionary."""
  71. return {
  72. "id": self.id,
  73. "event_name": self.event_name,
  74. "data": self.data,
  75. "timestamp": self.timestamp.isoformat(),
  76. "source": self.source,
  77. "metadata": self.metadata,
  78. "sequence": self.sequence,
  79. }
  80. @classmethod
  81. def from_dict(cls, data: dict[str, Any]) -> "StoredEvent":
  82. """
  83. Erstellt aus einem Dictionary.
  84. Args:
  85. data: Das Dictionary.
  86. Returns:
  87. StoredEvent.
  88. """
  89. return cls(
  90. id=data["id"],
  91. event_name=data["event_name"],
  92. data=data["data"],
  93. timestamp=datetime.fromisoformat(data["timestamp"]),
  94. source=data.get("source", ""),
  95. metadata=data.get("metadata", {}),
  96. sequence=data.get("sequence", 0),
  97. )
  98. def to_json(self) -> str:
  99. """Konvertiert in JSON."""
  100. return json.dumps(self.to_dict(), default=str)
  101. @classmethod
  102. def from_json(cls, json_str: str) -> "StoredEvent":
  103. """
  104. Erstellt aus JSON.
  105. Args:
  106. json_str: Der JSON-String.
  107. Returns:
  108. StoredEvent.
  109. """
  110. return cls.from_dict(json.loads(json_str))
  111. @dataclass
  112. class EventQuery:
  113. """
  114. Abfrage für gespeicherte Events.
  115. """
  116. event_names: list[str] | None = None
  117. """Filter nach Event-Namen."""
  118. start_time: datetime | None = None
  119. """Start-Zeitpunkt."""
  120. end_time: datetime | None = None
  121. """End-Zeitpunkt."""
  122. source: str | None = None
  123. """Filter nach Quelle."""
  124. metadata_filter: dict[str, Any] | None = None
  125. """Filter nach Metadaten."""
  126. limit: int = 1000
  127. """Maximale Anzahl Ergebnisse."""
  128. offset: int = 0
  129. """Offset für Paginierung."""
  130. order_ascending: bool = True
  131. """Sortierreihenfolge."""
  132. after_sequence: int | None = None
  133. """Events nach dieser Sequenznummer."""
  134. before_sequence: int | None = None
  135. """Events vor dieser Sequenznummer."""
  136. @dataclass
  137. class EventQueryResult:
  138. """
  139. Ergebnis einer Event-Abfrage.
  140. """
  141. events: list[StoredEvent]
  142. """Gefundene Events."""
  143. total_count: int
  144. """Gesamtzahl (ohne Limit)."""
  145. has_more: bool
  146. """Ob weitere Events existieren."""
  147. first_sequence: int | None = None
  148. """Erste Sequenznummer."""
  149. last_sequence: int | None = None
  150. """Letzte Sequenznummer."""
  151. class EventStore(ABC):
  152. """
  153. Abstrakte Basisklasse für Event-Speicherung.
  154. Speichert Events für Replay, Audit und Analyse.
  155. Example:
  156. store = InMemoryEventStore()
  157. # Event speichern
  158. await store.append("user_login", event_data)
  159. # Events abfragen
  160. query = EventQuery(
  161. event_names=["user_login"],
  162. start_time=datetime.now() - timedelta(hours=1),
  163. )
  164. result = await store.query(query)
  165. for event in result.events:
  166. print(f"{event.timestamp}: {event.event_name}")
  167. """
  168. @abstractmethod
  169. async def append(
  170. self,
  171. event_name: str,
  172. event_data: "EventData",
  173. ) -> StoredEvent:
  174. """
  175. Speichert ein Event.
  176. Args:
  177. event_name: Name des Events.
  178. event_data: Die Event-Daten.
  179. Returns:
  180. Das gespeicherte Event.
  181. """
  182. pass
  183. @abstractmethod
  184. async def get(self, event_id: str) -> StoredEvent | None:
  185. """
  186. Gibt ein Event nach ID zurück.
  187. Args:
  188. event_id: Die Event-ID.
  189. Returns:
  190. StoredEvent oder None.
  191. """
  192. pass
  193. @abstractmethod
  194. async def query(self, query: EventQuery) -> EventQueryResult:
  195. """
  196. Führt eine Abfrage aus.
  197. Args:
  198. query: Die Abfrage.
  199. Returns:
  200. EventQueryResult.
  201. """
  202. pass
  203. @abstractmethod
  204. async def count(self, query: EventQuery | None = None) -> int:
  205. """
  206. Zählt Events.
  207. Args:
  208. query: Optionale Abfrage.
  209. Returns:
  210. Anzahl der Events.
  211. """
  212. pass
  213. @abstractmethod
  214. async def delete(self, event_id: str) -> bool:
  215. """
  216. Löscht ein Event.
  217. Args:
  218. event_id: Die Event-ID.
  219. Returns:
  220. True wenn gelöscht.
  221. """
  222. pass
  223. @abstractmethod
  224. async def clear(self) -> int:
  225. """
  226. Löscht alle Events.
  227. Returns:
  228. Anzahl gelöschter Events.
  229. """
  230. pass
  231. async def get_latest(
  232. self,
  233. event_name: str | None = None,
  234. count: int = 1,
  235. ) -> list[StoredEvent]:
  236. """
  237. Gibt die neuesten Events zurück.
  238. Args:
  239. event_name: Optionaler Event-Name-Filter.
  240. count: Anzahl der Events.
  241. Returns:
  242. Liste der Events.
  243. """
  244. query = EventQuery(
  245. event_names=[event_name] if event_name else None,
  246. limit=count,
  247. order_ascending=False,
  248. )
  249. result = await self.query(query)
  250. return result.events
  251. async def get_by_sequence_range(
  252. self,
  253. start: int,
  254. end: int,
  255. ) -> list[StoredEvent]:
  256. """
  257. Gibt Events in einem Sequenzbereich zurück.
  258. Args:
  259. start: Start-Sequenz (inklusiv).
  260. end: End-Sequenz (exklusiv).
  261. Returns:
  262. Liste der Events.
  263. """
  264. query = EventQuery(
  265. after_sequence=start - 1,
  266. before_sequence=end,
  267. limit=end - start,
  268. )
  269. result = await self.query(query)
  270. return result.events
  271. async def stream(
  272. self,
  273. query: EventQuery | None = None,
  274. ) -> Iterator[StoredEvent]:
  275. """
  276. Streamt Events.
  277. Args:
  278. query: Optionale Abfrage.
  279. Yields:
  280. StoredEvent Objekte.
  281. """
  282. query = query or EventQuery()
  283. offset = 0
  284. batch_size = 100
  285. while True:
  286. batch_query = EventQuery(
  287. event_names=query.event_names,
  288. start_time=query.start_time,
  289. end_time=query.end_time,
  290. source=query.source,
  291. metadata_filter=query.metadata_filter,
  292. limit=batch_size,
  293. offset=offset,
  294. order_ascending=query.order_ascending,
  295. )
  296. result = await self.query(batch_query)
  297. for event in result.events:
  298. yield event
  299. if not result.has_more:
  300. break
  301. offset += batch_size