Module QFlow.builders
Sub-modules
QFlow.builders.fileQFlow.builders.folderQFlow.builders.icon-
This module defines the Icon class, which extends QPixmap to handle image loading and scaling …
QFlow.builders.jsonfQFlow.builders.objectQFlow.builders.tree
Classes
class File (filepath)-
Expand source code
class File: """ A generic file handler for reading, writing, and deleting files. This class provides a simple interface for common file operations with configurable read/write modes and encoding. Attributes: filepath (str): The path to the file to be managed. readType (str): The mode for reading files. Defaults to 'r'. encoding (str): The encoding used for file operations. Defaults to 'utf-8'. writeType (str): The mode for writing files. Defaults to 'w'. """ def __init__(self, filepath) -> None: """ Initializes the file handler with a specific filepath. Args: filepath (str): The path to the file to be managed. """ self.filepath: str = filepath self.readType: str = 'r' self.encoding: str = 'utf-8' self.writeType: str = 'w' def readFile(self, lines: bool = False) -> str | list | None: """ Reads the content of the file. Args: lines (bool, optional): If True, returns content as a list of lines. If False, returns content as a single string. Defaults to False. Returns: str | list | None: The file content as a string or list of lines, depending on the lines parameter. Raises: Exception: If the file is not found or an unexpected error occurs. """ try: with open(file=self.filepath, mode=self.readType, encoding=self.encoding) as file: return file.read() if not lines else file.readlines() except FileNotFoundError: raise Exception(f"Error: File '{self.filepath}' does not exist.") except Exception as e: raise Exception(f"Unexpected error reading file: '{e}'.") def writeFile(self, data: str) -> bool: """ Writes data to the file. Args: data (str): The content to write to the file. Returns: bool: True if the write operation was successful. Raises: Exception: If there are permission issues or an unexpected error occurs. """ try: with open(file=self.filepath, mode=self.writeType, encoding=self.encoding) as file: file.write(data) return True except PermissionError: raise Exception(f"Error: You do not have permissions to write to '{self.filepath}'.") except Exception as e: raise Exception(f"Unexpected error writing to file: '{e}'.") def deleteFile(self) -> bool: """ Deletes the file if it exists. Returns: bool: True if the file was successfully deleted, False if the file does not exist. """ if os.path.exists(self.filepath): os.remove(self.filepath) return True return FalseA generic file handler for reading, writing, and deleting files.
This class provides a simple interface for common file operations with configurable read/write modes and encoding.
- Attributes
- -----=
filepath:str- The path to the file to be managed.
readType:str- The mode for reading files. Defaults to 'r'.
encoding:str- The encoding used for file operations. Defaults to 'utf-8'.
writeType:str- The mode for writing files. Defaults to 'w'.
Initializes the file handler with a specific filepath.
- Args
- -----=
filepath:str- The path to the file to be managed.
Methods
def deleteFile(self) ‑> bool-
Expand source code
def deleteFile(self) -> bool: """ Deletes the file if it exists. Returns: bool: True if the file was successfully deleted, False if the file does not exist. """ if os.path.exists(self.filepath): os.remove(self.filepath) return True return FalseDeletes the file if it exists.
- Returns
- -----=
bool- True if the file was successfully deleted, False if the file does not exist.
def readFile(self, lines: bool = False) ‑> str | list | None-
Expand source code
def readFile(self, lines: bool = False) -> str | list | None: """ Reads the content of the file. Args: lines (bool, optional): If True, returns content as a list of lines. If False, returns content as a single string. Defaults to False. Returns: str | list | None: The file content as a string or list of lines, depending on the lines parameter. Raises: Exception: If the file is not found or an unexpected error occurs. """ try: with open(file=self.filepath, mode=self.readType, encoding=self.encoding) as file: return file.read() if not lines else file.readlines() except FileNotFoundError: raise Exception(f"Error: File '{self.filepath}' does not exist.") except Exception as e: raise Exception(f"Unexpected error reading file: '{e}'.")Reads the content of the file.
- Args
- -----=
lines:bool, optional- If True, returns content as a list of lines. If False, returns content as a single string. Defaults to False.
- Returns
- -----=
str | list | None- The file content as a string or list of lines, depending on the lines parameter.
- Raises
- -----=
Exception- If the file is not found or an unexpected error occurs.
def writeFile(self, data: str) ‑> bool-
Expand source code
def writeFile(self, data: str) -> bool: """ Writes data to the file. Args: data (str): The content to write to the file. Returns: bool: True if the write operation was successful. Raises: Exception: If there are permission issues or an unexpected error occurs. """ try: with open(file=self.filepath, mode=self.writeType, encoding=self.encoding) as file: file.write(data) return True except PermissionError: raise Exception(f"Error: You do not have permissions to write to '{self.filepath}'.") except Exception as e: raise Exception(f"Unexpected error writing to file: '{e}'.")Writes data to the file.
- Args
- -----=
data:str- The content to write to the file.
- Returns
- -----=
bool- True if the write operation was successful.
- Raises
- -----=
Exception- If there are permission issues or an unexpected error occurs.
class Folder (folderpath: str)-
Expand source code
class Folder: """ Class to manage folders easily. Allows creating, deleting, copying, moving, and listing folder contents. """ def __init__(self, folderpath: str): """ Initialize the folder manager. Args: folderpath: Path to the folder """ self.folderpath = folderpath self.path = Path(folderpath) def create(self, existOk: bool = True) -> None: """ Create the folder. Args: existOk: If True, don"t raise error if folder already exists Raises: FileExistsError: If folder exists and existOk is False """ try: os.makedirs(self.folderpath, existOk=existOk) except FileExistsError: raise FileExistsError(f"Folder '{self.folderpath}' already exists") def delete(self, confirm: bool = False) -> bool: """ Delete the folder and all its contents. Args: confirm: Safety flag to confirm deletion Returns: True if deleted, False if folder doesn"t exist Raises: ValueError: If confirm is False (safety measure) """ if not confirm: raise ValueError("Must set confirm=True to delete folder") if self.exists(): shutil.rmtree(self.folderpath) return True return False def exists(self) -> bool: """ Check if the folder exists. Returns: True if exists, False otherwise """ return os.path.exists(self.folderpath) and os.path.isdir(self.folderpath) def isEmpty(self) -> bool: """ Check if the folder is empty. Returns: True if empty, False otherwise Raises: FileNotFoundError: If folder doesn"t exist """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") return len(os.listdir(self.folderpath)) == 0 def listContents(self, pattern: Optional[str] = None, recursive: bool = False) -> List[str]: """ List contents of the folder. Args: pattern: Optional pattern to filter files (e.g., "*.txt") recursive: If True, list contents recursively Returns: List of file/folder names """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") if recursive: contents = [] for root, dirs, files in os.walk(self.folderpath): for item in dirs + files: fullPath = os.path.join(root, item) relPath = os.path.relpath(fullPath, self.folderpath) if pattern is None or fnmatch.fnmatch(item, pattern): contents.append(relPath) return contents else: contents = os.listdir(self.folderpath) if pattern: contents = [item for item in contents if fnmatch.fnmatch(item, pattern)] return contents def listFiles(self, pattern: Optional[str] = None, recursive: bool = False) -> List[str]: """ List only files in the folder. Args: pattern: Optional pattern to filter files (e.g., "*.txt") recursive: If True, list files recursively Returns: List of absolute file paths """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") files = [] if recursive: for root, _, filenames in os.walk(self.folderpath): for filename in filenames: if pattern is None or fnmatch.fnmatch(filename, pattern): fullPath = os.path.join(root, filename) files.append(os.path.abspath(fullPath)) else: for item in os.listdir(self.folderpath): itemPath = os.path.join(self.folderpath, item) if os.path.isfile(itemPath): if pattern is None or fnmatch.fnmatch(item, pattern): files.append(os.path.abspath(itemPath)) return files def listFolders(self, recursive: bool = False) -> List[str]: """ List only subfolders in the folder. Args: recursive: If True, list folders recursively Returns: List of folder names """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") folders = [] if recursive: for root, dirnames, _ in os.walk(self.folderpath): for dirname in dirnames: fullPath = os.path.join(root, dirname) relPath = os.path.relpath(fullPath, self.folderpath) folders.append(relPath) else: for item in os.listdir(self.folderpath): itemPath = os.path.join(self.folderpath, item) if os.path.isdir(itemPath): folders.append(item) return folders def copyTo(self, destination: str) -> None: """ Copy the folder to a new location. Args: destination: Destination path """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") shutil.copytree(self.folderpath, destination) def moveTo(self, destination: str) -> None: """ Move the folder to a new location. Args: destination: Destination path """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") shutil.move(self.folderpath, destination) self.folderpath = destination self.path = Path(destination) def rename(self, newName: str) -> None: """ Rename the folder. Args: newName: New folder name """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") parent = os.path.dirname(self.folderpath) newPath = os.path.join(parent, newName) os.rename(self.folderpath, newPath) self.folderpath = newPath self.path = Path(newPath) def getSize(self) -> int: """ Get total size of the folder in bytes. Returns: Size in bytes """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") totalSize = 0 for dirpath, dirnames, filenames in os.walk(self.folderpath): for filename in filenames: filepath = os.path.join(dirpath, filename) if os.path.exists(filepath): totalSize += os.path.getsize(filepath) return totalSize def getSizeFormatted(self) -> str: """ Get total size of the folder in human-readable format. Returns: Size as formatted string (e.g., "1.5 MB") """ size = self.getSize() for unit in ["B", "KB", "MB", "GB", "TB"]: if size < 1024.0: return f"{size:.2f} {unit}" size /= 1024.0 return f"{size:.2f} PB" def countItems(self, recursive: bool = False) -> dict: """ Count files and folders. Args: recursive: If True, count recursively Returns: Dictionary with "files" and "folders" counts """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") fileCount = 0 folderCount = 0 if recursive: for root, dirs, files in os.walk(self.folderpath): fileCount += len(files) folderCount += len(dirs) else: for item in os.listdir(self.folderpath): itemPath = os.path.join(self.folderpath, item) if os.path.isfile(itemPath): fileCount += 1 elif os.path.isdir(itemPath): folderCount += 1 return {"files": fileCount, "folders": folderCount} def createSubfolder(self, subfolderName: str) -> "Folder": """ Create a subfolder inside this folder. Args: subfolderName: Name of the subfolder Returns: Folder instance for the new subfolder """ subfolderPath = os.path.join(self.folderpath, subfolderName) subfolder = Folder(subfolderPath) subfolder.create() return subfolder def clear(self, confirm: bool = False) -> None: """ Delete all contents of the folder but keep the folder itself. Args: confirm: Safety flag to confirm clearing Raises: ValueError: If confirm is False (safety measure) """ if not confirm: raise ValueError("Must set confirm=True to clear folder") if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") for item in os.listdir(self.folderpath): itemPath = os.path.join(self.folderpath, item) if os.path.isfile(itemPath): os.remove(itemPath) elif os.path.isdir(itemPath): shutil.rmtree(itemPath)Class to manage folders easily. Allows creating, deleting, copying, moving, and listing folder contents.
Initialize the folder manager.
- Args
- -----=
folderpath- Path to the folder
Methods
def clear(self, confirm: bool = False) ‑> None-
Expand source code
def clear(self, confirm: bool = False) -> None: """ Delete all contents of the folder but keep the folder itself. Args: confirm: Safety flag to confirm clearing Raises: ValueError: If confirm is False (safety measure) """ if not confirm: raise ValueError("Must set confirm=True to clear folder") if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") for item in os.listdir(self.folderpath): itemPath = os.path.join(self.folderpath, item) if os.path.isfile(itemPath): os.remove(itemPath) elif os.path.isdir(itemPath): shutil.rmtree(itemPath)Delete all contents of the folder but keep the folder itself.
- Args
- -----=
confirm- Safety flag to confirm clearing
- Raises
- -----=
ValueError- If confirm is False (safety measure)
def copyTo(self, destination: str) ‑> None-
Expand source code
def copyTo(self, destination: str) -> None: """ Copy the folder to a new location. Args: destination: Destination path """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") shutil.copytree(self.folderpath, destination)Copy the folder to a new location.
- Args
- -----=
destination- Destination path
def countItems(self, recursive: bool = False) ‑> dict-
Expand source code
def countItems(self, recursive: bool = False) -> dict: """ Count files and folders. Args: recursive: If True, count recursively Returns: Dictionary with "files" and "folders" counts """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") fileCount = 0 folderCount = 0 if recursive: for root, dirs, files in os.walk(self.folderpath): fileCount += len(files) folderCount += len(dirs) else: for item in os.listdir(self.folderpath): itemPath = os.path.join(self.folderpath, item) if os.path.isfile(itemPath): fileCount += 1 elif os.path.isdir(itemPath): folderCount += 1 return {"files": fileCount, "folders": folderCount}Count files and folders.
- Args
- -----=
recursive- If True, count recursively
Returns -----= Dictionary with "files" and "folders" counts
def create(self, existOk: bool = True) ‑> None-
Expand source code
def create(self, existOk: bool = True) -> None: """ Create the folder. Args: existOk: If True, don"t raise error if folder already exists Raises: FileExistsError: If folder exists and existOk is False """ try: os.makedirs(self.folderpath, existOk=existOk) except FileExistsError: raise FileExistsError(f"Folder '{self.folderpath}' already exists")Create the folder.
- Args
- -----=
existOk- If True, don"t raise error if folder already exists
- Raises
- -----=
FileExistsError- If folder exists and existOk is False
def createSubfolder(self, subfolderName: str) ‑> Folder-
Expand source code
def createSubfolder(self, subfolderName: str) -> "Folder": """ Create a subfolder inside this folder. Args: subfolderName: Name of the subfolder Returns: Folder instance for the new subfolder """ subfolderPath = os.path.join(self.folderpath, subfolderName) subfolder = Folder(subfolderPath) subfolder.create() return subfolderCreate a subfolder inside this folder.
- Args
- -----=
subfolderName- Name of the subfolder
Returns -----= Folder instance for the new subfolder
def delete(self, confirm: bool = False) ‑> bool-
Expand source code
def delete(self, confirm: bool = False) -> bool: """ Delete the folder and all its contents. Args: confirm: Safety flag to confirm deletion Returns: True if deleted, False if folder doesn"t exist Raises: ValueError: If confirm is False (safety measure) """ if not confirm: raise ValueError("Must set confirm=True to delete folder") if self.exists(): shutil.rmtree(self.folderpath) return True return FalseDelete the folder and all its contents.
- Args
- -----=
confirm- Safety flag to confirm deletion
Returns -----= True if deleted, False if folder doesn"t exist
- Raises
- -----=
ValueError- If confirm is False (safety measure)
def exists(self) ‑> bool-
Expand source code
def exists(self) -> bool: """ Check if the folder exists. Returns: True if exists, False otherwise """ return os.path.exists(self.folderpath) and os.path.isdir(self.folderpath)Check if the folder exists.
Returns -----= True if exists, False otherwise
def getSize(self) ‑> int-
Expand source code
def getSize(self) -> int: """ Get total size of the folder in bytes. Returns: Size in bytes """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") totalSize = 0 for dirpath, dirnames, filenames in os.walk(self.folderpath): for filename in filenames: filepath = os.path.join(dirpath, filename) if os.path.exists(filepath): totalSize += os.path.getsize(filepath) return totalSizeGet total size of the folder in bytes.
Returns -----= Size in bytes
def getSizeFormatted(self) ‑> str-
Expand source code
def getSizeFormatted(self) -> str: """ Get total size of the folder in human-readable format. Returns: Size as formatted string (e.g., "1.5 MB") """ size = self.getSize() for unit in ["B", "KB", "MB", "GB", "TB"]: if size < 1024.0: return f"{size:.2f} {unit}" size /= 1024.0 return f"{size:.2f} PB"Get total size of the folder in human-readable format.
Returns -----= Size as formatted string (e.g., "1.5 MB")
def isEmpty(self) ‑> bool-
Expand source code
def isEmpty(self) -> bool: """ Check if the folder is empty. Returns: True if empty, False otherwise Raises: FileNotFoundError: If folder doesn"t exist """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") return len(os.listdir(self.folderpath)) == 0Check if the folder is empty.
Returns -----= True if empty, False otherwise
- Raises
- -----=
FileNotFoundError- If folder doesn"t exist
def listContents(self, pattern: str | None = None, recursive: bool = False) ‑> List[str]-
Expand source code
def listContents(self, pattern: Optional[str] = None, recursive: bool = False) -> List[str]: """ List contents of the folder. Args: pattern: Optional pattern to filter files (e.g., "*.txt") recursive: If True, list contents recursively Returns: List of file/folder names """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") if recursive: contents = [] for root, dirs, files in os.walk(self.folderpath): for item in dirs + files: fullPath = os.path.join(root, item) relPath = os.path.relpath(fullPath, self.folderpath) if pattern is None or fnmatch.fnmatch(item, pattern): contents.append(relPath) return contents else: contents = os.listdir(self.folderpath) if pattern: contents = [item for item in contents if fnmatch.fnmatch(item, pattern)] return contentsList contents of the folder.
- Args
- -----=
pattern- Optional pattern to filter files (e.g., "*.txt")
recursive- If True, list contents recursively
Returns -----= List of file/folder names
def listFiles(self, pattern: str | None = None, recursive: bool = False) ‑> List[str]-
Expand source code
def listFiles(self, pattern: Optional[str] = None, recursive: bool = False) -> List[str]: """ List only files in the folder. Args: pattern: Optional pattern to filter files (e.g., "*.txt") recursive: If True, list files recursively Returns: List of absolute file paths """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") files = [] if recursive: for root, _, filenames in os.walk(self.folderpath): for filename in filenames: if pattern is None or fnmatch.fnmatch(filename, pattern): fullPath = os.path.join(root, filename) files.append(os.path.abspath(fullPath)) else: for item in os.listdir(self.folderpath): itemPath = os.path.join(self.folderpath, item) if os.path.isfile(itemPath): if pattern is None or fnmatch.fnmatch(item, pattern): files.append(os.path.abspath(itemPath)) return filesList only files in the folder.
- Args
- -----=
pattern- Optional pattern to filter files (e.g., "*.txt")
recursive- If True, list files recursively
Returns -----= List of absolute file paths
def listFolders(self, recursive: bool = False) ‑> List[str]-
Expand source code
def listFolders(self, recursive: bool = False) -> List[str]: """ List only subfolders in the folder. Args: recursive: If True, list folders recursively Returns: List of folder names """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") folders = [] if recursive: for root, dirnames, _ in os.walk(self.folderpath): for dirname in dirnames: fullPath = os.path.join(root, dirname) relPath = os.path.relpath(fullPath, self.folderpath) folders.append(relPath) else: for item in os.listdir(self.folderpath): itemPath = os.path.join(self.folderpath, item) if os.path.isdir(itemPath): folders.append(item) return foldersList only subfolders in the folder.
- Args
- -----=
recursive- If True, list folders recursively
Returns -----= List of folder names
def moveTo(self, destination: str) ‑> None-
Expand source code
def moveTo(self, destination: str) -> None: """ Move the folder to a new location. Args: destination: Destination path """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") shutil.move(self.folderpath, destination) self.folderpath = destination self.path = Path(destination)Move the folder to a new location.
- Args
- -----=
destination- Destination path
def rename(self, newName: str) ‑> None-
Expand source code
def rename(self, newName: str) -> None: """ Rename the folder. Args: newName: New folder name """ if not self.exists(): raise FileNotFoundError(f"Folder '{self.folderpath}' does not exist") parent = os.path.dirname(self.folderpath) newPath = os.path.join(parent, newName) os.rename(self.folderpath, newPath) self.folderpath = newPath self.path = Path(newPath)Rename the folder.
- Args
- -----=
newName- New folder name
class Icon (path: str, w: int, h: int)-
Expand source code
class Icon(QPixmap): """ Planned for package use only. A QPixmap subclass that loads and scales an image if the file exists. This class checks whether the specified image file exists before loading it. If the file is found, it scales the image while maintaining its aspect ratio. Otherwise, it initializes an empty QPixmap. """ def __init__(self, path: str, w: int, h: int): """ Initializes an Icon object. Args: path (str): The file path of the image. w (int): The desired width of the icon. h (int): The desired height of the icon. """ if os.path.exists(path): pixmap = QPixmap(path).scaled(w, h, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) super().__init__(pixmap) else: super().__init__()Planned for package use only.
A QPixmap subclass that loads and scales an image if the file exists.
This class checks whether the specified image file exists before loading it. If the file is found, it scales the image while maintaining its aspect ratio. Otherwise, it initializes an empty QPixmap.
Initializes an Icon object.
- Args
- -----=
path:str- The file path of the image.
w:int- The desired width of the icon.
h:int- The desired height of the icon.
Ancestors
- PyQt6.QtGui.QPixmap
- PyQt6.QtGui.QPaintDevice
- PyQt6.sip.simplewrapper
class JSON (filepath: str)-
Expand source code
class JSON: """ Class to manage JSON files easily. Allows reading, writing, editing, and manipulating JSON files. """ def __init__(self, filepath: str): self.filepath = filepath self.data = None @privatemethod def resolve(self, data: Any, keys: list, create: bool = False) -> tuple[Any, str]: """ Walk data following keys and return (parent, lastKey). Args: data: Root object to traverse. keys: List of key segments already split by '.'. create: If True, missing intermediate dicts are created. Returns: (parentNode, finalKey) so the caller can do parent[key]. Raises: KeyError: A segment does not exist and create=False. TypeError: A segment exists but its value is not a dict. """ node = data for key in keys[:-1]: if isinstance(node, dict): if key not in node: if create: node[key] = {} else: raise KeyError(f"Key '{key}' not found.") node = node[key] else: raise TypeError(f"Expected a dict at '{key}', got {type(node).__name__}.") return node, keys[-1] def read(self) -> Dict[str, Any]: try: with open(self.filepath, 'r', encoding='utf-8') as f: self.data = json.load(f) return self.data except FileNotFoundError: raise FileNotFoundError(f"The file '{self.filepath}' does not exist.") except json.JSONDecodeError as e: raise json.JSONDecodeError(f"Error decoding JSON: '{e}'", e.doc, e.pos) def write(self, data: Dict[str, Any], indent: int = 4) -> None: with open(self.filepath, 'w', encoding='utf-8') as f: json.dump(data, f, indent=indent, ensure_ascii=False) self.data = data def create(self, data: Dict[str, Any] = None, indent: int = 4) -> None: self.write(data or {}, indent) def reload(self) -> Dict[str, Any]: return self.read() def exists(self) -> bool: return os.path.exists(self.filepath) def getAll(self) -> Dict[str, Any]: """ Get all data. """ if self.data is None: self.read() return self.data def get(self, key: str, default: Any = None) -> Any: """ Get a value using dot-notation key. """ if self.data is None: self.read() try: parent, lastKey = self.resolve(self.data, key.split('.')) return parent.get(lastKey, default) if isinstance(parent, dict) else default except (KeyError, TypeError): return default def set(self, key: str, value: Any) -> None: """ Set a value using dot-notation key, creating intermediate dicts when needed. """ if self.data is None: self.read() parent, lastKey = self.resolve(self.data, key.split('.'), create=True) parent[lastKey] = value self.write(self.data) def delete(self, key: str) -> bool: """ Delete a key using dot-notation. Returns True if deleted, False if not found. """ if self.data is None: self.read() try: parent, lastKey = self.resolve(self.data, key.split('.')) if isinstance(parent, dict) and lastKey in parent: del parent[lastKey] self.write(self.data) return True return False except (KeyError, TypeError): return False def update(self, key: str, value: Any) -> None: """Update a top-level key (use set() for nested keys).""" if self.data is None: self.read() self.data[key] = value self.write(self.data) def merge(self, newData: Dict[str, Any]) -> None: if self.data is None: self.read() self.data.update(newData) self.write(self.data) def clear(self) -> None: self.data = {} self.write(self.data) def keys(self) -> List[str]: if self.data is None: self.read() return list(self.data.keys()) def values(self) -> List[Any]: if self.data is None: self.read() return list(self.data.values()) def items(self) -> List[tuple]: if self.data is None: self.read() return list(self.data.items()) def indexar(self, data: Any = None, root: Dict[str, Any] = None) -> int: """ Traverse all values in the JSON and replace '${key.key}/literal' references with their resolved value plus any surrounding literal text. Args: data: Current node (used internally for recursion). root: JSON root (used internally to resolve paths). Returns: Number of replacements made. Raises: ValueError: If a reference does not exist in the JSON or points to a non-scalar value (dict or list). """ PATTERN = re.compile(r'\$\{([^}]+)\}') if root is None: if self.data is None: self.read() root = self.data data = self.data def resolveRef(refKey: str) -> str: try: parent, lastKey = self.resolve(root, refKey.split('.')) except (KeyError, TypeError): raise ValueError(f"Reference '${{{refKey}}}' does not exist in the JSON.") if not isinstance(parent, dict) or lastKey not in parent: raise ValueError(f"Reference '${{{refKey}}}' does not exist in the JSON.") value = parent[lastKey] if isinstance(value, (dict, list)): raise ValueError( f"Reference '${{{refKey}}}' points to an object/list, not a scalar value." ) return str(value) def processNode(node: Any) -> tuple[Any, int]: count = 0 if isinstance(node, dict): for key in node: node[key], subCount = processNode(node[key]) count += subCount elif isinstance(node, list): for i, item in enumerate(node): node[i], subCount = processNode(item) count += subCount elif isinstance(node, str) and '${' in node: def replacer(match): nonlocal count count += 1 return resolveRef(match.group(1)) node = PATTERN.sub(replacer, node) return node, count self.data, totalReplacements = processNode(data) return totalReplacementsClass to manage JSON files easily. Allows reading, writing, editing, and manipulating JSON files.
Methods
def clear(self) ‑> None-
Expand source code
def clear(self) -> None: self.data = {} self.write(self.data) def create(self, data: Dict[str, Any] = None, indent: int = 4) ‑> None-
Expand source code
def create(self, data: Dict[str, Any] = None, indent: int = 4) -> None: self.write(data or {}, indent) def delete(self, key: str) ‑> bool-
Expand source code
def delete(self, key: str) -> bool: """ Delete a key using dot-notation. Returns True if deleted, False if not found. """ if self.data is None: self.read() try: parent, lastKey = self.resolve(self.data, key.split('.')) if isinstance(parent, dict) and lastKey in parent: del parent[lastKey] self.write(self.data) return True return False except (KeyError, TypeError): return FalseDelete a key using dot-notation. Returns True if deleted, False if not found.
def exists(self) ‑> bool-
Expand source code
def exists(self) -> bool: return os.path.exists(self.filepath) def get(self, key: str, default: Any = None) ‑> Any-
Expand source code
def get(self, key: str, default: Any = None) -> Any: """ Get a value using dot-notation key. """ if self.data is None: self.read() try: parent, lastKey = self.resolve(self.data, key.split('.')) return parent.get(lastKey, default) if isinstance(parent, dict) else default except (KeyError, TypeError): return defaultGet a value using dot-notation key.
def getAll(self) ‑> Dict[str, Any]-
Expand source code
def getAll(self) -> Dict[str, Any]: """ Get all data. """ if self.data is None: self.read() return self.dataGet all data.
def indexar(self, data: Any = None, root: Dict[str, Any] = None) ‑> int-
Expand source code
def indexar(self, data: Any = None, root: Dict[str, Any] = None) -> int: """ Traverse all values in the JSON and replace '${key.key}/literal' references with their resolved value plus any surrounding literal text. Args: data: Current node (used internally for recursion). root: JSON root (used internally to resolve paths). Returns: Number of replacements made. Raises: ValueError: If a reference does not exist in the JSON or points to a non-scalar value (dict or list). """ PATTERN = re.compile(r'\$\{([^}]+)\}') if root is None: if self.data is None: self.read() root = self.data data = self.data def resolveRef(refKey: str) -> str: try: parent, lastKey = self.resolve(root, refKey.split('.')) except (KeyError, TypeError): raise ValueError(f"Reference '${{{refKey}}}' does not exist in the JSON.") if not isinstance(parent, dict) or lastKey not in parent: raise ValueError(f"Reference '${{{refKey}}}' does not exist in the JSON.") value = parent[lastKey] if isinstance(value, (dict, list)): raise ValueError( f"Reference '${{{refKey}}}' points to an object/list, not a scalar value." ) return str(value) def processNode(node: Any) -> tuple[Any, int]: count = 0 if isinstance(node, dict): for key in node: node[key], subCount = processNode(node[key]) count += subCount elif isinstance(node, list): for i, item in enumerate(node): node[i], subCount = processNode(item) count += subCount elif isinstance(node, str) and '${' in node: def replacer(match): nonlocal count count += 1 return resolveRef(match.group(1)) node = PATTERN.sub(replacer, node) return node, count self.data, totalReplacements = processNode(data) return totalReplacementsTraverse all values in the JSON and replace '${key.key}/literal' references with their resolved value plus any surrounding literal text.
- Args
- -----=
data- Current node (used internally for recursion).
root- JSON root (used internally to resolve paths).
Returns -----= Number of replacements made.
- Raises
- -----=
ValueError- If a reference does not exist in the JSON or points to a non-scalar value (dict or list).
def items(self) ‑> List[tuple]-
Expand source code
def items(self) -> List[tuple]: if self.data is None: self.read() return list(self.data.items()) def keys(self) ‑> List[str]-
Expand source code
def keys(self) -> List[str]: if self.data is None: self.read() return list(self.data.keys()) def merge(self, newData: Dict[str, Any]) ‑> None-
Expand source code
def merge(self, newData: Dict[str, Any]) -> None: if self.data is None: self.read() self.data.update(newData) self.write(self.data) def read(self) ‑> Dict[str, Any]-
Expand source code
def read(self) -> Dict[str, Any]: try: with open(self.filepath, 'r', encoding='utf-8') as f: self.data = json.load(f) return self.data except FileNotFoundError: raise FileNotFoundError(f"The file '{self.filepath}' does not exist.") except json.JSONDecodeError as e: raise json.JSONDecodeError(f"Error decoding JSON: '{e}'", e.doc, e.pos) def reload(self) ‑> Dict[str, Any]-
Expand source code
def reload(self) -> Dict[str, Any]: return self.read() def resolve(self, data: Any, keys: list, create: bool = False) ‑> tuple[typing.Any, str]-
Expand source code
@privatemethod def resolve(self, data: Any, keys: list, create: bool = False) -> tuple[Any, str]: """ Walk data following keys and return (parent, lastKey). Args: data: Root object to traverse. keys: List of key segments already split by '.'. create: If True, missing intermediate dicts are created. Returns: (parentNode, finalKey) so the caller can do parent[key]. Raises: KeyError: A segment does not exist and create=False. TypeError: A segment exists but its value is not a dict. """ node = data for key in keys[:-1]: if isinstance(node, dict): if key not in node: if create: node[key] = {} else: raise KeyError(f"Key '{key}' not found.") node = node[key] else: raise TypeError(f"Expected a dict at '{key}', got {type(node).__name__}.") return node, keys[-1]Walk data following keys and return (parent, lastKey).
- Args
- -----=
data- Root object to traverse.
keys- List of key segments already split by '.'.
create- If True, missing intermediate dicts are created.
Returns -----= (parentNode, finalKey) so the caller can do parent[key].
- Raises
- -----=
KeyError- A segment does not exist and create=False.
TypeError- A segment exists but its value is not a dict.
def set(self, key: str, value: Any) ‑> None-
Expand source code
def set(self, key: str, value: Any) -> None: """ Set a value using dot-notation key, creating intermediate dicts when needed. """ if self.data is None: self.read() parent, lastKey = self.resolve(self.data, key.split('.'), create=True) parent[lastKey] = value self.write(self.data)Set a value using dot-notation key, creating intermediate dicts when needed.
def update(self, key: str, value: Any) ‑> None-
Expand source code
def update(self, key: str, value: Any) -> None: """Update a top-level key (use set() for nested keys).""" if self.data is None: self.read() self.data[key] = value self.write(self.data)Update a top-level key (use set() for nested keys).
def values(self) ‑> List[Any]-
Expand source code
def values(self) -> List[Any]: if self.data is None: self.read() return list(self.data.values()) def write(self, data: Dict[str, Any], indent: int = 4) ‑> None-
Expand source code
def write(self, data: Dict[str, Any], indent: int = 4) -> None: with open(self.filepath, 'w', encoding='utf-8') as f: json.dump(data, f, indent=indent, ensure_ascii=False) self.data = data
class Object (data: dict)-
Expand source code
class Object: """Class to create an object using a dictionary.""" def __init__(self, data: dict): self.obj = self.toNamespace(data) def toNamespace(self, data): if isinstance(data, dict): return SimpleNamespace(**{k: self.toNamespace(v) for k, v in data.items()}) elif isinstance(data, list): return [self.toNamespace(item) for item in data] else: return dataClass to create an object using a dictionary.
Methods
def toNamespace(self, data)-
Expand source code
def toNamespace(self, data): if isinstance(data, dict): return SimpleNamespace(**{k: self.toNamespace(v) for k, v in data.items()}) elif isinstance(data, list): return [self.toNamespace(item) for item in data] else: return data
class Tree (configFile: str = 'config.json')-
Expand source code
class Tree: """ Tree that scans the folders/patterns defined in a JSON config file and exposes their absolute paths through: tree.f['folder', 'subfolder', ...] Prefix the key with 'unf' to resolve the SAME relative path against the normal (non-frozen) base path instead of the frozen one: tree.f['unf', 'folder', 'subfolder', ...] The config JSON must have the form: { "folders": ["app", "app/**/*.html"] } Patterns are resolved with glob (supports recursive '**') relative to the project's base path (see `basePath`), which changes automatically if the program is compiled/frozen. """ def __init__(self, configFile: str = 'config.json'): self.root = Tree.basePath() self.normalRoot = Tree.normalPath() cfgPath = ( configFile if os.path.isabs(configFile) else os.path.join(self.root, configFile) ) self.config = self.loadConfig(cfgPath) self.f = self.buildTree(getattr(self.config, 'folders', [])) def loadConfig(self, path: str) -> SimpleNamespace: if not os.path.isfile(path): raise FileNotFoundError(f"Config file not found: '{path}'.") with open(path, 'r', encoding='utf-8') as fh: data = json.load(fh) return Object(data).obj def buildTree(self, patterns) -> Node: root = Node(self.root, root=self.root, normalRoot=self.normalRoot) for pattern in patterns: patternPath = os.path.join(self.root, pattern) matches = glob.glob(patternPath, recursive=True) if not matches: matches = [patternPath] for absolutePath in matches: relative = os.path.relpath(absolutePath, self.root) parts = relative.split(os.sep) self.insertNode(root, parts, os.path.abspath(absolutePath)) return root def insertNode(self, node: Node, parts: list, absolutePath: str): current = node accumulated = self.root for part in parts: accumulated = os.path.join(accumulated, part) if part not in current: dict.__setitem__(current, part, Node(os.path.abspath(accumulated))) current = dict.__getitem__(current, part) current.path = absolutePath @staticmethod def basePath() -> str: """ Absolute base path of the project. - If the executable is frozen (PyInstaller or another bundler): * sys._MEIPASS -> --onefile mode (resources extracted to a temp folder) * dirname(sys.executable) -> --onedir mode (resources next to the binary) - If running as a normal script, uses the directory of the script that started the process (sys.argv[0]). """ if FROZENLIB: return os.path.abspath(sys._MEIPASS) return os.path.dirname(os.path.abspath(sys.argv[0])) @staticmethod def normalPath() -> str: """ Absolute path of the folder that actually contains the running script/executable, regardless of freezing mode. Unlike `basePath`, this NEVER returns a --onefile temp extraction folder (sys._MEIPASS): it always points to where the .exe/script physically lives, which is what you want for files that must be read/written alongside the binary (logs, user config, output, etc.) rather than looked up inside the bundled resources. """ if FROZENLIB: return os.path.dirname(os.path.abspath(sys.executable)) return os.path.dirname(os.path.abspath(sys.argv[0]))Tree that scans the folders/patterns defined in a JSON config file and exposes their absolute paths through:
tree.f['folder', 'subfolder', ...]Prefix the key with 'unf' to resolve the SAME relative path against the normal (non-frozen) base path instead of the frozen one:
tree.f['unf', 'folder', 'subfolder', ...]The config JSON must have the form: { "folders": ["app", "app/*/.html"] }
Patterns are resolved with glob (supports recursive '**') relative to the project's base path (see
basePath), which changes automatically if the program is compiled/frozen.Static methods
def basePath() ‑> str-
Expand source code
@staticmethod def basePath() -> str: """ Absolute base path of the project. - If the executable is frozen (PyInstaller or another bundler): * sys._MEIPASS -> --onefile mode (resources extracted to a temp folder) * dirname(sys.executable) -> --onedir mode (resources next to the binary) - If running as a normal script, uses the directory of the script that started the process (sys.argv[0]). """ if FROZENLIB: return os.path.abspath(sys._MEIPASS) return os.path.dirname(os.path.abspath(sys.argv[0]))Absolute base path of the project.
- If the executable is frozen (PyInstaller or another bundler):
- sys._MEIPASS -> –onefile mode (resources extracted to a temp folder)
- dirname(sys.executable) -> –onedir mode (resources next to the binary)
- If running as a normal script, uses the directory of the script that started the process (sys.argv[0]).
- If the executable is frozen (PyInstaller or another bundler):
def normalPath() ‑> str-
Expand source code
@staticmethod def normalPath() -> str: """ Absolute path of the folder that actually contains the running script/executable, regardless of freezing mode. Unlike `basePath`, this NEVER returns a --onefile temp extraction folder (sys._MEIPASS): it always points to where the .exe/script physically lives, which is what you want for files that must be read/written alongside the binary (logs, user config, output, etc.) rather than looked up inside the bundled resources. """ if FROZENLIB: return os.path.dirname(os.path.abspath(sys.executable)) return os.path.dirname(os.path.abspath(sys.argv[0]))Absolute path of the folder that actually contains the running script/executable, regardless of freezing mode.
Unlike
basePath, this NEVER returns a –onefile temp extraction folder (sys._MEIPASS): it always points to where the .exe/script physically lives, which is what you want for files that must be read/written alongside the binary (logs, user config, output, etc.) rather than looked up inside the bundled resources.
Methods
def buildTree(self, patterns) ‑> Node-
Expand source code
def buildTree(self, patterns) -> Node: root = Node(self.root, root=self.root, normalRoot=self.normalRoot) for pattern in patterns: patternPath = os.path.join(self.root, pattern) matches = glob.glob(patternPath, recursive=True) if not matches: matches = [patternPath] for absolutePath in matches: relative = os.path.relpath(absolutePath, self.root) parts = relative.split(os.sep) self.insertNode(root, parts, os.path.abspath(absolutePath)) return root def insertNode(self,
node: Node,
parts: list,
absolutePath: str)-
Expand source code
def insertNode(self, node: Node, parts: list, absolutePath: str): current = node accumulated = self.root for part in parts: accumulated = os.path.join(accumulated, part) if part not in current: dict.__setitem__(current, part, Node(os.path.abspath(accumulated))) current = dict.__getitem__(current, part) current.path = absolutePath def loadConfig(self, path: str) ‑> types.SimpleNamespace-
Expand source code
def loadConfig(self, path: str) -> SimpleNamespace: if not os.path.isfile(path): raise FileNotFoundError(f"Config file not found: '{path}'.") with open(path, 'r', encoding='utf-8') as fh: data = json.load(fh) return Object(data).obj