Reputation: 15236
I am attempting to use a Directory to hold my growing list of frames and when my MAIN.py
file is executed I want the code to load each frame into a Tkinter notebook widget.
I can do this manually by importing each file from the sub directory in my MAIN folder however I am attempting to have this load them pragmatically without having to know each file name and not having to import them myself.
I have made some progress but I got stuck at actually loading the class in the files. I can get a list of all files and I believe I am importing them just fine I just cannot get them to load as it keeps telling me that a module does not exist.
I must be misunderstanding something here as I cannot figure out how to properly call a class from the imported file.
Error:
C:\Users\USER\PycharmProjects\TEST\venv\Scripts\python.exe C:/Users/USER/PycharmProjects/TEST/MAIN/MAIN.py
# This below list is a result of my print statement to see if I got all the file names.
['TaskFrame', 'TestFrame1', 'TestFrame2', 'TestFrame3']
Traceback (most recent call last):
File "C:/Users/MCDOMI3/PycharmProjects/TEST/MAIN/MAIN.py", line 46, in <module>
ScriptManager().mainloop()
File "C:/Users/MCDOMI3/PycharmProjects/TEST/MAIN/MAIN.py", line 39, in __init__
self.add_frame_to_book(foo.tab_title)
AttributeError: module 'ScriptFrames.TaskFrame' has no attribute 'tab_title'
MAIN Code:
import tkinter as tk
import tkinter.ttk as ttk
from os.path import dirname, basename, isfile, join
import glob
import importlib.util
modules = glob.glob(join(dirname('.\ScriptFrames\\'), "*.py"))
__all__ = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
print(__all__)
class ScriptManager(tk.Tk):
def __init__(self):
super().__init__()
self.book = ttk.Notebook(self)
self.book.grid(row=0, column=0, sticky='nsew')
for fn in __all__:
spec = importlib.util.spec_from_file_location('ScriptFrames.{}'.format(fn),
'.\ScriptFrames\\{}.py'.format(fn))
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)
# Here is my problem I do not know if I am attempting to get the class
# attribute correctly here. I am sure that I am calling this wrong.
self.add_frame_to_book(foo.tab_title)
def add_frame_to_book(self, fname):
self.book.add(fname, text='test')
if __name__ == '__main__':
ScriptManager().mainloop()
Each test file is a simple tkinter frame.
Test Frame Code:
import tkinter as tk
import tkinter.ttk as ttk
class TabFrame(ttk.Frame):
def __init__(self):
super().__init__()
self.tab_title = 'Test Frame 1'
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
tk.Label(self, text='test').grid(row=0, column=0, sticky='nsew')
If it helps here is my file structure:
Upvotes: 3
Views: 443
Reputation: 41167
You're trying to access tab_title for the module, and not from the (Frame) class inside it.
Once the module is loaded, get its attributes using [Python 3.Docs]: Built-in Functions:
ttk.Frame
(as the module has other attributes as well)
Example:
# ...
# The rest of your code
spec.loader.exec_module(foo)
# At this point, 'foo' module is loaded. Get its attributes
module_attrs = [getattr(foo, attr_name, None) for attr_name in dir(foo)]
# Filter out the attributes only keeping the ones that we care about
frame_classes = [attr for attr in module_attrs if isinstance(attr, (type,)) and issubclass(attr, (ttk.Frame,))]
# Then handle the module classes (it will probably be only one)
for frame_class in frame_classes:
if frame_class.__name__.startswith("Tab"): # Or any other filtering
# It would have been much more efficient to filter names when computing attr_names, but it's more consistent to be here (especially if other filtering beside name is needed)
frame_class_instance = frame_class() # Instantiate the class, as tab_title is an instance attribute
print(frame_class.__name__, frame_class_instance.tab_title)
Upvotes: 2
Reputation: 1247
Here is what I do.
def load(self, file=None):
file_type = path.splitext(file)[1].lstrip('.').lower()
if file_type == 'py' and path.exists(file):
spec = spec_from_file_location("module.name", file)
module = module_from_spec(spec)
spec.loader.exec_module(module)
self.nodes = module.config
Upvotes: 0