diff options
| -rw-r--r-- | ExampleAssessments/example.yml | 8 | ||||
| -rw-r--r-- | ExampleSubmission/animals.py | 22 | ||||
| -rw-r--r-- | ExampleSubmission/example.py | 11 | ||||
| -rw-r--r-- | examplerun.bat | 2 | ||||
| -rw-r--r-- | reflect.py | 95 | 
5 files changed, 122 insertions, 16 deletions
| diff --git a/ExampleAssessments/example.yml b/ExampleAssessments/example.yml index 2ca03d3..bab1fb0 100644 --- a/ExampleAssessments/example.yml +++ b/ExampleAssessments/example.yml @@ -11,6 +11,12 @@ files:              - hello_world(2)              - an_undocumented_function(0)              - aFunctionThatIsntThere(2) +            - greet(2)      - aFileThatIsntThere.py:          functions: -            - hello_world(2)
\ No newline at end of file +            - hello_world(2) +    - animals.py: +        classes: +            - Dog: +            - Cat: +            - Kitten:
\ No newline at end of file diff --git a/ExampleSubmission/animals.py b/ExampleSubmission/animals.py new file mode 100644 index 0000000..10c1cb4 --- /dev/null +++ b/ExampleSubmission/animals.py @@ -0,0 +1,22 @@ +import datetime + +class Animal: +    def __init__(self): +        self.birthday = datetime.datetime.now() + +    def move(self): +        return "*moves*" + +class Dog(Animal): +    def speak(self): +        return "woof" + +class Cat(Animal): +    def speak(self): +        return "meow" + +class Kitten(Cat): +    """nyaa~~~ +    """ +    def speak(self): +        return "meow (but cuter)" diff --git a/ExampleSubmission/example.py b/ExampleSubmission/example.py index b64c98d..a226adb 100644 --- a/ExampleSubmission/example.py +++ b/ExampleSubmission/example.py @@ -1,3 +1,6 @@ +# Eden Attenborough +# 12-01-21 +  import tkinter as tk  class Application(tk.Tk): @@ -8,7 +11,7 @@ class Application(tk.Tk):          self.title("Hello World!") -    def a_method_with_defaults(self, arg_1 = "hello", arg_2 = "world"): +    def a_method_with_defaults(self, n:str, arg_1 = "hello", arg_2 = "world", count:int = 3):          """Adds two strings together with a space between them.          Args: @@ -32,6 +35,7 @@ class Application(tk.Tk):          """          return num1 + num2 +# hello world!  def hello_world(times):      """Prints 'hello world!' to stdout. Prints it out `times` times. @@ -46,6 +50,11 @@ def hello_world(times):  def an_undocumented_function():      return 3.14156 +# kwonlyargs demo +def greet(*names, greeting="Hello"): +    for name in names: +        print(greeting, name) +  if __name__ == "__main__":      app = Application()      app.mainloop()
\ No newline at end of file diff --git a/examplerun.bat b/examplerun.bat index 5252460..8ddbc7d 100644 --- a/examplerun.bat +++ b/examplerun.bat @@ -1,3 +1,3 @@  zip -r 100301654.zip .\ExampleSubmission\ -python .\mark.py -s 100301654.zip -a .\ExampleAssessments\example.yml -f json +python .\mark.py -s 100301654.zip -a .\ExampleAssessments\example.yml -f yaml  rm 100301654.zip
\ No newline at end of file @@ -1,4 +1,6 @@  from dataclasses import dataclass +from functools import reduce +from operator import getitem  import importlib  import inspect  import pkgutil @@ -38,7 +40,10 @@ class Reflect:          Returns:              str: Provided documentation          """ -        return inspect.getdoc(self.imported_modules[module_name]) +        return { +            "comments": inspect.getcomments(self.imported_modules[module_name]),  +            "doc": inspect.getdoc(self.imported_modules[module_name]) +        }      def get_classes(self, module_name):          """Gets the classes in a given module. The module must be imported first. @@ -51,9 +56,9 @@ class Reflect:              a tuple containing the class object and the classes' documentation.          """          return { -            i[0]: (i[1], inspect.getdoc(i[1]))  +            i[0]: (i[1], {"comments": inspect.getcomments(i[1]), "doc": inspect.getdoc(i[1])})               for i in inspect.getmembers(self.imported_modules[module_name])  -            if inspect.isclass(i[1]) +            if inspect.isclass(i[1]) and self.get_class_full_name(i[1]).split(".")[0] in self.imported_modules.keys()          }      def get_class_methods(self, module_name, class_name): @@ -65,10 +70,14 @@ class Reflect:          Returns:              dict: A dictionary of the methods. The index is the function name, followed by a tuple -            containing the function object, the documentation, and the args as a dict. +            containing the function object, the documentation, and the args as a nicely formatted string.          """          return { -            i[0]: (i[1], inspect.getdoc(i[1]), inspect.getfullargspec(i[1])._asdict())  +            i[0]: ( +                i[1],  +                {"comments": inspect.getcomments(i[1]), "doc": inspect.getdoc(i[1])},  +                str(inspect.signature(i[1])) +            )              for i in inspect.getmembers(                  self.get_classes(module_name)[class_name][0],                   predicate=inspect.isfunction @@ -77,11 +86,61 @@ class Reflect:      def get_functions(self, module_name):          return { -            i[0]: (i[1], inspect.getdoc(i[1]), inspect.getfullargspec(i[1])._asdict())  +            i[0]: ( +                i[1],  +                {"comments": inspect.getcomments(i[1]), "doc": inspect.getdoc(i[1])},  +                str(inspect.signature(i[1]))  +            )              for i in inspect.getmembers(self.imported_modules[module_name])               if inspect.isfunction(i[1])          } +    def get_class_full_name(self, class_): +        if class_.__module__ in ['builtins', 'exceptions']: +            return class_.__name__ +        return "%s.%s" % (class_.__module__, class_.__name__) + +    # classes that inherit from two classes doesn't print out nicely here. +    # using this method is better https://pastebin.com/YuxkkTkv +    def get_class_tree(self): +        """Generates a dictionary based tree structure showing inheritance of classes +        of all the *imported modules*. WARNING: It doesn't deal well with multiple inheritance.. +        Read the comments. +        """ + +        def expand(a:list): +            out = [] +            for l in a: +                for i in reversed(range(0, len(l))): +                    out.append(l[:len(l) - i]) +            return out + +        # https://www.geeksforgeeks.org/python-convert-a-list-of-lists-into-tree-like-dict/ +        def getTree(tree, mappings): +            return reduce(getitem, mappings, tree) +             +        # https://www.geeksforgeeks.org/python-convert-a-list-of-lists-into-tree-like-dict/ +        def setTree(tree, mappings): +            getTree(tree, mappings[:-1])[mappings[-1]] = dict() + +        unexpanded_class_paths = [] +        for module in self.imported_modules.keys(): +            for class_ in self.get_classes(module).values(): +                unexpanded_class_paths.append([ +                    self.get_class_full_name(i)  +                    for i in reversed(list(inspect.getmro(class_[0]))) +                ]) +        tree = {} +        added = []  # the expander makes duplicates. keep a list to remove them +                        # sadly a collections.Counter doesnt work with lists of lists     +        for s in expand(unexpanded_class_paths): +            if s not in added: +                setTree(tree, [i for i in reversed(s)][::-1]) +                added.append(s) + +        # return inspect.getclasstree(classes) +        return tree +  def gen_reflection_report(client_code_path, assessment_struct):      reflection = Reflect(client_code_path)      present_module_names = [i.name for i in reflection.client_modules] @@ -99,19 +158,31 @@ def gen_reflection_report(client_code_path, assessment_struct):          reflection.import_module(module_name)          required_files_features = assessment_struct["files"][i][required_file] +        out["files"][i][required_file]["documentation"] = reflection.get_module_doc(module_name)          if "classes" in required_files_features.keys():              present_classes = reflection.get_classes(module_name)              for j, class_name in enumerate(required_files_features["classes"], 0):                  class_name = list(class_name.keys())[0] +                stop_here_flag = False +                # surprised the yaml parser doesnt do this automatically... +                if out["files"][i][required_file]["classes"][j][class_name] is None: +                    out["files"][i][required_file]["classes"][j][class_name] = {} +                    stop_here_flag = True +                  if class_name in present_classes.keys():                      out["files"][i][required_file]["classes"][j][class_name]["present"] = True                  else: -                    out["files"][i][required_file]["classes"][j][class_name]["present"] = True +                    out["files"][i][required_file]["classes"][j][class_name]["present"] = False                      continue - +                 +                # print( present_classes[class_name][1])                  out["files"][i][required_file]["classes"][j][class_name]["documentation"] = present_classes[class_name][1] + +                if stop_here_flag: +                    continue +                  present_methods = reflection.get_class_methods(module_name, class_name)                  for k, required_method in enumerate(assessment_struct["files"][i][required_file]["classes"][j][class_name]["methods"], 0): @@ -129,7 +200,6 @@ def gen_reflection_report(client_code_path, assessment_struct):          if "functions" in required_files_features.keys():              present_functions = reflection.get_functions(module_name) -            # print(present_functions)              for j, required_function in enumerate(assessment_struct["files"][i][required_file]["functions"], 0):                  function_name = re.sub(r"\(\d+\)", "", required_function)                  out["files"][i][required_file]["functions"][j] = {required_function: {}} @@ -143,14 +213,13 @@ def gen_reflection_report(client_code_path, assessment_struct):                  out["files"][i][required_file]["functions"][j][required_function]["documentation"] = present_functions[function_name][-2]                      out["files"][i][required_file]["functions"][j][required_function]["arguments"] = present_functions[function_name][-1]     +    out["class_tree"] = reflection.get_class_tree()      return out - - -  if __name__ == "__main__":      user_code_path = "D:\\Edencloud\\UniStuff\\3.0 - CMP 3rd Year Project\\Smarker\\../ExampleSubmissions/Submission_A"      reflect = Reflect(user_code_path)      reflect.import_module("pjtool") -    print(reflect.get_class_methods("pjtool", "Date")) +    for c, v in reflect.get_classes(("pjtool")).items(): +        print(c, v) | 
