Coverage for src / competitive_verifier / log.py: 60%

52 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-05 16:00 +0000

1import pathlib 

2import sys 

3from contextlib import contextmanager 

4from dataclasses import asdict, dataclass 

5from logging import ( 

6 DEBUG, 

7 ERROR, 

8 INFO, 

9 NOTSET, 

10 WARNING, 

11 Handler, 

12 LogRecord, 

13 basicConfig, 

14) 

15from typing import TextIO 

16 

17import colorlog 

18from colorama import Fore, Style 

19 

20from competitive_verifier import github 

21 

22 

23@dataclass 

24class GitHubMessageParams: 

25 title: str | None = None 

26 file: str | pathlib.Path | None = None 

27 col: int | None = None 

28 end_column: int | None = None 

29 line: int | None = None 

30 end_line: int | None = None 

31 

32 

33class GitHubActionsHandler(Handler): 

34 def __init__(self, level: int | str = NOTSET, stream: TextIO = sys.stderr) -> None: 

35 super().__init__(level) 

36 self.stream = stream 

37 

38 def emit(self, record: LogRecord) -> None: 

39 if record.levelno >= ERROR: 

40 command = "error" 

41 elif record.levelno >= WARNING: 

42 command = "warning" 

43 elif record.levelno >= INFO: 

44 command = "notice" 

45 else: 

46 if record.levelno >= DEBUG: 

47 other_name = ( 

48 "" 

49 if record.name.startswith("competitive_verifier") 

50 else f"{record.name}:{record.lineno}:" 

51 ) 

52 github.debug(other_name + record.getMessage(), stream=self.stream) 

53 return 

54 

55 if (gh := getattr(record, "github", None)) is None or not isinstance( 

56 gh, GitHubMessageParams 

57 ): 

58 return 

59 

60 if not gh.title: 

61 gh.title = record.name 

62 

63 github.message(command, record.getMessage(), stream=sys.stderr, **asdict(gh)) 

64 

65 

66def configure_stderr_logging( 

67 default_level: int | None = None, 

68) -> None: # pragma: no cover 

69 colorlog_handler = colorlog.StreamHandler(sys.stderr) 

70 colorlog_handler.setLevel(default_level or WARNING) 

71 colorlog_handler.setFormatter( 

72 colorlog.ColoredFormatter( 

73 "%(log_color)s%(levelname)s%(reset)s:%(name)s:%(lineno)d:%(message)s" 

74 ) 

75 ) 

76 

77 handlers: list[Handler] = [colorlog_handler] 

78 

79 if github.env.is_in_github_actions(): 

80 handlers.append(GitHubActionsHandler()) 

81 

82 basicConfig( 

83 level=NOTSET, 

84 handlers=handlers, 

85 ) 

86 

87 

88def _console_group(category: str, *, title: str, file: TextIO | None): 

89 print( 

90 ( 

91 f"<------------- {Fore.CYAN}{category}:" 

92 f"{Fore.YELLOW}{title}" 

93 f"{Style.RESET_ALL} ------------->" 

94 ), 

95 file=file, 

96 ) 

97 

98 

99@contextmanager 

100def group(title: str, *, stream: TextIO | None = None): 

101 if stream is None: 

102 stream = sys.stderr 

103 if github.env.is_in_github_actions(): 

104 try: 

105 github.begin_group(title, stream=stream) 

106 yield 

107 finally: 

108 github.end_group(stream=stream) 

109 else: 

110 try: 

111 _console_group(" Start group", title=title, file=stream) 

112 yield 

113 finally: 

114 _console_group("Finish group", title=title, file=stream)