Module albow.themes.ThemeLoader

Source code
import logging

import configparser
from configparser import ConfigParser

from ast import literal_eval as make_tuple

from pkg_resources import resource_filename

from albow.themes.Theme import Theme


class ThemeLoader:

    DEFAULT_PKG = "albow.themes.resources"
    DEFAULT_THEME_FILENAME = "default-theme.ini"
    ROOT_THEME_NAME = "root"

    def __init__(self, themePkg: str = DEFAULT_PKG,  themeFilename: str = DEFAULT_THEME_FILENAME):
        """

        """
        self.logger = logging.getLogger(__name__)

        self.themePkg = themePkg
        self.themeFilename = themeFilename
        self.themeFullFilename = self.findThemeFile()
        self.topLevelClassThemes = []
        self.themeRoot = None

    def load(self):

        config = configparser.ConfigParser()
        config.read(self.themeFullFilename)

        self.themeRoot = self.loadAClass(config[ThemeLoader.ROOT_THEME_NAME])

        self.logger.debug(f"Initial themeRoot: {self.themeRoot}")

        self.extractThemeInstances(config)
        self.augmentInstancesWithBase()

    def augmentInstancesWithBase(self):

        varDict: dict = vars(self.themeRoot)

        for attr in varDict:
            if attr[0].isupper():
                embeddedTheme: Theme = getattr(self.themeRoot, attr)
                baseName: str = getattr(embeddedTheme, "base")
                if baseName is not None:
                    self.logger.debug(f"embeddedTheme: '{embeddedTheme}'' has base: '{embeddedTheme}'")
                    baseTheme: Theme = getattr(self.themeRoot, baseName)
                    setattr(embeddedTheme, "base", baseTheme)
                    self.logger.debug(f"Theme {embeddedTheme} has new base {baseTheme}")

    def findThemeFile(self):

        fileName = resource_filename(self.themePkg, self.themeFilename)

        return fileName

    def loadAClass(self, classDict: dict) -> Theme:

        themeName = classDict["name"]

        theme = Theme(name=themeName)

        for attr in classDict:
            if self.ignoreAttribute(attr):
                pass
            else:
                attrStrValue: str = classDict[attr]
                if not attrStrValue:
                    setattr(theme, attr, None)
                elif "color" in attr or "font" in attr:
                    attrTuple = make_tuple(attrStrValue)
                    setattr(theme, attr, attrTuple)
                elif "True" in attrStrValue or "False" in attrStrValue:
                    attrBool = bool(attrStrValue)
                    setattr(theme, attr, attrBool)
                elif attrStrValue.isnumeric():
                    attrInt = int(attrStrValue)
                    setattr(theme, attr, attrInt)
                elif ThemeLoader.isFloat(attrStrValue):
                    floatAttr = float(attrStrValue)
                    setattr(theme, attr, floatAttr)
                else:
                    setattr(theme, attr, attrStrValue)

        return theme

    def extractThemeInstances(self, config: ConfigParser):
        """
        Creates theme instances for the various configuration sections
        Ignores the "root" theme.  Assumes it has been set

        Args:
            config:  The config parser

        Returns: Update `themeRoot`

        """
        sections = config.sections()
        assert self.themeRoot is not None, "Code change broke me"

        for idx in range(len(sections)):
            sectionName = sections[idx]
            if sectionName == ThemeLoader.ROOT_THEME_NAME:
                continue
            else:
                self.logger.debug("sectionName: '%s'", sectionName)
                classDict = config[sectionName]
                theme = self.loadAClass(classDict)
                #
                # Section name matches the attribute name on the
                # theme root
                #
                setattr(self.themeRoot, sectionName, theme)

    def ignoreAttribute(self, theAttr: str) -> bool:
        """
        We'll never restore the logger; The base attribute is handled specially; We handle embedded
        themes in a separate loop

        Args:
            theAttr: The attribute name to inspect

        Returns: True when the attribute is the logger, an embedded theme, or the attribute "base"

        """
        ans: bool = False

        if "logger" in theAttr or theAttr[0].isupper():
            self.logger.debug("Ignoring: %s", theAttr)
            ans = True

        return ans

    @staticmethod
    def isFloat(strValue: str):
        try:
            float(strValue)
        except ValueError:
            return False
        else:
            return True

