Coverage for src/somesy/cli/util.py: 75%

36 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-04-30 09:42 +0000

1"""Utility functions for CLI commands.""" 

2import logging 

3import traceback 

4from typing import Optional 

5 

6import typer 

7import wrapt 

8from rich.markup import escape 

9from rich.pretty import pretty_repr 

10 

11from somesy.core.core import discover_input 

12from somesy.core.log import SomesyLogLevel, get_log_level, set_log_level 

13from somesy.core.models import SomesyConfig, SomesyInput 

14 

15logger = logging.getLogger("somesy") 

16 

17 

18# configuration dicts for CLI file arguments 

19file_arg_config = dict( 

20 file_okay=True, 

21 dir_okay=False, 

22 writable=True, 

23 readable=True, 

24 resolve_path=True, 

25) 

26existing_file_arg_config = dict(file_arg_config) 

27existing_file_arg_config.update(dict(exists=True)) 

28 

29 

30@wrapt.decorator 

31def wrap_exceptions(wrapped, instance, args, kwargs): 

32 """Format and log exceptions for cli commands.""" 

33 try: 

34 return wrapped(*args, **kwargs) 

35 

36 except Exception as e: 

37 # Escape the error message to prevent Rich from misinterpreting it 

38 escaped_error_message = escape(str(e)) 

39 escaped_traceback = escape(traceback.format_exc()) 

40 

41 logger.error(f"[bold red]Error: {escaped_error_message}[/bold red]") 

42 logger.debug(f"[red]{escaped_traceback}[/red]") 

43 raise typer.Exit(code=1) from e 

44 

45 

46def resolved_somesy_input(**cli_args) -> SomesyInput: 

47 """Return a combined `SomesyInput` based on config file and passed CLI args. 

48 

49 Will also adjust log levels accordingly. 

50 """ 

51 # figure out what input file to use 

52 input_file = discover_input(cli_args.pop("input_file", None)) 

53 

54 # create config based on passed arguments 

55 passed_args = {k: v for k, v in cli_args.items() if v is not None} 

56 somesy_conf = SomesyConfig(input_file=input_file, **passed_args) 

57 

58 # cli_log_level is None if the user did not pass a log level (-> "default") 

59 cli_log_level: Optional[SomesyLogLevel] = get_log_level() 

60 

61 if cli_log_level is not None: 

62 # update log level flags if cli log level was set 

63 somesy_conf.update_log_level(cli_log_level) 

64 

65 somesy_input: SomesyInput = somesy_conf.get_input() 

66 

67 if cli_log_level is None: 

68 # no cli log level -> set it according to the loaded configuration 

69 set_log_level(somesy_input.config.log_level()) 

70 

71 logger.debug( 

72 f"Combined config (Defaults + File + CLI):\n{pretty_repr(somesy_input.config)}" 

73 ) 

74 return somesy_input