created the observers and handling them

This commit is contained in:
Karma Riuk
2025-05-18 17:43:24 +02:00
parent 15a2d6d03e
commit 49b2606a53
4 changed files with 188 additions and 57 deletions

View File

@ -1,8 +1,10 @@
# routes/answers.py
from flask import Blueprint, request, jsonify, current_app
from threading import Thread
from flask import Blueprint, request, jsonify, current_app, url_for
from utils.errors import InvalidJsonFormatError
from utils.process_data import evaluate_comments, evaluate_refinement
from utils.observer import request2status
from utils.observer import SocketObserver, Status, Subject, request2status
import functools
import json, uuid
router = Blueprint('answers', __name__, url_prefix='/answers')
@ -68,6 +70,9 @@ def submit_comments():
return jsonify(results)
socket2observer = {}
@router.route('/submit/refinement', methods=['POST'])
def submit_refinement():
file = request.files.get('file')
@ -79,22 +84,60 @@ def submit_refinement():
except InvalidJsonFormatError as e:
return jsonify({'error': 'Invalid JSON format', 'message': str(e)}), 400
process_id = str(uuid.uuid4())
request2status[process_id] = "processing"
socketio = current_app.extensions['socketio']
sid = request.headers.get('X-Socket-Id')
socket_emit = functools.partial(socketio.emit, room=sid)
process_id = str(uuid.uuid4())
subject = Subject(process_id, evaluate_refinement)
request2status[process_id] = subject
if sid:
socketio.emit('successful-upload', room=sid)
socketio.emit('started-processing', {"id": process_id}, room=sid)
socket_emit('successful-upload')
socket_emit('started-processing')
obs = SocketObserver(socket_emit)
socket2observer[sid] = obs
subject.registerObserver(obs)
results = evaluate_refinement(
validated, lambda p: socketio.emit('progress', {'percent': p}, room=sid)
t = Thread(target=subject.launch_task, args=(validated,), daemon=True)
t.start()
url = url_for(f".status", id=process_id, _external=True)
return jsonify(
{
"id": process_id,
"status_url": url,
"help_msg": "Check the status of this process at /answers/status/<id>. Once the evaluation is complete, a call to this URL will return the results.",
}
)
return jsonify(results)
@router.route('/status/<id>')
def request_status(id):
return jsonify({"status": request2status.get(id, "doens't exist")})
def status(id):
if id not in request2status:
raise ValueError(f"Id {id} doesn't exist")
subject = request2status[id]
if subject.status == Status.COMPLETE:
return jsonify({"status": "complete", "results": subject.results})
elif subject.status == Status.PROCESSING:
socketio = current_app.extensions['socketio']
sid = request.headers.get('X-Socket-Id')
socket_emit = functools.partial(socketio.emit, room=sid)
request2status[id] = subject
if sid:
if sid in socket2observer:
raise AttributeError(
"You are already seeing the real-time progress of that request, please don't spam"
)
obs = SocketObserver(socket_emit)
socket2observer[sid] = obs
obs.updatePercentage(subject.percent)
subject.registerObserver(obs)
# if no socket, return current status
return jsonify({"status": "processing", "percent": subject.percent})
elif subject.status == Status.CREATED:
return jsonify({"status": "created"})
raise Exception("This code should be unreachable")

View File

@ -1 +1,69 @@
request2status = {}
from abc import ABC, abstractmethod
from enum import Enum
from typing import Callable, Optional, Set, Any
class Status(Enum):
CREATED = "created"
PROCESSING = "processing"
COMPLETE = "complete"
class Observer(ABC):
@abstractmethod
def updatePercentage(self, percentage: float):
...
@abstractmethod
def updateComplete(self, results: dict):
...
class SocketObserver(Observer):
def __init__(self, socket_emit: Callable[[str, Any], None]) -> None:
super().__init__()
self.socket_emit = socket_emit
def updatePercentage(self, percentage: float):
self.socket_emit("progress", {'percent': percentage})
def updateComplete(self, results: dict):
self.socket_emit("complete", results)
class Subject:
# TODO: maybe have a process or thread pool here to implement the queue
def __init__(self, id: str, task: Callable) -> None:
self.id = id
self.observers: Set[Observer] = set()
self.status: Status = Status.CREATED
self.results: Optional[dict] = None
self.task = task
self.percent: float = -1
def registerObserver(self, observer: Observer) -> None:
self.observers.add(observer)
def unregisterObserver(self, observer: Observer):
self.observers.remove(observer)
def notifyPercentage(self, percentage: float):
self.percent = percentage
for observer in self.observers:
observer.updatePercentage(percentage)
def notifyComplete(self, results: dict):
self.status = Status.COMPLETE
for observer in self.observers:
observer.updateComplete(results)
self.results = results
# TODO: maybe save results to disk here?
def launch_task(self, *args, **kwargs):
self.status = Status.PROCESSING
self.task(
*args, **kwargs, percent_cb=self.notifyPercentage, complete_cb=self.notifyComplete
)
request2status: dict[str, Subject] = {}

View File

@ -1,4 +1,5 @@
import sys
from typing_extensions import Callable
from utils.handlers import get_build_handler
from .paths import get_project_path
from sacrebleu import sentence_bleu as bleu
@ -35,7 +36,11 @@ def evaluate_comments(answers: dict[str, str], percent_cb):
return results
def evaluate_refinement(answers: dict[str, dict[str, str]], percent_cb):
def evaluate_refinement(
answers: dict[str, dict[str, str]],
percent_cb: Callable[[float], None] = lambda _: None,
complete_cb: Callable[[dict], None] = lambda _: None,
):
n_answers = len(answers)
n_steps = 4 # creating build handler + injecting the files in the repo + compilation + testing
total_number_of_steps = n_answers * n_steps
@ -92,4 +97,5 @@ def evaluate_refinement(answers: dict[str, dict[str, str]], percent_cb):
print(f"[INFO] Done with {id}...")
complete_cb(results)
return results