From 674c90a8b386dd2d5600e87a50eba28507cb3ade Mon Sep 17 00:00:00 2001
From: Tue Herlau <tuhe@dtu.dk>
Date: Wed, 25 Oct 2023 16:39:55 +0200
Subject: [PATCH] Updating with latest changes

---
 requirements.txt                    |  4 +--
 setup.py                            |  4 +--
 src/unitgrade.egg-info/PKG-INFO     | 16 ++++++++-
 src/unitgrade.egg-info/requires.txt |  4 +--
 src/unitgrade/evaluate.py           | 14 ++++++++
 src/unitgrade/framework.py          | 44 +++++++++++++++++++++--
 src/unitgrade/utils.py              | 55 ++++++++++++++++++++---------
 src/unitgrade/version.py            |  2 +-
 8 files changed, 115 insertions(+), 28 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index 777526b..4769c7f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,5 @@
 pandas
 coverage
-pyfiglet
 openpyxl
 tabulate
 compress_pickle
@@ -17,4 +16,5 @@ diskcache               # dashboard
 watchdog                # dashboard
 flask_socketio          # dashboard
 flask                   # dashboard
-Werkzeug>=2.2.0         # dashboard
+Werkzeug>=2.3.0         # dashboard. Bumping it one more bc. of other issue with Anaconda.
+pyfiglet<1.0.0
diff --git a/setup.py b/setup.py
index 6b001a3..5579571 100644
--- a/setup.py
+++ b/setup.py
@@ -38,8 +38,8 @@ setuptools.setup(
     packages=setuptools.find_packages(where="src"),
     python_requires=">=3.8",
     license="MIT",
-    install_requires=['numpy', 'tabulate', "pyfiglet", "coverage", "colorama", 'tqdm', 'importnb', 'requests', "pandas",
-                      'watchdog', 'flask_socketio', 'flask', 'Werkzeug', 'diskcache', # These are for the dashboard.
+    install_requires=['numpy', 'tabulate', "pyfiglet<1.0.0", "coverage", "colorama", 'tqdm', 'importnb', 'requests', "pandas",
+                      'watchdog', 'flask_socketio', 'flask', 'Werkzeug>=2.3.0', 'diskcache', # These are for the dashboard.
                       ],
     include_package_data=True,
     package_data={'': ['dashboard/static/*', 'dashboard/templates/*'],},  # so far no Manifest.in.
diff --git a/src/unitgrade.egg-info/PKG-INFO b/src/unitgrade.egg-info/PKG-INFO
index e790654..10eda57 100644
--- a/src/unitgrade.egg-info/PKG-INFO
+++ b/src/unitgrade.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: unitgrade
-Version: 1.0.0.0
+Version: 1.0.0.7
 Summary: A student homework/exam evaluation framework build on pythons unittest framework.
 Home-page: https://lab.compute.dtu.dk/tuhe/unitgrade
 Author: Tue Herlau
@@ -13,6 +13,20 @@ Classifier: Operating System :: OS Independent
 Requires-Python: >=3.8
 Description-Content-Type: text/markdown
 License-File: LICENSE
+Requires-Dist: numpy
+Requires-Dist: tabulate
+Requires-Dist: pyfiglet<1.0.0
+Requires-Dist: coverage
+Requires-Dist: colorama
+Requires-Dist: tqdm
+Requires-Dist: importnb
+Requires-Dist: requests
+Requires-Dist: pandas
+Requires-Dist: watchdog
+Requires-Dist: flask_socketio
+Requires-Dist: flask
+Requires-Dist: Werkzeug>=2.3.0
+Requires-Dist: diskcache
 
 # Unitgrade
 Unitgrade is an autograding framework which enables instructors to offer automatically evaluated programming assignments in a maximally convenient format for the students.
diff --git a/src/unitgrade.egg-info/requires.txt b/src/unitgrade.egg-info/requires.txt
index 6987ca6..249fdc2 100644
--- a/src/unitgrade.egg-info/requires.txt
+++ b/src/unitgrade.egg-info/requires.txt
@@ -1,6 +1,6 @@
 numpy
 tabulate
-pyfiglet
+pyfiglet<1.0.0
 coverage
 colorama
 tqdm
@@ -10,5 +10,5 @@ pandas
 watchdog
 flask_socketio
 flask
-Werkzeug
+Werkzeug>=2.3.0
 diskcache
diff --git a/src/unitgrade/evaluate.py b/src/unitgrade/evaluate.py
index eb8233a..c29657d 100644
--- a/src/unitgrade/evaluate.py
+++ b/src/unitgrade/evaluate.py
@@ -244,9 +244,23 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     return results, table_data
 
 
+def python_code_binary_id(python_code):
+    """
+    Return an unique id of this python code assuming it is in a binary encoding. This is similar to the method below,
+    but the method below removes docstrings and comments (and take a str as input). I have opted not to do that since
+    it mess up encoding on clients computers -- so we just digest everything.
+
+    :param python_code:
+    :return:
+    """
+    hash_object = hashlib.blake2b(python_code)
+    return hash_object.hexdigest()
+
+
 def python_code_str_id(python_code, strip_comments_and_docstring=True):
     s = python_code
 
+    print(s)
     if strip_comments_and_docstring:
         try:
             s = remove_comments_and_docstrings(s)
diff --git a/src/unitgrade/framework.py b/src/unitgrade/framework.py
index 404d307..4529544 100644
--- a/src/unitgrade/framework.py
+++ b/src/unitgrade/framework.py
@@ -18,6 +18,7 @@ from unitgrade.runners import UTextResult
 from unitgrade.utils import gprint, Capturing2, Capturing
 from unitgrade.artifacts import StdCapturing
 from unitgrade.utils import DKPupDB
+import platform
 
 
 colorama.init(autoreset=True)  # auto resets your settings after every output
@@ -40,7 +41,6 @@ class classmethod_dashboard(classmethod):
             r = np.random.randint(1000 * 1000)
             db.set('run_id', r)
             db.set('coverage_files_changed', None)
-
             state_ = 'fail'
             try:
                 _stdout = sys.stdout
@@ -74,6 +74,7 @@ class classmethod_dashboard(classmethod):
 
         super().__init__(dashboard_wrap)
 
+
 class Report:
     title = "report title"
     abbreviate_questions = False # Should the test items start with 'Question ...' or just be q1).
@@ -128,12 +129,49 @@ class Report:
         else:
             root_dir = self.pack_imports[0].__file__
 
+        self_file = self._file()
+
+        root_dir_0 = root_dir # Backup for pretty printing.
+        self_file_0 = self_file
+        if platform.system() == "Windows":
+            # Windows does not distinguish upper/lower case paths. We convert all of them to lower case for simplicity.
+            self_file = self_file.lower()
+            root_dir = root_dir.lower()
+
         root_dir = os.path.dirname(root_dir)
-        relative_path = os.path.relpath(self._file(), root_dir)
+        relative_path = os.path.relpath(self_file, root_dir)
         modules = os.path.normpath(relative_path[:-3]).split(os.sep)
         relative_path = relative_path.replace("\\", "/")
         if relative_path.startswith(".."):
-            raise Exception("Bad relative path. setup failed. ", root_dir, self._file())
+            error = """
+    --------------------------------------------------------------------------------------            
+    Oh no, you got an installation problem!
+    
+    You have accidentally downloaded (and installed) the course software in two locations on your computer.
+    The first place is: 
+    
+    > %s
+    
+    And the second place is the location that contains this file, namely:
+    
+    > %s 
+    
+    I can't be certain which of these two contains your actual homework, so therefore I have to give you an error :-(. 
+    
+    But it is easy to fix! Determine which of the two folders contain your homework and simply delete the other one. That
+    should take care of the problem!
+    (The terminal in VS Code will tell you the location on your computer you have open right now -- most likely, that is the 
+    location of the right 02002student folder!).
+    
+    In the future, try to avoid downloading and installing the course software many times -- most issues 
+    can be solved in a much simpler way.
+    
+    If this problem persists, please contact us on piazza, discord, or directly on tuhe@dtu.dk (or come by my office, building 321, room 127).
+    Include a copy of this error and a screenshot of VS Code.
+            """%(root_dir_0, self_file_0)
+            print(error)
+            sys.exit(1)
+            raise Exception(error)
 
         return root_dir, relative_path, modules
 
diff --git a/src/unitgrade/utils.py b/src/unitgrade/utils.py
index e687f07..712297a 100644
--- a/src/unitgrade/utils.py
+++ b/src/unitgrade/utils.py
@@ -282,10 +282,6 @@ def hash_string(s):
     Right now it is used in the function in 02002 when a creating the index of student-downloadable evaluations."""
     # gfg = hashlib.blake2b()
     return hashlib.blake2b(s.encode("utf-8")).hexdigest()
-    # return base64.b64encode(b).decode("utf-8")
-
-    # gfg.update(s.encode("utf-8"))
-    # return gfg.digest()
 
 def hash2url(hash):
     return hash[:16]
@@ -351,16 +347,50 @@ def checkout_remote_results(remote_url, manifest):
                 html = response.read().decode()
                 # print( html )
             break
+    # if debug:
+    # url = f"https://cp.pages.compute.dtu.dk/02002public/_static/evaluation/project_evaluations_2023fall/project0/student_html/5a2db54fcce2f3ee/index.html"
+    # with urllib.request.urlopen(url) as response:
+    #     html = response.read().decode()
 
     if html is not None:
-        import pandas as pd
-        dfs = pd.read_html(html)
-        df = dfs[0]
+        try:
+            from xml.etree import ElementTree as ET
+            s = html[html.find("<table"):html.find("</table>")+len("</table>")]
+            table = ET.XML(s)
+            head = list(iter(table))[0]
+            body = list(iter(table))[1]
+            from collections import defaultdict
+            dd = defaultdict(list)
+            keys = []
+            for tr in head:
+                for td in tr:
+                    keys.append(td.text)
+
+            for tr in body:
+                for k, td in enumerate(tr):
+                    dd[keys[k]].append(td.text)
+        except Exception as e:
+            print("Sorry results not parsed. Perhaps bad file upload?")
+            keys = ["Key", "Values"]
+            dd = {keys[0]: ['Results were not parsed', 'Score'], keys[1]: ['Results were not readable. Contact a TA', 0]}
+
+        # rows = iter(table)
+        # for r in list(iter(table)):
+        #     print(r)
+        # import tabulate
+        # print( tabulate.tabulate(dd, headers="keys") )
+        # headers = [col.text for col in next(rows)]
+        # for row in rows:
+        #     values = [col.text for col in row]
+        # print(dict(zip(headers, values)))
+        # dd[keys[1]][-1]
+        # dfs = pd.read_html(html)
+        # df = dfs[0]
         # df.__format__()
         # tabular
         # print( df.to_string(index=False) )
         # df.as
-        result = dict(html=html, df=df, score=float( df.iloc[2].to_list()[-1] ), url=url)
+        result = dict(html=html, dict=dd, score=float( dd[keys[1]][-1] ), url=url)
     else:
         result=dict(html=html)
 
@@ -368,8 +398,6 @@ def checkout_remote_results(remote_url, manifest):
 
 
 
-
-
 ## Key/value store related.
 class DKPupDB:
     DISABLE_DB = False
@@ -433,10 +461,3 @@ class DKPupDB:
         return item in self.dk[self.name_] #keys()
         # return item in self.dk
 
-# if __name__ == "__main__":
-#     url = "https://cp.pages.compute.dtu.dk/02002public/_static/evaluation/"
-#     manifest = """
-# /home/tuhe/Documents/unitgrade_private/src/unitgrade_private/pipelines/tmp/students/cp/project0/Project0_handin_0_of_10.token 7720b41ab925098956c7db37c8292ce3a7b4ded96f4442234dee493c021fc5f7294e543de78630aaf873b756d25bf7b4fd7eb6e66cec282b54f0c35b83e9071f
-# """
-#     # checkout_remote_results(url, manifest = manifest)
-
diff --git a/src/unitgrade/version.py b/src/unitgrade/version.py
index d566769..2d01970 100644
--- a/src/unitgrade/version.py
+++ b/src/unitgrade/version.py
@@ -1 +1 @@
-__version__ = "1.0.0.0"
+__version__ = "1.0.0.8"
-- 
GitLab