Coverage for sources/mimeogram/__/dictedits.py: 98%

40 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-02 23:41 +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''' Support for edits on nested dictionaries. ''' 

22# TODO: Independent package. 

23# TODO: Copying edits, not just in-place edits. 

24 

25 

26from . import imports as __ 

27from . import exceptions as _exceptions 

28 

29 

30class Edit( # pylint: disable=invalid-metaclass 

31 __.typx.Protocol, 

32 metaclass = __.ImmutableStandardProtocolDataclass, 

33 decorators = ( __.standard_dataclass, __.typx.runtime_checkable ), 

34): 

35 ''' Base representation of an edit to configuration. ''' 

36 

37 address: __.cabc.Sequence[ str ] 

38 

39 @__.abc.abstractmethod 

40 def __call__( self, configuration: __.NominativeDictionary ) -> None: 

41 ''' Performs edit. ''' 

42 raise NotImplementedError 

43 

44 def dereference( 

45 self, configuration: __.NominativeDictionary 

46 ) -> __.typx.Any: 

47 ''' Dereferences value at address in configuration. ''' 

48 configuration_ = configuration 

49 for part in self.address: 

50 if part not in configuration_: 

51 raise ( 

52 _exceptions.AddressLocateFailure( 

53 'configuration dictionary', self.address, part ) ) 

54 configuration_ = configuration_[ part ] 

55 return configuration_ 

56 

57 def inject( 

58 self, configuration: __.NominativeDictionary, value: __.typx.Any 

59 ) -> None: 

60 ''' Injects value at address in configuration. ''' 

61 configuration_ = configuration 

62 for part in self.address[ : -1 ]: 

63 if part not in configuration_: configuration_[ part ] = { } 

64 configuration_ = configuration_[ part ] 

65 configuration_[ self.address[ -1 ] ] = value 

66 

67 

68class ElementsEntryEdit( Edit, decorators = ( __.standard_dataclass, ) ): 

69 ''' Applies entry edit to every matching dictionary in array. ''' 

70 

71 editee: tuple[ str, __.typx.Any ] 

72 identifier: __.typx.Optional[ tuple[ str, __.typx.Any ] ] = None 

73 

74 def __call__( self, configuration: __.NominativeDictionary ) -> None: 

75 array = self.dereference( configuration ) 

76 if self.identifier: 

77 iname, ivalue = self.identifier # pylint: disable=unpacking-non-sequence 

78 else: iname, ivalue = None, None 

79 ename, evalue = self.editee 

80 for element in array: 

81 if iname: 

82 if iname not in element: 

83 raise _exceptions.EntryAssertionFailure( 

84 'configuration array element', iname ) 

85 if ivalue != element[ iname ]: continue 

86 element[ ename ] = evalue 

87 

88 

89class SimpleEdit( Edit, decorators = ( __.standard_dataclass, ) ): 

90 ''' Applies edit to single entity. ''' 

91 

92 value: __.typx.Any 

93 

94 def __call__( self, configuration: __.NominativeDictionary ) -> None: 

95 self.inject( configuration, self.value ) 

96 

97 

98Edits: __.typx.TypeAlias = __.cabc.Iterable[ Edit ]