Supported MySQL Changes Listening
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user