Classes

class ThemeLoader (themePkg='albow.themes.resources', themeFilename='default-theme.ini')
Source code
class ThemeLoader:

    DEFAULT_PKG = "albow.themes.resources"
    DEFAULT_THEME_FILENAME = "default-theme.ini"
    ROOT_THEME_NAME = "root"

    def __init__(self, themePkg: str = DEFAULT_PKG,  themeFilename: str = DEFAULT_THEME_FILENAME):
        """

        """
        self.logger = logging.getLogger(__name__)

        self.themePkg = themePkg
        self.themeFilename = themeFilename
        self.themeFullFilename = self.findThemeFile()
        self.topLevelClassThemes = []
        self.themeRoot = None

    def load(self):

        config = configparser.ConfigParser()
        config.read(self.themeFullFilename)

        self.themeRoot = self.loadAClass(config[ThemeLoader.ROOT_THEME_NAME])

        self.logger.debug(f"Initial themeRoot: {self.themeRoot}")

        self.extractThemeInstances(config)
        self.augmentInstancesWithBase()

    def augmentInstancesWithBase(self):

        varDict: dict = vars(self.themeRoot)

        for attr in varDict:
            if attr[0].isupper():
                embeddedTheme: Theme = getattr(self.themeRoot, attr)
                baseName: str = getattr(embeddedTheme, "base")
                if baseName is not None:
                    self.logger.debug(f"embeddedTheme: '{embeddedTheme}'' has base: '{embeddedTheme}'")
                    baseTheme: Theme = getattr(self.themeRoot, baseName)
                    setattr(embeddedTheme, "base", baseTheme)
                    self.logger.debug(f"Theme {embeddedTheme} has new base {baseTheme}")

    def findThemeFile(self):

        fileName = resource_filename(self.themePkg, self.themeFilename)

        return fileName

    def loadAClass(self, classDict: dict) -> Theme:

        themeName = classDict["name"]

        theme = Theme(name=themeName)

        for attr in classDict:
            if self.ignoreAttribute(attr):
                pass
            else:
                attrStrValue: str = classDict[attr]
                if not attrStrValue:
                    setattr(theme, attr, None)
                elif "color" in attr or "font" in attr:
                    attrTuple = make_tuple(attrStrValue)
                    setattr(theme, attr, attrTuple)
                elif "True" in attrStrValue or "False" in attrStrValue:
                    attrBool = bool(attrStrValue)
                    setattr(theme, attr, attrBool)
                elif attrStrValue.isnumeric():
                    attrInt = int(attrStrValue)
                    setattr(theme, attr, attrInt)
                elif ThemeLoader.isFloat(attrStrValue):
                    floatAttr = float(attrStrValue)
                    setattr(theme, attr, floatAttr)
                else:
                    setattr(theme, attr, attrStrValue)

        return theme

    def extractThemeInstances(self, config: ConfigParser):
        """
        Creates theme instances for the various configuration sections
        Ignores the "root" theme.  Assumes it has been set

        Args:
            config:  The config parser

        Returns: Update `themeRoot`

        """
        sections = config.sections()
        assert self.themeRoot is not None, "Code change broke me"

        for idx in range(len(sections)):
            sectionName = sections[idx]
            if sectionName == ThemeLoader.ROOT_THEME_NAME:
                continue
            else:
                self.logger.debug("sectionName: '%s'", sectionName)
                classDict = config[sectionName]
                theme = self.loadAClass(classDict)
                #
                # Section name matches the attribute name on the
                # theme root
                #
                setattr(self.themeRoot, sectionName, theme)

    def ignoreAttribute(self, theAttr: str) -> bool:
        """
        We'll never restore the logger; The base attribute is handled specially; We handle embedded
        themes in a separate loop

        Args:
            theAttr: The attribute name to inspect

        Returns: True when the attribute is the logger, an embedded theme, or the attribute "base"

        """
        ans: bool = False

        if "logger" in theAttr or theAttr[0].isupper():
            self.logger.debug("Ignoring: %s", theAttr)
            ans = True

        return ans

    @staticmethod
    def isFloat(strValue: str):
        try:
            float(strValue)
        except ValueError:
            return False
        else:
            return True

