diff --git a/.idea/workspace.xml b/.idea/workspace.xml index bc3d85955d18a4ee1dc98c5d31e88d2467b8f0b7..0c5ec36c45a0282c51b1e1a0e061dffedc0d043f 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,10 +2,11 @@ <project version="4"> <component name="ChangeListManager"> <list default="true" id="5c346737-53d8-4e87-88c5-7be2c8e7baeb" name="Default" comment=""> - <change afterPath="$PROJECT_DIR$/Report1_resources_do_not_hand_in.dat" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> - <change beforePath="$PROJECT_DIR$/MANIFEST.in" beforeDir="false" afterPath="$PROJECT_DIR$/MANIFEST.in" afterDir="false" /> - <change beforePath="$PROJECT_DIR$/setup.py" beforeDir="false" afterPath="$PROJECT_DIR$/setup.py" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/cs101courseware_example/Report1_resources_do_not_hand_in.dat" beforeDir="false" afterPath="$PROJECT_DIR$/cs101courseware_example/Report1_resources_do_not_hand_in.dat" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/cs101courseware_example/cs101report1_grade.py" beforeDir="false" afterPath="$PROJECT_DIR$/cs101courseware_example/cs101report1_grade.py" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/cs101courseware_example/cs101report2_grade.py" beforeDir="false" afterPath="$PROJECT_DIR$/cs101courseware_example/cs101report2_grade.py" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/unitgrade/unitgrade_helpers.py" beforeDir="false" afterPath="$PROJECT_DIR$/unitgrade/unitgrade_helpers.py" afterDir="false" /> </list> <option name="SHOW_DIALOG" value="false" /> <option name="HIGHLIGHT_CONFLICTS" value="true" /> @@ -140,7 +141,7 @@ <recent name="C:\Users\tuhe\Documents\unitgrade_private" /> </key> </component> - <component name="RunManager" selected="Python.deploy_cs101"> + <component name="RunManager" selected="Python.cs101report2"> <configuration name="cs101report1" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true"> <module name="unitgrade" /> <option name="INTERPRETER_OPTIONS" value="" /> @@ -162,7 +163,7 @@ <option name="INPUT_FILE" value="" /> <method v="2" /> </configuration> - <configuration name="cs101report2_grade" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> + <configuration name="cs101report2" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> <module name="unitgrade" /> <option name="INTERPRETER_OPTIONS" value="" /> <option name="PARENT_ENVS" value="true" /> @@ -174,7 +175,7 @@ <option name="IS_MODULE_SDK" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" /> - <option name="SCRIPT_NAME" value="$PROJECT_DIR$/../unitgrade_private/cs101courseware/cs101report2_grade.py" /> + <option name="SCRIPT_NAME" value="$PROJECT_DIR$/../unitgrade_private/cs101courseware/cs101report2.py" /> <option name="PARAMETERS" value="" /> <option name="SHOW_COMMAND_LINE" value="false" /> <option name="EMULATE_TERMINAL" value="false" /> @@ -269,19 +270,19 @@ </configuration> <list> <item itemvalue="Python.cs101report1" /> - <item itemvalue="Python.cs101report2_grade" /> <item itemvalue="Python.submission_autograder" /> <item itemvalue="Python.shopSmart" /> <item itemvalue="Python.fruit_project" /> <item itemvalue="Python.deploy_cs101" /> + <item itemvalue="Python.cs101report2" /> </list> <recent_temporary> <list> + <item itemvalue="Python.cs101report2" /> <item itemvalue="Python.deploy_cs101" /> <item itemvalue="Python.fruit_project" /> <item itemvalue="Python.shopSmart" /> <item itemvalue="Python.submission_autograder" /> - <item itemvalue="Python.cs101report2_grade" /> </list> </recent_temporary> </component> @@ -350,16 +351,16 @@ <line>28</line> <option name="timeStamp" value="63" /> </line-breakpoint> - <line-breakpoint enabled="true" suspend="THREAD" type="python-line"> - <url>file://$PROJECT_DIR$/unitgrade/unitgrade.py</url> - <line>145</line> - <option name="timeStamp" value="85" /> - </line-breakpoint> <line-breakpoint enabled="true" suspend="THREAD" type="python-line"> <url>file://$PROJECT_DIR$/unitgrade/unitgrade.py</url> <line>54</line> <option name="timeStamp" value="89" /> </line-breakpoint> + <line-breakpoint enabled="true" suspend="THREAD" type="python-line"> + <url>file://$PROJECT_DIR$/unitgrade/unitgrade_helpers.py</url> + <line>62</line> + <option name="timeStamp" value="90" /> + </line-breakpoint> </breakpoints> </breakpoint-manager> </component> diff --git a/Report1_resources_do_not_hand_in.dat b/Report1_resources_do_not_hand_in.dat deleted file mode 100644 index 8a6e89d1b0a9df89ef3999538619b344fd90c88f..0000000000000000000000000000000000000000 Binary files a/Report1_resources_do_not_hand_in.dat and /dev/null differ diff --git a/cs101courseware_example/Report1_resources_do_not_hand_in.dat b/cs101courseware_example/Report1_resources_do_not_hand_in.dat index 8a6e89d1b0a9df89ef3999538619b344fd90c88f..4ee120f4b7d596130d45560d99302cdbf38c443c 100644 Binary files a/cs101courseware_example/Report1_resources_do_not_hand_in.dat and b/cs101courseware_example/Report1_resources_do_not_hand_in.dat differ diff --git a/cs101courseware_example/cs101report1_grade.py b/cs101courseware_example/cs101report1_grade.py index 24709b7184c48858c8e9b9b51a8046e797f26a78..3b3f62dd0f825a4a72b7788a825e8912b3706ee8 100644 --- a/cs101courseware_example/cs101report1_grade.py +++ b/cs101courseware_example/cs101report1_grade.py @@ -1,189 +1,189 @@ '''WARNING: Modifying, decompiling or otherwise tampering with this script, it's data or the resulting .token file will be investigated as a cheating attempt.''' import numpy as np -WebLqOFrgMVsHPQuIXjh=str -WebLqOFrgMVsHPQuIXjT=None -WebLqOFrgMVsHPQuIXjn=int -WebLqOFrgMVsHPQuIXjJ=False -WebLqOFrgMVsHPQuIXjl=print -WebLqOFrgMVsHPQuIXjc=len -WebLqOFrgMVsHPQuIXjG=enumerate -WebLqOFrgMVsHPQuIXjy=issubclass -WebLqOFrgMVsHPQuIXjf=getattr -WebLqOFrgMVsHPQuIXjD=open -WebLqOFrgMVsHPQuIXjN=eval -WebLqOFrgMVsHPQuIXjz=globals -WebLqOFrgMVsHPQuIXjR=True -WebLqOFrgMVsHPQuIXjp=np.asarray +fsQIGnDNmJuSbCgMlxcj=str +fsQIGnDNmJuSbCgMlxcd=None +fsQIGnDNmJuSbCgMlxci=int +fsQIGnDNmJuSbCgMlxcH=False +fsQIGnDNmJuSbCgMlxca=print +fsQIGnDNmJuSbCgMlxcW=len +fsQIGnDNmJuSbCgMlxco=enumerate +fsQIGnDNmJuSbCgMlxcw=issubclass +fsQIGnDNmJuSbCgMlxcB=getattr +fsQIGnDNmJuSbCgMlxch=open +fsQIGnDNmJuSbCgMlxcP=eval +fsQIGnDNmJuSbCgMlxcr=globals +fsQIGnDNmJuSbCgMlxcy=True +fsQIGnDNmJuSbCgMlxck=np.asarray from tabulate import tabulate from datetime import datetime -WebLqOFrgMVsHPQuIXja=datetime.now +fsQIGnDNmJuSbCgMlxcq=datetime.now import pickle -WebLqOFrgMVsHPQuIXjw=pickle.loads +fsQIGnDNmJuSbCgMlxct=pickle.loads import pyfiglet -WebLqOFrgMVsHPQuIXjE=pyfiglet.figlet_format +fsQIGnDNmJuSbCgMlxcp=pyfiglet.figlet_format import inspect -WebLqOFrgMVsHPQuIXjk=inspect.currentframe -WebLqOFrgMVsHPQuIXjm=inspect.getouterframes +fsQIGnDNmJuSbCgMlxcF=inspect.currentframe +fsQIGnDNmJuSbCgMlxcT=inspect.getouterframes import os -WebLqOFrgMVsHPQuIXjK=os.getcwd -WebLqOFrgMVsHPQuIXjC=os.path +fsQIGnDNmJuSbCgMlxcX=os.getcwd +fsQIGnDNmJuSbCgMlxcK=os.path import argparse -WebLqOFrgMVsHPQuIXjd=argparse.RawTextHelpFormatter -WebLqOFrgMVsHPQuIXjS=argparse.ArgumentParser -WebLqOFrgMVsHPQuIXBj=WebLqOFrgMVsHPQuIXjS(description='Evaluate your report.',epilog="""Example: +fsQIGnDNmJuSbCgMlxcV=argparse.RawTextHelpFormatter +fsQIGnDNmJuSbCgMlxcz=argparse.ArgumentParser +fsQIGnDNmJuSbCgMlxUc=fsQIGnDNmJuSbCgMlxcz(description='Evaluate your report.',epilog="""Example: To run all tests in a report: -> python report1.py +> python assignment1_dp.py To run only question 2 or question 2.1 -> python report1.py -q 2 -> python report1.py -q 2.1 +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 Note this scripts does not grade your report. To grade your report, use: > python report1_grade.py Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful. -As an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to 'Documents/` and execute: +As an example, suppose the report file is Documents/course_package/assignment1_dp.py, and `course_package` is a python package. Change directory to 'Documents/` and execute: > python -m course_package.report1 see https://docs.python.org/3.9/using/cmdline.html -""", formatter_class=WebLqOFrgMVsHPQuIXjd) -WebLqOFrgMVsHPQuIXBj.add_argument('-q',nargs='?',type=WebLqOFrgMVsHPQuIXjh,default=WebLqOFrgMVsHPQuIXjT,help='Only evaluate this question (example: -q 2)') -WebLqOFrgMVsHPQuIXBj.add_argument('--showexpected',action="store_true",help='Show the expected/desired result') -WebLqOFrgMVsHPQuIXBj.add_argument('--showcomputed',action="store_true",help='Show the answer your code computes') -WebLqOFrgMVsHPQuIXBj.add_argument('--unmute',action="store_true",help='Show result of print(...) commands in code') -WebLqOFrgMVsHPQuIXBj.add_argument('--passall',action="store_true",help='Automatically pass all tests. Useful when debugging.') -def WebLqOFrgMVsHPQuIXBv(WebLqOFrgMVsHPQuIXBG,question=WebLqOFrgMVsHPQuIXjT,qitem=WebLqOFrgMVsHPQuIXjT): - WebLqOFrgMVsHPQuIXBa=WebLqOFrgMVsHPQuIXBj.parse_args() - if question is WebLqOFrgMVsHPQuIXjT and WebLqOFrgMVsHPQuIXBa.q is not WebLqOFrgMVsHPQuIXjT: - question=WebLqOFrgMVsHPQuIXBa.q +""", formatter_class=fsQIGnDNmJuSbCgMlxcV) +fsQIGnDNmJuSbCgMlxUc.add_argument('-q',nargs='?',type=fsQIGnDNmJuSbCgMlxcj,default=fsQIGnDNmJuSbCgMlxcd,help='Only evaluate this question (example: -q 2)') +fsQIGnDNmJuSbCgMlxUc.add_argument('--showexpected',action="store_true",help='Show the expected/desired result') +fsQIGnDNmJuSbCgMlxUc.add_argument('--showcomputed',action="store_true",help='Show the answer your code computes') +fsQIGnDNmJuSbCgMlxUc.add_argument('--unmute',action="store_true",help='Show result of print(...) commands in code') +fsQIGnDNmJuSbCgMlxUc.add_argument('--passall',action="store_true",help='Automatically pass all tests. Useful when debugging.') +def fsQIGnDNmJuSbCgMlxUE(fsQIGnDNmJuSbCgMlxUo,question=fsQIGnDNmJuSbCgMlxcd,qitem=fsQIGnDNmJuSbCgMlxcd): + fsQIGnDNmJuSbCgMlxUq=fsQIGnDNmJuSbCgMlxUc.parse_args() + if question is fsQIGnDNmJuSbCgMlxcd and fsQIGnDNmJuSbCgMlxUq.q is not fsQIGnDNmJuSbCgMlxcd: + question=fsQIGnDNmJuSbCgMlxUq.q if "." in question: - question,qitem=[WebLqOFrgMVsHPQuIXjn(v)for v in question.split(".")] + question,qitem=[fsQIGnDNmJuSbCgMlxci(v)for v in question.split(".")] else: - question=WebLqOFrgMVsHPQuIXjn(question) - WebLqOFrgMVsHPQuIXBm,WebLqOFrgMVsHPQuIXBk=WebLqOFrgMVsHPQuIXBo(WebLqOFrgMVsHPQuIXBG,question=question,qitem=qitem,verbose=WebLqOFrgMVsHPQuIXjJ,passall=WebLqOFrgMVsHPQuIXBa.passall,show_expected=WebLqOFrgMVsHPQuIXBa.showexpected,show_computed=WebLqOFrgMVsHPQuIXBa.showcomputed,unmute=WebLqOFrgMVsHPQuIXBa.unmute) - if question is WebLqOFrgMVsHPQuIXjT: - WebLqOFrgMVsHPQuIXjl("Provisional evaluation") - tabulate(WebLqOFrgMVsHPQuIXBk) - WebLqOFrgMVsHPQuIXBC=WebLqOFrgMVsHPQuIXBk - WebLqOFrgMVsHPQuIXjl(tabulate(WebLqOFrgMVsHPQuIXBC)) - WebLqOFrgMVsHPQuIXjl(" ") - WebLqOFrgMVsHPQuIXjl("Note your results have not yet been registered. \nTo register your results, please run the file:") - fr=WebLqOFrgMVsHPQuIXjm(WebLqOFrgMVsHPQuIXjk())[1].filename - WebLqOFrgMVsHPQuIXjl(">>>",WebLqOFrgMVsHPQuIXjC.basename(fr)[:-3]+"_grade.py") - WebLqOFrgMVsHPQuIXjl("In the same manner as you ran this file.") - return WebLqOFrgMVsHPQuIXBm -def WebLqOFrgMVsHPQuIXBx(q): + question=fsQIGnDNmJuSbCgMlxci(question) + fsQIGnDNmJuSbCgMlxUT,fsQIGnDNmJuSbCgMlxUF=fsQIGnDNmJuSbCgMlxUO(fsQIGnDNmJuSbCgMlxUo,question=question,qitem=qitem,verbose=fsQIGnDNmJuSbCgMlxcH,passall=fsQIGnDNmJuSbCgMlxUq.passall,show_expected=fsQIGnDNmJuSbCgMlxUq.showexpected,show_computed=fsQIGnDNmJuSbCgMlxUq.showcomputed,unmute=fsQIGnDNmJuSbCgMlxUq.unmute) + if question is fsQIGnDNmJuSbCgMlxcd: + fsQIGnDNmJuSbCgMlxca("Provisional evaluation") + tabulate(fsQIGnDNmJuSbCgMlxUF) + fsQIGnDNmJuSbCgMlxUK=fsQIGnDNmJuSbCgMlxUF + fsQIGnDNmJuSbCgMlxca(tabulate(fsQIGnDNmJuSbCgMlxUK)) + fsQIGnDNmJuSbCgMlxca(" ") + fsQIGnDNmJuSbCgMlxca("Note your results have not yet been registered. \nTo register your results, please run the file:") + fr=fsQIGnDNmJuSbCgMlxcT(fsQIGnDNmJuSbCgMlxcF())[1].filename + fsQIGnDNmJuSbCgMlxca(">>>",fsQIGnDNmJuSbCgMlxcK.basename(fr)[:-3]+"_grade.py") + fsQIGnDNmJuSbCgMlxca("In the same manner as you ran this file.") + return fsQIGnDNmJuSbCgMlxUT +def fsQIGnDNmJuSbCgMlxUe(q): h=[(i['w'],i['possible'],i['obtained'])for i in q.values()] - h=WebLqOFrgMVsHPQuIXjp(h) + h=fsQIGnDNmJuSbCgMlxck(h) return h[:,0],h[:,1],h[:,2], -def WebLqOFrgMVsHPQuIXBo(WebLqOFrgMVsHPQuIXBG,question=WebLqOFrgMVsHPQuIXjT,qitem=WebLqOFrgMVsHPQuIXjT,passall=WebLqOFrgMVsHPQuIXjJ,verbose=WebLqOFrgMVsHPQuIXjJ,show_expected=WebLqOFrgMVsHPQuIXjJ,show_computed=WebLqOFrgMVsHPQuIXjJ,unmute=WebLqOFrgMVsHPQuIXjJ): - WebLqOFrgMVsHPQuIXBK=WebLqOFrgMVsHPQuIXja() - WebLqOFrgMVsHPQuIXBS=WebLqOFrgMVsHPQuIXjE("UnitGrade",font="doom") - b="\n".join([l for l in WebLqOFrgMVsHPQuIXBS.splitlines()if WebLqOFrgMVsHPQuIXjc(l.strip())>0]) - WebLqOFrgMVsHPQuIXjl(b+" v"+__version__) - WebLqOFrgMVsHPQuIXBd=WebLqOFrgMVsHPQuIXBK.strftime("%d/%m/%Y %H:%M:%S") - WebLqOFrgMVsHPQuIXjl("Started: "+WebLqOFrgMVsHPQuIXBd) - WebLqOFrgMVsHPQuIXjl("Evaluating "+WebLqOFrgMVsHPQuIXBG.title,"(use --help for options)") - WebLqOFrgMVsHPQuIXjl(f"Loaded answers from: ",WebLqOFrgMVsHPQuIXBG.computed_answers_file,"\n") - WebLqOFrgMVsHPQuIXBk=[] +def fsQIGnDNmJuSbCgMlxUO(fsQIGnDNmJuSbCgMlxUo,question=fsQIGnDNmJuSbCgMlxcd,qitem=fsQIGnDNmJuSbCgMlxcd,passall=fsQIGnDNmJuSbCgMlxcH,verbose=fsQIGnDNmJuSbCgMlxcH,show_expected=fsQIGnDNmJuSbCgMlxcH,show_computed=fsQIGnDNmJuSbCgMlxcH,unmute=fsQIGnDNmJuSbCgMlxcH): + fsQIGnDNmJuSbCgMlxUX=fsQIGnDNmJuSbCgMlxcq() + fsQIGnDNmJuSbCgMlxUz=fsQIGnDNmJuSbCgMlxcp("UnitGrade",font="doom") + b="\n".join([l for l in fsQIGnDNmJuSbCgMlxUz.splitlines()if fsQIGnDNmJuSbCgMlxcW(l.strip())>0]) + fsQIGnDNmJuSbCgMlxca(b+" v"+__version__) + fsQIGnDNmJuSbCgMlxUV=fsQIGnDNmJuSbCgMlxUX.strftime("%d/%m/%Y %H:%M:%S") + fsQIGnDNmJuSbCgMlxca("Started: "+fsQIGnDNmJuSbCgMlxUV) + fsQIGnDNmJuSbCgMlxca("Evaluating "+fsQIGnDNmJuSbCgMlxUo.title,"(use --help for options)") + fsQIGnDNmJuSbCgMlxca(f"Loaded answers from: ",fsQIGnDNmJuSbCgMlxUo.computed_answers_file,"\n") + fsQIGnDNmJuSbCgMlxUF=[] nL=80 - WebLqOFrgMVsHPQuIXBt={} - for n,(q,w)in WebLqOFrgMVsHPQuIXjG(WebLqOFrgMVsHPQuIXBG.questions): - WebLqOFrgMVsHPQuIXBh=WebLqOFrgMVsHPQuIXjy(q.__class__,Hidden) - if question is not WebLqOFrgMVsHPQuIXjT and n+1!=question: + fsQIGnDNmJuSbCgMlxUY={} + for n,(q,w)in fsQIGnDNmJuSbCgMlxco(fsQIGnDNmJuSbCgMlxUo.questions): + fsQIGnDNmJuSbCgMlxUj=fsQIGnDNmJuSbCgMlxcw(q.__class__,Hidden) + if question is not fsQIGnDNmJuSbCgMlxcd and n+1!=question: continue - WebLqOFrgMVsHPQuIXjl(f"Question {n+1}: {q.title}") - WebLqOFrgMVsHPQuIXjl("="*nL) + fsQIGnDNmJuSbCgMlxca(f"Question {n+1}: {q.title}") + fsQIGnDNmJuSbCgMlxca("="*nL) q.possible=0 q.obtained=0 q_={} - for j,(item,iw)in WebLqOFrgMVsHPQuIXjG(q.items): - if qitem is not WebLqOFrgMVsHPQuIXjT and question is not WebLqOFrgMVsHPQuIXjT and item is not WebLqOFrgMVsHPQuIXjT and j+1!=qitem: + for j,(item,iw)in fsQIGnDNmJuSbCgMlxco(q.items): + if qitem is not fsQIGnDNmJuSbCgMlxcd and question is not fsQIGnDNmJuSbCgMlxcd and item is not fsQIGnDNmJuSbCgMlxcd and j+1!=qitem: continue ss=f"*** q{n+1}.{j+1}) {item.title}" el=nL-4 - if WebLqOFrgMVsHPQuIXjc(ss)<el: - ss+='.'*(el-WebLqOFrgMVsHPQuIXjc(ss)) - WebLqOFrgMVsHPQuIXBT=WebLqOFrgMVsHPQuIXjy(item.__class__,Hidden) - if not WebLqOFrgMVsHPQuIXBT: - WebLqOFrgMVsHPQuIXjl(ss,end="") - (WebLqOFrgMVsHPQuIXBJ,WebLqOFrgMVsHPQuIXBl)=item.get_points(show_expected=show_expected,show_computed=show_computed,unmute=unmute,passall=passall) - q_[j]={'w':iw,'possible':WebLqOFrgMVsHPQuIXBl,'obtained':WebLqOFrgMVsHPQuIXBJ,'hidden':WebLqOFrgMVsHPQuIXBT} - if not WebLqOFrgMVsHPQuIXBT: - if WebLqOFrgMVsHPQuIXBJ==WebLqOFrgMVsHPQuIXBl: - WebLqOFrgMVsHPQuIXjl(f"PASS") + if fsQIGnDNmJuSbCgMlxcW(ss)<el: + ss+='.'*(el-fsQIGnDNmJuSbCgMlxcW(ss)) + fsQIGnDNmJuSbCgMlxUd=fsQIGnDNmJuSbCgMlxcw(item.__class__,Hidden) + if not fsQIGnDNmJuSbCgMlxUd: + fsQIGnDNmJuSbCgMlxca(ss,end="") + (fsQIGnDNmJuSbCgMlxUH,fsQIGnDNmJuSbCgMlxUa)=item.get_points(show_expected=show_expected,show_computed=show_computed,unmute=unmute,passall=passall) + q_[j]={'w':iw,'possible':fsQIGnDNmJuSbCgMlxUa,'obtained':fsQIGnDNmJuSbCgMlxUH,'hidden':fsQIGnDNmJuSbCgMlxUd} + if not fsQIGnDNmJuSbCgMlxUd: + if fsQIGnDNmJuSbCgMlxUH==fsQIGnDNmJuSbCgMlxUa: + fsQIGnDNmJuSbCgMlxca(f"PASS") else: - WebLqOFrgMVsHPQuIXjl(f"*** FAILED") - ws,WebLqOFrgMVsHPQuIXBl,WebLqOFrgMVsHPQuIXBc=WebLqOFrgMVsHPQuIXBx(q_) - WebLqOFrgMVsHPQuIXBl=WebLqOFrgMVsHPQuIXjn(ws@WebLqOFrgMVsHPQuIXBl) - WebLqOFrgMVsHPQuIXBc=WebLqOFrgMVsHPQuIXjn(ws@WebLqOFrgMVsHPQuIXBc) - WebLqOFrgMVsHPQuIXBc=WebLqOFrgMVsHPQuIXjn(myround(WebLqOFrgMVsHPQuIXjn((w*WebLqOFrgMVsHPQuIXBc)/WebLqOFrgMVsHPQuIXBl)))if WebLqOFrgMVsHPQuIXBl>0 else 0 - WebLqOFrgMVsHPQuIXBt[n]={'w':w,'possible':w,'obtained':WebLqOFrgMVsHPQuIXBc,'Ãtems':q_,'hidden':WebLqOFrgMVsHPQuIXBh} - q.obtained=WebLqOFrgMVsHPQuIXBc - q.possible=WebLqOFrgMVsHPQuIXBl + fsQIGnDNmJuSbCgMlxca(f"*** FAILED") + ws,fsQIGnDNmJuSbCgMlxUa,fsQIGnDNmJuSbCgMlxUW=fsQIGnDNmJuSbCgMlxUe(q_) + fsQIGnDNmJuSbCgMlxUa=fsQIGnDNmJuSbCgMlxci(ws@fsQIGnDNmJuSbCgMlxUa) + fsQIGnDNmJuSbCgMlxUW=fsQIGnDNmJuSbCgMlxci(ws@fsQIGnDNmJuSbCgMlxUW) + fsQIGnDNmJuSbCgMlxUW=fsQIGnDNmJuSbCgMlxci(myround(fsQIGnDNmJuSbCgMlxci((w*fsQIGnDNmJuSbCgMlxUW)/fsQIGnDNmJuSbCgMlxUa)))if fsQIGnDNmJuSbCgMlxUa>0 else 0 + fsQIGnDNmJuSbCgMlxUY[n]={'w':w,'possible':w,'obtained':fsQIGnDNmJuSbCgMlxUW,'Ãtems':q_,'hidden':fsQIGnDNmJuSbCgMlxUj} + q.obtained=fsQIGnDNmJuSbCgMlxUW + q.possible=fsQIGnDNmJuSbCgMlxUa s1=f"*** Question q{n+1}" s2=f" {q.obtained}/{w}" - WebLqOFrgMVsHPQuIXjl(s1+("."*(nL-WebLqOFrgMVsHPQuIXjc(s1)-WebLqOFrgMVsHPQuIXjc(s2)))+s2) - WebLqOFrgMVsHPQuIXjl(" ") - WebLqOFrgMVsHPQuIXBk.append([f"Question q{n+1}",f"{q.obtained}/{w}"]) - ws,WebLqOFrgMVsHPQuIXBl,WebLqOFrgMVsHPQuIXBc=WebLqOFrgMVsHPQuIXBx(WebLqOFrgMVsHPQuIXBt) - WebLqOFrgMVsHPQuIXBl=WebLqOFrgMVsHPQuIXjn(msum(WebLqOFrgMVsHPQuIXBl)) - WebLqOFrgMVsHPQuIXBc=WebLqOFrgMVsHPQuIXjn(msum(WebLqOFrgMVsHPQuIXBc)) - WebLqOFrgMVsHPQuIXBG.possible=WebLqOFrgMVsHPQuIXBl - WebLqOFrgMVsHPQuIXBG.obtained=WebLqOFrgMVsHPQuIXBc - WebLqOFrgMVsHPQuIXBK=WebLqOFrgMVsHPQuIXja() - WebLqOFrgMVsHPQuIXBd=WebLqOFrgMVsHPQuIXBK.strftime("%H:%M:%S") - WebLqOFrgMVsHPQuIXjl(f"Completed: "+WebLqOFrgMVsHPQuIXBd) - WebLqOFrgMVsHPQuIXBk.append(["Total",""+WebLqOFrgMVsHPQuIXjh(WebLqOFrgMVsHPQuIXBG.obtained)+"/"+WebLqOFrgMVsHPQuIXjh(WebLqOFrgMVsHPQuIXBG.possible)]) - WebLqOFrgMVsHPQuIXBm={'total':(WebLqOFrgMVsHPQuIXBc,WebLqOFrgMVsHPQuIXBl),'details':WebLqOFrgMVsHPQuIXBt} - return WebLqOFrgMVsHPQuIXBm,WebLqOFrgMVsHPQuIXBk + fsQIGnDNmJuSbCgMlxca(s1+("."*(nL-fsQIGnDNmJuSbCgMlxcW(s1)-fsQIGnDNmJuSbCgMlxcW(s2)))+s2) + fsQIGnDNmJuSbCgMlxca(" ") + fsQIGnDNmJuSbCgMlxUF.append([f"Question q{n+1}",f"{q.obtained}/{w}"]) + ws,fsQIGnDNmJuSbCgMlxUa,fsQIGnDNmJuSbCgMlxUW=fsQIGnDNmJuSbCgMlxUe(fsQIGnDNmJuSbCgMlxUY) + fsQIGnDNmJuSbCgMlxUa=fsQIGnDNmJuSbCgMlxci(msum(fsQIGnDNmJuSbCgMlxUa)) + fsQIGnDNmJuSbCgMlxUW=fsQIGnDNmJuSbCgMlxci(msum(fsQIGnDNmJuSbCgMlxUW)) + fsQIGnDNmJuSbCgMlxUo.possible=fsQIGnDNmJuSbCgMlxUa + fsQIGnDNmJuSbCgMlxUo.obtained=fsQIGnDNmJuSbCgMlxUW + fsQIGnDNmJuSbCgMlxUX=fsQIGnDNmJuSbCgMlxcq() + fsQIGnDNmJuSbCgMlxUV=fsQIGnDNmJuSbCgMlxUX.strftime("%H:%M:%S") + fsQIGnDNmJuSbCgMlxca(f"Completed: "+fsQIGnDNmJuSbCgMlxUV) + fsQIGnDNmJuSbCgMlxUF.append(["Total",""+fsQIGnDNmJuSbCgMlxcj(fsQIGnDNmJuSbCgMlxUo.obtained)+"/"+fsQIGnDNmJuSbCgMlxcj(fsQIGnDNmJuSbCgMlxUo.possible)]) + fsQIGnDNmJuSbCgMlxUT={'total':(fsQIGnDNmJuSbCgMlxUW,fsQIGnDNmJuSbCgMlxUa),'details':fsQIGnDNmJuSbCgMlxUY} + return fsQIGnDNmJuSbCgMlxUT,fsQIGnDNmJuSbCgMlxUF from tabulate import tabulate from datetime import datetime -WebLqOFrgMVsHPQuIXja=datetime.now +fsQIGnDNmJuSbCgMlxcq=datetime.now import inspect -WebLqOFrgMVsHPQuIXjk=inspect.currentframe -WebLqOFrgMVsHPQuIXjm=inspect.getouterframes +fsQIGnDNmJuSbCgMlxcF=inspect.currentframe +fsQIGnDNmJuSbCgMlxcT=inspect.getouterframes import json -WebLqOFrgMVsHPQuIXjt=json.dumps +fsQIGnDNmJuSbCgMlxcY=json.dumps import os -WebLqOFrgMVsHPQuIXjK=os.getcwd -WebLqOFrgMVsHPQuIXjC=os.path +fsQIGnDNmJuSbCgMlxcX=os.getcwd +fsQIGnDNmJuSbCgMlxcK=os.path import bz2 import pickle -WebLqOFrgMVsHPQuIXjw=pickle.loads -def WebLqOFrgMVsHPQuIXBA(WebLqOFrgMVsHPQuIXBy,WebLqOFrgMVsHPQuIXBz): - with WebLqOFrgMVsHPQuIXjf(bz2,'open')(WebLqOFrgMVsHPQuIXBz,"wt")as f: - f.write(WebLqOFrgMVsHPQuIXBy) -def WebLqOFrgMVsHPQuIXjB(WebLqOFrgMVsHPQuIXBG,output_dir=WebLqOFrgMVsHPQuIXjT): +fsQIGnDNmJuSbCgMlxct=pickle.loads +def fsQIGnDNmJuSbCgMlxUA(fsQIGnDNmJuSbCgMlxUw,fsQIGnDNmJuSbCgMlxUr): + with fsQIGnDNmJuSbCgMlxcB(bz2,'open')(fsQIGnDNmJuSbCgMlxUr,"wt")as f: + f.write(fsQIGnDNmJuSbCgMlxUw) +def fsQIGnDNmJuSbCgMlxcU(fsQIGnDNmJuSbCgMlxUo,output_dir=fsQIGnDNmJuSbCgMlxcd): n=80 - WebLqOFrgMVsHPQuIXBm,WebLqOFrgMVsHPQuIXBk=WebLqOFrgMVsHPQuIXBo(WebLqOFrgMVsHPQuIXBG) - WebLqOFrgMVsHPQuIXjl(" ") - WebLqOFrgMVsHPQuIXjl("="*n) - WebLqOFrgMVsHPQuIXjl("Final evaluation") - WebLqOFrgMVsHPQuIXjl(tabulate(WebLqOFrgMVsHPQuIXBk)) - WebLqOFrgMVsHPQuIXBm['sources']={} - WebLqOFrgMVsHPQuIXjl("Gathering files...") - for m in WebLqOFrgMVsHPQuIXBG.pack_imports: - with WebLqOFrgMVsHPQuIXjD(m.__file__,'r')as f: - WebLqOFrgMVsHPQuIXBm['sources'][m.__name__]=f.read() - WebLqOFrgMVsHPQuIXjl(f"*** {m.__name__}") - WebLqOFrgMVsHPQuIXBm['sources']={} - WebLqOFrgMVsHPQuIXBy=WebLqOFrgMVsHPQuIXjt(WebLqOFrgMVsHPQuIXBm,indent=4) - if output_dir is WebLqOFrgMVsHPQuIXjT: - output_dir=WebLqOFrgMVsHPQuIXjK() - WebLqOFrgMVsHPQuIXBD=WebLqOFrgMVsHPQuIXBG.__class__.__name__+"_handin" - WebLqOFrgMVsHPQuIXBN,WebLqOFrgMVsHPQuIXBl=WebLqOFrgMVsHPQuIXBm['total'] - WebLqOFrgMVsHPQuIXBz="%s_%i_of_%i.token"%(WebLqOFrgMVsHPQuIXBD,WebLqOFrgMVsHPQuIXBN,WebLqOFrgMVsHPQuIXBl) - WebLqOFrgMVsHPQuIXBz=WebLqOFrgMVsHPQuIXjC.join(output_dir,WebLqOFrgMVsHPQuIXBz) - WebLqOFrgMVsHPQuIXBA(WebLqOFrgMVsHPQuIXBy,WebLqOFrgMVsHPQuIXBz) - WebLqOFrgMVsHPQuIXjl(" ") - WebLqOFrgMVsHPQuIXjl("To get credit for your results, please upload the single file: ") - WebLqOFrgMVsHPQuIXjl(">",WebLqOFrgMVsHPQuIXBz) - WebLqOFrgMVsHPQuIXjl("To campusnet without any modifications.") -def WebLqOFrgMVsHPQuIXjY(WebLqOFrgMVsHPQuIXBi,WebLqOFrgMVsHPQuIXBR,payload): - WebLqOFrgMVsHPQuIXjN("exec")(WebLqOFrgMVsHPQuIXBR,WebLqOFrgMVsHPQuIXjz()) - pl=WebLqOFrgMVsHPQuIXjw(payload) - WebLqOFrgMVsHPQuIXBG=WebLqOFrgMVsHPQuIXjN(WebLqOFrgMVsHPQuIXBi)(payload=pl,strict=WebLqOFrgMVsHPQuIXjR) - return WebLqOFrgMVsHPQuIXBG -WebLqOFrgMVsHPQuIXBR='__version__ = "0.0.5"\nimport os\nimport compress_pickle\n\ndef cache_write(object, file_name, verbose=True):\n dn = os.path.dirname(file_name)\n if not os.path.exists(dn):\n os.mkdir(dn)\n if verbose: print("Writing cache...", file_name)\n with open(file_name, \'wb\', ) as f:\n compress_pickle.dump(object, f, compression="lzma")\n if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n # file_name = cn_(file_name) if cache_prefix else file_name\n return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n # file_name = cn_(file_name) if cache_prefix else file_name\n if os.path.exists(file_name):\n with open(file_name, \'rb\') as f:\n return compress_pickle.load(f, compression="lzma")\n # return pickle.load(f)\n else:\n return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push && pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nfrom io import StringIO\nimport sys\nimport collections\nimport inspect\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n name = C.__class__.__name__\n # base_dir = os.path.join(base_dir, name)\n # if not os.path.isdir(base_dir):\n # os.makedirs(base_dir)\n return base_dir, name\n\nclass Hidden:\n def hide(self):\n return True\n\nimport sys\n\nclass Logger(object):\n def __init__(self, buffer):\n self.terminal = sys.stdout\n self.log = buffer\n\n def write(self, message):\n self.terminal.write(message)\n self.log.write(message)\n\n def flush(self):\n # this flush method is needed for python 3 compatibility.\n pass\n\n# sys.stdout = Logger()\n\nclass Capturing(list):\n def __init__(self, *args, unmute=False, **kwargs):\n self.unmute = unmute\n super().__init__(*args, **kwargs)\n\n def __enter__(self, capture_errors=True): # don\'t put arguments here.\n self._stdout = sys.stdout\n self._stringio = StringIO()\n if self.unmute:\n sys.stdout = Logger(self._stringio)\n else:\n sys.stdout = self._stringio\n\n if capture_errors:\n self._sterr = sys.stderr\n sys.sterr = StringIO() # memory hole it\n self.capture_errors = capture_errors\n return self\n\n def __exit__(self, *args):\n self.extend(self._stringio.getvalue().splitlines())\n del self._stringio # free up some memory\n sys.stdout = self._stdout\n if self.capture_errors:\n sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n title = None\n testfun = unittest.TestCase.assertEqual\n tol = 0\n\n def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs):\n self.name = self.__class__.__name__\n self._correct_answer_payload = correct_answer_payload\n super().__init__(*args, **kwargs)\n if self.title is None:\n self.title = self.name\n\n def assertL2(self, computed, expected, tol=None):\n if tol == None:\n tol = self.tol\n diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n if np.max(diff) > tol:\n print("Not equal within tolerance {tol}")\n print(f"Element-wise differences {diff.tolist()}")\n self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n def assertL2Relative(self, computed, expected, tol=None):\n if tol == None:\n tol = self.tol\n diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n if np.sum(diff > tol) > 0:\n print(f"Not equal within tolerance {tol}")\n print(f"Element-wise differences {diff.tolist()}")\n self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n def compute_answer(self, unmute=False):\n raise NotImplementedError("test code here")\n\n def test(self, computed, expected):\n self.testfun(computed, expected)\n\n def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, **kwargs):\n possible = 1\n computed = None\n def show_computed_(computed):\n print(">>> Your output:")\n print(computed)\n\n def show_expected_(expected):\n print(">>> Expected output:")\n print(expected)\n\n try:\n if unmute:\n print("\\n")\n computed = self.compute_answer(unmute=unmute)\n except Exception as e:\n if not passall:\n print("\\n=================================================================================")\n print(f"When trying to run test class \'{self.name}\' your code threw an error:")\n print(e)\n print("=================================================================================")\n import traceback\n traceback.print_exc()\n return (0, possible)\n correct = self._correct_answer_payload\n if show_expected or show_computed:\n print("\\n")\n if show_expected:\n show_expected_(correct)\n if show_computed:\n show_computed_(computed)\n try:\n # print(computed, correct)\n # if passall:\n # computed = correct\n if not passall:\n self.test(computed=computed, expected=correct)\n except Exception as e:\n print("\\n=================================================================================")\n print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n print(e)\n show_computed_(computed)\n show_expected_(correct)\n # print("-------------------------Your output:--------------------------------------------")\n # print(computed)\n # print("-------------------------Expected output:----------------------------------------")\n # print(correct)\n # print("=================================================================================")\n return (0, possible)\n return (1, possible)\n\n def score(self):\n try:\n self.test()\n except Exception as e:\n return 0\n return 1\n\nclass QPrintItem(QItem):\n def compute_answer_print(self):\n """\n Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n are send to process_output (see compute_answer below). In other words, the text generated is:\n\n res = compute_Answer_print()\n txt = (any terminal output generated above)\n numbers = (any numbers found in terminal-output txt)\n\n self.test(process_output(res, txt, numbers), <expected result>)\n\n :return: Optional values for comparison\n """\n raise Exception("Generate output here. The output is passed to self.process_output")\n\n def process_output(self, res, txt, numbers):\n return (res, txt)\n\n def compute_answer(self, unmute=False):\n with Capturing(unmute=unmute) as output:\n res = self.compute_answer_print()\n s = "\\n".join(output)\n numbers = extract_numbers(s)\n return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n @classmethod\n def __prepare__(self, name, bases):\n return collections.OrderedDict()\n def __new__(self, name, bases, classdict):\n classdict[\'__ordered__\'] = [key for key in classdict.keys() if key not in (\'__module__\', \'__qualname__\')]\n return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n title = "Graph search"\n items = None\n partially_scored = False\n\n def __init__(self, *args, **kwargs):\n self.name = self.__class__.__name__\n if self.items is None:\n self.items = []\n members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__] if inspect.isclass(gt) and issubclass(gt, QItem)]\n for gt in members:\n self.items.append( (gt, 1) )\n self.items = [(I(), w) for I, w in self.items]\n\nclass Report():\n title = "report title"\n questions = []\n pack_imports = []\n\n def __init__(self, strict=False,payload=None):\n working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self))))\n # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/")\n self.wdir, self.name = setup_dir_by_class(self, working_directory)\n self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions]\n # self.strict = strict\n if payload is not None:\n self.set_payload(payload, strict=strict)\n else:\n if os.path.isfile(self.computed_answers_file):\n self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n else:\n s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n if strict:\n raise Exception(s)\n else:\n print(s)\n\n def set_payload(self, payloads, strict=False):\n for q, _ in self.questions:\n for item, _ in q.items:\n if q.name not in payloads or item.name not in payloads[q.name]:\n s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n if strict:\n raise Exception(s)\n else:\n print(s)\n item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n\ndef extract_numbers(txt):\n import re\n numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n rx = re.compile(numeric_const_pattern, re.VERBOSE)\n all = rx.findall(txt)\n all = [float(a) if \'.\' in a else int(a) for a in all]\n return all\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pickle\nimport pyfiglet\n\n# from unitgrade.unitgrade import Hidden\n# import unitgrade as ug\n# import unitgrade.unitgrade as ug\nimport inspect\nimport os\nimport argparse\n\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python report1.py\n\nTo run only question 2 or question 2.1\n\n> python report1.py -q 2\n> python report1.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nAs an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to \'Documents/` and execute:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (example: -q 2)\')\nparser.add_argument(\'--showexpected\', action="store_true", help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\', action="store_true", help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\', action="store_true", help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\', action="store_true", help=\'Automatically pass all tests. Useful when debugging.\')\n\n# parser.add_argument(\'integers\', metavar=\'N\', type=int, nargs=\'+\',\n# help=\'an integer for the accumulator\')\n# parser.add_argument(\'--sum\', dest=\'accumulate\', action=\'store_const\',\n# const=sum, default=max,\n# help=\'sum the integers (default: find the max)\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None):\n args = parser.parse_args()\n if question is None and args.q is not None:\n question = args.q\n if "." in question:\n question, qitem = [int(v) for v in question.split(".")]\n else:\n question = int(question)\n\n # print(args)\n results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=False, passall=args.passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=args.unmute)\n\n if question is None:\n print("Provisional evaluation")\n tabulate(table_data)\n table = table_data\n print(tabulate(table))\n print(" ")\n print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n\n fr = inspect.getouterframes(inspect.currentframe())[1].filename\n print(">>>", os.path.basename(fr)[:-3] + "_grade.py")\n print("In the same manner as you ran this file.")\n return results\n\n\ndef upack(q):\n # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n h = np.asarray(h)\n return h[:,0], h[:,1], h[:,2],\n #\n # ws, possible, obtained = (np.asarray(x).squeeze() for x in zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]))\n # return ws, possible, obtained\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False, show_expected=False, show_computed=False,unmute=False):\n now = datetime.now()\n # show_expected = True\n ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n print(b + " v" + __version__)\n dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print("Started: " + dt_string)\n print("Evaluating " + report.title, "(use --help for options)")\n print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n table_data = []\n nL = 80\n\n score = {}\n for n, (q, w) in enumerate(report.questions):\n q_hidden = issubclass(q.__class__, Hidden)\n if question is not None and n+1 != question:\n continue\n\n print(f"Question {n+1}: {q.title}")\n print("="*nL)\n q.possible = 0\n q.obtained = 0\n\n q_ = {} # Gather score in this class.\n for j, (item, iw) in enumerate(q.items):\n if qitem is not None and question is not None and item is not None and j+1 != qitem:\n continue\n\n ss = f"*** q{n+1}.{j+1}) {item.title}"\n el = nL-4\n if len(ss) < el:\n ss += \'.\'*(el-len(ss))\n hidden = issubclass(item.__class__, Hidden)\n if not hidden:\n print(ss, end="")\n (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall)\n q_[j] = {\'w\': iw, \'possible\': possible, \'obtained\': current, \'hidden\': hidden}\n # q.possible += possible * iw\n # q.obtained += current * iw\n if not hidden:\n if current == possible:\n print(f"PASS")\n else:\n print(f"*** FAILED")\n\n ws, possible, obtained = upack(q_)\n possible = int(ws @ possible)\n obtained = int(ws @ obtained)\n obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'Ãtems\': q_, \'hidden\': q_hidden}\n\n q.obtained = obtained\n q.possible = possible\n\n s1 = f"*** Question q{n+1}"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n ws, possible, obtained = upack(score)\n possible = int( msum(possible) )\n obtained = int( msum(obtained) ) # Cast to python int\n report.possible = possible\n report.obtained = obtained\n now = datetime.now()\n dt_string = now.strftime("%H:%M:%S")\n print(f"Completed: "+ dt_string)\n table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n results = {\'total\': (obtained, possible), \'details\': score}\n return results, table_data\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n with getattr(bz2, \'open\')(token, "wt") as f:\n f.write(json_str)\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n n = 80\n results, table_data = evaluate_report(report)\n print(" ")\n print("="*n)\n print("Final evaluation")\n print(tabulate(table_data))\n # also load the source code of missing files...\n results[\'sources\'] = {}\n print("Gathering files...")\n for m in report.pack_imports:\n with open(m.__file__, \'r\') as f:\n results[\'sources\'][m.__name__] = f.read()\n print(f"*** {m.__name__}")\n\n results[\'sources\'] = {}\n json_str = json.dumps(results, indent=4)\n # now = datetime.now()\n # dname = os.path.dirname(inspect.getfile(report.__class__))\n # dname = os.getcwd()\n if output_dir is None:\n output_dir = os.getcwd()\n\n payload_out_base = report.__class__.__name__ + "_handin"\n\n obtain, possible = results[\'total\']\n token = "%s_%i_of_%i.token"%(payload_out_base, obtain, possible) # + str(obtained) +"_" + ".token"\n token = os.path.join(output_dir, token)\n bzwrite(json_str, token)\n\n print(" ")\n print("To get credit for your results, please upload the single file: ")\n print(">", token)\n print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n eval("exec")(report1_source, globals())\n pl = pickle.loads(payload)\n report = eval(name)(payload=pl, strict=True)\n # report.set_payload(pl)\n return report\n\n\nfrom cs101courseware_example import homework1\n\nclass ListReversalQuestion(QuestionGroup):\n title = "Reversal of list"\n\n class ListReversalItem(QPrintItem):\n l = [1, 3, 5, 1, 610]\n def compute_answer_print(self):\n from cs101courseware_example.homework1 import reverse_list\n return reverse_list(self.l)\n\n class ListReversalWordsItem(ListReversalItem):\n l = ["hello", "world", "summer", "dog"]\n\nclass LinearRegressionQuestion(QuestionGroup):\n title = "Linear regression and Boston dataset"\n class CoefficientsItem(QPrintItem):\n testfun = QPrintItem.assertL2\n tol = 0.03\n\n def compute_answer_print(self):\n from cs101courseware_example.homework1 import boston_linear\n boston_linear()\n\n def process_output(self, res, txt, numbers):\n return numbers[:-1]\n\n class RMSEItem(CoefficientsItem):\n def process_output(self, res, txt, numbers):\n return numbers[-1]\n\nclass Report1(Report):\n title = "CS 101 Report 1"\n questions = [(ListReversalQuestion, 5), (LinearRegressionQuestion, 13)]\n pack_imports = [homework1] # Include this file in .token file' -WebLqOFrgMVsHPQuIXBU=b'\x80\x03}q\x00(X\x14\x00\x00\x00ListReversalQuestionq\x01}q\x02(X\x10\x00\x00\x00ListReversalItemq\x03}q\x04X\x07\x00\x00\x00payloadq\x05]q\x06(Mb\x02K\x01K\x05K\x03K\x01eX\x00\x00\x00\x00q\x07\x86q\x08sX\x15\x00\x00\x00ListReversalWordsItemq\t}q\nh\x05]q\x0b(X\x03\x00\x00\x00dogq\x0cX\x06\x00\x00\x00summerq\rX\x05\x00\x00\x00worldq\x0eX\x05\x00\x00\x00helloq\x0feh\x07\x86q\x10suX\x18\x00\x00\x00LinearRegressionQuestionq\x11}q\x12(X\x10\x00\x00\x00CoefficientsItemq\x13}q\x14h\x05]q\x15(G\xbf\xbb\xa8"\x03\xc7\x1b\x18G?\xa7\xc4Gy-\x96\xd1G?\x94\xea\xdd\x08M9@G@\x05\x81\x15 \xce\x81\xaeG\xc01\xc3\xc7[s\xc4\x1dG@\x0e}g\x88zbrG?GB;\xd8\r\xa6KG\xbf\xf7\x9a\xf1\x0c\xf3\x02\x97G?\xd3\x95\xfa\x13h"\xe5G\xbf\x89D\x06\xeep[iG\xbf\xee{\t\x0e\x82\xb5\xe4G?\x83\x13Zm\xbcH\x7fG\xbf\xe0\xc9*\xc7\xba\xfbOesX\x08\x00\x00\x00RMSEItemq\x16}q\x17h\x05G@\x12\xb7\x8a\xa8\xe06\xe6suu.' -WebLqOFrgMVsHPQuIXBi="Report1" -WebLqOFrgMVsHPQuIXBG=WebLqOFrgMVsHPQuIXjY(WebLqOFrgMVsHPQuIXBi,WebLqOFrgMVsHPQuIXBR,WebLqOFrgMVsHPQuIXBU) -WebLqOFrgMVsHPQuIXBf=WebLqOFrgMVsHPQuIXjC.dirname(__file__) -WebLqOFrgMVsHPQuIXjB(WebLqOFrgMVsHPQuIXBG,WebLqOFrgMVsHPQuIXBf) \ No newline at end of file + fsQIGnDNmJuSbCgMlxUT,fsQIGnDNmJuSbCgMlxUF=fsQIGnDNmJuSbCgMlxUO(fsQIGnDNmJuSbCgMlxUo) + fsQIGnDNmJuSbCgMlxca(" ") + fsQIGnDNmJuSbCgMlxca("="*n) + fsQIGnDNmJuSbCgMlxca("Final evaluation") + fsQIGnDNmJuSbCgMlxca(tabulate(fsQIGnDNmJuSbCgMlxUF)) + fsQIGnDNmJuSbCgMlxUT['sources']={} + fsQIGnDNmJuSbCgMlxca("Gathering files...") + for m in fsQIGnDNmJuSbCgMlxUo.pack_imports: + with fsQIGnDNmJuSbCgMlxch(m.__file__,'r')as f: + fsQIGnDNmJuSbCgMlxUT['sources'][m.__name__]=f.read() + fsQIGnDNmJuSbCgMlxca(f"*** {m.__name__}") + fsQIGnDNmJuSbCgMlxUT['sources']={} + fsQIGnDNmJuSbCgMlxUw=fsQIGnDNmJuSbCgMlxcY(fsQIGnDNmJuSbCgMlxUT,indent=4) + if output_dir is fsQIGnDNmJuSbCgMlxcd: + output_dir=fsQIGnDNmJuSbCgMlxcX() + fsQIGnDNmJuSbCgMlxUh=fsQIGnDNmJuSbCgMlxUo.__class__.__name__+"_handin" + fsQIGnDNmJuSbCgMlxUP,fsQIGnDNmJuSbCgMlxUa=fsQIGnDNmJuSbCgMlxUT['total'] + fsQIGnDNmJuSbCgMlxUr="%s_%i_of_%i.token"%(fsQIGnDNmJuSbCgMlxUh,fsQIGnDNmJuSbCgMlxUP,fsQIGnDNmJuSbCgMlxUa) + fsQIGnDNmJuSbCgMlxUr=fsQIGnDNmJuSbCgMlxcK.join(output_dir,fsQIGnDNmJuSbCgMlxUr) + fsQIGnDNmJuSbCgMlxUA(fsQIGnDNmJuSbCgMlxUw,fsQIGnDNmJuSbCgMlxUr) + fsQIGnDNmJuSbCgMlxca(" ") + fsQIGnDNmJuSbCgMlxca("To get credit for your results, please upload the single file: ") + fsQIGnDNmJuSbCgMlxca(">",fsQIGnDNmJuSbCgMlxUr) + fsQIGnDNmJuSbCgMlxca("To campusnet without any modifications.") +def fsQIGnDNmJuSbCgMlxcR(fsQIGnDNmJuSbCgMlxUv,fsQIGnDNmJuSbCgMlxUy,payload): + fsQIGnDNmJuSbCgMlxcP("exec")(fsQIGnDNmJuSbCgMlxUy,fsQIGnDNmJuSbCgMlxcr()) + pl=fsQIGnDNmJuSbCgMlxct(payload) + fsQIGnDNmJuSbCgMlxUo=fsQIGnDNmJuSbCgMlxcP(fsQIGnDNmJuSbCgMlxUv)(payload=pl,strict=fsQIGnDNmJuSbCgMlxcy) + return fsQIGnDNmJuSbCgMlxUo +fsQIGnDNmJuSbCgMlxUy='__version__ = "0.0.5"\nimport os\nimport compress_pickle\n\ndef cache_write(object, file_name, verbose=True):\n dn = os.path.dirname(file_name)\n if not os.path.exists(dn):\n os.mkdir(dn)\n if verbose: print("Writing cache...", file_name)\n with open(file_name, \'wb\', ) as f:\n compress_pickle.dump(object, f, compression="lzma")\n if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n # file_name = cn_(file_name) if cache_prefix else file_name\n return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n # file_name = cn_(file_name) if cache_prefix else file_name\n if os.path.exists(file_name):\n with open(file_name, \'rb\') as f:\n return compress_pickle.load(f, compression="lzma")\n # return pickle.load(f)\n else:\n return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push && pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nfrom io import StringIO\nimport sys\nimport collections\nimport inspect\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n name = C.__class__.__name__\n # base_dir = os.path.join(base_dir, name)\n # if not os.path.isdir(base_dir):\n # os.makedirs(base_dir)\n return base_dir, name\n\nclass Hidden:\n def hide(self):\n return True\n\nimport sys\n\nclass Logger(object):\n def __init__(self, buffer):\n self.terminal = sys.stdout\n self.log = buffer\n\n def write(self, message):\n self.terminal.write(message)\n self.log.write(message)\n\n def flush(self):\n # this flush method is needed for python 3 compatibility.\n pass\n\n# sys.stdout = Logger()\n\nclass Capturing(list):\n def __init__(self, *args, unmute=False, **kwargs):\n self.unmute = unmute\n super().__init__(*args, **kwargs)\n\n def __enter__(self, capture_errors=True): # don\'t put arguments here.\n self._stdout = sys.stdout\n self._stringio = StringIO()\n if self.unmute:\n sys.stdout = Logger(self._stringio)\n else:\n sys.stdout = self._stringio\n\n if capture_errors:\n self._sterr = sys.stderr\n sys.sterr = StringIO() # memory hole it\n self.capture_errors = capture_errors\n return self\n\n def __exit__(self, *args):\n self.extend(self._stringio.getvalue().splitlines())\n del self._stringio # free up some memory\n sys.stdout = self._stdout\n if self.capture_errors:\n sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n title = None\n testfun = unittest.TestCase.assertEqual\n tol = 0\n\n def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs):\n self.name = self.__class__.__name__\n self._correct_answer_payload = correct_answer_payload\n super().__init__(*args, **kwargs)\n if self.title is None:\n self.title = self.name\n\n def assertL2(self, computed, expected, tol=None):\n if tol == None:\n tol = self.tol\n diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n if np.max(diff) > tol:\n print("Not equal within tolerance {tol}")\n print(f"Element-wise differences {diff.tolist()}")\n self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n def assertL2Relative(self, computed, expected, tol=None):\n if tol == None:\n tol = self.tol\n diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n if np.sum(diff > tol) > 0:\n print(f"Not equal within tolerance {tol}")\n print(f"Element-wise differences {diff.tolist()}")\n self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n def compute_answer(self, unmute=False):\n raise NotImplementedError("test code here")\n\n def test(self, computed, expected):\n self.testfun(computed, expected)\n\n def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, **kwargs):\n possible = 1\n computed = None\n def show_computed_(computed):\n print(">>> Your output:")\n print(computed)\n\n def show_expected_(expected):\n print(">>> Expected output:")\n print(expected)\n\n try:\n if unmute:\n print("\\n")\n computed = self.compute_answer(unmute=unmute)\n except Exception as e:\n if not passall:\n print("\\n=================================================================================")\n print(f"When trying to run test class \'{self.name}\' your code threw an error:")\n print(e)\n print("=================================================================================")\n import traceback\n traceback.print_exc()\n return (0, possible)\n correct = self._correct_answer_payload\n if show_expected or show_computed:\n print("\\n")\n if show_expected:\n show_expected_(correct)\n if show_computed:\n show_computed_(computed)\n try:\n # print(computed, correct)\n # if passall:\n # computed = correct\n if not passall:\n self.test(computed=computed, expected=correct)\n except Exception as e:\n print("\\n=================================================================================")\n print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n print(e)\n show_computed_(computed)\n show_expected_(correct)\n # print("-------------------------Your output:--------------------------------------------")\n # print(computed)\n # print("-------------------------Expected output:----------------------------------------")\n # print(correct)\n # print("=================================================================================")\n return (0, possible)\n return (1, possible)\n\n def score(self):\n try:\n self.test()\n except Exception as e:\n return 0\n return 1\n\nclass QPrintItem(QItem):\n def compute_answer_print(self):\n """\n Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n are send to process_output (see compute_answer below). In other words, the text generated is:\n\n res = compute_Answer_print()\n txt = (any terminal output generated above)\n numbers = (any numbers found in terminal-output txt)\n\n self.test(process_output(res, txt, numbers), <expected result>)\n\n :return: Optional values for comparison\n """\n raise Exception("Generate output here. The output is passed to self.process_output")\n\n def process_output(self, res, txt, numbers):\n return (res, txt)\n\n def compute_answer(self, unmute=False):\n with Capturing(unmute=unmute) as output:\n res = self.compute_answer_print()\n s = "\\n".join(output)\n numbers = extract_numbers(s)\n return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n @classmethod\n def __prepare__(self, name, bases):\n return collections.OrderedDict()\n def __new__(self, name, bases, classdict):\n classdict[\'__ordered__\'] = [key for key in classdict.keys() if key not in (\'__module__\', \'__qualname__\')]\n return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n title = "Graph search"\n items = None\n partially_scored = False\n\n def __init__(self, *args, **kwargs):\n self.name = self.__class__.__name__\n if self.items is None:\n self.items = []\n members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__] if inspect.isclass(gt) and issubclass(gt, QItem)]\n for gt in members:\n self.items.append( (gt, 1) )\n self.items = [(I(), w) for I, w in self.items]\n\nclass Report():\n title = "report title"\n questions = []\n pack_imports = []\n\n def __init__(self, strict=False,payload=None):\n working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self))))\n # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/")\n self.wdir, self.name = setup_dir_by_class(self, working_directory)\n self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions]\n # self.strict = strict\n if payload is not None:\n self.set_payload(payload, strict=strict)\n else:\n if os.path.isfile(self.computed_answers_file):\n self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n else:\n s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n if strict:\n raise Exception(s)\n else:\n print(s)\n\n def set_payload(self, payloads, strict=False):\n for q, _ in self.questions:\n for item, _ in q.items:\n if q.name not in payloads or item.name not in payloads[q.name]:\n s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n if strict:\n raise Exception(s)\n else:\n print(s)\n item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n\ndef extract_numbers(txt):\n import re\n numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n rx = re.compile(numeric_const_pattern, re.VERBOSE)\n all = rx.findall(txt)\n all = [float(a) if \'.\' in a else int(a) for a in all]\n return all\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pickle\nimport pyfiglet\n\n# from unitgrade.unitgrade import Hidden\n# import unitgrade as ug\n# import unitgrade.unitgrade as ug\nimport inspect\nimport os\nimport argparse\n\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nAs an example, suppose the report file is Documents/course_package/assignment1_dp.py, and `course_package` is a python package. Change directory to \'Documents/` and execute:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (example: -q 2)\')\nparser.add_argument(\'--showexpected\', action="store_true", help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\', action="store_true", help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\', action="store_true", help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\', action="store_true", help=\'Automatically pass all tests. Useful when debugging.\')\n\n# parser.add_argument(\'integers\', metavar=\'N\', type=int, nargs=\'+\',\n# help=\'an integer for the accumulator\')\n# parser.add_argument(\'--sum\', dest=\'accumulate\', action=\'store_const\',\n# const=sum, default=max,\n# help=\'sum the integers (default: find the max)\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None):\n args = parser.parse_args()\n if question is None and args.q is not None:\n question = args.q\n if "." in question:\n question, qitem = [int(v) for v in question.split(".")]\n else:\n question = int(question)\n\n # print(args)\n results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=False, passall=args.passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=args.unmute)\n\n if question is None:\n print("Provisional evaluation")\n tabulate(table_data)\n table = table_data\n print(tabulate(table))\n print(" ")\n print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n\n fr = inspect.getouterframes(inspect.currentframe())[1].filename\n print(">>>", os.path.basename(fr)[:-3] + "_grade.py")\n print("In the same manner as you ran this file.")\n return results\n\n\ndef upack(q):\n # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n h = np.asarray(h)\n return h[:,0], h[:,1], h[:,2],\n #\n # ws, possible, obtained = (np.asarray(x).squeeze() for x in zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]))\n # return ws, possible, obtained\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False, show_expected=False, show_computed=False,unmute=False):\n now = datetime.now()\n # show_expected = True\n ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n print(b + " v" + __version__)\n dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print("Started: " + dt_string)\n print("Evaluating " + report.title, "(use --help for options)")\n print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n table_data = []\n nL = 80\n\n score = {}\n for n, (q, w) in enumerate(report.questions):\n q_hidden = issubclass(q.__class__, Hidden)\n if question is not None and n+1 != question:\n continue\n\n print(f"Question {n+1}: {q.title}")\n print("="*nL)\n q.possible = 0\n q.obtained = 0\n\n q_ = {} # Gather score in this class.\n for j, (item, iw) in enumerate(q.items):\n if qitem is not None and question is not None and item is not None and j+1 != qitem:\n continue\n\n ss = f"*** q{n+1}.{j+1}) {item.title}"\n el = nL-4\n if len(ss) < el:\n ss += \'.\'*(el-len(ss))\n hidden = issubclass(item.__class__, Hidden)\n if not hidden:\n print(ss, end="")\n (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall)\n q_[j] = {\'w\': iw, \'possible\': possible, \'obtained\': current, \'hidden\': hidden}\n # q.possible += possible * iw\n # q.obtained += current * iw\n if not hidden:\n if current == possible:\n print(f"PASS")\n else:\n print(f"*** FAILED")\n\n ws, possible, obtained = upack(q_)\n possible = int(ws @ possible)\n obtained = int(ws @ obtained)\n obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'Ãtems\': q_, \'hidden\': q_hidden}\n\n q.obtained = obtained\n q.possible = possible\n\n s1 = f"*** Question q{n+1}"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n ws, possible, obtained = upack(score)\n possible = int( msum(possible) )\n obtained = int( msum(obtained) ) # Cast to python int\n report.possible = possible\n report.obtained = obtained\n now = datetime.now()\n dt_string = now.strftime("%H:%M:%S")\n print(f"Completed: "+ dt_string)\n table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n results = {\'total\': (obtained, possible), \'details\': score}\n return results, table_data\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n with getattr(bz2, \'open\')(token, "wt") as f:\n f.write(json_str)\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n n = 80\n results, table_data = evaluate_report(report)\n print(" ")\n print("="*n)\n print("Final evaluation")\n print(tabulate(table_data))\n # also load the source code of missing files...\n results[\'sources\'] = {}\n print("Gathering files...")\n for m in report.pack_imports:\n with open(m.__file__, \'r\') as f:\n results[\'sources\'][m.__name__] = f.read()\n print(f"*** {m.__name__}")\n\n results[\'sources\'] = {}\n json_str = json.dumps(results, indent=4)\n # now = datetime.now()\n # dname = os.path.dirname(inspect.getfile(report.__class__))\n # dname = os.getcwd()\n if output_dir is None:\n output_dir = os.getcwd()\n\n payload_out_base = report.__class__.__name__ + "_handin"\n\n obtain, possible = results[\'total\']\n token = "%s_%i_of_%i.token"%(payload_out_base, obtain, possible) # + str(obtained) +"_" + ".token"\n token = os.path.join(output_dir, token)\n bzwrite(json_str, token)\n\n print(" ")\n print("To get credit for your results, please upload the single file: ")\n print(">", token)\n print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n eval("exec")(report1_source, globals())\n pl = pickle.loads(payload)\n report = eval(name)(payload=pl, strict=True)\n # report.set_payload(pl)\n return report\n\n\nfrom cs101courseware_example import homework1\n\nclass ListReversalQuestion(QuestionGroup):\n title = "Reversal of list"\n\n class ListReversalItem(QPrintItem):\n l = [1, 3, 5, 1, 610]\n def compute_answer_print(self):\n from cs101courseware_example.homework1 import reverse_list\n return reverse_list(self.l)\n\n class ListReversalWordsItem(ListReversalItem):\n l = ["hello", "world", "summer", "dog"]\n\nclass LinearRegressionQuestion(QuestionGroup):\n title = "Linear regression and Boston dataset"\n class CoefficientsItem(QPrintItem):\n testfun = QPrintItem.assertL2\n tol = 0.03\n\n def compute_answer_print(self):\n from cs101courseware_example.homework1 import boston_linear\n boston_linear()\n\n def process_output(self, res, txt, numbers):\n return numbers[:-1]\n\n class RMSEItem(CoefficientsItem):\n def process_output(self, res, txt, numbers):\n return numbers[-1]\n\nclass Report1(Report):\n title = "CS 101 Report 1"\n questions = [(ListReversalQuestion, 5), (LinearRegressionQuestion, 13)]\n pack_imports = [homework1] # Include this file in .token file' +fsQIGnDNmJuSbCgMlxUL=b'\x80\x03}q\x00(X\x14\x00\x00\x00ListReversalQuestionq\x01}q\x02(X\x10\x00\x00\x00ListReversalItemq\x03}q\x04X\x07\x00\x00\x00payloadq\x05]q\x06(Mb\x02K\x01K\x05K\x03K\x01eX\x00\x00\x00\x00q\x07\x86q\x08sX\x15\x00\x00\x00ListReversalWordsItemq\t}q\nh\x05]q\x0b(X\x03\x00\x00\x00dogq\x0cX\x06\x00\x00\x00summerq\rX\x05\x00\x00\x00worldq\x0eX\x05\x00\x00\x00helloq\x0feh\x07\x86q\x10suX\x18\x00\x00\x00LinearRegressionQuestionq\x11}q\x12(X\x10\x00\x00\x00CoefficientsItemq\x13}q\x14h\x05]q\x15(G\xbf\xbb\xa9\xccl\xaa\xa3,G?\xa7\xc2\xcc*\xd6\xe7ZG?\x95\x87{p\x1f\x08\x14G@\x05~\x82\xfb\xf3U\xf9G\xc01\xc9ZT\xe0,%G@\x0e{U\xd8N\xb6\x1cG?Gc\x97&\xd0\xc8mG\xbf\xf7\x99S\xe4\x02\xc9^G?\xd3\x99W\xb5\x1d\xae\x92G\xbf\x89G\x85<\xcf\xc5\xfaG\xbf\xee~\xdc\x03v\x82\x12G?\x83\x11\xdf\xd3\xc9\x16\rG\xbf\xe0\xc9\xe8Q\x0c\xb7\xf4esX\x08\x00\x00\x00RMSEItemq\x16}q\x17h\x05G@\x12\xb72\xbc\x18\x9aqsuu.' +fsQIGnDNmJuSbCgMlxUv="Report1" +fsQIGnDNmJuSbCgMlxUo=fsQIGnDNmJuSbCgMlxcR(fsQIGnDNmJuSbCgMlxUv,fsQIGnDNmJuSbCgMlxUy,fsQIGnDNmJuSbCgMlxUL) +fsQIGnDNmJuSbCgMlxUB=fsQIGnDNmJuSbCgMlxcK.dirname(__file__) +fsQIGnDNmJuSbCgMlxcU(fsQIGnDNmJuSbCgMlxUo,fsQIGnDNmJuSbCgMlxUB) \ No newline at end of file diff --git a/cs101courseware_example/cs101report2_grade.py b/cs101courseware_example/cs101report2_grade.py index 66d72faacf2ab86566e2e5137264961da87c10d0..540fff65035e19d7fcd30a119d3cbbc1a354bd88 100644 --- a/cs101courseware_example/cs101report2_grade.py +++ b/cs101courseware_example/cs101report2_grade.py @@ -1,3 +1,3 @@ +'''WARNING: Modifying, decompiling or otherwise tampering with this script, it's data or the resulting .token file will be investigated as a cheating attempt.''' import bz2, base64 -exec(bz2.decompress(base64.b64decode('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'))) -# Created by pyminifier (https://github.com/liftoff/pyminifier) +exec(bz2.decompress(base64.b64decode('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'))) \ No newline at end of file diff --git a/unitgrade/__init__.py b/unitgrade/__init__.py index 01e7f87b091bbdf0cd19034d52248d24a5b51141..cec05824b4522b231634cf9250204befecc9dd0c 100644 --- a/unitgrade/__init__.py +++ b/unitgrade/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.5" +__version__ = "0.0.6" import os import compress_pickle diff --git a/unitgrade/__pycache__/unitgrade.cpython-36.pyc b/unitgrade/__pycache__/unitgrade.cpython-36.pyc index 0155aeaaf3e2c6ab75983532c19ad3b178c91e99..3dbdaf3f8434d6c5205c947ba76046b8931a8f50 100644 Binary files a/unitgrade/__pycache__/unitgrade.cpython-36.pyc and b/unitgrade/__pycache__/unitgrade.cpython-36.pyc differ diff --git a/unitgrade/__pycache__/unitgrade_helpers.cpython-36.pyc b/unitgrade/__pycache__/unitgrade_helpers.cpython-36.pyc index 8c4ce2bf27d8a6115771da2acb7ede2a13c81169..09cee7c7ec98a576ca03d2a35372f051fefca641 100644 Binary files a/unitgrade/__pycache__/unitgrade_helpers.cpython-36.pyc and b/unitgrade/__pycache__/unitgrade_helpers.cpython-36.pyc differ diff --git a/unitgrade/unitgrade.py b/unitgrade/unitgrade.py index e7e0f5d4890cbbac979c2b59bb0a2ae477d12c80..b8ce55eb976335f8c43d3359d2c8cbadaba12975 100644 --- a/unitgrade/unitgrade.py +++ b/unitgrade/unitgrade.py @@ -7,9 +7,9 @@ import unittest import numpy as np import os from io import StringIO -import sys import collections import inspect +import re myround = lambda x: np.round(x) # required. msum = lambda x: sum(x) @@ -75,6 +75,7 @@ class QItem(unittest.TestCase): testfun = unittest.TestCase.assertEqual tol = 0 + _computed_answer = None # Internal helper to later get results. def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs): self.name = self.__class__.__name__ self._correct_answer_payload = correct_answer_payload @@ -115,9 +116,10 @@ class QItem(unittest.TestCase): print(computed) def show_expected_(expected): - print(">>> Expected output:") + print(">>> Expected output (note: may have been processed; read text script):") print(expected) + correct = self._correct_answer_payload try: if unmute: print("\n") @@ -125,13 +127,16 @@ class QItem(unittest.TestCase): except Exception as e: if not passall: print("\n=================================================================================") - print(f"When trying to run test class '{self.name}' your code threw an error:") - print(e) - print("=================================================================================") + print(f"When trying to run test class '{self.name}' your code threw an error:", e) + show_expected_(correct) import traceback - traceback.print_exc() + print(traceback.format_exc()) + print("=================================================================================") return (0, possible) - correct = self._correct_answer_payload + + if self._computed_answer is None: + self._computed_answer = computed + if show_expected or show_computed: print("\n") if show_expected: @@ -189,6 +194,7 @@ class QPrintItem(QItem): res = self.compute_answer_print() s = "\n".join(output) numbers = extract_numbers(s) + self._computed_answer = (res, s, numbers) return self.process_output(res, s, numbers) class OrderedClassMembers(type): @@ -215,16 +221,17 @@ class QuestionGroup(metaclass=OrderedClassMembers): class Report(): title = "report title" + version = None questions = [] pack_imports = [] + individual_imports = [] - def __init__(self, strict=False,payload=None): + def __init__(self, strict=False, payload=None): working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self)))) # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/") self.wdir, self.name = setup_dir_by_class(self, working_directory) self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat") self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions] - # self.strict = strict if payload is not None: self.set_payload(payload, strict=strict) else: @@ -246,10 +253,10 @@ class Report(): raise Exception(s) else: print(s) - item._correct_answer_payload = payloads[q.name][item.name]['payload'] + else: + item._correct_answer_payload = payloads[q.name][item.name]['payload'] def extract_numbers(txt): - import re numeric_const_pattern = '[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?' rx = re.compile(numeric_const_pattern, re.VERBOSE) all = rx.findall(txt) diff --git a/unitgrade/unitgrade_helpers.py b/unitgrade/unitgrade_helpers.py index 8105085a4839bc238222a83b1306895b3b1ee6d2..d6680171d1328080dbbdb88c671121959c447e24 100644 --- a/unitgrade/unitgrade_helpers.py +++ b/unitgrade/unitgrade_helpers.py @@ -18,19 +18,19 @@ import argparse parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: -> python report1.py +> python assignment1_dp.py To run only question 2 or question 2.1 -> python report1.py -q 2 -> python report1.py -q 2.1 +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 Note this scripts does not grade your report. To grade your report, use: > python report1_grade.py Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful. -As an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to 'Documents/` and execute: +As an example, suppose the report file is Documents/course_package/assignment1_dp.py, and `course_package` is a python package. Change directory to 'Documents/` and execute: > python -m course_package.report1 @@ -48,8 +48,6 @@ parser.add_argument('--passall', action="store_true", help='Automatically pass # const=sum, default=max, # help='sum the integers (default: find the max)') - - def evaluate_report_student(report, question=None, qitem=None): args = parser.parse_args() if question is None and args.q is not None: @@ -58,8 +56,8 @@ def evaluate_report_student(report, question=None, qitem=None): question, qitem = [int(v) for v in question.split(".")] else: question = int(question) - - # print(args) + if not os.path.isfile(report.computed_answers_file): + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=False, passall=args.passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=args.unmute) if question is None: @@ -93,11 +91,15 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") print("Started: " + dt_string) - print("Evaluating " + report.title, "(use --help for options)") + s = report.title + if report.version is not None: + s += " version " + report.version + print("Evaluating " + s, "(use --help for options)") print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] nL = 80 + score = {} for n, (q, w) in enumerate(report.questions): q_hidden = issubclass(q.__class__, Hidden) @@ -122,7 +124,7 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa if not hidden: print(ss, end="") (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall) - q_[j] = {'w': iw, 'possible': possible, 'obtained': current, 'hidden': hidden} + q_[j] = {'w': iw, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer)} # q.possible += possible * iw # q.obtained += current * iw if not hidden: @@ -135,7 +137,7 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa possible = int(ws @ possible) obtained = int(ws @ obtained) obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0 - score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'ítems': q_, 'hidden': q_hidden} + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'hidden': q_hidden} q.obtained = obtained q.possible = possible diff --git a/unitgrade_demo.mp4 b/unitgrade_demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..3826fec3bb8c9fc33acf056d26479a42be0630be Binary files /dev/null and b/unitgrade_demo.mp4 differ