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

36 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-07-29 07:50 +0000

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

2 

3import logging 

4import traceback 

5from typing import Optional 

6 

7import typer 

8import wrapt 

9from rich.markup import escape 

10from rich.pretty import pretty_repr 

11 

12from somesy.core.core import discover_input 

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

14from somesy.core.models import SomesyConfig, SomesyInput 

15 

16logger = logging.getLogger("somesy") 

17 

18 

19# configuration dicts for CLI file arguments 

20file_arg_config = dict( 

21 file_okay=True, 

22 dir_okay=False, 

23 writable=True, 

24 readable=True, 

25 resolve_path=True, 

26) 

27existing_file_arg_config = dict(file_arg_config) 

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

29 

30 

31@wrapt.decorator 

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

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

34 try: 

35 return wrapped(*args, **kwargs) 

36 

37 except Exception as e: 

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

39 escaped_error_message = escape(str(e)) 

40 escaped_traceback = escape(traceback.format_exc()) 

41 

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

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

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

45 

46 

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

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

49 

50 Will also adjust log levels accordingly. 

51 """ 

52 # figure out what input file to use 

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

54 

55 # create config based on passed arguments 

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

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

58 

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

60 cli_log_level: Optional[SomesyLogLevel] = get_log_level() 

61 

62 if cli_log_level is not None: 

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

64 somesy_conf.update_log_level(cli_log_level) 

65 

66 somesy_input: SomesyInput = somesy_conf.get_input() 

67 

68 if cli_log_level is None: 

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

70 set_log_level(somesy_input.config.log_level()) 

71 

72 logger.debug( 

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

74 ) 

75 return somesy_input