Source code for fenn.core.fenn

from pathlib import Path
from typing import Any, Callable, Optional

from colorama import Fore, Style

from fenn.args import Parser
from fenn.export.exporter import Exporter
from fenn.logging import Logger
from fenn.secrets.keystore import KeyStore
from fenn.utils import generate_session_id


[docs] class Fenn: """Base class for a PyFenn application. Wires together argument parsing, logging, secret storage, and result export into a single entry point. Subclass this or instantiate it directly, register your main logic with :meth:`entrypoint`, then call :meth:`run`. Example: >>> app = Fenn() >>> @app.entrypoint ... def main(args): ... print(args) >>> app.run() """
[docs] def __init__(self) -> None: self._session_id: str = generate_session_id() self._parser: Parser = Parser() self._keystore: KeyStore = KeyStore() self._logger: Logger = Logger() self._exporter: Exporter = Exporter() # DISCLAIMER: # This class is the base class for all Fenn applications. # It is designed to be subclassed, not instantiated directly. # Please do not modify this class unless you know what you are doing. self._config_file: str = None self._entrypoint_fn: Optional[Callable] = None self._disable_disclaimer = False self._load_configuration()
def _load_configuration(self): # Load config self._parser.config_file = ( self._config_file if self._config_file is not None else "fenn.yaml" ) self._args = self._parser.load_configuration() self._args["session_id"] = self._session_id
[docs] def entrypoint(self, entrypoint_fn: Callable) -> Callable: """Register the application's main function. Use as a decorator. The decorated function receives the parsed configuration dict as its only argument and may return any value, which :meth:`run` will forward to the caller. Args: entrypoint_fn: The callable to register as the application entry point. Must accept a single ``args`` dict argument. Returns: The original callable, unchanged (decorator passthrough). """ self._entrypoint_fn = entrypoint_fn return entrypoint_fn
[docs] def run(self) -> Any: """Execute the application. Loads configuration from the YAML file, initialises logging and export directories, then calls the registered entrypoint function. Logging is always stopped cleanly, even if the entrypoint raises. Returns: The value returned by the entrypoint function. Raises: RuntimeError: If no entrypoint has been registered via :meth:`entrypoint`. """ if not self._disable_disclaimer: self._logger._logging_backend._original_print( "***********************************************************************************\n" f"{Style.BRIGHT}Hi, thank you for using the {Fore.GREEN}PyFenn{Style.RESET_ALL}{Style.BRIGHT} framework.{Style.RESET_ALL}\n" f"PyFenn is still in {Fore.CYAN}early access{Style.RESET_ALL}.\n" "If you find a bug or inconsistency, if you want to contribute or request a feature,\nplease open an issue at " f"{Fore.CYAN}https://github.com/pyfenn/fenn/issues{Style.RESET_ALL}.\n" f"{Style.BRIGHT}Thank you for your support!{Style.RESET_ALL}\n" f"{Fore.LIGHTYELLOW_EX}Use app.disable_disclaimer() to stop seeing this message.{Style.RESET_ALL}\n" "***********************************************************************************\n" ) if not self._entrypoint_fn: raise RuntimeError( f"{Fore.RED}[EXCEPTION] No main function registered. " f"Please use {Fore.LIGHTYELLOW_EX}@app.entrypoint{Style.RESET_ALL} " "to register your main function." ) Exporter().configure(self._args) # Start logging self._logger.start() # Print parsed config (user logs) self._parser.print() try: # System startup message self._logger.display_info( f"Application starting from entrypoint: {self._entrypoint_fn.__name__}" ) # Execute user function result = self._entrypoint_fn(self._args) return result finally: self._logger.stop()
[docs] def disable_disclaimer(self) -> None: """Suppress the startup banner printed before each run.""" self._disable_disclaimer = True
[docs] def set_config_file(self, config_file: str) -> None: """Override the default YAML configuration file path. Args: config_file: Path to the YAML file to load on :meth:`run`. Defaults to ``"fenn.yaml"`` in the working directory. """ self._config_file = config_file self._load_configuration()
[docs] def get_environ(self, key: str) -> str: return self._keystore.get_key(key)
@property def parameters(self) -> map: """""" return self._args @property def config_file(self) -> str: """Path to the active YAML configuration file.""" return self._config_file @property def export_dir(self) -> Path: """Output directory for run artefacts (logs, models, exports).""" return self._exporter.export_dir