diff options
author | jwansek <eddie.atten.ea29@gmail.com> | 2022-04-27 18:31:52 +0100 |
---|---|---|
committer | jwansek <eddie.atten.ea29@gmail.com> | 2022-04-27 18:31:52 +0100 |
commit | 2c891af807fd46b72a539ee99040dee18b3cd6e3 (patch) | |
tree | b10c83596e8d157d8e7cbdfd57797d2b4bd15505 | |
parent | 24dbd9f360be1a533bb33e2506a88fd9af85eac2 (diff) | |
download | Smarker-2c891af807fd46b72a539ee99040dee18b3cd6e3.tar.gz Smarker-2c891af807fd46b72a539ee99040dee18b3cd6e3.zip |
Added some sql stuff, fixed bugs with pdf rendering
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Dockerfile | 3 | ||||
-rw-r--r-- | ExampleAssessments/example.yml | 5 | ||||
-rw-r--r-- | ExampleSubmission/example.py | 4 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | Smarker/assessments.py | 96 | ||||
-rw-r--r-- | Smarker/database.py | 66 | ||||
-rw-r--r-- | Smarker/misc_classes.py | 8 | ||||
-rw-r--r-- | Smarker/requirements.txt | 1 | ||||
-rw-r--r-- | Smarker/smarker.py | 39 |
10 files changed, 200 insertions, 28 deletions
@@ -1,6 +1,8 @@ +out/ *_report.* *.zip smarker.conf +*.aux # Byte-compiled / optimized / DLL files __pycache__/ @@ -3,7 +3,8 @@ MAINTAINER Eden Attenborough "gae19jtu@uea.ac.uk" ENV TZ=Europe/London RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt-get update -y -RUN apt-get install -y python3-pip python3-dev build-essential texlive-base wkhtmltopdf +RUN apt-get install -y python3-pip python3-dev build-essential texlive-latex-base texlive-pictures texlive-science texlive-latex-extra wkhtmltopdf +RUN mkdir /out COPY ./Smarker /Smarker WORKDIR /Smarker RUN pip3 install -r requirements.txt diff --git a/ExampleAssessments/example.yml b/ExampleAssessments/example.yml index ec09561..c7695d4 100644 --- a/ExampleAssessments/example.yml +++ b/ExampleAssessments/example.yml @@ -1,4 +1,4 @@ -name: CMP-5021B-19-20
+name: example
files:
- example.py:
classes:
@@ -55,5 +55,4 @@ produced_files: - animals.txt
dependencies:
files:
- - ../wsData.txt
- - ../IncludeDirectory
\ No newline at end of file + - ../wsData.txt
\ No newline at end of file diff --git a/ExampleSubmission/example.py b/ExampleSubmission/example.py index 07f21d4..d6f7015 100644 --- a/ExampleSubmission/example.py +++ b/ExampleSubmission/example.py @@ -1,11 +1,11 @@ # Eden Attenborough
# 12-01-21
-import tkinter as tk
+#import tkinter as tk
from dataclasses import dataclass
import sys
-class Application(tk.Tk):
+class Application:
"""An example class, which implements a GUI by inheriting from tkinter.Tk
"""
def __init__(self, *args, **kwargs):
@@ -3,4 +3,6 @@ An automated marking system for UEA programming assessments ## Running in docker -`sudo docker run -v "$(pwd)/../wsData.txt":/wsData.txt -v "$(pwd)/ExampleAssessments/CMP-4009B.yml":/tmp/CMP-4009.yml -v "$(pwd)/../ExampleSubmissions/123456789.zip":/tmp/123456789.zip -e submission=/tmp/123456789.zip -e assessment=/tmp/CMP-4009.yml -e SMARKERDEPS=matplotlib smarker` +`sudo docker run -v "$(pwd)/../wsData.txt":/wsData.txt -v "$(pwd)/100301654.zip":/tmp/100301654.zip -v "$(pwd)/out/":/out/ -e submission=/tmp/100301654.zip -e assessment=example -e SMARKERDEPS=matplotlib -e format=pdf -e output=/out/100301654.pdf --rm smarker` + +`sudo docker run -it --entrypoint python --rm smarker assessments.py --list yes`
\ No newline at end of file diff --git a/Smarker/assessments.py b/Smarker/assessments.py new file mode 100644 index 0000000..d5c8daf --- /dev/null +++ b/Smarker/assessments.py @@ -0,0 +1,96 @@ +import misc_classes +import configparser +import jinja_helpers +import database +import argparse +import yaml +import os + +if __name__ == "__main__": + config = configparser.ConfigParser() + config.read(os.path.join(os.path.split(__file__)[0], "smarker.conf")) + + parser = argparse.ArgumentParser() + parser.add_argument( + "-l", "--list", + action = misc_classes.EnvDefault, + envvar = "list", + help = "List assessment names, number enrolled, and number of files", + required = False + ) + parser.add_argument( + "-c", "--create_assessment", + action = misc_classes.EnvDefault, + envvar = "create_assessment", + help = "Path to an assessment .yml file", + required = False + ) + parser.add_argument( + "-rm", "--remove_assessment", + action = misc_classes.EnvDefault, + envvar = "remove_assessment", + help = "Name of an assessment to remove", + required = False + ) + parser.add_argument( + "-e", "--number_enrolled", + action = misc_classes.EnvDefault, + envvar = "number_enrolled", + help = "Number of students enrolled onto an assessment. Required argument to create.", + required = False, + type = int + ) + parser.add_argument( + "-s", "--create_student", + action = misc_classes.EnvDefault, + envvar = "create_student", + help = "Add a student in the form e.g. 123456789,Eden,Attenborough,E.Attenborough@uea.ac.uk", + required = False + ) + + for option in config.options("mysql"): + parser.add_argument( + "--%s_%s" % ("mysql", option), + action = misc_classes.EnvDefault, + envvar = "--%s_%s" % ("mysql", option), + default = config.get("mysql", option), + help = "Optional argument inherited from config file. Read smarker.conf for details." + ) + + args = vars(parser.parse_args()) + + with database.SmarkerDatabase( + args["mysql_host"], args["mysql_user"], args["mysql_passwd"], + "Smarker", int(args["mysql_port"])) as db: + + if args["create_assessment"] is not None: + with open(args["create_assessment"], "r") as f: + assessment = yaml.safe_load(f) + + db.create_assessment( + assessment["name"], + yaml.dump(assessment), + args["number_enrolled"], + jinja_helpers.flatten_struct(assessment["files"]).keys() + ) + + print("Added assessment %s..." % assessment["name"]) + + if args["remove_assessment"] is not None: + db.remove_assessment(args["remove_assessment"]) + + print("Removed %s..." % args["remove_assessment"]) + + if args["list"] is not None: + if args["list"] in ["True", "yes"]: + print(yaml.dump(db.get_assessments(), indent = 4)) + + if args["create_student"] is not None: + sid, name, email = args["create_student"].split(",") + db.add_student(int(sid), name, email) + + print("Added student %s" % name) + + + # print(db.get_assessment_yaml("CMP-4009B-2020-A2")) + diff --git a/Smarker/database.py b/Smarker/database.py index a0e3640..f17c285 100644 --- a/Smarker/database.py +++ b/Smarker/database.py @@ -1,5 +1,6 @@ from dataclasses import dataclass import pymysql +import yaml @dataclass class SmarkerDatabase: @@ -12,7 +13,8 @@ class SmarkerDatabase: def __enter__(self): try: self.__connection = self.__get_connection() - except pymysql.err.OperationalError as e: + except Exception as e: + print(e.args[1]) if e.args[0] == 1049: self.__build_db() return self @@ -62,7 +64,7 @@ class SmarkerDatabase: student_no VARCHAR(10) NOT NULL, assessment_name VARCHAR(30) NOT NULL, submission_dt DATETIME NOT NULL default CURRENT_TIMESTAMP, - submission_zip_path TEXT NOT NULL, + report_yaml TEXT NOT NULL, FOREIGN KEY (student_no) REFERENCES students(student_no), FOREIGN KEY (assessment_name) REFERENCES assessment(assessment_name) ); @@ -79,14 +81,13 @@ class SmarkerDatabase: CREATE TABLE submitted_files( submission_id INT UNSIGNED NOT NULL, file_id INT UNSIGNED NOT NULL, - file_text TEXT NOT NULL, + file_text TEXT NULL, FOREIGN KEY (submission_id) REFERENCES submissions(submission_id), FOREIGN KEY (file_id) REFERENCES assessment_file(file_id), PRIMARY KEY(submission_id, file_id) ); """) - self.__connection.commit() return self.__connection @@ -95,3 +96,60 @@ class SmarkerDatabase: with self.__connection.cursor() as cursor: cursor.execute("SHOW TABLES;") return cursor.fetchall() + + def create_assessment(self, name, yaml_f, num_enrolled, files): + with self.__connection.cursor() as cursor: + cursor.execute("INSERT INTO assessment VALUES (%s, %s, %s);", ( + name, yaml_f, num_enrolled + )) + + for file_ in files: + cursor.execute("INSERT INTO assessment_file (assessment_name, file_name) VALUES (%s, %s);", ( + name, file_ + )) + self.__connection.commit() + + def remove_assessment(self, name): + with self.__connection.cursor() as cursor: + cursor.execute("DELETE FROM assessment_file WHERE assessment_name = %s;", (name, )) + cursor.execute("DELETE FROM assessment WHERE assessment_name = %s;", (name, )) + self.__connection.commit() + + def get_assessments(self): + with self.__connection.cursor() as cursor: + cursor.execute(""" + SELECT assessment.assessment_name, num_enrolled, COUNT(assessment.assessment_name) + FROM assessment + INNER JOIN assessment_file + ON assessment.assessment_name = assessment_file.assessment_name + GROUP BY assessment.assessment_name; + """) + return cursor.fetchall() + + def get_assessment_yaml(self, name): + with self.__connection.cursor() as cursor: + cursor.execute("SELECT yaml_path FROM assessment WHERE assessment_name = %s;", (name, )) + return yaml.safe_load(cursor.fetchone()[0]) + + def add_student(self, student_id, name, email): + with self.__connection.cursor() as cursor: + cursor.execute("INSERT INTO students VALUES (%s, %s, %s);", + (student_id, name, email)) + self.__connection.commit() + + def add_submission(self, student_id, assessment_name, report_yaml, files): + with self.__connection.cursor() as cursor: + cursor.execute("INSERT INTO submissions (student_no, assessment_name, report_yaml) VALUES (%s, %s, %s);", ( + student_id, assessment_name, report_yaml + )) + submission_id = cursor.lastrowid + + for file_name, file_contents in files: + cursor.execute(""" + INSERT INTO submitted_files + (submission_id, file_id, file_text) + VALUES (%s, (SELECT file_id FROM assessment_file WHERE file_name = %s), %s); + """, ( + submission_id, file_name, file_contents + )) + diff --git a/Smarker/misc_classes.py b/Smarker/misc_classes.py index 7ab37de..d5dd7e1 100644 --- a/Smarker/misc_classes.py +++ b/Smarker/misc_classes.py @@ -23,9 +23,11 @@ latex_jinja_env = jinja2.Environment( # https://stackoverflow.com/questions/10551117/setting-options-from-environment-variables-when-using-argparse
class EnvDefault(argparse.Action):
def __init__(self, envvar, required=True, default=None, **kwargs):
- if not default and envvar:
- if envvar in os.environ:
- default = os.environ[envvar]
+ # if not default and envvar:
+ # if envvar in os.environ:
+ # default = os.environ[envvar]
+ if envvar in os.environ:
+ default = os.environ[envvar]
if required and default:
required = False
super(EnvDefault, self).__init__(default=default, required=required,
diff --git a/Smarker/requirements.txt b/Smarker/requirements.txt index 3831b5e..944b77a 100644 --- a/Smarker/requirements.txt +++ b/Smarker/requirements.txt @@ -8,3 +8,4 @@ pytest junit2html
pdfkit
lxml
+pymysql
diff --git a/Smarker/smarker.py b/Smarker/smarker.py index 46feb5f..8b8527e 100644 --- a/Smarker/smarker.py +++ b/Smarker/smarker.py @@ -3,6 +3,7 @@ import jinja_helpers import configparser import misc_classes import subprocess +import database import argparse import tempfile import zipfile @@ -17,8 +18,11 @@ def main(**kwargs): student_no = os.path.splitext(os.path.split(args["submission"])[-1])[0] with misc_classes.ExtractZipToTempDir(args["submission"]) as submission_files: - with open(kwargs["assessment"], "r") as f: - assessment_struct = yaml.safe_load(f) + with database.SmarkerDatabase( + kwargs["mysql_host"], kwargs["mysql_user"], kwargs["mysql_passwd"], + "Smarker", int(kwargs["mysql_port"])) as db: + + assessment_struct = db.get_assessment_yaml(kwargs["assessment"]) with misc_classes.FileDependencies(assessment_struct): output = reflect.gen_reflection_report(submission_files, assessment_struct, student_no, kwargs, args["submission"]) @@ -46,19 +50,26 @@ def main(**kwargs): if output_file == "auto": output_file = "%s_report.%s" % (student_no, kwargs["format"]) - with open(output_file, "w") as f: - f.write(strout) - if kwargs["format"] == "pdf": os.environ["TEXINPUTS"] = os.path.join(os.path.split(__file__)[0], "python-latex-highlighting") + ":" - os.rename(output_file, os.path.splitext(output_file)[0] + ".tex") + output_file = os.path.splitext(output_file)[0] + ".tex" + with open(output_file, "w") as f: + f.write(strout) subprocess.run(["pdflatex", output_file]) - - os.remove(os.path.splitext(output_file)[0] + ".tex") - os.remove(os.path.splitext(output_file)[0] + ".log") - os.remove(os.path.splitext(output_file)[0] + ".aux") + subprocess.run(["mv", os.path.splitext(os.path.split(output_file)[-1])[0] + ".pdf", os.path.split(output_file)[0]]) + + if os.path.exists(os.path.splitext(output_file)[0] + ".tex"): + os.remove(os.path.splitext(output_file)[0] + ".tex") + if os.path.exists(os.path.splitext(output_file)[0] + ".log"): + os.remove(os.path.splitext(output_file)[0] + ".log") + if os.path.exists(os.path.splitext(output_file)[0] + ".aux"): + os.remove(os.path.splitext(output_file)[0] + ".aux") + + else: + with open(output_file, "w") as f: + f.write(strout) # input("\n\n[tempdir: %s] Press any key to close..." % tempdir) @@ -71,7 +82,7 @@ if __name__ == "__main__": "-a", "--assessment", action = misc_classes.EnvDefault, envvar = "assessment", - help = "Path to an assessment .yml file", + help = "Name of the assessment", # type = os.path.abspath, required = True ) @@ -86,18 +97,18 @@ if __name__ == "__main__": parser.add_argument( "-f", "--format", action = misc_classes.EnvDefault, + default = "txt", envvar = "format", help = "Output format type", type = str, choices = ["yaml", "json", "pdf"] + [os.path.splitext(f)[0] for f in os.listdir(os.path.join(os.path.split(__file__)[0], "templates"))], - default = "txt" ) parser.add_argument( "-o", "--out", action = misc_classes.EnvDefault, - envvar = "out", - help = "Path to write the output to, or, by default write to stdout. 'auto' automatically generates a file name.", default = "stdout", + envvar = "output", + help = "Path to write the output to, or, by default write to stdout. 'auto' automatically generates a file name.", ) for section in config.sections(): |