Coverage for sources/frigid/modules.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-24 04:09 +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''' Immutable modules. 

22 

23 Provides a module type that enforces complete attribute immutability. 

24 This helps ensure that module-level constants remain constant and that 

25 module interfaces remain stable during runtime. 

26 

27 The module implementation is derived from :py:class:`types.ModuleType` and 

28 adds immutability. This makes it particularly useful for: 

29 

30 * Ensuring constants remain constant 

31 * Preventing modification of module interfaces 

32 

33 Also provides a convenience function: 

34 

35 * ``reclassify_modules``: Converts existing modules to immutable modules. 

36''' 

37 

38 

39from . import __ 

40 

41 

42class Module( __.types.ModuleType ): 

43 ''' Immutable modules. ''' 

44 

45 def __delattr__( self, name: str ) -> None: 

46 from .exceptions import AttributeImmutabilityError 

47 raise AttributeImmutabilityError( name ) 

48 

49 def __setattr__( self, name: str, value: __.typx.Any ) -> None: 

50 from .exceptions import AttributeImmutabilityError 

51 raise AttributeImmutabilityError( name ) 

52 

53Module.__doc__ = __.generate_docstring( 

54 Module, 'description of module', 'module attributes immutability' ) 

55 

56 

57def reclassify_modules( 

58 attributes: __.typx.Annotated[ 

59 __.cabc.Mapping[ str, __.typx.Any ] | __.types.ModuleType | str, 

60 __.typx.Doc( 

61 'Module, module name, or dictionary of object attributes.' ), 

62 ], 

63 recursive: __.typx.Annotated[ 

64 bool, __.typx.Doc( 'Recursively reclassify package modules?' ) 

65 ] = False, 

66) -> None: 

67 ''' Reclassifies modules to be immutable. 

68 

69 This function converts existing modules to immutable modules, enforcing 

70 attribute immutability after conversion. It can operate on individual 

71 modules or entire package hierarchies. 

72 

73 Notes 

74 ----- 

75 * Only converts modules within the same package to prevent unintended 

76 modifications to external modules 

77 * When used with a dictionary, converts any module objects found as 

78 values if they belong to the same package 

79 * Module conversion is permanent for the runtime session 

80 * Has no effect on already-immutable modules 

81 ''' 

82 from inspect import ismodule 

83 from sys import modules 

84 if isinstance( attributes, str ): 

85 attributes = modules[ attributes ] 

86 if isinstance( attributes, __.types.ModuleType ): 

87 module = attributes 

88 attributes = attributes.__dict__ 

89 else: module = None 

90 package_name = ( 

91 attributes.get( '__package__' ) or attributes.get( '__name__' ) ) 

92 if not package_name: return 

93 for value in attributes.values( ): 

94 if not ismodule( value ): continue 

95 if not value.__name__.startswith( f"{package_name}." ): continue 

96 if recursive: reclassify_modules( value, recursive = True ) 

97 if isinstance( value, Module ): continue 

98 value.__class__ = Module 

99 if module and not isinstance( module, Module ): 

100 module.__class__ = Module