Skip to content

Instantly share code, notes, and snippets.

@sccreeper
Created January 4, 2025 17:06
Show Gist options
  • Save sccreeper/8e39485d694e5d6daab2f0cb45db7bca to your computer and use it in GitHub Desktop.
Save sccreeper/8e39485d694e5d6daab2f0cb45db7bca to your computer and use it in GitHub Desktop.
Runner for my university's programming worksheets.
import importlib
import os
from inspect import getmembers, isfunction, signature
import typing
# Purpose of this file is to act as a shim around all the files in the practical folders.
# It allows you to run specific functions and also means they can share common libraries in the lib folder
# All functions must have type hints in order to work properly
# Never try and do "reflection" in Python, if you can even call it that
def main():
practical_week = input("Practical week: ")
files = os.listdir(f"./week{practical_week}")
if len(files) == 0:
print("No files in week!")
return
elif not f"pract{practical_week}.py" in files:
file_list: list[str] = [ x.split(".")[0] for x in files if
not os.path.isdir(f"./week{practical_week}/{x}")
and os.path.splitext(f"./week{practical_week}/{x}")[1] == ".py" ]
for i in range(len(file_list)):
print(f"{i}: {file_list[i]}")
sel: int = int(input("Select file number: "))
if sel < 0 or sel >= len(file_list):
print("Invalid number!")
return
os.system("cls" if os.name == "nt" else "clear")
os.chdir(f"./week{practical_week}")
importlib.import_module(f"week{practical_week}.{file_list[sel]}")
else:
print("Importing module...")
func_module = importlib.import_module(f"week{practical_week}.pract{practical_week}")
method_list = getmembers(func_module, isfunction)
for i in range(len(method_list)):
# Default blank explanation if no doc string exists, otherwise format it nicely
doc_str: str
if method_list[i][1].__doc__ == None:
doc_str = ""
else:
doc_str = f"| {str(method_list[i][1].__doc__).replace('\n', '')}"
print(f"{i} | {method_list[i][0]} {doc_str}")
func_no = int(input("Select function number: "))
if func_no < 0 or func_no >= len(method_list):
print("Invalid number")
return
# Any file reads are relative to the relevant practical file in it's directory.
os.chdir(f"./week{practical_week}")
func_sig = signature(method_list[func_no][1])
# If we don't have any parameters, then we can just can the function normally.
# Otherwise we loop through the parameters, asking the user for a value and converting it to the appropriate type.
# In the case of arrays, we ask the user for multiple array items, before stopping on Ctrl+C followed by enter.
# Doesn't support dictionaries yet
if len(func_sig.parameters) == 0:
os.system("cls" if os.name == "nt" else "clear")
val = method_list[func_no][1]()
if not isinstance(val, type(None)):
print(val)
else:
params = []
for param_name, param_type in func_sig.parameters.items():
param_val: any = None
# If the type annotation is a list, then we have different logic
# Below is the logic for a dict
# Both only go 1 level deep, no recursion yet.
# Finally is the logic for a base type e.g. int, str, float etc.
if typing.get_origin(param_type.annotation) is list:
# Continuously add arguments of the right type until user presses Ctrl+C followed by Enter
param_val: list = []
while True:
try:
list_item = typing.get_args(param_type.annotation)[0](
input(f"list[{typing.get_args(param_type.annotation)[0].__name__}][{len(param_val)}] -> ")
)
param_val.append(list_item)
except KeyboardInterrupt:
break
params.append(param_val)
elif typing.get_origin(param_type.annotation) is dict:
param_val: dict = {}
while True:
try:
key_type = typing.get_args(param_type.annotation)[0]
value_type = typing.get_args(param_type.annotation)[1]
key_val = key_type(input(f"dict[{len(param_val)}].key: {key_type.__name__} -> "))
value_val = value_type(input(f"dict[{len(param_val)}].value: {value_type.__name__} -> "))
param_val[key_val] = value_val
except KeyboardInterrupt:
break
params.append(param_val)
else:
param_val = input(f"{param_name}: {param_type.annotation.__name__} -> ")
params.append(param_type.annotation(param_val))
# One liner to clear the screen
os.system("cls" if os.name == "nt" else "clear")
val = method_list[func_no][1](*params)
if not isinstance(val, type(None)):
print(val)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment