Coverage for sources/classcore/standard/classes.py: 100%
115 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-15 20:56 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-15 20:56 +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''' Standard classes and class factories. '''
24from . import __
25from . import decorators as _decorators
26from . import dynadoc as _dynadoc
27from . import nomina as _nomina
30_abc_class_mutables = (
31 '_abc_cache',
32 '_abc_negative_cache',
33 '_abc_negative_cache_version',
34 '_abc_registry',
35 '_is_runtime_protocol',
36 '__non_callable_proto_members__',
37)
38_protocol_cls_set = frozenset( { __.typx.Protocol } )
39# Attributes never considered declared protocol members: typing internals,
40# class-creation machinery, and framework attributes declared in classcore
41# base class bodies.
42_protocol_attr_excluded = frozenset( {
43 '_is_protocol', '_is_runtime_protocol',
44 '__protocol_attrs__', '__subclasshook__',
45 '__non_callable_proto_members__',
46 '_dynadoc_fragments_',
47 # Injected during class creation by ABC, Generic, and typing machinery.
48 # '__init__' is '_no_init' on protocol bases; as a structural member it
49 # is vacuous since every candidate object has an '__init__'.
50 '__abstractmethods__', '__parameters__', '__init__', '__orig_bases__',
51} )
52_protocol_attr_prefixes = ( '_classcore_', '_abc_' )
53# Standard Python attributes present in every pre-decoration namespace.
54_python_class_defaults = frozenset( {
55 '__module__', '__qualname__', '__doc__', '__dict__', '__weakref__',
56 '__slots__', '__annotations__',
57 '__firstlineno__', '__static_attributes__', # Python 3.13+
58} )
61def _snapshot_declared_protocol_attrs( cls: type ) -> None:
62 ''' Records protocol members declared before framework decoration.
64 Called in metaclass '__new__' immediately after class creation,
65 before behavior decorators and dataclass machinery inject their
66 attributes. The pre-decoration namespace contains exactly the
67 members declared by the author, so a user-declared dunder (e.g.,
68 '__repr__') is preserved while framework-generated ones are absent.
69 '''
70 # Dataclass slot machinery reproduces classes, copying '__dict__'.
71 # Retain the original snapshot; the reproduction's namespace already
72 # contains decorated attributes.
73 if '_classcore_protocol_declared_' in cls.__dict__: return
74 declared = (
75 set( cls.__dict__ )
76 | set( __.inspect.get_annotations( cls ) ) )
77 declared -= _python_class_defaults
78 declared = {
79 attr for attr in declared
80 if attr not in _protocol_attr_excluded
81 and not any( attr.startswith( p ) for p in _protocol_attr_prefixes )
82 }
83 setattr( cls, '_classcore_protocol_declared_', frozenset( declared ) )
85_dynadoc_configuration = (
86 _dynadoc.produce_dynadoc_configuration( table = __.fragments ) )
87_class_factory = __.funct.partial(
88 _decorators.class_factory, dynadoc_configuration = _dynadoc_configuration )
91class ClassFactoryExtraArguments( __.typx.TypedDict, total = False ):
92 ''' Extra arguments accepted by standard metaclasses. '''
94 class_mutables: _nomina.BehaviorExclusionVerifiersOmni
95 class_visibles: _nomina.BehaviorExclusionVerifiersOmni
96 dynadoc_configuration: _nomina.DynadocConfiguration
97 instances_assigner_core: _nomina.AssignerCore
98 instances_deleter_core: _nomina.DeleterCore
99 instances_surveyor_core: _nomina.SurveyorCore
100 instances_ignore_init_arguments: bool
101 instances_mutables: _nomina.BehaviorExclusionVerifiersOmni
102 instances_visibles: _nomina.BehaviorExclusionVerifiersOmni
105@_class_factory( )
106class Class( type ):
107 ''' Metaclass for standard classes. '''
109 _dynadoc_fragments_ = (
110 'cfc class conceal', 'cfc class protect', 'cfc dynadoc',
111 'cfc instance conceal', 'cfc instance protect' )
113 def __new__( # Typechecker stub.
114 clscls: type[ __.T ],
115 name: str,
116 bases: tuple[ type, ... ],
117 namespace: dict[ str, __.typx.Any ], *,
118 decorators: _nomina.Decorators[ __.T ] = ( ),
119 **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
120 ) -> __.T:
121 return super( ).__new__( clscls, name, bases, namespace )
124@_class_factory( )
125class AbstractClass( Class, __.abc.ABCMeta ):
126 ''' Metaclass for abstract classes with standard behaviors.
128 Combines the standard behaviors of `Class` with the machinery of
129 `abc.ABCMeta` (abstract method enforcement, virtual subclass
130 registration) via a diamond hierarchy. `Class` itself remains
131 backed by plain `type`, so ABC machinery applies only where this
132 metaclass is used.
133 '''
135 _dynadoc_fragments_ = (
136 'cfc class conceal', 'cfc class protect', 'cfc dynadoc',
137 'cfc instance conceal', 'cfc instance protect' )
139 def __new__( # Typechecker stub.
140 clscls: type[ __.T ],
141 name: str,
142 bases: tuple[ type, ... ],
143 namespace: dict[ str, __.typx.Any ], *,
144 decorators: _nomina.Decorators[ __.T ] = ( ),
145 **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
146 ) -> __.T:
147 return super( ).__new__( clscls, name, bases, namespace )
150@_class_factory( )
151@__.typx.dataclass_transform( frozen_default = True, kw_only_default = True )
152class Dataclass( Class ):
153 ''' Metaclass for standard dataclasses. '''
155 _dynadoc_fragments_ = (
156 'cfc produce dataclass',
157 'cfc class conceal', 'cfc class protect', 'cfc dynadoc',
158 'cfc instance conceal', 'cfc instance protect' )
160 def __new__( # Typechecker stub.
161 clscls: type[ __.T ],
162 name: str,
163 bases: tuple[ type, ... ],
164 namespace: dict[ str, __.typx.Any ], *,
165 decorators: _nomina.Decorators[ __.T ] = ( ),
166 **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
167 ) -> __.T:
168 return super( ).__new__( clscls, name, bases, namespace )
171@_class_factory( )
172@__.typx.dataclass_transform( kw_only_default = True )
173class DataclassMutable( Dataclass ):
174 ''' Metaclass for dataclasses with mutable instance attributes. '''
176 _dynadoc_fragments_ = (
177 'cfc produce dataclass',
178 'cfc class conceal', 'cfc class protect', 'cfc dynadoc',
179 'cfc instance conceal' )
181 def __new__( # Typechecker stub.
182 clscls: type[ __.T ],
183 name: str,
184 bases: tuple[ type, ... ],
185 namespace: dict[ str, __.typx.Any ], *,
186 decorators: _nomina.Decorators[ __.T ] = ( ),
187 **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
188 ) -> __.T:
189 return super( ).__new__( clscls, name, bases, namespace )
192@_class_factory( )
193class ProtocolClass( AbstractClass, type( __.typx.Protocol ) ):
194 ''' Metaclass for standard protocol classes. '''
196 _dynadoc_fragments_ = (
197 'cfc produce protocol class',
198 'cfc class conceal', 'cfc class protect', 'cfc dynadoc',
199 'cfc instance conceal', 'cfc instance protect' )
201 def __new__( # Typechecker stub.
202 clscls: type[ __.T ],
203 name: str,
204 bases: tuple[ type, ... ],
205 namespace: dict[ str, __.typx.Any ], *,
206 decorators: _nomina.Decorators[ __.T ] = ( ),
207 **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
208 ) -> __.T:
209 cls = super( ).__new__( clscls, name, bases, namespace )
210 # typing_extensions.Protocol.__init_subclass__ uses identity comparison
211 # (b is Protocol) to set _is_protocol on subclasses. Classcore protocol
212 # base classes are not typing.Protocol, so subclasses incorrectly get
213 # _is_protocol = False. Detect protocol base classes structurally and
214 # fix _is_protocol before decorators are applied
215 # (e.g., runtime_checkable).
216 if not cls.__dict__.get( '_is_protocol', False ):
217 for base in cls.__bases__:
218 if ( base.__dict__.get( '_is_protocol', False )
219 and bool( _protocol_cls_set & set( base.__bases__ ) ) ):
220 setattr( cls, '_is_protocol', True )
221 break
222 if cls.__dict__.get( '_is_protocol', False ):
223 _snapshot_declared_protocol_attrs( cls )
224 return cls
226 def __init__(
227 cls,
228 name: str,
229 bases: tuple[ type, ... ],
230 namespace: dict[ str, __.typx.Any ],
231 **kwargs: __.typx.Any,
232 ) -> None:
233 super( ).__init__( name, bases, namespace, **kwargs )
234 # Replace _ProtocolMeta's scan of the decorated namespace with the
235 # pre-decoration snapshot, so isinstance() only checks declared
236 # protocol members. Snapshot persists because metaclass __init__ can
237 # run more than once when dataclass machinery reproduces classes.
238 if getattr( cls, '_is_protocol', False ):
239 declared: frozenset[ str ] = cls.__dict__.get(
240 '_classcore_protocol_declared_', frozenset( ) )
241 inherited: set[ str ] = set( )
242 for base in cls.__mro__[ 1: ]:
243 if not isinstance( base, ProtocolClass ): continue
244 inherited.update( base.__dict__.get(
245 '__protocol_attrs__', ( ) ) )
246 attrs = {
247 attr for attr in declared | inherited
248 if attr not in _protocol_attr_excluded
249 and not any(
250 attr.startswith( p ) for p in _protocol_attr_prefixes )
251 }
252 setattr( cls, '__protocol_attrs__', attrs )
255@_class_factory( )
256@__.typx.dataclass_transform( frozen_default = True, kw_only_default = True )
257class ProtocolDataclass( ProtocolClass ):
258 ''' Metaclass for standard protocol dataclasses. '''
260 _dynadoc_fragments_ = (
261 'cfc produce protocol class', 'cfc produce dataclass',
262 'cfc class conceal', 'cfc class protect', 'cfc dynadoc',
263 'cfc instance conceal', 'cfc instance protect' )
265 def __new__( # Typechecker stub.
266 clscls: type[ __.T ],
267 name: str,
268 bases: tuple[ type, ... ],
269 namespace: dict[ str, __.typx.Any ], *,
270 decorators: _nomina.Decorators[ __.T ] = ( ),
271 **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
272 ) -> __.T:
273 return super( ).__new__( clscls, name, bases, namespace )
276@_class_factory( )
277@__.typx.dataclass_transform( kw_only_default = True )
278class ProtocolDataclassMutable( ProtocolDataclass ):
279 ''' Metaclass for protocol dataclasses with mutable instance attributes.
280 '''
282 _dynadoc_fragments_ = (
283 'cfc produce protocol class', 'cfc produce dataclass',
284 'cfc class conceal', 'cfc class protect', 'cfc dynadoc',
285 'cfc instance conceal' )
287 def __new__( # Typechecker stub.
288 clscls: type[ __.T ],
289 name: str,
290 bases: tuple[ type, ... ],
291 namespace: dict[ str, __.typx.Any ], *,
292 decorators: _nomina.Decorators[ __.T ] = ( ),
293 **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
294 ) -> __.T:
295 return super( ).__new__( clscls, name, bases, namespace )
298class Object( metaclass = Class ):
299 ''' Standard base class. '''
301 _dynadoc_fragments_ = (
302 'class concealment', 'class protection', 'class dynadoc',
303 'class instance conceal', 'class instance protect' )
306class ObjectMutable( metaclass = Class, instances_mutables = '*' ):
307 ''' Base class with mutable instance attributes. '''
309 _dynadoc_fragments_ = (
310 'class concealment', 'class protection', 'class dynadoc',
311 'class instance conceal' )
314class AbstractObject(
315 metaclass = AbstractClass,
316 class_mutables = _abc_class_mutables,
317):
318 ''' Base class for abstract classes with standard behaviors.
320 Supports abstract method enforcement and virtual subclass
321 registration, and mixes with external classes whose metaclass is
322 `abc.ABCMeta`.
323 '''
325 _dynadoc_fragments_ = (
326 'class concealment', 'class protection', 'class dynadoc',
327 'class instance conceal', 'class instance protect' )
330class DataclassObject( metaclass = Dataclass ):
331 ''' Standard base dataclass. '''
333 _dynadoc_fragments_ = (
334 'dataclass',
335 'class concealment', 'class protection', 'class dynadoc',
336 'class instance conceal', 'class instance protect' )
339class DataclassObjectMutable( metaclass = DataclassMutable ):
340 ''' Base dataclass with mutable instance attributes. '''
342 _dynadoc_fragments_ = (
343 'dataclass',
344 'class concealment', 'class protection', 'class dynadoc',
345 'class instance conceal' )
348class Protocol(
349 __.typx.Protocol,
350 metaclass = ProtocolClass,
351 class_mutables = _abc_class_mutables,
352):
353 ''' Standard base protocol class. '''
355 _dynadoc_fragments_ = (
356 'protocol class',
357 'class concealment', 'class protection', 'class dynadoc',
358 'class instance conceal', 'class instance protect' )
361class ProtocolMutable(
362 __.typx.Protocol,
363 metaclass = ProtocolClass,
364 class_mutables = _abc_class_mutables,
365 instances_mutables = '*',
366):
367 ''' Base protocol class with mutable instance attributes. '''
369 _dynadoc_fragments_ = (
370 'protocol class',
371 'class concealment', 'class protection', 'class dynadoc',
372 'class instance conceal' )
375class DataclassProtocol(
376 __.typx.Protocol,
377 metaclass = ProtocolDataclass,
378 class_mutables = _abc_class_mutables,
379):
380 ''' Standard base protocol dataclass. '''
382 _dynadoc_fragments_ = (
383 'dataclass', 'protocol class',
384 'class concealment', 'class protection', 'class dynadoc',
385 'class instance conceal', 'class instance protect' )
388class DataclassProtocolMutable(
389 __.typx.Protocol,
390 metaclass = ProtocolDataclassMutable,
391 class_mutables = _abc_class_mutables,
392):
393 ''' Base protocol dataclass with mutable instance attributes. '''
395 _dynadoc_fragments_ = (
396 'dataclass', 'protocol class',
397 'class concealment', 'class protection', 'class dynadoc',
398 'class instance conceal' )
401# =========================================================================== #
402# Type checker canaries. Private declarations, excluded from the public API
403# and the test suite. They exist so that Pyright (and other type checkers)
404# evaluates the structures declared above; regressions in checker support
405# surface as diagnostics on these declarations during routine linting.
406# =========================================================================== #
409class _CanaryAbstractObject( AbstractObject ):
410 ''' Canary for abstract base classes. '''
412 def provide( self ) -> int:
413 return 42
416class _CanaryProtocolUse( Protocol ):
417 ''' Canary for protocol definitions. '''
419 value: int
421 def greet( self ) -> str: ...
424class _CanaryProtocolImpl( _CanaryProtocolUse ):
425 ''' Canary for concrete protocol implementations. '''
427 def __init__( self ) -> None:
428 self.value = 7
430 def greet( self ) -> str:
431 return 'canary'
434_canary_abstract: int = _CanaryAbstractObject( ).provide( )
435_canary_protocol_iface: _CanaryProtocolUse = _CanaryProtocolImpl( )
436_canary_greeting: str = _canary_protocol_iface.greet( )