Source code for src.args

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Module for command-line argument parsing and validation.

:module: args
:author: Le Bars, Yoann
"""

import argparse
from enum import IntEnum, Enum
from PySide6.QtWidgets import QApplication

from src.hformatter import create_parser, InvalidCommandLine
from src import constants


[docs] class ExitCode(IntEnum): """ Exit codes for the application. """ SUCCESS = 0 CMD_LINE_ERROR = -1 MISSING_ABOUT_FILE = -2 NOT_ENOUGH_ITERATIONS = -3 NOT_ENOUGH_BODIES = -4 NEGATIVE_DENSITY = -5 NEGATIVE_TIME_STEP = -6 NEGATIVE_GRAVITY = -7 NEGATIVE_EPSILON = -8 NEGATIVE_SEED = -9 UNKNOWN_EXCEPTION = -11
[docs] class LogLevel(Enum): """ Log level for the application. """ DEBUG = "debug" INFO = "info" WARNING = "warning" CRITICAL = "critical" FATAL = "fatal"
[docs] class CmdLineValidationError(Exception): """ Exception for command line validation errors. """ def __init__(self, message: str, code: ExitCode): """ Class constructor. :param str message: Error message. :param ExitCode code: Exit code. """ super().__init__(message) self.code = code
[docs] class CmdLineArgs(argparse.Namespace): """ Holds and parses command-line arguments. :ivar int n_iter: Number of iterations. :ivar int n_bodies: Number of bodies. :ivar float dens: Objects density. :ivar float dt: Model time step (in s). :ivar float universal_g: Universal gravity constant (in m³/kg/s²). :ivar float epsilon: Computing precision. :ivar int seed: Seed for random number generation. :ivar bool debug_console: Whether to enable logs through the standard output. :ivar str | None debug_file: File path for logging. :ivar str | None debug_syslog: Identifier for syslog logging. :ivar LogLevel log_level: Log level filter. """ n_iter: int = constants.DEFAULT_N_ITER n_bodies: int = constants.DEFAULT_N_BODIES dens: float = constants.DEFAULT_DENS dt: float = constants.DEFAULT_DT universal_g: float = constants.G epsilon: float = constants.EPSILON seed: int = constants.DEFAULT_SEED debug_console: bool = False debug_file: str | None = None debug_syslog: str | None = None log_level: LogLevel = LogLevel.CRITICAL
[docs] @classmethod def parse(cls, app: QApplication, args: list[str] | None = None) -> 'CmdLineArgs': """ Parses and validates command-line arguments. :param QApplication app: The application instance for translations. :param list[str] | None args: Command-line arguments to parse. Defaults to `sys.argv[1:]`. :returns: An instance of CmdLineArgs. :rtype: CmdLineArgs """ parser = create_parser( app.translate( 'main', 'A benchmark for Python using Qt framework.'), app.translate('main', 'Positional arguments'), app.translate('main', 'Optional arguments'), constants.__version__, app.translate('main', 'Display program version and exit.'), app.translate('main', 'Show this help message and exit.'), app.translate('main', 'Usage: '), app.translate('main', 'Default') ) parser.add_argument( '-i', '--niter', dest='n_iter', type=int, default=constants.DEFAULT_N_ITER, help=app.translate( 'main', 'Number of simulation iterations to run.') ) parser.add_argument( '-n', '--nbodies', dest='n_bodies', type=int, default=constants.DEFAULT_N_BODIES, help=app.translate('main', 'Number of bodies to simulate.') ) parser.add_argument( '-d', '--dens', type=float, default=constants.DEFAULT_DENS, help=app.translate('main', 'Object density, used as a mass-to-radius ' 'proportionality coefficient.') ) parser.add_argument( '-t', '--dt', dest='dt', type=float, default=constants.DEFAULT_DT, help=app.translate('main', 'Initial time step (in s) for the simulation. ' 'The time step is adaptive.') ) parser.add_argument( '-g', '--universalg', dest='universal_g', type=float, default=constants.G, help=app.translate( 'main', 'Universal gravity constant (in m³/kg/s²).') ) parser.add_argument( '-e', '--epsilon', dest='epsilon', type=float, default=constants.EPSILON, help=app.translate('main', 'Softening factor for force calculations to prevent ' 'singularities.') ) parser.add_argument( '-S', '--seed', dest='seed', type=int, default=constants.DEFAULT_SEED, help=app.translate('main', 'Seed for random number generation. If 0, a ' 'random seed is used.') ) parser.add_argument( '-c', '--debugConsole', dest='debug_console', action='store_true', help=app.translate( 'main', 'Enable logs through the standard output.') ) parser.add_argument( '-f', '--debugFile', dest='debug_file', type=str, help=app.translate('main', 'Enable logs into a file.') ) parser.add_argument( '-s', '--debugSyslog', dest='debug_syslog', type=str, help=app.translate('main', 'Enable logs through Syslog.') ) parser.add_argument( '-l', '--level', dest='log_level', type=LogLevel, default=LogLevel.CRITICAL, choices=list(LogLevel), help=app.translate( 'main', 'Log level filter (debug, info, warning, critical, fatal). ' ) ) parsed_args = cls() try: # Parse arguments directly into the CmdLineArgs instance # The 'version' and 'help' actions exit, so we won't reach here if they are used. parser.parse_args(args=args, namespace=parsed_args) except (InvalidCommandLine, argparse.ArgumentError) as e: raise CmdLineValidationError( f"{app.translate('main', 'Incorrect command line.')}: {e}", ExitCode.CMD_LINE_ERROR) from e return parsed_args
[docs] def validate(self, app: QApplication) -> None: """ Validates the arguments. :param QApplication app: The application instance for translations. """ checks = [ (self.n_iter <= 0, 'Number of iterations must be positive.', ExitCode.NOT_ENOUGH_ITERATIONS), (self.n_bodies <= 0, 'Number of bodies must be positive.', ExitCode.NOT_ENOUGH_BODIES), (self.dens <= 0.0, 'Density must be positive.', ExitCode.NEGATIVE_DENSITY), (self.dt <= 0.0, 'Time step must be positive.', ExitCode.NEGATIVE_TIME_STEP), (self.universal_g <= 0.0, 'Universal gravity constant must be positive.', ExitCode.NEGATIVE_GRAVITY), (self.epsilon <= 0.0, 'Epsilon (precision) must be positive.', ExitCode.NEGATIVE_EPSILON), (self.seed < 0, 'Seed must be non-negative.', ExitCode.NEGATIVE_SEED), ] for condition, message, code in checks: if condition: raise CmdLineValidationError( app.translate('main', message), code)