ported backend to python

This commit is contained in:
Karma Riuk
2025-05-13 13:27:38 +02:00
parent e5bd1d3a08
commit 3a4bfd611b
22 changed files with 330 additions and 658 deletions

View File

@ -1,92 +0,0 @@
import { jest } from '@jest/globals';
import express from 'express';
import request from 'supertest';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import datasetsRouter from '../datasets.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Mock the paths utility
jest.mock('../../utils/paths.js', () => ({
getProjectPath: (path) => join(__dirname, '../../..', path)
}));
// Create Express app for testing
const app = express();
app.use('/datasets', datasetsRouter);
describe('Datasets Router', () => {
// Mock environment variables
const originalEnv = process.env;
beforeEach(() => {
jest.resetModules();
process.env = { ...originalEnv };
process.env.DATA_DIR = './test-data';
});
afterEach(() => {
process.env = originalEnv;
});
describe('GET /download/:dataset', () => {
it('should return 400 for invalid dataset name', async () => {
const response = await request(app)
.get('/datasets/download/invalid_dataset')
.expect(400);
expect(response.body).toEqual({
error: 'Invalid dataset name'
});
});
it('should download comment_generation without context', async () => {
const response = await request(app)
.get('/datasets/download/comment_generation')
.expect(200);
expect(response.headers['content-type']).toBe('application/zip');
expect(response.headers['content-disposition']).toContain('comment_generation_no_context.zip');
});
it('should download comment_generation with context', async () => {
const response = await request(app)
.get('/datasets/download/comment_generation')
.query({ withContext: true })
.expect(200);
expect(response.headers['content-type']).toBe('application/zip');
expect(response.headers['content-disposition']).toContain('comment_generation_with_context.zip');
});
it('should download code_refinement without context', async () => {
const response = await request(app)
.get('/datasets/download/code_refinement')
.expect(200);
expect(response.headers['content-type']).toBe('application/zip');
expect(response.headers['content-disposition']).toContain('code_refinement_no_context.zip');
});
it('should download code_refinement with context', async () => {
const response = await request(app)
.get('/datasets/download/code_refinement')
.query({ withContext: true })
.expect(200);
expect(response.headers['content-type']).toBe('application/zip');
expect(response.headers['content-disposition']).toContain('code_refinement_with_context.zip');
});
it('should handle JSON boolean for withContext parameter', async () => {
const response = await request(app)
.get('/datasets/download/comment_generation')
.query({ withContext: 'true' })
.expect(200);
expect(response.headers['content-disposition']).toContain('comment_generation_with_context.zip');
});
});
});

View File

@ -1,129 +0,0 @@
import { Router } from "express";
import multer from "multer";
import { InvalidJsonFormatError } from "../utils/errors.js";
import { evaluate_comments } from "../utils/process_data.js";
const router = Router();
// Configure multer for file uploads
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 200 * 1024 * 1024, // 200MB limit, since the comement gen is 147MB (deflated)
},
fileFilter: (_req, file, cb) => {
// Accept only JSON files
if (file.mimetype === "application/json") {
cb(null, true);
} else {
cb(new Error("Only JSON files are allowed"));
}
},
});
// Helper function to validate JSON format
const validateJsonFormat = (data) => {
try {
const parsed = JSON.parse(data);
// Check if it's an object
if (
typeof parsed !== "object" ||
parsed === null ||
Array.isArray(parsed)
) {
throw new InvalidJsonFormatError(
"Submitted json doesn't contain an object",
);
}
// Check if all values are strings
if (
!Object.values(parsed).every((value) => typeof value === "string")
) {
throw new InvalidJsonFormatError(
"Submitted json object must only be str -> str. Namely id -> comment",
);
}
return parsed;
} catch (error) {
if (error instanceof InvalidJsonFormatError) {
throw error;
}
throw new InvalidJsonFormatError("Invalid JSON format");
}
};
router.post("/submit/comments", upload.single("file"), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: "No file uploaded" });
}
const fileContent = req.file.buffer.toString();
let validatedData;
try {
validatedData = validateJsonFormat(fileContent);
} catch (error) {
if (error instanceof InvalidJsonFormatError) {
return res.status(400).json({
error: "Invalid JSON format",
message: error.message,
});
}
throw error;
}
const io = req.app.get("io");
const header = req.get("X-Socket-Id");
const socketId = header && header.trim();
if (socketId && io.sockets.sockets.has(socketId)) {
io.to(socketId).emit("successul-upload");
io.to(socketId).emit("started-processing");
}
const results = evaluate_comments(validatedData, (percent) => {
if (!(socketId && io.sockets.sockets.has(socketId))) return;
io.to(socketId).emit("progress", { percent });
});
res.status(200).json(results);
} catch (error) {
console.error("Error processing submission:", error);
res.status(500).json({ error: "Error processing submission" });
}
});
router.post("/submit/refinement", upload.single("file"), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: "No file uploaded" });
}
const fileContent = req.file.buffer.toString();
let validatedData;
try {
validatedData = validateJsonFormat(fileContent);
} catch (error) {
if (error instanceof InvalidJsonFormatError) {
return res.status(400).json({
error: "Invalid JSON format",
message: error.message,
});
}
throw error;
}
socket.emit("started-processing");
evaluate_comments(validatedData);
res.status(200).json({
message: "Answer submitted successfully",
data: validatedData,
});
} catch (error) {
console.error("Error processing submission:", error);
res.status(500).json({ error: "Error processing submission" });
}
});
export default router;

