Supported MySQL Changes Listening

This commit is contained in:
2025-03-09 00:12:28 +03:00
parent 41d98aafe9
commit 77b23eaad2
9 changed files with 304 additions and 49 deletions

View File

@@ -1,4 +1,5 @@
from fastapi import WebSocket
import asyncio, time, decimal, datetime
from fastapi import WebSocket, WebSocketDisconnect, WebSocketException
from fastapi.routing import APIRouter
from fastapi.responses import StreamingResponse
from typing_extensions import Annotated
@@ -17,9 +18,12 @@ from core.exceptions import (
CursorNotFound,
)
from dbs import mysql
from utils.binlog import changes_queue, queue
router = APIRouter()
database_changes_active_websocket: WebSocket | None = None
@router.post("/execute", dependencies=[Depends(get_current_user)])
async def execute_select(
@@ -97,7 +101,7 @@ async def server_side_events_stream_cursor(
result = await fetch_cursor(cursor_id=cursor_id, page_size=page_size)
serialized_result = result.model_dump_json()
yield f"data: {serialized_result}\n\n" # Format as Server-Sent Event (SSE)
yield f"data: {serialized_result}\n\n"
if result.cursor.is_closed:
break
@@ -119,7 +123,7 @@ async def websocket_stream_cursor(
if user is None:
await websocket.close(reason="Invalid credentials", code=1008)
return
cached_cursor = mysql.cached_cursors.get(cursor_id, None)
if cached_cursor is None:
e = CursorNotFound()
@@ -136,3 +140,69 @@ async def websocket_stream_cursor(
if result.cursor.is_closed:
break
await websocket.close(reason="Done")
@router.websocket("/databases_changes")
async def websocket_endpoint(
websocket: WebSocket,
db=Depends(get_db),
):
global database_changes_active_websocket
await websocket.accept()
api_key = websocket.headers.get("Authorization")
user = await get_user_from_api_key(db=db, api_key=api_key)
if user is None:
await websocket.close(reason="Invalid credentials", code=1008)
return
if database_changes_active_websocket:
try:
await database_changes_active_websocket.close(
code=1001, reason="New connection established"
)
except Exception as e:
print(e)
database_changes_active_websocket = websocket
await websocket.send_json({"message":"status", "status":"Accepted."})
try:
await feed_databases_changes_ws(websocket=websocket)
except WebSocketDisconnect:
print('Closed websocket.')
def serialize_list(l:list):
serialized = []
for value in l:
if isinstance(value, str | int | None | float):
serialized.append(str(value))
elif isinstance(value, decimal.Decimal):
serialized.append(float(value))
elif isinstance(value, datetime.date):
serialized.append(value.strftime("%Y-%m-%d"))
else:
serialized.append(str(value))
return serialized
async def feed_databases_changes_ws(websocket:WebSocket):
last_update = 0
while True:
try:
change = changes_queue.get_nowait()
if change.action == 'UPDATE':
change.after_values = serialize_list(change.after_values)
change.before_values = serialize_list(change.before_values)
else:
change.values = serialize_list(change.values)
await websocket.send_json({"message": "change", 'change': change.model_dump()})
except queue.Empty:
if last_update + 10 < time.time():
await websocket.send_json({"message":"status", "status":"Alive."})
last_update = time.time()
await asyncio.sleep(1)
continue