Coverage for sources/mimeogram/__/dictedits.py: 98%
41 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-07 04:07 +0000
« 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 -*-
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''' Support for edits on nested dictionaries. '''
22# TODO: Independent package.
23# TODO: Copying edits, not just in-place edits.
26from . import imports as __
27from . import nomina as _nomina
28from . import exceptions as _exceptions
31class Edit(
32 __.typx.Protocol,
33 metaclass = __.ImmutableStandardProtocolDataclass,
34 decorators = ( __.standard_dataclass, __.typx.runtime_checkable ),
35):
36 ''' Base representation of an edit to configuration. '''
38 address: __.cabc.Sequence[ str ]
40 @__.abc.abstractmethod
41 def __call__( self, configuration: _nomina.NominativeDictionary ) -> None:
42 ''' Performs edit. '''
43 raise NotImplementedError
45 def dereference(
46 self, configuration: _nomina.NominativeDictionary
47 ) -> __.typx.Any:
48 ''' Dereferences value at address in configuration. '''
49 configuration_ = configuration
50 for part in self.address:
51 if part not in configuration_:
52 raise (
53 _exceptions.AddressLocateFailure( # noqa: TRY003
54 'configuration dictionary', self.address, part ) )
55 configuration_ = configuration_[ part ]
56 return configuration_
58 def inject(
59 self, configuration: _nomina.NominativeDictionary, value: __.typx.Any
60 ) -> None:
61 ''' Injects value at address in configuration. '''
62 configuration_ = configuration
63 for part in self.address[ : -1 ]:
64 if part not in configuration_: configuration_[ part ] = { }
65 configuration_ = configuration_[ part ]
66 configuration_[ self.address[ -1 ] ] = value
69class ElementsEntryEdit( Edit, decorators = ( __.standard_dataclass, ) ):
70 ''' Applies entry edit to every matching dictionary in array. '''
72 editee: tuple[ str, __.typx.Any ]
73 identifier: __.typx.Optional[ tuple[ str, __.typx.Any ] ] = None
75 def __call__( self, configuration: _nomina.NominativeDictionary ) -> None:
76 array = self.dereference( configuration )
77 if self.identifier:
78 iname, ivalue = self.identifier
79 else: iname, ivalue = None, None
80 ename, evalue = self.editee
81 for element in array:
82 if iname:
83 if iname not in element:
84 raise _exceptions.EntryAssertionFailure( # noqa: TRY003
85 'configuration array element', iname )
86 if ivalue != element[ iname ]: continue
87 element[ ename ] = evalue
90class SimpleEdit( Edit, decorators = ( __.standard_dataclass, ) ):
91 ''' Applies edit to single entity. '''
93 value: __.typx.Any
95 def __call__( self, configuration: _nomina.NominativeDictionary ) -> None:
96 self.inject( configuration, self.value )
99Edits: __.typx.TypeAlias = __.cabc.Iterable[ Edit ]