55
src/routes/answers.py Normal file
View File

@ -0,0 +1,55 @@
# routes/answers.py
from flask import Blueprint, request, jsonify, current_app
from utils.errors import InvalidJsonFormatError
from utils.process_data import evaluate_comments
import json
router = Blueprint('answers', __name__, url_prefix='/answers')
ALLOWED_EXT = {'json'}
def validate_json_format(data: str) -> dict[str, str]:
try:
obj = json.loads(data)
if not isinstance(obj, dict):
raise InvalidJsonFormatError("Submitted json doesn't contain an object")
if not all(isinstance(v, str) for v in obj.values()):
raise InvalidJsonFormatError(
"Submitted json object must only be str -> str. Namely id -> comment"
)
return obj
except InvalidJsonFormatError as e:
raise e
except Exception:
raise InvalidJsonFormatError()
@router.route('/submit/comments', methods=['POST'])
def submit_comments():
file = request.files.get('file')
if file is None or file.filename is None or file.filename.split('.')[-1] not in ALLOWED_EXT:
return jsonify({'error': 'Only JSON files are allowed'}), 400
data = file.read().decode()
try:
validated = validate_json_format(data)
except InvalidJsonFormatError as e:
return jsonify({'error': 'Invalid JSON format', 'message': str(e)}), 400
socketio = current_app.extensions['socketio']
sid = request.headers.get('X-Socket-Id')
if sid:
socketio.emit('successful-upload', room=sid)
socketio.emit('started-processing', room=sid)
results = evaluate_comments(
validated, lambda p: socketio.emit('progress', {'percent': p}, room=sid)
)
return jsonify(results)
@router.route('/submit/refinement', methods=['POST'])
def submit_refinement():
file = request.files.get('file')
# similar to above
return jsonify({'message': 'Answer submitted successfully'})

View File

@ -1,33 +0,0 @@
import { Router } from "express";
import { join } from "path";
import { getProjectPath } from "../utils/paths.js";
const router = Router();
// Environment variables for paths (all relative to project root)
const DATA_DIR = getProjectPath("data");
const DATASETS = ["comment_generation", "code_refinement"];
router.get("/download/:dataset", async (req, res) => {
const { dataset } = req.params;
const withContext = req.query.withContext
? JSON.parse(req.query.withContext)
: false;
if (!DATASETS.includes(dataset)) {
return res.status(400).json({ error: "Invalid dataset name" });
}
const fileName = `${dataset}_${withContext ? "with_context" : "no_context"}.zip`;
const filePath = join(DATA_DIR, fileName);
try {
res.download(filePath);
} catch (error) {
console.error("Error serving file:", error);
res.status(500).json({ error: "Error serving file" });
}
});
export default router;

17
src/routes/datasets.py Normal file
View File

@ -0,0 +1,17 @@
# routes/datasets.py
from flask import Blueprint, send_from_directory, request, jsonify
from utils.paths import get_project_path
router = Blueprint('datasets', __name__, url_prefix='/datasets')
DATASETS = {'comment_generation', 'code_refinement'}
DATA_DIR = get_project_path('../data')
@router.route('/download/<dataset>')
def download(dataset):
if dataset not in DATASETS:
return jsonify({'error': 'Invalid dataset name'}), 400
with_ctx = request.args.get('withContext', 'false').lower() == 'true'
fname = f"{dataset}_{'with_context' if with_ctx else 'no_context'}.zip"
return send_from_directory(DATA_DIR, fname, as_attachment=True)

View File

@ -1,23 +0,0 @@
import { Router } from 'express';
import datasetRoutes from './datasets.js';
import answerRoutes from './answers.js';
const router = Router();
// Routes
router.get('/', (_req, res) => {
res.json({ message: 'Welcome to the Express backend!' });
});
// Example route
router.get('/api/hello', (_req, res) => {
res.json({ message: 'Hello from the backend!' });
});
// Dataset routes
router.use('/datasets', datasetRoutes);
// Answer submission routes
router.use('/answers', answerRoutes);
export default router;

16
src/routes/index.py Normal file
View File

@ -0,0 +1,16 @@
# routes/index.py
from flask import Blueprint, jsonify, current_app
router = Blueprint('index', __name__)
@router.route('/')
def welcome():
print("hello")
return current_app.send_static_file('index.html')
@router.route('/api/hello')
def hello():
return jsonify({'message': 'Hello from the backend!'})