Coverage for sources/mimeogram/__/inscription.py: 100%

62 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-16 02:11 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Scribes for debugging and logging. ''' 

22 

23 

24from . import imports as __ 

25 

26 

27class Modes( __.enum.Enum ): # TODO: Python 3.11: StrEnum 

28 ''' Possible modes for logging output. ''' 

29 

30 Null = 'null' # suppress library logs 

31 Pass = 'pass' # pass library logs to root logger # nosec 

32 Rich = 'rich' # print rich library logs to stderr 

33 

34 

35class Control( 

36 metaclass = __.ImmutableStandardDataclass, 

37 decorators = ( __.standard_dataclass, ), 

38): 

39 ''' Logging and debug printing behavior. ''' 

40 

41 mode: Modes = Modes.Null 

42 level: __.typx.Optional[ __.typx.Literal[ 

43 'debug', 'info', 'warn', 'error', 'critical' # noqa: F821 

44 ] ] = None 

45 

46 # TODO? Support capture file and stream choice. 

47 

48 

49def prepare( control: Control ) -> None: 

50 ''' Prepares various scribes in a sensible manner. ''' 

51 prepare_scribe_icecream( control = control ) 

52 prepare_scribe_logging( control = control ) 

53 

54 

55def prepare_scribe_icecream( control: Control ) -> None: 

56 ''' Prepares Icecream debug printing. ''' 

57 from os import environ 

58 match environ.get( '_DEVELOPMENT_MODE_', 'FALSE' ).upper( ): 

59 case '1' | 'ON' | 'T' | 'TRUE' | 'Y' | 'YES': pass 

60 case _: 

61 import builtins 

62 setattr( builtins, 'ic', _passthrough ) 

63 return 

64 from icecream import ic, install 

65 nomargs: dict[ str, __.typx.Any ] = dict( 

66 includeContext = True, prefix = 'DEBUG ' ) 

67 match control.mode: 

68 case Modes.Null: 

69 ic.configureOutput( **nomargs ) 

70 ic.disable( ) 

71 case Modes.Pass: 

72 ic.configureOutput( **nomargs ) 

73 case Modes.Rich: # pragma: no branch 

74 from rich.pretty import pretty_repr 

75 ic.configureOutput( argToStringFunction = pretty_repr, **nomargs ) 

76 install( ) 

77 

78 

79def prepare_scribe_logging( control: Control ) -> None: 

80 ''' Prepares standard Python logging. ''' 

81 # https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library 

82 import logging 

83 level_name = _discover_inscription_level_name( control ) 

84 level = getattr( logging, level_name.upper( ) ) 

85 scribe = __.produce_scribe( __.package_name ) 

86 scribe.propagate = False # prevent double-logging 

87 scribe.setLevel( level ) 

88 match control.mode: 

89 case Modes.Null: 

90 scribe.addHandler( logging.NullHandler( ) ) 

91 case Modes.Pass: 

92 scribe.propagate = True 

93 case Modes.Rich: # pragma: no branch 

94 from rich.console import Console 

95 from rich.logging import RichHandler 

96 formatter = logging.Formatter( "%(name)s: %(message)s" ) 

97 handler = RichHandler( 

98 console = Console( stderr = True ), 

99 rich_tracebacks = True, 

100 show_time = False ) 

101 handler.setFormatter( formatter ) 

102 scribe.addHandler( handler ) 

103 scribe.debug( "Logging initialized." ) 

104 

105 

106def _discover_inscription_level_name( control: Control ) -> str: 

107 if control.level is None: 

108 from os import environ 

109 for envvar_name_base in ( 'INSCRIPTION', 'LOG' ): 

110 envvar_name = ( 

111 "{name}_{base}_LEVEL".format( 

112 base = envvar_name_base, 

113 name = __.package_name.upper( ) ) ) 

114 if envvar_name not in environ: continue 

115 return environ[ envvar_name ] 

116 return 'INFO' 

117 return control.level 

118 

119 

120def _passthrough( *args: __.typx.Any ) -> __.cabc.Sequence[ __.typx.Any ]: 

121 return args