Coverage for sources/accretive/dictionaries.py: 100%

72 statements  

« prev     ^ index     » next       coverage.py v7.5.4, created at 2024-07-06 17:17 +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''' Accretive dictionaries. ''' 

22 

23 

24from . import __ 

25from . import _annotations as _a 

26from . import classes as _classes 

27from . import objects as _objects 

28 

29 

30class _Dictionary( # type: ignore 

31 __.CoreDictionary, metaclass = _classes.Class 

32): pass 

33 

34 

35_no_value: object = object( ) 

36 

37 

38class Dictionary( _objects.Object ): # pylint: disable=eq-without-hash 

39 ''' Accretive dictionary. ''' 

40 

41 __slots__ = ( '_data_', ) 

42 

43 _data_: _Dictionary 

44 

45 def __init__( 

46 self, 

47 *iterables: _a.DictionaryPositionalArgument, 

48 **entries: _a.DictionaryNominativeArgument, 

49 ) -> None: 

50 self._data_ = _Dictionary( *iterables, **entries ) 

51 super( ).__init__( ) 

52 

53 def __iter__( self ) -> _a.Iterator[ _a.Hashable ]: 

54 return iter( self._data_ ) 

55 

56 def __len__( self ) -> int: 

57 return len( self._data_ ) 

58 

59 def __repr__( self ) -> str: 

60 return "{fqname}( {contents} )".format( 

61 fqname = __.discover_fqname( self ), 

62 contents = str( self._data_ ) ) 

63 

64 def __str__( self ) -> str: 

65 return str( self._data_ ) 

66 

67 def __contains__( self, key: _a.Hashable ) -> bool: 

68 return key in self._data_ 

69 

70 def __delitem__( self, key: _a.Hashable ) -> None: 

71 from .exceptions import IndelibleEntryError 

72 raise IndelibleEntryError( key ) 

73 

74 def __getitem__( self, key: _a.Hashable ) -> _a.Any: 

75 return self._data_[ key ] 

76 

77 def __setitem__( self, key: _a.Hashable, value: _a.Any ) -> None: 

78 self._data_[ key ] = value 

79 

80 def __eq__( self, other: _a.Any ) -> _a.ComparisonResult: 

81 if isinstance( other, __.AbstractDictionary ): 

82 return self._data_ == other 

83 return NotImplemented 

84 

85 def __ne__( self, other: _a.Any ) -> _a.ComparisonResult: 

86 if isinstance( other, __.AbstractDictionary ): 

87 return self._data_ != other 

88 return NotImplemented 

89 

90 def copy( self ) -> _a.Self: 

91 ''' Provides fresh copy of dictionary. ''' 

92 return type( self )( self ) 

93 

94 def get( 

95 self, key: _a.Hashable, default: _a.Any = _no_value 

96 ) -> _a.Annotation[ 

97 _a.Any, 

98 _a.Doc( 

99 'Value of entry, if it exists. ' 

100 'Else, supplied default value or ``None``.' ) 

101 ]: 

102 ''' Retrieves entry associated with key, if it exists. ''' 

103 if _no_value is default: return self._data_.get( key ) 

104 return self._data_.get( key, default ) 

105 

106 def update( 

107 self, 

108 *iterables: _a.DictionaryPositionalArgument, 

109 **entries: _a.DictionaryNominativeArgument, 

110 ) -> _a.Self: 

111 ''' Adds new entries as a batch. ''' 

112 self._data_.update( *iterables, **entries ) 

113 return self 

114 

115 def keys( self ) -> _a.KeysView[ _a.Hashable ]: 

116 ''' Provides iterable view over dictionary keys. ''' 

117 return self._data_.keys( ) 

118 

119 def items( self ) -> _a.ItemsView[ _a.Hashable, _a.Any ]: 

120 ''' Provides iterable view over dictionary items. ''' 

121 return self._data_.items( ) 

122 

123 def values( self ) -> _a.ValuesView[ _a.Any ]: 

124 ''' Provides iterable view over dictionary values. ''' 

125 return self._data_.values( ) 

126 

127Dictionary.__doc__ = __.generate_docstring( 

128 Dictionary, 

129 'dictionary entries accretion', 

130 'instance attributes accretion', 

131) 

132# Register as subclass of AbstractDictionary rather than use it as mixin. 

133# We directly implement, for the sake of efficiency, the methods which the 

134# mixin would provide. 

135__.AbstractDictionary.register( Dictionary ) 

136 

137 

138class ProducerDictionary( Dictionary ): 

139 ''' Accretive dictionary with default value for missing entries. ''' 

140 

141 __slots__ = ( '_producer_', ) 

142 

143 _producer_: _a.DictionaryProducer 

144 

145 def __init__( 

146 self, 

147 producer: _a.DictionaryProducer, 

148 /, 

149 *iterables: _a.DictionaryPositionalArgument, 

150 **entries: _a.DictionaryNominativeArgument 

151 ): 

152 # TODO: Validate producer argument. 

153 self._producer_ = producer 

154 super( ).__init__( *iterables, **entries ) 

155 

156 def __repr__( self ) -> str: 

157 return "{fqname}( {producer}, {contents} )".format( 

158 fqname = __.discover_fqname( self ), 

159 producer = self._producer_, 

160 contents = str( self._data_ ) ) 

161 

162 def __getitem__( self, key: _a.Hashable ) -> _a.Any: 

163 if key not in self: 

164 value = self._producer_( ) 

165 self[ key ] = value 

166 else: value = super( ).__getitem__( key ) 

167 return value 

168 

169 def copy( self ) -> _a.Self: 

170 ''' Provides fresh copy of dictionary. ''' 

171 dictionary = type( self )( self._producer_ ) 

172 return dictionary.update( self ) 

173 

174ProducerDictionary.__doc__ = __.generate_docstring( 

175 ProducerDictionary, 

176 'dictionary entries accretion', 

177 'dictionary entries production', 

178 'instance attributes accretion', 

179) 

180 

181 

182__all__ = __.discover_public_attributes( globals( ) )