Skip to content

Commit

Permalink
ClassManager Rewrite (#101)
Browse files Browse the repository at this point in the history
* ClassManager Rewrite
* Basic ClassManager tests
  • Loading branch information
infeeeee authored Mar 2, 2024
1 parent 9678c83 commit 249d879
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 129 deletions.
170 changes: 98 additions & 72 deletions IoTuring/ClassManager/ClassManager.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,133 @@
from __future__ import annotations

import os
from pathlib import Path
from os import path
import importlib.util
import importlib.machinery
import sys
import inspect
from pathlib import Path

from IoTuring.ClassManager.consts import *
from IoTuring.Logger.LogObject import LogObject
from IoTuring.MyApp.App import App


# from IoTuring.ClassManager import consts
class ClassManager(LogObject):
"""Base class for ClassManagers
This class is used to find and load classes without importing them
The important this is that the class is inside a folder that exactly the same name of the Class and of the file (obviously not talking about extensions)
"""

# This is a parent class
def __init__(self, class_key:str) -> None:

# Implement subclasses in this way:
if class_key not in CLASS_PATH:
raise Exception(f"Invalid class key {class_key}")
else:
self.classesRelativePath = CLASS_PATH[class_key]

# def __init__(self):
# ClassManager.__init__(self)
# self.baseClass = Entity : Select the class to find
# self.GetModulesFilename(consts.ENTITIES_PATH) : Select path where it should look for classes and add all classes to found list
# Store loaded classes here:
self.loadedClasses = []

# This class is used to find and load classes without importing them
# The important this is that the class is inside a folder that exactly the same name of the Class and of the file (obviously not talking about extensions)
# Collect paths
self.moduleFilePaths = self.GetModuleFilePaths()

def GetModuleFilePaths(self) -> list[Path]:
"""Get the paths of of python files of this class
Raises:
Exception: If path not defined or exists
FileNotFoundError: No module in the dir
Returns:
list[Path]: List of paths of python files
"""

if not self.classesRelativePath:
raise Exception("Path to deployments not defined")

# Get the absolute path of the dir of files:
classesRootPath = App.getRootPath().joinpath(self.classesRelativePath)

if not classesRootPath.exists:
raise Exception(f"Path does not exist: {classesRootPath}")

self.Log(self.LOG_DEVELOPMENT,
f'Looking for python files in "{classesRootPath}"...')

python_files = classesRootPath.rglob("*.py")

# Check if a py files are in a folder with the same name !!! (same without extension)
filepaths = [f for f in python_files if f.stem == f.parent.stem]

if not filepaths:
raise FileNotFoundError(
f"No module files found in {classesRootPath}")

self.Log(self.LOG_DEVELOPMENT,
f"Found {str(len(filepaths))} modules files")

return filepaths

def GetClassFromName(self, wantedName: str) -> type | None:
"""Get the class of given name, and load it
Args:
wantedName (str): The name to look for
Returns:
type | None: The class if found, None if not found
"""

# Check from already loaded classes:
module_class = next(
(m for m in self.loadedClasses if m.__name__ == wantedName), None)

if module_class:
return module_class

modulePath = next(
(m for m in self.moduleFilePaths if m.stem == wantedName), None)

if modulePath:

loadedModule = self.LoadModule(modulePath)
loadedClass = self.GetClassFromModule(loadedModule)
self.loadedClasses.append(loadedClass)
return loadedClass

class ClassManager(LogObject):
def __init__(self):
self.modulesFilename = []
module_path = sys.modules[self.__class__.__module__].__file__
if not module_path:
raise Exception("Error getting path: " + str(module_path))
else:
self.mainPath = path.dirname(path.abspath(module_path))
# THIS MUST BE IMPLEMENTED IN SUBCLASSES, IS THE CLASS I WANT TO SEARCH !!!!
self.baseClass = None

def GetClassFromName(self, wantedName) -> type | None:
# From name, load the correct module and extract the class
for module in self.modulesFilename: # Search the module file
moduleName = self.ModuleNameFromPath(module)
# Check if the module name matches the given name
if wantedName == moduleName:
# Load the module
loadedModule = self.LoadModule(module)
# Now get the class
return self.GetClassFromModule(loadedModule)
return None

def LoadModule(self, path): # Get module and load it from the path
return None

def LoadModule(self, module_path: Path): # Get module and load it from the path
try:
loader = importlib.machinery.SourceFileLoader(
self.ModuleNameFromPath(path), path)
module_path.stem, str(module_path))
spec = importlib.util.spec_from_loader(loader.name, loader)

if not spec:
raise Exception("Spec not found")

module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
moduleName = os.path.split(path)[1][:-3]
sys.modules[moduleName] = module
sys.modules[module_path.stem] = module
return module
except Exception as e:
self.Log(self.LOG_ERROR, "Error while loading module " +
path + ": " + str(e))
self.Log(self.LOG_ERROR,
f"Error while loading module {module_path.stem}: {str(e)}")

# From the module passed, I search for a Class that has className=moduleName
def GetClassFromModule(self, module):
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj):
if(name == module.__name__):
if (name == module.__name__):
return obj
raise Exception(f"No class found: {module.__name__}")

# List files in the _path directory and get only files in subfolders
def GetModulesFilename(self, _path):
classesRootPath = path.join(self.mainPath, _path)
if os.path.exists(classesRootPath):
self.Log(self.LOG_DEVELOPMENT,
"Looking for python files in \"" + _path + os.sep + "\"...")
result = list(Path(classesRootPath).rglob("*.py"))
entities = []
for file in result:
filename = str(file)
# TO check if a py files is in a folder !!!! with the same name !!! (same without extension)
pathList = filename.split(os.sep)
if len(pathList) >= 2:
if pathList[len(pathList)-1][:-3] == pathList[len(pathList)-2]:
entities.append(filename)

self.modulesFilename = self.modulesFilename + entities
self.Log(self.LOG_DEVELOPMENT, "Found " +
str(len(entities)) + " modules files")

def ModuleNameFromPath(self, path):
classname = os.path.split(path)
return classname[1][:-3]

def ListAvailableClassesNames(self) -> list:
res = []
for py in self.modulesFilename:
res.append(path.basename(py).split(".py")[0])
return res

def ListAvailableClasses(self) -> list:
return [self.GetClassFromName(n) for n in self.ListAvailableClassesNames()]
"""Get all classes of this ClassManager
Returns:
list: The list of classes
"""

return [self.GetClassFromName(f.stem) for f in self.moduleFilePaths]
12 changes: 0 additions & 12 deletions IoTuring/ClassManager/EntityClassManager.py

This file was deleted.

11 changes: 0 additions & 11 deletions IoTuring/ClassManager/WarehouseClassManager.py

This file was deleted.

9 changes: 7 additions & 2 deletions IoTuring/ClassManager/consts.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
ENTITIES_PATH = "../Entity/Deployments/"
WAREHOUSES_PATH = "../Warehouse/Deployments/"
KEY_ENTITY = "entity"
KEY_WAREHOUSE = "warehouse"

CLASS_PATH = {
KEY_ENTITY: "Entity/Deployments",
KEY_WAREHOUSE: "Warehouse/Deployments"
}
Loading

0 comments on commit 249d879

Please sign in to comment.