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

20 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-09-25 18:07 +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 namespaces. 

22 

23 Provides a namespace type that can grow but never shrink. Once an attribute is 

24 set, it cannot be modified or removed. This provides a simple way to create 

25 objects with named attributes that become immutable after assignment. 

26 

27 The namespace implementation is modeled after :py:class:`types.SimpleNamespace` 

28 but adds accretive behavior. Like :py:class:`types.SimpleNamespace`, it 

29 provides a simple ``__repr__`` which lists all attributes. 

30 

31 >>> from accretive import Namespace 

32 >>> ns = Namespace( apples = 12, bananas = 6 ) 

33 >>> ns.cherries = 42 # Add new attribute 

34 >>> ns.apples = 14 # Attempt modification 

35 Traceback (most recent call last): 

36 ... 

37 accretive.exceptions.AttributeImmutability: Could not assign or delete existing attribute 'apples'. 

38 >>> del ns.bananas # Attempt deletion 

39 Traceback (most recent call last): 

40 ... 

41 accretive.exceptions.AttributeImmutability: Could not assign or delete existing attribute 'bananas'. 

42''' # noqa: E501 

43 

44 

45from . import __ 

46from . import iclasses as _iclasses 

47 

48 

49class Namespace( # noqa: PLW1641 

50 metaclass = _iclasses.Class, 

51 instances_assigner_core = _iclasses.assign_attribute_if_absent_mutable, 

52): 

53 # TODO: Dynadoc fragments. 

54 ''' Accretive namespaces. ''' 

55 

56 __slots__ = ( '__dict__', ) 

57 

58 def __init__( 

59 self, 

60 *iterables: __.DictionaryPositionalArgument[ __.H, __.V ], 

61 **attributes: __.DictionaryNominativeArgument[ __.V ], 

62 ) -> None: 

63 super( ).__init__( ) 

64 super( ).__getattribute__( '__dict__' ).update( 

65 __.AccretiveDictionary( *iterables, **attributes ) ) 

66 

67 def __repr__( self ) -> str: 

68 attributes = ', '.join( tuple( 

69 f"{key} = {value!r}" for key, value 

70 in getattr( self, '__dict__', { } ).items( ) ) ) 

71 fqname = __.ccutils.qualify_class_name( type( self ) ) 

72 if not attributes: return f"{fqname}( )" 

73 return f"{fqname}( {attributes} )" 

74 

75 def __eq__( self, other: __.typx.Any ) -> __.ComparisonResult: 

76 if isinstance( other, ( Namespace, __.types.SimpleNamespace ) ): 

77 return self.__dict__ == other.__dict__ 

78 return NotImplemented 

79 

80 def __ne__( self, other: __.typx.Any ) -> __.ComparisonResult: 

81 if isinstance( other, ( Namespace, __.types.SimpleNamespace ) ): 

82 return self.__dict__ != other.__dict__ 

83 return NotImplemented