Coverage for sources/appcore/dictedits.py: 100%
38 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-16 02:09 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-16 02: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''' Support for edits on nested dictionaries. '''
22# TODO: Independent package.
23# TODO: Copying edits, not just in-place edits.
26from . import __
27from . import exceptions as _exceptions
30class Edit(
31 __.immut.DataclassProtocol, __.typx.Protocol,
32 decorators = ( __.typx.runtime_checkable, ),
33):
34 ''' Base representation of an edit to configuration. '''
36 address: __.cabc.Sequence[ str ]
38 @__.abc.abstractmethod
39 def __call__(
40 self, configuration: __.NominativeDictionaryMutable
41 ) -> None:
42 ''' Performs edit. '''
43 raise NotImplementedError # pragma: no cover
45 def dereference(
46 self, configuration: __.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,
60 configuration: __.NominativeDictionaryMutable,
61 value: __.typx.Any,
62 ) -> None:
63 ''' Injects value at address in configuration. '''
64 configuration_ = configuration
65 for part in self.address[ : -1 ]:
66 if part not in configuration_: configuration_[ part ] = { }
67 configuration_ = configuration_[ part ]
68 configuration_[ self.address[ -1 ] ] = value
71class ElementsEntryEdit( Edit ):
72 ''' Applies entry edit to every matching dictionary in array. '''
74 editee: tuple[ str, __.typx.Any ]
75 identifier: __.typx.Optional[ tuple[ str, __.typx.Any ] ] = None
77 def __call__(
78 self, configuration: __.NominativeDictionaryMutable
79 ) -> None:
80 array = self.dereference( configuration )
81 if self.identifier:
82 iname, ivalue = self.identifier
83 else: iname, ivalue = None, None
84 ename, evalue = self.editee
85 for element in array:
86 if iname:
87 if iname not in element:
88 raise _exceptions.EntryAssertionFailure( # noqa: TRY003
89 'configuration array element', iname )
90 if ivalue != element[ iname ]: continue
91 element[ ename ] = evalue
94class SimpleEdit( Edit ):
95 ''' Applies edit to single entity. '''
97 value: __.typx.Any
99 def __call__(
100 self, configuration: __.NominativeDictionaryMutable
101 ) -> None: self.inject( configuration, self.value )
104Edits: __.typx.TypeAlias = __.cabc.Iterable[ Edit ]