File size: 6,617 Bytes
b92d20c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d2c6e4
b92d20c
 
 
 
 
 
 
 
 
 
2d2c6e4
b92d20c
 
 
 
2d2c6e4
b92d20c
 
 
2d2c6e4
 
b92d20c
 
 
 
2d2c6e4
b92d20c
 
 
2d2c6e4
 
 
b92d20c
 
2d2c6e4
b92d20c
 
 
 
 
 
 
 
 
 
2d2c6e4
b92d20c
 
 
2d2c6e4
b92d20c
 
 
 
2d2c6e4
 
 
b92d20c
 
 
 
2d2c6e4
 
 
b92d20c
 
 
 
 
 
 
 
 
 
 
 
 
2d2c6e4
b92d20c
 
 
 
 
 
 
2d2c6e4
 
 
 
b92d20c
 
2d2c6e4
 
 
 
 
b92d20c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d2c6e4
b92d20c
 
 
 
 
 
 
 
2d2c6e4
b92d20c
2d2c6e4
 
 
b92d20c
2d2c6e4
 
 
 
 
b92d20c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FILE: dataset_loader.py
FOLDER: server/
PURPOSE: Loads PR diffs from HuggingFace dataset β€” feeds the environment
USED BY: server/environment.py, server/app.py
KEY CLASSES/FUNCTIONS: DatasetLoader
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"""

import random
import logging
from typing import Dict, Any

try:
    from datasets import load_dataset

    DATASETS_AVAILABLE = True
except ImportError:
    DATASETS_AVAILABLE = False

logger = logging.getLogger(__name__)

FALLBACK_SAMPLES = [
    {
        "filename": "utils/pagination.py",
        "patch": "def get_page_items(items, page, page_size):\n-    start = page * page_size\n-    end = start + page_size\n-    return items[start:end]\n+    start = (page - 1) * page_size\n+    end = start + page_size\n+    return items[start:end]",
        "comment": "Line 2: page indexing was wrong β€” pages are 1-indexed so (page-1)*page_size is correct",
    },
    {
        "filename": "api/userController.js",
        "patch": "- const userName = user.profile.name;\n+ const userName = user?.profile?.name ?? 'Anonymous';",
        "comment": "Missing null check β€” user.profile could be undefined causing TypeError",
    },
    {
        "filename": "db/queries.py",
        "patch": '- query = f"SELECT * FROM users WHERE id = {user_id}"\n+ query = "SELECT * FROM users WHERE id = %s"\n+ cursor.execute(query, (user_id,))',
        "comment": "Critical: SQL injection vulnerability β€” never format user input into queries",
    },
    {
        "filename": "services/emailService.js",
        "patch": "- const result = sendEmail(user.email, template);\n+ const result = await sendEmail(user.email, template);",
        "comment": "Missing await β€” sendEmail is async, without await result is a Promise not the value",
    },
    {
        "filename": "core/processor.py",
        "patch": '- except:\n+ except (ValueError, TypeError) as e:\n+     logger.error(f"Processing failed: {e}")',
        "comment": "Bare except catches everything including SystemExit β€” always catch specific exceptions",
    },
]


class DatasetLoader:
    """
    Loads real PR diffs from the microsoft/CodeReviewer dataset.
    Falls back to 5 synthetic samples if HuggingFace is unavailable.
    """

    def __init__(self):
        """Initializes the dataset loader, attempting to pull from HF."""
        self.dataset = None
        self.is_loaded = False

        # We try to load dataset if HF `datasets` library is available
        if DATASETS_AVAILABLE:
            try:
                # Use a specific split or subset if possible, but CodeReviewer is large.
                # We'll just configure it gracefully.
                logger.info("Attempting to load 'microsoft/CodeReviewer' dataset...")
                # To avoid downloading 20GB in hackathon setup, we might load with streaming=True
                # But typically we can just rely on the fallback samples if it takes too long.
                ds = load_dataset(
                    "microsoft/CodeReviewer", split="train", streaming=True
                )
                # Keep a robust iterator bounded cache
                self._iterator = iter(ds)
                self.is_loaded = True
            except Exception as e:
                logger.warning(
                    f"Failed to load HuggingFace dataset: {e}. Using fallback samples."
                )
        else:
            logger.warning("datasets module not found. Using fallback samples.")

        self.samples = []
        for sample in FALLBACK_SAMPLES:
            sample["language"] = self.get_language_from_filename(sample["filename"])
            self.samples.append(sample)

    def get_random_sample(self) -> Dict[str, Any]:
        """Returns a random PR diff sample."""
        if self.is_loaded and self._iterator:
            try:
                # Try getting next valid sample from HF stream
                for _ in range(50):  # try up to 50 times to find bounded patch
                    record = next(self._iterator)
                    patch = record.get("patch", "")
                    if 50 <= len(patch) <= 2000:
                        return {
                            "filename": record.get("filename", "unknown"),
                            "patch": patch,
                            "comment": record.get("comment", ""),
                            "language": self.get_language_from_filename(
                                record.get("filename", "unknown")
                            ),
                            "msg": record.get("msg", ""),
                        }
            except Exception as e:
                logger.warning(
                    f"Error fetching from dataset stream: {e}. Falling back to default."
                )
                self.is_loaded = False  # fallback forever

        # Return fallback if streaming failed or isn't loaded
        return random.choice(self.samples)

    def get_language_from_filename(self, filename: str) -> str:
        """Detects programming language from file extension."""
        ext = filename.split(".")[-1].lower() if "." in filename else ""
        mapping = {
            "py": "python",
            "js": "javascript",
            "ts": "typescript",
            "java": "java",
            "cpp": "cpp",
            "c": "c",
            "go": "go",
            "rs": "rust",
            "rb": "ruby",
            "cs": "csharp",
            "php": "php",
            "html": "html",
            "css": "css",
            "json": "json",
        }
        return mapping.get(ext, "unknown")

    def get_dataset_stats(self) -> Dict[str, Any]:
        """Returns dummy stats or real stats for the `/stats` endpoint."""
        lang_counts = {}
        for s in self.samples:
            lang_counts[s["language"]] = lang_counts.get(s["language"], 0) + 1

        return {
            "total_samples": (
                len(self.samples) if not self.is_loaded else "1M+ (Streaming)"
            ),
            "languages_breakdown": lang_counts if not self.is_loaded else "Mixed",
            "avg_patch_length": sum(len(s.get("patch", "")) for s in self.samples)
            / max(1, len(self.samples)),
            "source": (
                "microsoft/CodeReviewer" if self.is_loaded else "Fallback Synthetic"
            ),
        }