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

63 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-05 19:15 +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 __ 

25from . import nomina as _nomina 

26 

27 

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

29 ''' Possible modes for logging output. ''' 

30 

31 Null = 'null' # suppress library logs 

32 Pass = 'pass' # pass library logs to root logger # noqa: S105 

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

34 

35 

36class Control( __.immut.DataclassObject ): 

37 ''' Logging and debug printing behavior. ''' 

38 

39 mode: Modes = Modes.Null 

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

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

42 ] ] = None 

43 

44 # TODO? Support capture file and stream choice. 

45 

46 

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

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

49 prepare_scribe_icecream( control = control ) 

50 prepare_scribe_logging( control = control ) 

51 

52 

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

54 ''' Prepares Icecream debug printing. ''' 

55 from os import environ 

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

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

58 case _: 

59 import builtins 

60 setattr( builtins, 'ic', _passthrough ) 

61 return 

62 from icecream import ic, install 

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

64 includeContext = True, prefix = 'DEBUG ' ) 

65 match control.mode: 

66 case Modes.Null: 

67 ic.configureOutput( **nomargs ) 

68 ic.disable( ) 

69 case Modes.Pass: 

70 ic.configureOutput( **nomargs ) 

71 case Modes.Rich: # pragma: no branch 

72 from rich.pretty import pretty_repr 

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

74 install( ) 

75 

76 

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

78 ''' Prepares standard Python logging. ''' 

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

80 import logging 

81 level_name = _discover_inscription_level_name( control ) 

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

83 scribe = __.produce_scribe( _nomina.package_name ) 

84 scribe.propagate = False # prevent double-logging 

85 scribe.setLevel( level ) 

86 match control.mode: 

87 case Modes.Null: 

88 scribe.addHandler( logging.NullHandler( ) ) 

89 case Modes.Pass: 

90 scribe.propagate = True 

91 case Modes.Rich: # pragma: no branch 

92 from rich.console import Console 

93 from rich.logging import RichHandler 

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

95 handler = RichHandler( 

96 console = Console( stderr = True ), 

97 rich_tracebacks = True, 

98 show_time = False ) 

99 handler.setFormatter( formatter ) 

100 scribe.addHandler( handler ) 

101 scribe.debug( "Logging initialized." ) 

102 

103 

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

105 if control.level is None: 

106 from os import environ 

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

108 envvar_name = ( 

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

110 base = envvar_name_base, 

111 name = _nomina.package_name.upper( ) ) ) 

112 if envvar_name not in environ: continue 

113 return environ[ envvar_name ] 

114 return 'INFO' 

115 return control.level 

116 

117 

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

119 return args