From ce6d67a83b3fbe02b704b5c7f4340113b78e14d6 Mon Sep 17 00:00:00 2001 From: Lukas Horst Date: Sun, 6 Oct 2024 12:45:23 +0200 Subject: [PATCH] Grouping the violations and updating the overall grading in by finalising --- eprgrader.py | 37 +++--- rating_table_adjuster.py | 52 +++++++++ violation_checker.py | 243 ++++++++++++++++++++------------------- 3 files changed, 199 insertions(+), 133 deletions(-) create mode 100644 rating_table_adjuster.py diff --git a/eprgrader.py b/eprgrader.py index 0caace3..d56e627 100644 --- a/eprgrader.py +++ b/eprgrader.py @@ -29,6 +29,7 @@ from datetime import datetime from pylint.lint import Run as RunPylint import pycodestyle +from rating_table_adjuster import get_points, update_rating from violation_checker import ViolationChecker PYLINT_ARGS = [ @@ -61,26 +62,20 @@ PYLINT_ARGS = [ # E0102: function-redefined # E0211: no-method-argument [when it should at least have self] "E0001,E0102,E0211," + - # I0011: locally-disabled [to notify of 'pylint disable=...' comments] - # I0013: file-ignored - # I0020: suppressed-message - "I0011,I0013,I0020," + # W0104: pointless-statement - # W0106: expression-not-assigned # W0201: attribute-defined-outside-init # W0231: super-init-not-called # W0232: no-init # W0301: unnecessary-semicolon # W0311: bad-indentation # W0401: wildcard-import - # W0402: deprecated-module # W0404: reimported # W0603: global-statement # W0622: redefined-builtin # W0702: bare-except # W0705: duplicate-except # W0706: try-except-raise - "W0104,W0106,W0201,W0231,W0232,W0301,W0311,W0401,W0402,W0404,W0603,W0622,W0702,W0705,W0706" + "W0104,W0201,W0231,W0232,W0301,W0311,W0401,W0404,W0603,W0622,W0702,W0705,W0706" ] PYCODESTYLE_SELECT = [ @@ -125,10 +120,6 @@ PYCODESTYLE_SELECT = [ 'E714', # E721: use 'isinstance' instead of comparing types 'E721', - # E731: use 'def' instead of assigning lambdas - 'E731', - # E741-3: ambiguous single-character names (I, l, O) for var/class/func - 'E741', 'E742', 'E743', ] tmp_storage = {} @@ -183,13 +174,15 @@ def lint_files(folders, author_pairs): with open(folder / 'stylecheck.txt', 'w', encoding='utf-8') as outfile: if lintcache.tell() > 0: style_check = remove_unnecessary_violations(lintcache.getvalue()) - violation_checker = ViolationChecker(style_check) - violation_checker.check_violations() - style_check += (f'\n-----Verstöße insgesamt-----' - f'\n{violation_checker.list_violation()}') - outfile.write(style_check) else: - outfile.write("Alles sieht gut aus -- weiter so!\n") + style_check = "" + violation_checker = ViolationChecker(style_check) + violation_checker.check_violations() + if violation_checker.count_violations(-1) == 0: + style_check = "Alles sieht gut aus -- weiter so!\n" + violation_string = violation_checker.list_violation() + style_check += f'\n{violation_string}' + outfile.write(style_check) def remove_unnecessary_violations(style_check): @@ -284,7 +277,13 @@ def finalise_grading(folder: pathlib.Path): issues = 0 print("Copying grades...") folders = list(folder.glob("**/abgaben")) + count = 0 for f in folders: + overall_rating_path = '' + for file_name in os.listdir(f.parent): + if file_name.startswith('Bewertungen-'): + overall_rating_path = os.path.join(f.parent, file_name) + break target = f.parent / 'korrekturen' target.mkdir() for handin in (x for x in f.iterdir() if x.name != '.DS_Store'): @@ -297,6 +296,10 @@ def finalise_grading(folder: pathlib.Path): glob = list(handin.glob('Bewertung *')) if len(glob) == 1: shutil.copy(glob[0], this_target) + # If the overall rating file is given, the points will be written in + if len(overall_rating_path) != 0: + student_name = handin.name.split('_')[0] + update_rating(overall_rating_path, glob[0], student_name) elif not glob: print(f" ! {handin.name}: no grading file") issues += 1 diff --git a/rating_table_adjuster.py b/rating_table_adjuster.py new file mode 100644 index 0000000..b02d558 --- /dev/null +++ b/rating_table_adjuster.py @@ -0,0 +1,52 @@ +__author__ = 'Lukas Horst' + +import csv + +import openpyxl + + +def get_points(file_path): + """Reads the given rating table and returns the total points""" + wb = openpyxl.load_workbook(file_path, data_only=True) + ws = wb['Sheet1'] + rows = ws.iter_rows(min_row=20, max_row=75, min_col=1, max_col=1) + row_of_sum = -1 + for i in rows: # searching the right row + if i[0].value == 'Summe': + row_of_sum = i[0].row + break + return ws[f'C{row_of_sum}'].value + + +def read_csv_file(file_path): + """Function to read a csv file and returns a list with each row in a dic""" + with open(file_path, mode='r', newline='', encoding='utf-8') as file: + reader = csv.DictReader(file) + rows = [] + for row in reader: + rows.append(row) + return rows + + +def write_csv_file(file_path, data): + """Function to (over)write a csv file with the given data""" + with open(file_path, mode='w', newline='', encoding='utf-8') as file: + fieldnames = list(data[0].keys()) + writer = csv.DictWriter(file, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(data) + + +def update_rating(overall_rating_path, student_rating_path, student_name): + """Function to update the points of the given student""" + csv_data = read_csv_file(overall_rating_path) + for row in csv_data: + if row['Vollständiger Name'] == student_name: + points = str(get_points(student_rating_path)).replace('.', ',') + row['Bewertung'] = points + write_csv_file(overall_rating_path, csv_data) + return + + +if __name__ == '__main__': + print(*read_csv_file('Test.csv'), sep='\n') diff --git a/violation_checker.py b/violation_checker.py index d6bc2e5..6153abc 100644 --- a/violation_checker.py +++ b/violation_checker.py @@ -5,129 +5,140 @@ import re class ViolationChecker: - w0311 = 0 # Bad indention - w0401 = 0 # Wildcard import - w0622 = 0 # Redefined builtin - c0103 = 0 # Invalid name - c0116 = 0 # Missing function or method docstring - c0114 = 0 # Missing module docstring - c0121 = 0 # Singleton-comparison - c0325 = 0 # Superfluous-parens - c0413 = 0 # Wrong import position - c2100 = 0 # Missing author variable - c2101 = 0 # Malformed author variable - c2102 = 0 # Incorrectly assigned author variable - e0001 = 0 # Syntax error - e0102 = 0 # Function redefined - e231 = 0 # Missing whitespace after ',' - e251 = 0 # Unexpected spaces around keyword / parameter equals - e261 = 0 # At least two spaces before inline comment - e265 = 0 # Block comment should start with '# ' - e271 = 0 # Multiple space after keyword - e302 = 0 # Expected 2 blank lines - e501 = 0 # Line too long > 99 - style_check = '' + violation_groups = [ + 'No deduction', + 'Global statements', + 'Imports', + 'Author variable', + 'Naming', + 'Docstring', + 'Spacing', + 'Classes', + 'Override', + 'Syntax' + ] + # {violation_name: [amount of the violation, description, violation_group]} + violations = {'W0104': [0, 'Pointless statement', 0], + 'W0201': [0, 'Attribute defined outside init', 7], + 'W0231': [0, 'Super init not called', 7], + 'W0232': [0, 'No init', 7], + 'W0301': [0, 'Unnecessary semicolon', 0], + 'W0311': [0, 'Bad indention', 6], + 'W0401': [0, 'Wildcard import', 2], + 'W0404': [0, 'Reimported', 2], + 'W0603': [0, 'Global statement', 1], + 'W0622': [0, 'Redefined builtin', 8], + 'W0702': [0, 'Bare except', 0], + 'W0705': [0, 'Duplicate except', 0], + 'W0706': [0, 'Try except raise', 0], + 'C0102': [0, 'Blacklisted name', 4], + 'C0103': [0, 'Invalid name', 4], + 'C0112': [0, 'Empty docstring', 5], + 'C0114': [0, 'Missing module docstring', 5], + 'C0115': [0, 'Missing class docstring', 5], + 'C0116': [0, 'Missing function or method docstring', 5], + 'C0121': [0, 'Singleton-comparison', 0], + 'C0144': [0, 'Non ascii name', 4], + 'C0321': [0, 'Multiple statements', 0], + 'C0325': [0, 'Superfluous-parens', 0], + 'C0410': [0, 'Multiple imports', 2], + 'C0411': [0, 'Wrong import order', 2], + 'C0412': [0, 'Ungrouped imports', 2], + 'C0413': [0, 'Wrong import position', 2], + 'C2100': [0, 'Missing author variable', 3], + 'C2101': [0, 'Malformed author variable', 3], + 'C2102': [0, 'Incorrectly assigned author variable', 3], + + 'E0001': [0, 'Syntax error', 9], + 'E0102': [0, 'Function redefined', 8], + 'E0211': [0, 'No Method argument', 7], + 'E201': [0, 'Whitespace after \'(\'', 6], + 'E202': [0, 'Whitespace before \')\'', 6], + 'E203': [0, 'Whitespace before \':\'', 6], + 'E211': [0, 'Whitespace before \'(\'', 6], + 'E221': [0, 'Multiple spaces before operator', 6], + 'E222': [0, 'Multiple spaces after operator', 6], + 'E223': [0, 'Tab before operator', 6], + 'E224': [0, 'Tab after operator', 6], + 'E225': [0, 'Missing whitespace around operator', 6], + 'E231': [0, 'Missing whitespace after \',\', \';\', or \':\'', 6], + 'E251': [0, 'Unexpected spaces around keyword / parameter equals', 6], + 'E261': [0, 'At least two spaces before inline comment', 6], + 'E262': [0, 'Inline comment should start with \'# \'', 6], + 'E265': [0, 'Block comment should start with \'# \'', 6], + 'E271': [0, 'Multiple space after keyword', 6], + 'E302': [0, 'Expected 2 blank lines', 6], + 'E501': [0, 'Line too long > 99', 6], + 'E502': [0, 'Backslash redundant between brackets', 0], + 'E713': [0, 'Negative membership test should use \'not in\'', 0], + 'E714': [0, 'Negative identity test should use \'is not\'', 0], + 'E721': [0, 'Use \'isinstance\' instead of comparing types', 0]} + style_check = '' def __init__(self, style_check): self.style_check = style_check - def check_violations(self): - w0311_violations = re.findall(r',*W0311.*', self.style_check) - self.w0311 = len(w0311_violations) - - w0401_violations = re.findall(r',*W0401.*', self.style_check) - self.w0401 = len(w0401_violations) - - w0622_violations = re.findall(r',*W0622.*', self.style_check) - self.w0622 = len(w0622_violations) - - c0103_violations = re.findall(r',*C0103.*', self.style_check) - self.c0103 = len(c0103_violations) - - c0114_violations = re.findall(r',*C0114.*', self.style_check) - self.c0114 = len(c0114_violations) - - c0116_violations = re.findall(r',*C0116.*', self.style_check) - self.c0116 = len(c0116_violations) - - c0121_violations = re.findall(r',*C0121.*', self.style_check) - self.c0121 = len(c0121_violations) - - c0325_violations = re.findall(r',*C0325.*', self.style_check) - self.c0325 = len(c0325_violations) - - c0413_violations = re.findall(r',*C0413.*', self.style_check) - self.c0413 = len(c0413_violations) - - c2100_violations = re.findall(r',*C2100.*', self.style_check) - self.c2100 = len(c2100_violations) - - c2101_violations = re.findall(r',*C2101.*', self.style_check) - self.c2101 = len(c2101_violations) - - c2102_violations = re.findall(r',*C2102.*', self.style_check) - self.c2102 = len(c2102_violations) - - e0001_violations = re.findall(r',*E0001.*', self.style_check) - self.e0001 = len(e0001_violations) - - e0102_violations = re.findall(r',*E0102.*', self.style_check) - self.e0102 = len(e0102_violations) - - e265_violations = re.findall(r',*E265.*', self.style_check) - self.e265 = len(e265_violations) - - e501_violations = re.findall(r',*E501.*', self.style_check) - self.e501 = len(e501_violations) - - e302_violations = re.findall(r',*E302.*', self.style_check) - self.e302 = len(e302_violations) - - e231_violations = re.findall(r',*E231.*', self.style_check) - self.e231 = len(e231_violations) - - e261_violations = re.findall(r',*E261.*', self.style_check) - self.e261 = len(e261_violations) - - e271_violations = re.findall(r',*E271.*', self.style_check) - self.e271 = len(e271_violations) - - e251_violations = re.findall(r',*E251.*', self.style_check) - self.e251 = len(e251_violations) - + """Method to search for all violations""" + for violation_name, _ in self.violations.items(): + all_violations = re.findall(rf',*{violation_name}.*', self.style_check) + self.violations[violation_name][0] = len(all_violations) def list_violation(self): - violations = '' - violations += (f'W03111 (Bad indention): {self.w0311}' - f'\nW0401 (Wildcard import): {self.w0401}' - f'\nW0622 (Redefined builtin): {self.w0622}' - f'\nC0103 (Invalid name): {self.c0103}' - f'\nC0114 (Missing module docstring): {self.c0114}' - f'\nC0116 (Missing function or method docstring): {self.c0116}' - f'\nC0121 (Singleton-comparison): {self.c0121}' - f'\nC0325 (Superfluous-parens): {self.c0325}' - f'\nC0413 (Wrong import position): {self.c0413}' - f'\nC2100 (Missing author variable): {self.c2100}' - f'\nC2101 (Missing author variable): {self.c2101}' - f'\nC2102 (Incorrectly assigned author variable): {self.c2102}' - f'\nE0001 (Syntax error): {self.e0001}' - f'\nE0102 (Function redefined): {self.e0102}' - f'\nE231 (Missing whitespace after \',\'): {self.e231}' - f'\nE251 (Unexpected spaces around keyword / parameter equals): {self.e251}' - f'\nE261 (At least two spaces before inline comment): {self.e261}' - f'\nE265 (Block comment should start with \'# \'): {self.e265}' - f'\nE271 (Multiple space after keyword): {self.e271}' - f'\nE302 (Expected 2 blank lines): {self.e302}' - f'\nE501 (Line too long > 99): {self.e501}') - return violations + """Method to return a list with all violations and the amount of the violations sort by + groups""" + violation_string = '' + violation_groups_strings = [] + for i in range(10): + violation_groups_strings.append('') + for violation_name, value in self.violations.items(): + violation_groups_strings[value[2]] += f'{violation_name} ({value[1]}): {value[0]}\n' + for i, violation_group in enumerate(self.violation_groups): + if i == 0: continue + violation_string += f'-----{violation_group}-----\n{violation_groups_strings[i]}' + violation_amount = self.count_violations(i) + violation_string += (f'\nFehler Insgesamt: {violation_amount} Abzug: ' + f'{self.count_deduction(i, violation_amount)} Punkt(e)\n\n') + if i == 9: + violation_string += f'-----No deduction-----\n{violation_groups_strings[0]}' + violation_string += f'\nFehler Insgesamt: {violation_amount} Abzug: 0 Punkte' + return violation_string + def count_violations(self, violation_group): + """Method to count all violations""" + all_violations = 0 + if violation_group == -1: + for _, value in self.violations.items(): + all_violations += value[0] + else: + for _, value in self.violations.items(): + if value[2] == violation_group: + all_violations += value[0] + return all_violations -if __name__ == '__main__': - with open('G02_Voll/abgaben/Cynthia Celoudis_691452_assignsubmission_file/stylecheck.txt', 'r', - encoding='utf-8') as file: - file_content = file.read() - violation_checker = ViolationChecker(file_content) - violation_checker.check_violations() - print(violation_checker.list_violation()) + def count_deduction(self, violation_group, violation_amount): + """Method to count the deduction based on the group and amount""" + if violation_group == 0: + return 0 + elif violation_group == 3: + if violation_amount > 0: + # The deduction for the author variable + return 2 + else: + return 0 + elif violation_group == 5: + # Violation for docstrings with a max deduction + return min(violation_amount*0.5, 2) + else: + # Cause group 6 is bigger it can get a higher deduction than 0.5 + if violation_group == 6 and violation_amount > 30: + return 1 + elif violation_group == 6 and violation_amount > 20: + return 0.75 + elif violation_amount > 9: + return 0.5 + elif violation_amount > 1: + return 0.25 + else: + return 0