Skip to content

Commit

Permalink
SettingsClassManager and SettingsManager
Browse files Browse the repository at this point in the history
  • Loading branch information
infeeeee committed Feb 24, 2024
1 parent 1fd5522 commit a68fb80
Show file tree
Hide file tree
Showing 17 changed files with 309 additions and 267 deletions.
142 changes: 72 additions & 70 deletions IoTuring/ClassManager/ClassManager.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,109 @@
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 IoTuring.Logger.LogObject import LogObject

# from IoTuring.ClassManager import consts

# This is a parent class

# Implement subclasses in this way:

# 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

# 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)


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))

# Set up these class variables in subclasses:
classesRelativePath = None # Change in subclasses


def __init__(self) -> None:

classmanager_file_path = sys.modules[self.__class__.__module__].__file__
if not classmanager_file_path:
raise Exception("Error getting path: " +
str(classmanager_file_path))

self.rootPath = Path(classmanager_file_path).parents[1]

# Store loaded classes here:
self.loadedClasses = []

# Collect paths
self.moduleFilePaths = self.GetModuleFilePaths()


def GetModuleFilePaths(self) -> list:
if not self.classesRelativePath:
raise Exception("Path to deployments not defined")

classesRootPath = self.rootPath.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")

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

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

return filepaths

def GetClassFromName(self, wantedName: str) -> type | None:

# 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

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()]

return [self.GetClassFromName(f.stem) for f in self.moduleFilePaths]
9 changes: 2 additions & 7 deletions IoTuring/ClassManager/EntityClassManager.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
from IoTuring.ClassManager.ClassManager import ClassManager
from IoTuring.ClassManager import consts
from IoTuring.Entity.Entity import Entity


# Class to load Entities from the Entitties dir and get them from name
# Class to load Entities from the Entitties dir
class EntityClassManager(ClassManager):
def __init__(self):
ClassManager.__init__(self)
self.baseClass = Entity
self.GetModulesFilename(consts.ENTITIES_PATH)
# self.GetModulesFilename(consts.CUSTOM_ENTITIES_PATH) # TODO Decide if I'll use customs
classesRelativePath = consts.ENTITIES_PATH
10 changes: 10 additions & 0 deletions IoTuring/ClassManager/SettingsClassManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from IoTuring.ClassManager.ClassManager import ClassManager
from IoTuring.ClassManager import consts


# Class to load Entities from the Entitties dir
class SettingsClassManager(ClassManager):

classesRelativePath = consts.SETTINGS_PATH


10 changes: 4 additions & 6 deletions IoTuring/ClassManager/WarehouseClassManager.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from IoTuring.ClassManager.ClassManager import ClassManager
from IoTuring.ClassManager import consts
from IoTuring.Warehouse.Warehouse import Warehouse


# Class to load Entities from the Entitties dir and get them from name
# Class to load Warehouses from the Warehouses dir

class WarehouseClassManager(ClassManager):
def __init__(self):
ClassManager.__init__(self)
self.baseClass = Warehouse
self.GetModulesFilename(consts.WAREHOUSES_PATH)

classesRelativePath = consts.WAREHOUSES_PATH
5 changes: 3 additions & 2 deletions IoTuring/ClassManager/consts.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ENTITIES_PATH = "../Entity/Deployments/"
WAREHOUSES_PATH = "../Warehouse/Deployments/"
ENTITIES_PATH = "Entity/Deployments"
WAREHOUSES_PATH = "Warehouse/Deployments"
SETTINGS_PATH = "Settings/Deployments"
15 changes: 10 additions & 5 deletions IoTuring/Configurator/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,13 @@ def ToDict(self) -> dict:


class SingleConfiguration:
"""Single configuraiton of an entity or warehouse or AppSettings"""
"""Single configuration of an entity or warehouse or setting"""

config_category: str
type: str
configurations: dict

def __init__(self, config_category: str, config_dict: dict) -> None:
def __init__(self, config_category: str = "", config_dict: dict = {}) -> None:
"""Create a new SingleConfiguration
Args:
Expand All @@ -149,7 +149,11 @@ def __init__(self, config_category: str, config_dict: dict) -> None:
self.config_category = config_category

# self.type:
setattr(self, KEY_ENTITY_TYPE, config_dict.pop(KEY_ENTITY_TYPE))
type_name = ""
if KEY_ENTITY_TYPE in config_dict:
type_name = config_dict.pop(KEY_ENTITY_TYPE)

setattr(self, KEY_ENTITY_TYPE, type_name)

self.configurations = config_dict

Expand Down Expand Up @@ -227,8 +231,9 @@ def HasConfigKey(self, config_key: str) -> bool:
"""
return bool(config_key in self.configurations)

