#!/usr/bin/env python3 import functools import logging import os import sys # This module is commonly used by others in here and should avoid # taking any unnecessary dependencies back on them. from argparse_utils import ActionNoYes import config import logging_utils logger = logging.getLogger(__name__) args = config.add_commandline_args( f'Bootstrap ({__file__})', 'Args related to python program bootstrapper and Swiss army knife') args.add_argument( '--debug_unhandled_exceptions', action=ActionNoYes, default=False, help='Break into pdb on top level unhandled exceptions.' ) args.add_argument( '--show_random_seed', action=ActionNoYes, default=False, help='Should we display (and log.debug) the global random seed?' ) args.add_argument( '--set_random_seed', type=int, nargs=1, default=None, metavar='SEED_INT', help='Override the global random seed with a particular number.' ) original_hook = sys.excepthook def handle_uncaught_exception(exc_type, exc_value, exc_tb): global original_hook msg = f'Unhandled top level exception {exc_type}' logger.exception(msg) print(msg, file=sys.stderr) if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_tb) return else: if ( not sys.stderr.isatty() or not sys.stdin.isatty() ): # stdin or stderr is redirected, just do the normal thing original_hook(exc_type, exc_value, exc_tb) else: # a terminal is attached and stderr is not redirected, debug. if config.config['debug_unhandled_exceptions']: import traceback import pdb traceback.print_exception(exc_type, exc_value, exc_tb) logger.info("Invoking the debugger...") pdb.pm() else: original_hook(exc_type, exc_value, exc_tb) def initialize(entry_point): """Remember to initialize config and logging before running main.""" @functools.wraps(entry_point) def initialize_wrapper(*args, **kwargs): if sys.excepthook == sys.__excepthook__: sys.excepthook = handle_uncaught_exception if ( '__globals__' in entry_point.__dict__ and '__file__' in entry_point.__globals__ ): config.parse(entry_point.__globals__['__file__']) else: config.parse(None) logging_utils.initialize_logging(logging.getLogger()) config.late_logging() # Allow programs that don't bother to override the random seed # to be replayed via the commandline. import random random_seed = config.config['set_random_seed'] if random_seed is not None: random_seed = random_seed[0] else: random_seed = int.from_bytes(os.urandom(4), 'little') if config.config['show_random_seed']: msg = f'Global random seed is: {random_seed}' print(msg) logger.debug(msg) random.seed(random_seed) logger.debug(f'Starting {entry_point.__name__} (program entry point)') ret = None import stopwatch with stopwatch.Timer() as t: ret = entry_point(*args, **kwargs) logger.debug( f'{entry_point.__name__} (program entry point) returned {ret}.' ) walltime = t() (utime, stime, cutime, cstime, elapsed_time) = os.times() logger.debug('\n' f'user: {utime}s\n' f'system: {stime}s\n' f'child user: {cutime}s\n' f'child system: {cstime}s\n' f'machine uptime: {elapsed_time}s\n' f'walltime: {walltime}s') if ret is not None and ret != 0: logger.error(f'Exit {ret}') else: logger.debug(f'Exit {ret}') sys.exit(ret) return initialize_wrapper