Source code for corelp.modules.Section_LP.Section

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Date          : 2025-08-28
# Author        : Lancelot PINCET
# GitHub        : https://github.com/LancelotPincet
# Library       : coreLP
# Module        : Section

"""
This class defines decorator instances allowing to create section functions.
"""



# %% Libraries
from corelp import print, folder, selfkwargs, kwargsself
from dataclasses import dataclass, field
import pickle
import joblib
from pathlib import Path
from functools import wraps
import hashlib
import inspect
import os



# %% Class
[docs] @dataclass(slots=True, kw_only=True) class Section() : ''' This class defines decorator instances allowing to create section functions. Cache results into a folder, if another call occurs, can load-back the precalculated data. Parameters ---------- path : str or Path path where to save section folder results. new : bool True to ignore pre-calculated data and crush them. num : int Index of section, after one call, adds 1 for next call. parent_path : str or Path Path to the parent folder if bulk processing. Examples -------- >>> from corelp import Section ... >>> section = Section(path=export_path) ... >>> @section() ... def add(a, b=0) : ... testfunc.print('Hello World') ... return a + b ... >>> testfunc.print('3+0=', add(3)) # First call calculates and save result >>> testfunc.print('3+0=', add(3, 0)) # Second call loads back precalculated results >>> testfunc.print('1+3=', add(1, 3)) # New call with other parameters : crushed previous results with new ones >>> testfunc.print('1+3=', add(1, b=3)) # Second call with these parameters : loads precalculated results ... >>> @section(cache=False) # Creates an index of 2, does no caching ... def sub(a, b=0) : ... return a - b ... >>> @section(num=10) # Creates an index of 10 ... def mul(a, b=0) : ... return a * b ... >>> @section(new=True) # Creates an index of 11, always creates new cache ... def div(a, b) : ... return a / b ''' # Attributes path : Path | str = None new :bool = False num :int = 0 parent_path : Path | str = None # Init def __post_init__(self) : if self.path is not None : self.path = Path(self.path) if self.parent_path is not None : self.parent_path = Path(self.parent_path) # Decorator def __call__(self, *, new=None, num=None, symlink=None, cache=True): if new is None : new = self.new if num is None : num = self.num self.num = num+1 def decorator(func) : name = func.__name__ @wraps(func) def wrapper(*args, **kwargs): wrapper.path = self.path / f"{num:03}_{name}" print(f'\n#### {num}. {name.replace("_"," ")} section\n') # Creating hash if cache : print('**Call hash:**') bound = inspect.signature(func).bind(*args, **kwargs) bound.apply_defaults() serialized = pickle.dumps(bound.arguments) args_hash = hashlib.md5(serialized).hexdigest() result_file = wrapper.path / f'{args_hash}.pkl' print(f'*{args_hash}*\n') # Checking already calculated exists if result_file.exists() and not new : print('**Loading from *precalculated* results...**') with open(result_file, 'rb') as f: result = joblib.load(f) print('...loaded\n') return result # Calculations folder(wrapper.path, warning=False) print('**Calculating results:**') print_status = kwargsself(print) print.file = wrapper.path / f'{name}_log.md' result = func(*args, **kwargs) selfkwargs(print, print_status) print('...calculated\n') # Caching if cache : print('**Saving results:**') with open(result_file, 'wb') as f: joblib.dump(result, f) print('...saved\n') # Create symlink if symlink is not None : print('**Creating symlinks:**') for link in symlink : print(f"- {link}") link_path = Path(link) link_folder = self.parent_path / link_path.stem new_stem = str(self.subfolder.as_posix()).replace('/', '--') if not link_folder.exists() : folder(link_folder, warning=False) link_from = wrapper.path / link_path link_to = link_folder / f"{new_stem}{link_path.suffix}" if link_to.exists() or link_to.is_symlink(): link_to.unlink() if os.name == "nt": try : link_to.symlink_to(link_from, link_from.is_dir()) except OSError : print("Windows does not allow to create symlink, aborting. Consider using Windows in Developper mode.") break else: link_to.symlink_to(link_from) print('...created\n') return result return wrapper return decorator @property def subfolder(self) : return self.path.relative_to(self.parent_path)
# %% Test function run if __name__ == "__main__": from corelp import test test(__file__)