classcore 1.3.1__tar.gz → 1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {classcore-1.3.1 → classcore-1.4}/PKG-INFO +1 -1
  2. {classcore-1.3.1 → classcore-1.4}/sources/classcore/__init__.py +1 -1
  3. {classcore-1.3.1 → classcore-1.4}/sources/classcore/decorators.py +66 -33
  4. {classcore-1.3.1 → classcore-1.4}/sources/classcore/exceptions.py +5 -5
  5. {classcore-1.3.1 → classcore-1.4}/sources/classcore/standard/behaviors.py +26 -1
  6. {classcore-1.3.1 → classcore-1.4}/sources/classcore/standard/classes.py +11 -7
  7. {classcore-1.3.1 → classcore-1.4}/sources/classcore/standard/decorators.py +156 -66
  8. {classcore-1.3.1 → classcore-1.4}/sources/classcore/utilities.py +53 -23
  9. {classcore-1.3.1 → classcore-1.4}/.gitignore +0 -0
  10. {classcore-1.3.1 → classcore-1.4}/LICENSE.txt +0 -0
  11. {classcore-1.3.1 → classcore-1.4}/README.rst +0 -0
  12. {classcore-1.3.1 → classcore-1.4}/pyproject.toml +0 -0
  13. {classcore-1.3.1 → classcore-1.4}/sources/classcore/__/__init__.py +0 -0
  14. {classcore-1.3.1 → classcore-1.4}/sources/classcore/__/doctab.py +0 -0
  15. {classcore-1.3.1 → classcore-1.4}/sources/classcore/__/imports.py +0 -0
  16. {classcore-1.3.1 → classcore-1.4}/sources/classcore/__/nomina.py +0 -0
  17. {classcore-1.3.1 → classcore-1.4}/sources/classcore/factories.py +0 -0
  18. {classcore-1.3.1 → classcore-1.4}/sources/classcore/nomina.py +0 -0
  19. {classcore-1.3.1 → classcore-1.4}/sources/classcore/py.typed +0 -0
  20. {classcore-1.3.1 → classcore-1.4}/sources/classcore/standard/__.py +0 -0
  21. {classcore-1.3.1 → classcore-1.4}/sources/classcore/standard/__init__.py +0 -0
  22. {classcore-1.3.1 → classcore-1.4}/sources/classcore/standard/dynadoc.py +0 -0
  23. {classcore-1.3.1 → classcore-1.4}/sources/classcore/standard/modules.py +0 -0
  24. {classcore-1.3.1 → classcore-1.4}/sources/classcore/standard/nomina.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: classcore
3
- Version: 1.3.1
3
+ Version: 1.4
4
4
  Summary: Foundational class factories and decorators.
5
5
  Project-URL: Homepage, https://github.com/emcd/python-classcore
6
6
  Project-URL: Documentation, https://emcd.github.io/python-classcore
@@ -62,7 +62,7 @@ from .factories import *
62
62
 
63
63
 
64
64
  __version__: __.typx.Annotated[ str, __.dynadoc.Visibilities.Reveal ]
65
- __version__ = '1.3.1'
65
+ __version__ = '1.4'
66
66
 
67
67
 
68
68
  standard.dynadoc.assign_module_docstring( __name__, table = __.fragments )
