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

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''' Standard classes and class factories. ''' 

22 

23 

24from . import __ 

25from . import decorators as _decorators 

26from . import dynadoc as _dynadoc 

27from . import nomina as _nomina 

28 

29 

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} ) 

59 

60 

61def _snapshot_declared_protocol_attrs( cls: type ) -> None: 

62 ''' Records protocol members declared before framework decoration. 

63 

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 ) ) 

84 

85_dynadoc_configuration = ( 

86 _dynadoc.produce_dynadoc_configuration( table = __.fragments ) ) 

87_class_factory = __.funct.partial( 

88 _decorators.class_factory, dynadoc_configuration = _dynadoc_configuration ) 

89 

90 

91class ClassFactoryExtraArguments( __.typx.TypedDict, total = False ): 

92 ''' Extra arguments accepted by standard metaclasses. ''' 

93 

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 

103 

104 

105@_class_factory( ) 

106class Class( type ): 

107 ''' Metaclass for standard classes. ''' 

108 

109 _dynadoc_fragments_ = ( 

110 'cfc class conceal', 'cfc class protect', 'cfc dynadoc', 

111 'cfc instance conceal', 'cfc instance protect' ) 

112 

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 ) 

122 

123 

124@_class_factory( ) 

125class AbstractClass( Class, __.abc.ABCMeta ): 

126 ''' Metaclass for abstract classes with standard behaviors. 

127 

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 ''' 

134 

135 _dynadoc_fragments_ = ( 

136 'cfc class conceal', 'cfc class protect', 'cfc dynadoc', 

137 'cfc instance conceal', 'cfc instance protect' ) 

138 

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 ) 

148 

149 

150@_class_factory( ) 

151@__.typx.dataclass_transform( frozen_default = True, kw_only_default = True ) 

152class Dataclass( Class ): 

153 ''' Metaclass for standard dataclasses. ''' 

154 

155 _dynadoc_fragments_ = ( 

156 'cfc produce dataclass', 

157 'cfc class conceal', 'cfc class protect', 'cfc dynadoc', 

158 'cfc instance conceal', 'cfc instance protect' ) 

159 

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 ) 

169 

170 

171@_class_factory( ) 

172@__.typx.dataclass_transform( kw_only_default = True ) 

173class DataclassMutable( Dataclass ): 

174 ''' Metaclass for dataclasses with mutable instance attributes. ''' 

175 

176 _dynadoc_fragments_ = ( 

177 'cfc produce dataclass', 

178 'cfc class conceal', 'cfc class protect', 'cfc dynadoc', 

179 'cfc instance conceal' ) 

180 

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 ) 

190 

191 

192@_class_factory( ) 

193class ProtocolClass( AbstractClass, type( __.typx.Protocol ) ): 

194 ''' Metaclass for standard protocol classes. ''' 

195 

196 _dynadoc_fragments_ = ( 

197 'cfc produce protocol class', 

198 'cfc class conceal', 'cfc class protect', 'cfc dynadoc', 

199 'cfc instance conceal', 'cfc instance protect' ) 

200 

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 

225 

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 ) 

253 

254 

255@_class_factory( ) 

256@__.typx.dataclass_transform( frozen_default = True, kw_only_default = True ) 

257class ProtocolDataclass( ProtocolClass ): 

258 ''' Metaclass for standard protocol dataclasses. ''' 

259 

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' ) 

264 

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 ) 

274 

275 

276@_class_factory( ) 

277@__.typx.dataclass_transform( kw_only_default = True ) 

278class ProtocolDataclassMutable( ProtocolDataclass ): 

279 ''' Metaclass for protocol dataclasses with mutable instance attributes. 

