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

63 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-07 04:07 +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( 

37 metaclass = __.ImmutableStandardDataclass, 

38 decorators = ( __.standard_dataclass, ), 

39): 

40 ''' Logging and debug printing behavior. ''' 

41 

42 mode: Modes = Modes.Null 

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

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

45 ] ] = None 

46 

47 # TODO? Support capture file and stream choice. 

48 

49 

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

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

52 prepare_scribe_icecream( control = control ) 

53 prepare_scribe_logging( control = control ) 

54 

55 

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

57 ''' Prepares Icecream debug printing. ''' 

58 from os import environ 

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

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

61 case _: 

62 import builtins 

63 setattr( builtins, 'ic', _passthrough ) 

64 return 

65 from icecream import ic, install 

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

67 includeContext = True, prefix = 'DEBUG ' ) 

68 match control.mode: 

69 case Modes.Null: 

70 ic.configureOutput( **nomargs ) 

71 ic.disable( ) 

72 case Modes.Pass: 

73 ic.configureOutput( **nomargs ) 

74 case Modes.Rich: # pragma: no branch 

75 from rich.pretty import pretty_repr 

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

77 install( ) 

78 

79 

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

81 ''' Prepares standard Python logging. ''' 

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

83 import logging 

84 level_name = _discover_inscription_level_name( control ) 

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

86 scribe = __.produce_scribe( _nomina.package_name ) 

87 scribe.propagate = False # prevent double-logging 

88 scribe.setLevel( level ) 

89 match control.mode: 

90 case Modes.Null: 

91 scribe.addHandler( logging.NullHandler( ) ) 

92 case Modes.Pass: 

93 scribe.propagate = True 

94 case Modes.Rich: # pragma: no branch 

95 from rich.console import Console 

96 from rich.logging import RichHandler 

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

98 handler = RichHandler( 

99 console = Console( stderr = True ), 

100 rich_tracebacks = True, 

101 show_time = False ) 

102 handler.setFormatter( formatter ) 

103 scribe.addHandler( handler ) 

104 scribe.debug( "Logging initialized." ) 

105 

106 

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

108 if control.level is None: 

109 from os import environ 

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

111 envvar_name = ( 

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

113 base = envvar_name_base, 

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

115 if envvar_name not in environ: continue 

116 return environ[ envvar_name ] 

117 return 'INFO' 

118 return control.level 

119 

120 

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

122 return args