summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjwansek <eddie.atten.ea29@gmail.com>2022-01-17 19:47:01 +0000
committerjwansek <eddie.atten.ea29@gmail.com>2022-01-17 19:47:01 +0000
commit06ec17c5e65d03ffd14a60ce89fe71234eb81c8a (patch)
tree89f52069d6b39b387f8644cd83a4f70c02180e58
parent37236fdc957f5900f6a4bbffbff6ccc07d412c44 (diff)
downloadSmarker-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.yml8
-rw-r--r--ExampleSubmission/animals.py22
-rw-r--r--ExampleSubmission/example.py11
-rw-r--r--examplerun.bat2
-rw-r--r--reflect.py95
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
diff --git a/reflect.py b/reflect.py
index b8f83c4..90fa5b6 100644
--- a/reflect.py
+++ b/reflect.py
@@ -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)