Coverage for sources/frigid/objects.py: 100%
44 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-05 03:04 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-05 03:04 +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# pylint: disable=line-too-long
22''' Immutable objects.
24 Provides a base class and decorator for creating objects with immutable
25 attributes. Once an object is initialized, its attributes cannot be modified
26 or deleted.
28 The implementation uses a special dictionary type for attribute storage that
29 enforces immutability. This makes it suitable for:
31 * Configuration objects
32 * Value objects
33 * Immutable data containers
34 * Objects requiring attribute stability
36 >>> from frigid import Object
37 >>> class Point( Object ):
38 ... def __init__( self, x, y ):
39 ... self.x = x
40 ... self.y = y
41 ... super( ).__init__( )
42 ...
43 >>> obj = Point( 1, 2 ) # Initialize with attributes
44 >>> obj.z = 3 # Attempt to add attribute
45 Traceback (most recent call last):
46 ...
47 frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'z'.
48 >>> obj.x = 4 # Attempt modification
49 Traceback (most recent call last):
50 ...
51 frigid.exceptions.AttributeImmutabilityError: Cannot assign or delete attribute 'x'.
52'''
53# pylint: enable=line-too-long
56from . import __
59def _check_behavior( obj: object ) -> bool:
60 behaviors: __.cabc.MutableSet[ str ]
61 if _check_dict( obj ):
62 attributes = getattr( obj, '__dict__' )
63 behaviors = attributes.get( '_behaviors_', set( ) )
64 else: behaviors = getattr( obj, '_behaviors_', set( ) )
65 return __.behavior_label in behaviors
68def _check_dict( obj: object ) -> bool:
69 # Return False even if '__dict__' in '__slots__'.
70 if hasattr( obj, '__slots__' ): return False
71 return hasattr( obj, '__dict__' )
74def immutable( class_: type[ __.C ] ) -> type[ __.C ]: # pylint: disable=too-complex
75 ''' Decorator which makes class immutable after initialization.
77 Cannot be applied to classes which define their own __setattr__
78 or __delattr__ methods.
79 '''
80 for method in ( '__setattr__', '__delattr__' ):
81 if method in class_.__dict__:
82 from .exceptions import DecoratorCompatibilityError
83 raise DecoratorCompatibilityError( class_.__name__, method )
84 original_init = next(
85 base.__dict__[ '__init__' ] for base in class_.__mro__
86 if '__init__' in base.__dict__ ) # pylint: disable=magic-value-comparison
88 def __init__(
89 self: object, *posargs: __.a.Any, **nomargs: __.a.Any
90 ) -> None:
91 # TODO: Use accretive set for behaviors.
92 original_init( self, *posargs, **nomargs )
93 behaviors: __.cabc.MutableSet[ str ]
94 if _check_dict( self ):
95 attributes = getattr( self, '__dict__' )
96 behaviors = attributes.get( '_behaviors_', set( ) )
97 if not behaviors: attributes[ '_behaviors_' ] = behaviors
98 setattr( self, '__dict__', __.ImmutableDictionary( attributes ) )
99 else:
100 behaviors = getattr( self, '_behaviors_', set( ) )
101 if not behaviors: setattr( self, '_behaviors_', behaviors )
102 behaviors.add( __.behavior_label )
104 def __delattr__( self: object, name: str ) -> None:
105 if _check_behavior( self ):
106 from .exceptions import AttributeImmutabilityError
107 raise AttributeImmutabilityError( name )
108 super( class_, self ).__delattr__( name )
110 def __setattr__( self: object, name: str, value: __.a.Any ) -> None:
111 if _check_behavior( self ):
112 from .exceptions import AttributeImmutabilityError
113 raise AttributeImmutabilityError( name )
114 super( class_, self ).__setattr__( name, value )
116 class_.__init__ = __init__
117 class_.__delattr__ = __delattr__
118 class_.__setattr__ = __setattr__
119 return class_
122@immutable
123class Object:
124 ''' Immutable objects. '''
126 __slots__ = ( '__dict__', '_behaviors_' )
128Object.__doc__ = __.generate_docstring(
129 Object, 'instance attributes immutability' )