280 ''' 

281 

282 _dynadoc_fragments_ = ( 

283 'cfc produce protocol class', 'cfc produce dataclass', 

284 'cfc class conceal', 'cfc class protect', 'cfc dynadoc', 

285 'cfc instance conceal' ) 

286 

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 ) 

296 

297 

298class Object( metaclass = Class ): 

299 ''' Standard base class. ''' 

300 

301 _dynadoc_fragments_ = ( 

302 'class concealment', 'class protection', 'class dynadoc', 

303 'class instance conceal', 'class instance protect' ) 

304 

305 

306class ObjectMutable( metaclass = Class, instances_mutables = '*' ): 

307 ''' Base class with mutable instance attributes. ''' 

308 

309 _dynadoc_fragments_ = ( 

310 'class concealment', 'class protection', 'class dynadoc', 

311 'class instance conceal' ) 

312 

313 

314class AbstractObject( 

315 metaclass = AbstractClass, 

316 class_mutables = _abc_class_mutables, 

317): 

318 ''' Base class for abstract classes with standard behaviors. 

319 

320 Supports abstract method enforcement and virtual subclass 

321 registration, and mixes with external classes whose metaclass is 

322 `abc.ABCMeta`. 

323 ''' 

324 

325 _dynadoc_fragments_ = ( 

326 'class concealment', 'class protection', 'class dynadoc', 

327 'class instance conceal', 'class instance protect' ) 

328 

329 

330class DataclassObject( metaclass = Dataclass ): 

331 ''' Standard base dataclass. ''' 

332 

333 _dynadoc_fragments_ = ( 

334 'dataclass', 

335 'class concealment', 'class protection', 'class dynadoc', 

336 'class instance conceal', 'class instance protect' ) 

337 

338 

339class DataclassObjectMutable( metaclass = DataclassMutable ): 

340 ''' Base dataclass with mutable instance attributes. ''' 

341 

342 _dynadoc_fragments_ = ( 

343 'dataclass', 

344 'class concealment', 'class protection', 'class dynadoc', 

345 'class instance conceal' ) 

346 

347 

348class Protocol( 

349 __.typx.Protocol, 

350 metaclass = ProtocolClass, 

351 class_mutables = _abc_class_mutables, 

352): 

353 ''' Standard base protocol class. ''' 

354 

355 _dynadoc_fragments_ = ( 

356 'protocol class', 

357 'class concealment', 'class protection', 'class dynadoc', 

358 'class instance conceal', 'class instance protect' ) 

359 

360 

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. ''' 

368 

369 _dynadoc_fragments_ = ( 

370 'protocol class', 

371 'class concealment', 'class protection', 'class dynadoc', 

372 'class instance conceal' ) 

373 

374 

375class DataclassProtocol( 

376 __.typx.Protocol, 

377 metaclass = ProtocolDataclass, 

378 class_mutables = _abc_class_mutables, 

379): 

380 ''' Standard base protocol dataclass. ''' 

381 

382 _dynadoc_fragments_ = ( 

383 'dataclass', 'protocol class', 

384 'class concealment', 'class protection', 'class dynadoc', 

385 'class instance conceal', 'class instance protect' ) 

386 

387 

388class DataclassProtocolMutable( 

389 __.typx.Protocol, 

390 metaclass = ProtocolDataclassMutable, 

391 class_mutables = _abc_class_mutables, 

392): 

393 ''' Base protocol dataclass with mutable instance attributes. ''' 

394 

395 _dynadoc_fragments_ = ( 

396 'dataclass', 'protocol class', 

397 'class concealment', 'class protection', 'class dynadoc', 

398 'class instance conceal' ) 

399 

400 

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# =========================================================================== # 

407 

408 

409class _CanaryAbstractObject( AbstractObject ): 

410 ''' Canary for abstract base classes. ''' 

411 

412 def provide( self ) -> int: 

413 return 42 

414 

415 

416class _CanaryProtocolUse( Protocol ): 

417 ''' Canary for protocol definitions. ''' 

418 

419 value: int 

420 

421 def greet( self ) -> str: ... 

422 

423 

424class _CanaryProtocolImpl( _CanaryProtocolUse ): 

425 ''' Canary for concrete protocol implementations. ''' 

426 

427 def __init__( self ) -> None: 

428 self.value = 7 

429 

430 def greet( self ) -> str: 

431 return 'canary' 

432 

433 

434_canary_abstract: int = _CanaryAbstractObject( ).provide( ) 

435_canary_protocol_iface: _CanaryProtocolUse = _CanaryProtocolImpl( ) 

436_canary_greeting: str = _canary_protocol_iface.greet( )