Class variables

var DEFAULT_PKG
var DEFAULT_THEME_FILENAME
var ROOT_THEME_NAME

Static methods

def isFloat(strValue)
Source code
@staticmethod
def isFloat(strValue: str):
    try:
        float(strValue)
    except ValueError:
        return False
    else:
        return True

Methods

def augmentInstancesWithBase(self)
Source code
def augmentInstancesWithBase(self):

    varDict: dict = vars(self.themeRoot)

    for attr in varDict:
        if attr[0].isupper():
            embeddedTheme: Theme = getattr(self.themeRoot, attr)
            baseName: str = getattr(embeddedTheme, "base")
            if baseName is not None:
                self.logger.debug(f"embeddedTheme: '{embeddedTheme}'' has base: '{embeddedTheme}'")
                baseTheme: Theme = getattr(self.themeRoot, baseName)
                setattr(embeddedTheme, "base", baseTheme)
                self.logger.debug(f"Theme {embeddedTheme} has new base {baseTheme}")
def extractThemeInstances(self, config)

Creates theme instances for the various configuration sections Ignores the "root" theme. Assumes it has been set

Args

config
The config parser
Returns : Update themeRoot
 
Source code
def extractThemeInstances(self, config: ConfigParser):
    """
    Creates theme instances for the various configuration sections
    Ignores the "root" theme.  Assumes it has been set

    Args:
        config:  The config parser

    Returns: Update `themeRoot`

    """
    sections = config.sections()
    assert self.themeRoot is not None, "Code change broke me"

    for idx in range(len(sections)):
        sectionName = sections[idx]
        if sectionName == ThemeLoader.ROOT_THEME_NAME:
            continue
        else:
            self.logger.debug("sectionName: '%s'", sectionName)
            classDict = config[sectionName]
            theme = self.loadAClass(classDict)
            #
            # Section name matches the attribute name on the
            # theme root
            #
            setattr(self.themeRoot, sectionName, theme)
def findThemeFile(self)
Source code
def findThemeFile(self):

    fileName = resource_filename(self.themePkg, self.themeFilename)

    return fileName
def ignoreAttribute(self, theAttr)

We'll never restore the logger; The base attribute is handled specially; We handle embedded themes in a separate loop

Args

theAttr
The attribute name to inspect
Returns : True when the attribute is the logger, an embedded theme, or the attribute "base"
 
Source code
def ignoreAttribute(self, theAttr: str) -> bool:
    """
    We'll never restore the logger; The base attribute is handled specially; We handle embedded
    themes in a separate loop

    Args:
        theAttr: The attribute name to inspect

    Returns: True when the attribute is the logger, an embedded theme, or the attribute "base"

    """
    ans: bool = False

    if "logger" in theAttr or theAttr[0].isupper():
        self.logger.debug("Ignoring: %s", theAttr)
        ans = True

    return ans
def load(self)
Source code
def load(self):

    config = configparser.ConfigParser()
    config.read(self.themeFullFilename)

    self.themeRoot = self.loadAClass(config[ThemeLoader.ROOT_THEME_NAME])

    self.logger.debug(f"Initial themeRoot: {self.themeRoot}")

    self.extractThemeInstances(config)
    self.augmentInstancesWithBase()
def loadAClass(self, classDict)
Source code
def loadAClass(self, classDict: dict) -> Theme:

    themeName = classDict["name"]

    theme = Theme(name=themeName)

    for attr in classDict:
        if self.ignoreAttribute(attr):
            pass
        else:
            attrStrValue: str = classDict[attr]
            if not attrStrValue:
                setattr(theme, attr, None)
            elif "color" in attr or "font" in attr:
                attrTuple = make_tuple(attrStrValue)
                setattr(theme, attr, attrTuple)
            elif "True" in attrStrValue or "False" in attrStrValue:
                attrBool = bool(attrStrValue)
                setattr(theme, attr, attrBool)
            elif attrStrValue.isnumeric():
                attrInt = int(attrStrValue)
                setattr(theme, attr, attrInt)
            elif ThemeLoader.isFloat(attrStrValue):
                floatAttr = float(attrStrValue)
                setattr(theme, attr, floatAttr)
            else:
                setattr(theme, attr, attrStrValue)

    return theme