diff options
Diffstat (limited to 'Smarker')
| -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 | 
5 files changed, 189 insertions, 21 deletions
| 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(): | 
