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
« 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 -*-
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#============================================================================#
21''' Immutable modules.
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.
27 The module implementation is derived from :py:class:`types.ModuleType` and
28 adds immutability. This makes it particularly useful for:
30 * Ensuring constants remain constant
31 * Preventing modification of module interfaces
33 Also provides a convenience function:
35 * ``reclassify_modules``: Converts existing modules to immutable modules.
36'''
39from . import __
42class Module( __.types.ModuleType ):
43 ''' Immutable modules. '''
45 def __delattr__( self, name: str ) -> None:
46 from .exceptions import AttributeImmutabilityError
47 raise AttributeImmutabilityError( name )
49 def __setattr__( self, name: str, value: __.typx.Any ) -> None:
50 from .exceptions import AttributeImmutabilityError
51 raise AttributeImmutabilityError( name )
53Module.__doc__ = __.generate_docstring(
54 Module, 'description of module', 'module attributes immutability' )
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.
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.
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