diff options
author | jwansek <eddie.atten.ea29@gmail.com> | 2022-01-17 19:47:01 +0000 |
---|---|---|
committer | jwansek <eddie.atten.ea29@gmail.com> | 2022-01-17 19:47:01 +0000 |
commit | 06ec17c5e65d03ffd14a60ce89fe71234eb81c8a (patch) | |
tree | 89f52069d6b39b387f8644cd83a4f70c02180e58 | |
parent | 37236fdc957f5900f6a4bbffbff6ccc07d412c44 (diff) | |
download | Smarker-06ec17c5e65d03ffd14a60ce89fe71234eb81c8a.tar.gz Smarker-06ec17c5e65d03ffd14a60ce89fe71234eb81c8a.zip |
added getting class inheritance tree, getting method args nicely, fixed bug with importing too many modules
-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) |