def ToDict(self) -> dict:
def ToDict(self, include_type: bool = True) -> dict:
"""Full configuration as a dict, as it would be saved to a file """
full_dict = self.configurations
full_dict[KEY_ENTITY_TYPE] = getattr(self, KEY_ENTITY_TYPE)
if include_type:
full_dict[KEY_ENTITY_TYPE] = getattr(self, KEY_ENTITY_TYPE)
return full_dict
19 changes: 14 additions & 5 deletions IoTuring/Configurator/Configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@

from IoTuring.ClassManager.EntityClassManager import EntityClassManager
from IoTuring.ClassManager.WarehouseClassManager import WarehouseClassManager
from IoTuring.ClassManager.SettingsClassManager import SettingsClassManager

from IoTuring.Configurator import ConfiguratorIO
from IoTuring.Configurator import messages

from IoTuring.MyApp.SystemConsts import OperatingSystemDetection as OsD
from IoTuring.MyApp.AppSettings import AppSettings

from InquirerPy import inquirer
from InquirerPy.separator import Separator
Expand Down Expand Up @@ -170,10 +170,19 @@ def ManageWarehouses(self) -> None:

def ManageSettings(self) -> None:
""" UI for App and Log Settings """
choices = [
{"name": "Log Settings", "value": Logger},
{"name": "App Settings", "value": AppSettings}
]

scm = SettingsClassManager()

choices = []

availableSettings = scm.ListAvailableClasses()
for sClass in availableSettings:



choices.append(
{"name": sClass.NAME + " Settings",
"value": sClass})

choice = self.DisplayMenu(
choices=choices,
Expand Down
23 changes: 12 additions & 11 deletions IoTuring/Configurator/ConfiguratorLoader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
from IoTuring.Configurator.Configurator import Configurator, KEY_ACTIVE_ENTITIES, KEY_ACTIVE_WAREHOUSES, KEY_SETTINGS
from IoTuring.ClassManager.WarehouseClassManager import WarehouseClassManager
from IoTuring.ClassManager.EntityClassManager import EntityClassManager
from IoTuring.ClassManager.SettingsClassManager import SettingsClassManager
from IoTuring.Warehouse.Warehouse import Warehouse

from IoTuring.MyApp.AppSettings import AppSettings
from IoTuring.Logger.Logger import Logger


class ConfiguratorLoader(LogObject):
Expand All @@ -35,7 +34,6 @@ def LoadWarehouses(self) -> list[Warehouse]:
self.LOG_ERROR, f"Can't find {whConfig.GetType()} warehouse, check your configurations.")
else:
wh = whClass(whConfig)
wh.AddMissingDefaultConfigs()
self.Log(
self.LOG_DEBUG, f"Full configuration with defaults: {wh.configurations.ToDict()}")
warehouses.append(wh)
Expand All @@ -57,7 +55,6 @@ def LoadEntities(self) -> list[Entity]:
self.LOG_ERROR, f"Can't find {entityConfig.GetType()} entity, check your configurations.")
else:
ec = entityClass(entityConfig)
ec.AddMissingDefaultConfigs()
self.Log(
self.LOG_DEBUG, f"Full configuration with defaults: {ec.configurations.ToDict()}")
entities.append(ec) # Entity instance
Expand All @@ -71,11 +68,15 @@ def LoadEntities(self) -> list[Entity]:
# - pass the configuration to the warehouse function that uses the configuration to init the Warehouse
# - append the Warehouse to the list

def LoadSettings(self) -> None:
settingsClasses = [AppSettings, Logger]
def LoadSettings(self) -> list:
scm = SettingsClassManager()
settings = []
settingsClasses = scm.ListAvailableClasses()

for settingsClass in settingsClasses:
sc = settingsClass()
sc.configurations = self.configurations.LoadSingleConfig(
sc.NAME, KEY_SETTINGS)
sc.AddMissingDefaultConfigs()
sc.__init__()
settingsConfig = self.configurations.LoadSingleConfig(
settingsClass.NAME, KEY_SETTINGS)
sc = settingsClass(settingsConfig)
settings.append(sc)
return settings

Loading

0 comments on commit a68fb80

Please sign in to comment.