@@ -75,25 +75,45 @@ def produce_class_construction_decorator(
75
75
  Decorator overrides ``__new__`` on metaclass.
76
76
  '''
77
77
  def decorate( clscls: type[ __.T ] ) -> type[ __.T ]:
78
- constructor_name = attributes_namer( 'classes', 'constructor' )
79
- extant = getattr( clscls, constructor_name, None )
80
- original = getattr( clscls, '__new__' )
81
- if extant is original: return clscls
82
-
83
- def construct(
84
- clscls_: type[ __.T ],
85
- name: str,
86
- bases: tuple[ type, ... ],
87
- namespace: dict[ str, __.typx.Any ], *,
88
- decorators: _nomina.Decorators[ __.T ] = ( ),
89
- **arguments: __.typx.Any,
90
- ) -> type[ object ]:
91
- return constructor(
92
- clscls_, original,
93
- name, bases, namespace, arguments, decorators )
94
-
95
- setattr( clscls, constructor_name, construct )
96
- setattr( clscls, '__new__', construct )
78
+ original = __.typx.cast(
79
+ _nomina.ClassConstructorLigation | None,
80
+ clscls.__dict__.get( '__new__' ) ) # pyright: ignore
81
+
82
+ if original is None:
83
+
84
+ def construct_with_super(
85
+ clscls_: type[ __.T ],
86
+ name: str,
87
+ bases: tuple[ type, ... ],
88
+ namespace: dict[ str, __.typx.Any ], *,
89
+ decorators: _nomina.Decorators[ __.T ] = ( ),
90
+ **arguments: __.typx.Any,
91
+ ) -> type[ object ]:
92
+ superf = super( clscls, clscls_ ).__new__
93
+ # TODO? Short-circuit if not at start of MRO.
94
+ return constructor(
95
+ clscls_, superf,
96
+ name, bases, namespace, arguments, decorators )
97
+
98
+ setattr( clscls, '__new__', construct_with_super )
99
+
100
+ else:
101
+
102
+ def construct_with_original(
103
+ clscls_: type[ __.T ],
104
+ name: str,
105
+ bases: tuple[ type, ... ],
106
+ namespace: dict[ str, __.typx.Any ], *,
107
+ decorators: _nomina.Decorators[ __.T ] = ( ),
108
+ **arguments: __.typx.Any,
109
+ ) -> type[ object ]:
110
+ # TODO? Short-circuit if not at start of MRO.
111
+ return constructor(
112
+ clscls_, original,
113
+ name, bases, namespace, arguments, decorators )
114
+
115
+ setattr( clscls, '__new__', construct_with_original )
116
+
97
117
  return clscls
98
118
 
99
119
  return decorate
@@ -108,20 +128,33 @@ def produce_class_initialization_decorator(
108
128
  Decorator overrides ``__init__`` on metaclass.
109
129
  '''
110
130
  def decorate( clscls: type[ __.T ] ) -> type[ __.T ]:
111
- initializer_name = attributes_namer( 'classes', 'initializer' )
112
- extant = getattr( clscls, initializer_name, None )
113
- original = getattr( clscls, '__init__' )
114
- if extant is original: return clscls
115
-
116
- @__.funct.wraps( original )
117
- def initialize(
118
- cls: type, *posargs: __.typx.Any, **nomargs: __.typx.Any
119
- ) -> None:
120
- ligation = __.funct.partial( original, cls )
121
- initializer( cls, ligation, posargs, nomargs )
122
-
123
- setattr( clscls, initializer_name, initialize )
124
- clscls.__init__ = initialize
131
+ original = __.typx.cast(
132
+ _nomina.InitializerLigation | None,
133
+ clscls.__dict__.get( '__init__' ) ) # pyright: ignore
134
+
135
+ if original is None:
136
+
137
+ def initialize_with_super(
138
+ cls: type, *posargs: __.typx.Any, **nomargs: __.typx.Any
139
+ ) -> None:
140
+ ligation = super( clscls, cls ).__init__
141
+ # TODO? Short-circuit if not at start of MRO.
142
+ initializer( cls, ligation, posargs, nomargs )
143
+
144
+ clscls.__init__ = initialize_with_super
145
+
146
+ else:
147
+
148
+ @__.funct.wraps( original )
149
+ def initialize_with_original(
150
+ cls: type, *posargs: __.typx.Any, **nomargs: __.typx.Any
151
+ ) -> None:
152
+ ligation = __.funct.partial( original, cls )
153
+ # TODO? Short-circuit if not at start of MRO.
154
+ initializer( cls, ligation, posargs, nomargs )
155
+
156
+ clscls.__init__ = initialize_with_original
157
+
125
158
  return clscls
126
159
 
127
160
  return decorate
@@ -26,31 +26,31 @@ from . import standard as _standard
26
26
 
27
27
 
28
28
  class Omniexception(
29
- BaseException, _standard.Object,
29
+ _standard.Object, BaseException,
30
30
  instances_visibles = ( '__cause__', '__context__' ),
31
31
  ):
32
32
  ''' Base exception for package. '''
33
33
 
34
34
 
35
- class Omnierror( Exception, Omniexception ):
35
+ class Omnierror( Omniexception, Exception ):
36
36
  ''' Base error for package. '''
37
37
 
38
38
 
39
- class AttributeImmutability( AttributeError, Omnierror ):
39
+ class AttributeImmutability( Omnierror, AttributeError ):
40
40
 
41
41
  def __init__( self, name: str, target: str ):
42
42
  super( ).__init__(
43
43
  f"Could not assign or delete attribute {name!r} on {target}." )
44
44
 
45
45
 
46
- class BehaviorExclusionInvalidity( TypeError, ValueError, Omnierror ):
46
+ class BehaviorExclusionInvalidity( Omnierror, TypeError, ValueError ):
47
47
 
48
48
  def __init__( self, verifier: __.typx.Any ):
49
49
  super( ).__init__(
50
50
  f"Invalid behavior exclusion verifier: {verifier!r}" )
51
51
 
52
52
 
53
- class ErrorProvideFailure( RuntimeError, Omnierror ):
53
+ class ErrorProvideFailure( Omnierror, RuntimeError ):
54
54
 
55
55
  def __init__( self, name: str, reason: str ):
56
56
  super( ).__init__(
@@ -151,6 +151,29 @@ def survey_visible_attributes(
151
151
  return names_
152
152
 
153
153
 
154
+ def augment_class_attributes_allocations(
155
+ attributes_namer: _nomina.AttributesNamer,
156
+ namespace: dict[ str, __.typx.Any ],
157
+ ) -> None:
158
+ ''' Adds necessary slots for record-keeping attributes. '''
159
+ behaviors_name = attributes_namer( 'instance', 'behaviors' )
160
+ slots: __.typx.Union[
161
+ __.cabc.Mapping[ str, __.typx.Any ],
162
+ __.cabc.Sequence[ str ],
163
+ None
164
+ ] = namespace.get( '__slots__' )
165
+ if isinstance( slots, __.cabc.Mapping ):
166
+ slots_ = dict( slots )
167
+ slots_[ behaviors_name ] = 'Active behaviors.'
168
+ slots_ = __.types.MappingProxyType( slots_ )
169
+ elif isinstance( slots, __.cabc.Sequence ):
170
+ slots_ = list( slots )
171
+ slots_.append( behaviors_name )
172
+ slots_ = tuple( slots_ )
173
+ else: return # pragma: no cover
174
+ namespace[ '__slots__' ] = slots_
175
+
176
+
154
177
  def classify_behavior_exclusion_verifiers(
155
178
  verifiers: _nomina.BehaviorExclusionVerifiers
156
179
  ) -> tuple[
@@ -190,6 +213,8 @@ def produce_class_construction_preprocessor(
190
213
  ) -> None:
191
214
  record_class_construction_arguments(
192
215
  attributes_namer, namespace, arguments )
216
+ if '__slots__' in namespace:
217
+ augment_class_attributes_allocations( attributes_namer, namespace )
193
218
 
194
219
  return preprocess
195
220
 
@@ -329,11 +354,11 @@ def record_class_construction_arguments(
329
354
  arguments_ = { }
330
355
  for name in (
331
356
  'class_mutables', 'class_visibles',
357
+ 'dynadoc_configuration',
332
358
  'instances_assigner_core',
333
359
  'instances_deleter_core',
334
360
  'instances_surveyor_core',
335
361
  'instances_mutables', 'instances_visibles',
336
- 'dynadoc_configuration',
337
362
  ):
338
363
  if name not in arguments: continue
339
364
  arguments_[ name ] = arguments.pop( name )
@@ -33,11 +33,15 @@ _class_factory = __.funct.partial(
33
33
  _decorators.class_factory, dynadoc_configuration = _dynadoc_configuration )
34
34
 
35
35
 
36
- class _CfcExtraArguments( __.typx.TypedDict, total = False ):
36
+ class ClassFactoryExtraArguments( __.typx.TypedDict, total = False ):
37
+ ''' Extra arguments accepted by standard metaclasses. '''
37
38
 
38
39
  class_mutables: _nomina.BehaviorExclusionVerifiersOmni
39
40
  class_visibles: _nomina.BehaviorExclusionVerifiersOmni
40
41
  dynadoc_configuration: _nomina.DynadocConfiguration
42
+ instances_assigner_core: _nomina.AssignerCore
43
+ instances_deleter_core: _nomina.DeleterCore
44
+ instances_surveyor_core: _nomina.SurveyorCore
41
45
  instances_mutables: _nomina.BehaviorExclusionVerifiersOmni
42
46
  instances_visibles: _nomina.BehaviorExclusionVerifiersOmni
43
47
 
@@ -56,7 +60,7 @@ class Class( type ):
56
60
  bases: tuple[ type, ... ],
57
61
  namespace: dict[ str, __.typx.Any ], *,
58
62
  decorators: _nomina.Decorators[ __.T ] = ( ),
59
- **arguments: __.typx.Unpack[ _CfcExtraArguments ],
63
+ **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
60
64
  ) -> __.T:
61
65
  return super( ).__new__( clscls, name, bases, namespace )
62
66
 
@@ -77,7 +81,7 @@ class Dataclass( type ):
77
81
  bases: tuple[ type, ... ],
78
82
  namespace: dict[ str, __.typx.Any ], *,
79
83
  decorators: _nomina.Decorators[ __.T ] = ( ),
80
- **arguments: __.typx.Unpack[ _CfcExtraArguments ],
84
+ **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
81
85
  ) -> __.T:
82
86
  return super( ).__new__( clscls, name, bases, namespace )
83
87
 
@@ -98,7 +102,7 @@ class DataclassMutable( type ):
98
102
  bases: tuple[ type, ... ],
99
103
  namespace: dict[ str, __.typx.Any ], *,
100
104
  decorators: _nomina.Decorators[ __.T ] = ( ),
101
- **arguments: __.typx.Unpack[ _CfcExtraArguments ],
105
+ **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
102
106
  ) -> __.T:
103
107
  return super( ).__new__( clscls, name, bases, namespace )
104
108
 
@@ -118,7 +122,7 @@ class ProtocolClass( type( __.typx.Protocol ) ):
118
122
  bases: tuple[ type, ... ],
119
123
  namespace: dict[ str, __.typx.Any ], *,
120
124
  decorators: _nomina.Decorators[ __.T ] = ( ),
121
- **arguments: __.typx.Unpack[ _CfcExtraArguments ],
125
+ **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
122
126
  ) -> __.T:
123
127
  return super( ).__new__( clscls, name, bases, namespace )
124
128
 
@@ -139,7 +143,7 @@ class ProtocolDataclass( type( __.typx.Protocol ) ):
139
143
  bases: tuple[ type, ... ],
140
144
  namespace: dict[ str, __.typx.Any ], *,
141
145
  decorators: _nomina.Decorators[ __.T ] = ( ),
142
- **arguments: __.typx.Unpack[ _CfcExtraArguments ],
146
+ **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
143
147
  ) -> __.T:
144
148
  return super( ).__new__( clscls, name, bases, namespace )
145
149
 
@@ -161,7 +165,7 @@ class ProtocolDataclassMutable( type( __.typx.Protocol ) ):
161
165
  bases: tuple[ type, ... ],
162
166
  namespace: dict[ str, __.typx.Any ], *,
163
167
  decorators: _nomina.Decorators[ __.T ] = ( ),
164
- **arguments: __.typx.Unpack[ _CfcExtraArguments ],
168
+ **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
165
169
  ) -> __.T:
166
170
  return super( ).__new__( clscls, name, bases, namespace )
167
171
 
@@ -49,6 +49,7 @@ def prepare_dataclass_for_instances(
49
49
  ''' Annotates dataclass in support of instantiation machinery. '''
50
50
  annotations = __.inspect.get_annotations( cls )
51
51
  behaviors_name = attributes_namer( 'instance', 'behaviors' )
52
+ # TODO? Only use mangling if not slotted.
52
53
  behaviors_name_m = _utilities.mangle_name( cls, behaviors_name )
53
54
  annotations[ behaviors_name_m ] = set[ str ]
54
55
  setattr( cls, '__annotations__', annotations ) # in case of absence
@@ -89,7 +90,7 @@ def apply_cfc_constructor(
89
90
 
90
91
 
91
92
  def apply_cfc_initializer(
92
- clscls: type[ __.T ], attributes_namer: _nomina.AttributesNamer
93
+ clscls: type[ __.T ], /, attributes_namer: _nomina.AttributesNamer
93
94
  ) -> None:
94
95
  ''' Injects '__init__' method into metaclass. '''
95
96
  completers = (
@@ -112,7 +113,7 @@ def apply_cfc_attributes_assigner(
112
113
  ) -> None:
113
114
  ''' Injects '__setattr__' method into metaclass. '''
114
115
  decorator = produce_attributes_assignment_decorator(
115
- level = 'class',
116
+ level = 'classes',
116
117
  attributes_namer = attributes_namer,
117
118
  error_class_provider = error_class_provider,
118
119
  implementation_core = implementation_core )
@@ -127,7 +128,7 @@ def apply_cfc_attributes_deleter(
127
128
  ) -> None:
128
129
  ''' Injects '__delattr__' method into metaclass. '''
129
130
  decorator = produce_attributes_deletion_decorator(
130
- level = 'class',
131
+ level = 'classes',
131
132
  attributes_namer = attributes_namer,
132
133
  error_class_provider = error_class_provider,
133
134
  implementation_core = implementation_core )
@@ -141,7 +142,7 @@ def apply_cfc_attributes_surveyor(
141
142
  ) -> None:
142
143
  ''' Injects '__dir__' method into metaclass. '''
143
144
  decorator = produce_attributes_surveillance_decorator(
144
- level = 'class',
145
+ level = 'classes',
145
146
  attributes_namer = attributes_namer,
146
147
  implementation_core = implementation_core )
147
148
  decorator( clscls )
@@ -196,13 +197,8 @@ def produce_instances_initialization_decorator(
196
197
  ) -> _nomina.Decorator[ __.U ]:
197
198
  ''' Produces decorator to inject '__init__' method into class. '''
198
199
  def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
199
- initializer_name = attributes_namer( 'instances', 'initializer' )
200
- extant = getattr( cls, initializer_name, None )
201
- original = getattr( cls, '__init__' )
202
- if extant is original: return cls
203
200
  behaviors: set[ str ] = set( )
204
201
  behaviors_name = attributes_namer( 'instance', 'behaviors' )
205
- behaviors_name_m = _utilities.mangle_name( cls, behaviors_name )
206
202
  _behaviors.record_behavior(
207
203
  cls, attributes_namer = attributes_namer,
208
204
  level = 'instances', basename = 'mutables',
@@ -213,18 +209,41 @@ def produce_instances_initialization_decorator(
213
209
  level = 'instances', basename = 'visibles',
214
210
  label = _nomina.concealment_label, behaviors = behaviors,
215
211
  verifiers = visibles )
212
+ original = cls.__dict__.get( '__init__' )
213
+
214
+ if original is None:
215
+
216
+ def initialize_with_super(
217
+ self: object, *posargs: __.typx.Any, **nomargs: __.typx.Any
218
+ ) -> None:
219
+ super( cls, self ).__init__( *posargs, **nomargs )
220
+ # Only record behaviors at start of MRO.
221
+ if cls is not type( self ): return
222
+ behaviors_: set[ str ] = (
223
+ _utilities.getattr0( self, behaviors_name, set( ) ) )
224
+ behaviors_.update( behaviors )
225
+ _utilities.setattr0(
226
+ self, behaviors_name, frozenset( behaviors_ ) )
227
+
228
+ cls.__init__ = initialize_with_super
229
+
230
+ else:
231
+
232
+ @__.funct.wraps( original )
233
+ def initialize_with_original(
234
+ self: object, *posargs: __.typx.Any, **nomargs: __.typx.Any
235
+ ) -> None:
236
+ original( self, *posargs, **nomargs )
237
+ # Only record behaviors at start of MRO.
238
+ if cls is not type( self ): return
239
+ behaviors_: set[ str ] = (
240
+ _utilities.getattr0( self, behaviors_name, set( ) ) )
241
+ behaviors_.update( behaviors )
242
+ _utilities.setattr0(
243
+ self, behaviors_name, frozenset( behaviors_ ) )
244
+
245
+ cls.__init__ = initialize_with_original
216
246
 
217
- @__.funct.wraps( original )
218
- def initialize(
219
- self: object, *posargs: __.typx.Any, **nomargs: __.typx.Any
220
- ) -> None:
221
- original( self, *posargs, **nomargs )
222
- behaviors_: set[ str ] = getattr( self, behaviors_name_m, set( ) )
223
- behaviors_.update( behaviors )
224
- setattr( self, behaviors_name_m, frozenset( behaviors_ ) )
225
-
226
- setattr( cls, initializer_name, initialize )
227
- cls.__init__ = initialize
228
247
  return cls
229
248
 
230
249
  return decorate
@@ -238,23 +257,50 @@ def produce_attributes_assignment_decorator(
238
257
  ) -> _nomina.Decorator[ __.U ]:
239
258
  ''' Produces decorator to inject '__setattr__' method into class. '''
240
259
  def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
241
- assigner_name = attributes_namer( level, 'assigner' )
242
- extant = getattr( cls, assigner_name, None )
243
- original = getattr( cls, '__setattr__' )
244
- if extant is original: return cls
245
-
246
- @__.funct.wraps( original )
247
- def assign( self: object, name: str, value: __.typx.Any ) -> None:
248
- implementation_core(
249
- self,
250
- ligation = __.funct.partial( original, self ),
251
- attributes_namer = attributes_namer,
252
- error_class_provider = error_class_provider,
253
- level = level,
254
- name = name, value = value )
260
+ leveli = 'class' if level == 'classes' else level
261
+ original = cls.__dict__.get( '__setattr__' )
262
+
263
+ if original is None:
264
+
265
+ def assign_with_super(
266
+ self: object, name: str, value: __.typx.Any
267
+ ) -> None:
268
+ ligation = super( cls, self ).__setattr__
269
+ # Only enforce behaviors at start of MRO.
270
+ if cls is not type( self ):
271
+ ligation( name, value )
272
+ return
273
+ implementation_core(
274
+ self,
275
+ ligation = ligation,
276
+ attributes_namer = attributes_namer,
277
+ error_class_provider = error_class_provider,
278
+ level = leveli,
279
+ name = name, value = value )
280
+
281
+ cls.__setattr__ = assign_with_super
282
+
283
+ else:
284
+
285
+ @__.funct.wraps( original )
286
+ def assign_with_original(
287
+ self: object, name: str, value: __.typx.Any
288
+ ) -> None:
289
+ ligation = __.funct.partial( original, self )
290
+ # Only enforce behaviors at start of MRO.
291
+ if cls is not type( self ):
292
+ ligation( name, value )
293
+ return
294
+ implementation_core(
295
+ self,
296
+ ligation = ligation,
297
+ attributes_namer = attributes_namer,
298
+ error_class_provider = error_class_provider,
299
+ level = leveli,
300
+ name = name, value = value )
301
+
302
+ cls.__setattr__ = assign_with_original
255
303
 
256
- setattr( cls, assigner_name, assign )
257
- cls.__setattr__ = assign
258
304
  return cls
259
305
 
260
306
  return decorate
@@ -268,23 +314,46 @@ def produce_attributes_deletion_decorator(
268
314
  ) -> _nomina.Decorator[ __.U ]:
269
315
  ''' Produces decorator to inject '__delattr__' method into class. '''
270
316
  def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
271
- deleter_name = attributes_namer( level, 'deleter' )
272
- extant = getattr( cls, deleter_name, None )
273
- original = getattr( cls, '__delattr__' )
274
- if extant is original: return cls
275
-
276
- @__.funct.wraps( original )
277
- def delete( self: object, name: str ) -> None:
278
- implementation_core(
279
- self,
280
- ligation = __.funct.partial( original, self ),
281
- attributes_namer = attributes_namer,
282
- error_class_provider = error_class_provider,
283
- level = level,
284
- name = name )
317
+ leveli = 'class' if level == 'classes' else level
318
+ original = cls.__dict__.get( '__delattr__' )
319
+
320
+ if original is None:
321
+
322
+ def delete_with_super( self: object, name: str ) -> None:
323
+ ligation = super( cls, self ).__delattr__
324
+ # Only enforce behaviors at start of MRO.
325
+ if cls is not type( self ):
326
+ ligation( name )
327
+ return
328
+ implementation_core(
329
+ self,
330
+ ligation = ligation,
331
+ attributes_namer = attributes_namer,
332
+ error_class_provider = error_class_provider,
333
+ level = leveli,
334
+ name = name )
335
+
336
+ cls.__delattr__ = delete_with_super
337
+
338
+ else:
339
+
340
+ @__.funct.wraps( original )
341
+ def delete_with_original( self: object, name: str ) -> None:
342
+ ligation = __.funct.partial( original, self )
343
+ # Only enforce behaviors at start of MRO.
344
+ if cls is not type( self ):
345
+ ligation( name )
346
+ return
347
+ implementation_core(
348
+ self,
349
+ ligation = ligation,
350
+ attributes_namer = attributes_namer,
351
+ error_class_provider = error_class_provider,
352
+ level = leveli,
353
+ name = name )
354
+
355
+ cls.__delattr__ = delete_with_original
285
356
 
286
- setattr( cls, deleter_name, delete )
287
- cls.__delattr__ = delete
288
357
  return cls
289
358
 
290
359
  return decorate
@@ -297,21 +366,42 @@ def produce_attributes_surveillance_decorator(
297
366
  ) -> _nomina.Decorator[ __.U ]:
298
367
  ''' Produces decorator to inject '__dir__' method into class. '''
299
368
  def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
300
- surveyor_name = attributes_namer( level, 'surveyor' )
301
- extant = getattr( cls, surveyor_name, None )
302
- original = getattr( cls, '__dir__' )
303
- if extant is original: return cls
304
-
305
- @__.funct.wraps( original )
306
- def survey( self: object ) -> __.cabc.Iterable[ str ]:
307
- return implementation_core(
308
- self,
309
- ligation = __.funct.partial( original, self ),
310
- attributes_namer = attributes_namer,
311
- level = level )
369
+ leveli = 'class' if level == 'classes' else level
370
+ original = cls.__dict__.get( '__dir__' )
371
+
372
+ if original is None:
373
+
374
+ def survey_with_super(
375
+ self: object
376
+ ) -> __.cabc.Iterable[ str ]:
377
+ ligation = super( cls, self ).__dir__
378
+ # Only enforce behaviors at start of MRO.
379
+ if cls is not type( self ): return ligation( )
380
+ return implementation_core(
381
+ self,
382
+ ligation = ligation,
383
+ attributes_namer = attributes_namer,
384
+ level = leveli )
385
+
386
+ cls.__dir__ = survey_with_super
387
+
388
+ else:
389
+
390
+ @__.funct.wraps( original )
391
+ def survey_with_original(
392
+ self: object
393
+ ) -> __.cabc.Iterable[ str ]:
394
+ ligation = __.funct.partial( original, self )
395
+ # Only enforce behaviors at start of MRO.
396
+ if cls is not type( self ): return ligation( )
397
+ return implementation_core(
398
+ self,
399
+ ligation = ligation,
400
+ attributes_namer = attributes_namer,
401
+ level = leveli )
402
+
403
+ cls.__dir__ = survey_with_original
312
404
 
313
- setattr( cls, surveyor_name, survey )
314
- cls.__dir__ = survey
315
405
  return cls
316
406
 
317
407
  return decorate
@@ -24,51 +24,81 @@
24
24
  from . import __
25
25
 
26
26
 
27
- def describe_object( obj: object ) -> str:
27
+ def describe_object( objct: object, / ) -> str:
28
28
  ''' Returns object type with fully-qualified name. '''
29
- if __.inspect.isclass( obj ):
30
- return "class '{}'".format( qualify_class_name( obj ) )
29
+ if __.inspect.isclass( objct ):
30
+ return "class '{}'".format( qualify_class_name( objct ) )
31
31
  # TODO? functions, methods, etc...
32
- return "instance of {}".format( describe_object( type( obj ) ) )
32
+ return "instance of {}".format( describe_object( type( objct ) ) )
33
33
 
34
34
 
35
- def getattr0( obj: object, name: str, default: __.typx.Any ) -> __.typx.Any:
36
- ''' Returns private attribute from object.
35
+ def getattr0(
36
+ objct: object, /, name: str, default: __.typx.Any
37
+ ) -> __.typx.Any:
38
+ ''' Returns special private attribute from object.
37
39
 
38
- Uses mangled attribute name which is unique to the class.
40
+ This avoids inheritance-related collisions.
41
+
42
+ Uses mangled attribute name which is unique to the class,
43
+ except when attribute is slotted. Slotted attributes are effectively
44
+ isolated from inheritance.
39
45
  '''
40
- name_m = mangle_name( obj, name )
41
- return getattr( obj, name_m, default )
46
+ if not __.inspect.isclass( objct ):
47
+ for base in type( objct ).mro( ):
48
+ slots = getattr( base, '__slots__', ( ) )
49
+ if name in slots: return getattr( objct, name, default )
50
+ name_m = mangle_name( objct, name )
51
+ return getattr( objct, name_m, default )
52
+
42
53
 
54
+ def delattr0( objct: object, /, name: str ) -> None:
55
+ ''' Deletes special private attribute on object.
43
56
 
44
- def delattr0( obj: object, name: str ) -> None:
45
- ''' Deletes private attribute on object.
57
+ This avoids inheritance-related collisions.
46
58
 
47
- Uses mangled attribute name which is unique to the class.
59
+ Uses mangled attribute name which is unique to the class,
60
+ except when attribute is slotted. Slotted attributes are effectively
61
+ isolated from inheritance.
48
62
  '''
49
- name_m = mangle_name( obj, name )
50
- delattr( obj, name_m )
63
+ if not __.inspect.isclass( objct ):
64
+ for base in type( objct ).mro( ):
65
+ slots = getattr( base, '__slots__', ( ) )
66
+ if name in slots:
67
+ delattr( objct, name )
68
+ return
69
+ name_m = mangle_name( objct, name )
70
+ delattr( objct, name_m )
71
+
51
72
 
73
+ def setattr0( objct: object, /, name: str, value: __.typx.Any ) -> None:
74
+ ''' Assigns special private attribute to object.
52
75
 
53
- def setattr0( obj: object, name: str, value: __.typx.Any ) -> None:
54
- ''' Assigns private attribute to object.
76
+ This avoids inheritance-related collisions.
55
77
 
56
- Uses mangled attribute name which is unique to the class.
78
+ Uses mangled attribute name which is unique to the class,
79
+ except when attribute is slotted. Slotted attributes are effectively
80
+ isolated from inheritance.
57
81
  '''
58
- name_m = mangle_name( obj, name )
59
- setattr( obj, name_m, value )
82
+ if not __.inspect.isclass( objct ):
83
+ for base in type( objct ).mro( ):
84
+ slots = getattr( base, '__slots__', ( ) )
85
+ if name in slots:
86
+ setattr( objct, name, value )
87
+ return
88
+ name_m = mangle_name( objct, name )
89
+ setattr( objct, name_m, value )
60
90
 
61
91
 
62
- def mangle_name( obj: object, name: str ) -> str:
92
+ def mangle_name( objct: object, /, name: str ) -> str:
63
93
  ''' Mangles attribute name so that it is unique.
64
94
 
65
95
  Effectively provides name of private member attribute,
66
96
  which is unique across class inheritance.
67
97
  '''
68
- if not __.inspect.isclass( obj ):
69
- return mangle_name( type( obj ), name )
98
+ if not __.inspect.isclass( objct ):
99
+ return mangle_name( type( objct ), name )
70
100
  namehash = __.hashlib.sha256( )
71
- namehash.update( qualify_class_name( obj ).encode( ) )
101
+ namehash.update( qualify_class_name( objct ).encode( ) )
72
102
  namehash_hex = namehash.hexdigest( )
73
103
  return f"{name}_{namehash_hex}"
74
104
 
File without changes
File without changes
File without changes
File without changes