[Python] How should I go about adding a plugin feature for gui based python applications?

Discussão em 'Python' iniciado por Stack, Fevereiro 16, 2025.

  1. Stack

    Stack Membro Participativo

    I am currently working on a school project where I have to plot some data and take in config values. So I chose to use tkinter and create a function caller using introspection and signatures.

    import inspect, sys, functools

    import re

    import tkinter as tk
    import tkinter.ttk as ttk
    import tkinter.constants as tkconst
    import tkinter.simpledialog as diag
    import tkinter.messagebox as msgbox

    typePattern = re.compile(r'[^\|\s]*(?=([.*])*)')
    print(re.match(typePattern,"int | x").group())


    def toBytes(x:str):
    return x.encode('ascii','strict')

    def gui_call_wrapper(*positional_types,**kwargs_types):

    def partial_decorator(func):
    pos_types = list(positional_types).copy()
    kw_types = kwargs_types.copy()
    argspec = inspect.getfullargspec(func)
    argspec.args
    @functools.wraps(func)
    def arg_transform_wrapper(*args,**kwargs):
    nonlocal pos_types,kw_types,argspec
    print(args,kwargs)
    args = [pos_types(args.get()) for i in range(len(args))]
    pos_args= [ *args]+ [ None for i in range(max(len(argspec.args) - len(args),0)) ]
    kwargs = {i:kw_types(kwargs.get()) for i in kwargs}
    kargs={}
    for i in kwargs:
    index =argspec.args.index(i)
    if index!= -1:
    pos_args[index]=kwargs
    else:
    kargs=kwargs
    if len(kargs.keys())==0:
    return func(*pos_args)
    return func(*args,**kwargs)
    arg_transform_wrapper.__signature__ = inspect.signature(func)
    return arg_transform_wrapper
    return partial_decorator



    def argumentToTypeFactory(extras:dict = None):
    transTable = {bool:bool,int:int,float:float,list:list,
    "bool":bool,"int":int,"float":float,"list":list,
    str:str,"str":str, bytes:bytes, "bytes":bytes
    }
    if extras is not None:
    transTable.update(**extras)
    def arguments(annot:'str|type'=None,val=None):
    nonlocal transTable
    print('Annotation Type',type(annot))
    if val is None and annot is None:
    return str

    if isinstance(annot,type):
    if issubclass(annot,(str,bytes)):
    if issubclass(annot,(bytes)):
    return bytes
    else:
    return str
    currtype = transTable.get(annot)
    elif isinstance(annot,str,bytes):
    currtype = transTable.get(re.match(typePattern,annot).group())
    else:
    currtype = None
    if(currtype is None):
    transTable.get(type(val))
    print(currtype)
    if currtype is None:
    return str
    return currtype

    return arguments

    defaultArgsToTypes = argumentToTypeFactory()

    def getArgsToType(func:'function',argumetMapper = defaultArgsToTypes):
    func_info = inspect.getfullargspec(func)
    arg_types = {i:argumetMapper(func_info.annotations.get(i),({**(func_info.kwonlydefaults or {})}).get(i)) for i in [*func_info.args,*func_info.kwonlyargs]}

    if (func_info.varargs) is not None:
    arg_types[func_info.varargs]=list
    return arg_types

    def add_Field_Var(master, field_name, field_type,field_value=None):
    varname =f'val_{field_name}'
    innerFrame =tk.LabelFrame(master,text=field_name)
    if(field_type==bool):
    entry= tk.BooleanVar(master, value=field_value, name=varname)
    entry_field = ttk.Checkbutton(innerFrame,name=field_name,text=field_name, variable=entry)
    if(field_type==str):
    entry= tk.StringVar(master, value=field_value, name=varname)
    entry_field = ttk.Entry(innerFrame,name=field_name,text=field_name, validate='all', textvariable=entry)
    if(field_type==int):
    entry= tk.IntVar(master, value=field_value, name=varname)
    entry_field = ttk.Entry(innerFrame, name=field_name,text=field_name, validate='all',validatecommand=lambda x: x.isnumeric(), textvariable=entry)
    if(field_type==float):
    entry= tk.BooleanVar(master, value=field_value, name=varname)
    entry_field = ttk.Entry(innerFrame,name=field_name,text=field_name, validate='all',validatecommand=lambda x: x.isdecimal(), textvariable=entry)
    else:
    entry= tk.StringVar(master, value=field_value, name=varname)
    entry_field = ttk.Entry(innerFrame,name=field_name,text=field_name, validate='all', textvariable=entry)
    return entry,entry_field,innerFrame
    def callFunc(ev:'tk.Event[ttk.Button]',func:'function',*args, **kwargs):
    form = ev.widget.master
    for i in kwargs:
    print(i,kwargs.get())
    msgbox.showinfo(title=func.__name__,message=f"{func(**(kwargs))}")
    return None

    def makeFunctionCallable(func:'function'):
    argMapping=getArgsToType(func)
    root=tk.Tk()
    wmain=ttk.Labelframe(root,width=640,height=480, text=func.__name__)
    stores = {}
    for i in argMapping:
    store,field, iframe =add_Field_Var(wmain,i,argMapping)
    field.grid(sticky='w')
    iframe.grid(sticky='w')
    stores.setdefault(i,store)
    btn=ttk.Button(wmain,text="Submit", class_='Submit', name='submit_button')
    btn.bind_class('Submit','<Button-1>',lambda ev:callFunc(ev,func,**stores))
    btn.grid()

    # root.wm_geometry('640x480')
    # wmain.master.wm_geometry('640x480')
    wmain.grid(sticky='nw',padx=5,pady=5)
    wmain.grid_columnconfigure(0,weight=1)
    wmain.grid_rowconfigure(0,weight=1)


    root.mainloop()
    if __name__ == '__main__':
    bin_gui = gui_call_wrapper(int,number=int)(bin)
    makeFunctionCallable(bin_gui)



    I am currently stuck on giving the user the ability to add their own defined python files and add them using. I was wondering the best way to achieve such the plugin feature while following good coding practice and if there is a better way than using the importlib.util submodule function module_from_spec.

    I have made an function that uses import lib for the scene finder before but only for a known path.

    import importlib.machinery
    import importlib.metadata
    import importlib.simple
    import importlib.util
    import tkinter as tk
    import tkinter.ttk as ttk
    from typing import Iterable

    import importlib
    import os, os.path as pth

    __base__ = pth.abspath(pth.dirname(__file__))



    class IScene(tk.Widget):
    name:str
    class TabbedView(ttk.Notebook):
    def __init__(self, master=None, **kw):
    super().__init__(master,width=640, height=480, **kw)

    def buildTab(self,dir):
    for i in os.listdir(dir) :
    print(i)
    if i.startswith('gui_scene'):
    # print(i)
    ldr = importlib.machinery.SourceFileLoader
    finder =importlib.machinery.FileFinder(dir,(ldr,['.py']))
    mod= finder.find_spec(i.removesuffix('.py'))
    scene_module=importlib.util.module_from_spec(mod)
    importlib.machinery.SourcelessFileLoader.exec_module(scene_module.__loader__,scene_module)
    scene =(scene_module.__dict__.get('scene'))
    if scene != None:
    curr_scene:IScene = scene(self)
    self.add(curr_scene,state='normal',sticky='nesw',text=curr_scene.name or '',)
    else:
    print(scene_module, 'No Scene')

    self.pack()

    if __name__=='__main__':
    root=tk.Tk()
    tabsolder = TabbedView(root)
    tabsolder.buildTab(pth.abspath(pth.join(__base__,'../scenes')))
    root.mainloop()

    Continue reading...

Compartilhe esta Página