classcore 1.11__tar.gz → 1.12__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 (26) hide show
  1. {classcore-1.11 → classcore-1.12}/PKG-INFO +2 -2
  2. {classcore-1.11 → classcore-1.12}/pyproject.toml +1 -0
  3. {classcore-1.11 → classcore-1.12}/sources/classcore/README.md +12 -7
  4. {classcore-1.11 → classcore-1.12}/sources/classcore/__/imports.py +1 -0
  5. {classcore-1.11 → classcore-1.12}/sources/classcore/__init__.py +1 -1
  6. {classcore-1.11 → classcore-1.12}/sources/classcore/decorators.py +23 -5
  7. {classcore-1.11 → classcore-1.12}/sources/classcore/factories.py +1 -1
  8. {classcore-1.11 → classcore-1.12}/sources/classcore/nomina.py +36 -13
  9. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/README.md +31 -17
  10. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/behaviors.py +1 -1
  11. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/classes.py +178 -6
  12. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/decorators.py +5 -5
  13. {classcore-1.11 → classcore-1.12}/.gitignore +0 -0
  14. {classcore-1.11 → classcore-1.12}/LICENSE.txt +0 -0
  15. {classcore-1.11 → classcore-1.12}/README.rst +0 -0
  16. {classcore-1.11 → classcore-1.12}/sources/classcore/__/__init__.py +0 -0
  17. {classcore-1.11 → classcore-1.12}/sources/classcore/__/doctab.py +0 -0
  18. {classcore-1.11 → classcore-1.12}/sources/classcore/__/nomina.py +0 -0
  19. {classcore-1.11 → classcore-1.12}/sources/classcore/exceptions.py +0 -0
  20. {classcore-1.11 → classcore-1.12}/sources/classcore/py.typed +0 -0
  21. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/__.py +0 -0
  22. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/__init__.py +0 -0
  23. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/dynadoc.py +0 -0
  24. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/modules.py +0 -0
  25. {classcore-1.11 → classcore-1.12}/sources/classcore/standard/nomina.py +0 -0
  26. {classcore-1.11 → classcore-1.12}/sources/classcore/utilities.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: classcore
3
- Version: 1.11
3
+ Version: 1.12
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
@@ -255,6 +255,7 @@ cache_dir = '.auxiliary/caches/pytest'
255
255
  markers = [
256
256
  "slow: long-running tests",
257
257
  ]
258
+ asyncio_default_fixture_loop_scope = "function"
258
259
 
259
260
  [tool.ruff]
260
261
  #builtins = [ 'ic' ]
@@ -83,19 +83,20 @@ Pre-configured implementations for common use cases:
83
83
  **Base Classes:**
84
84
  - `Object`: Standard class with immutability and concealment
85
85
  - `DataclassObject`: Automatic dataclass conversion with standard behaviors
86
- - `ProtocolObject`: Protocol class with immutability and concealment
86
+ - `Protocol`: Protocol base class with immutability and concealment
87
+ - `AbstractObject`: Abstract class with ABC machinery and behaviors
87
88
  - Variants for selective mutability (`ObjectMutable`, etc.)
88
89
 
89
90
  **Metaclasses:**
90
91
  - `Class`: Metaclass for standard classes
91
92
  - `Dataclass`: Metaclass that auto-applies dataclass decorator
92
- - `Protocol`: Metaclass for protocol classes
93
+ - `AbstractClass`: Metaclass combining `Class` with `abc.ABCMeta`
94
+ - `ProtocolClass`: Metaclass for protocol classes
93
95
  - Mutable variants of each
94
96
 
95
97
  **Decorators:**
96
- - `class_with_standard_behaviors`: Add behaviors to existing classes
98
+ - `with_standard_behaviors`: Add behaviors to existing classes
97
99
  - `dataclass_with_standard_behaviors`: Dataclass creation with behaviors
98
- - `protocol_with_standard_behaviors`: Protocol creation with behaviors
99
100
 
100
101
  **Module Utilities:**
101
102
  - `reclassify_module`: Apply immutability and concealment to module objects
@@ -381,16 +382,20 @@ sources/
381
382
  : Standard metaclasses and base classes:
382
383
 
383
384
  - `Class` / `Object`: Standard class with full behaviors
385
+ - `AbstractClass` / `AbstractObject`: Abstract class with ABC machinery
386
+ (abstract method enforcement, virtual subclass registration); mixes
387
+ with external `abc.ABCMeta`-based classes
384
388
  - `Dataclass` / `DataclassObject`: Auto-dataclass with behaviors
385
- - `Protocol` / `ProtocolObject`: Protocol with behaviors
389
+ - `ProtocolClass` / `Protocol`, `ProtocolMutable`,
390
+ `DataclassProtocol`, `DataclassProtocolMutable`: Protocol classes
391
+ with behaviors
386
392
  - Mutable variants for each
387
393
 
388
394
  `decorators.py`
389
395
  : Standard class decorators:
390
396
 
391
- - `class_with_standard_behaviors`: Apply behaviors to existing class
397
+ - `with_standard_behaviors`: Apply behaviors to existing class
392
398
  - `dataclass_with_standard_behaviors`: Create dataclass with behaviors
393
- - `protocol_with_standard_behaviors`: Create protocol with behaviors
394
399
  - `class_factory`: Generic factory for creating custom metaclasses
395
400
 
396
401
  `dynadoc.py`
@@ -23,6 +23,7 @@
23
23
  # ruff: noqa: F401
24
24
 
25
25
 
26
+ import abc
26
27
  import collections.abc as cabc
27
28
  import dataclasses as dcls
28
29
  import functools as funct
@@ -62,7 +62,7 @@ from .factories import *
62
62
 
63
63
 
64
64
  __version__: __.typx.Annotated[ str, __.ddoc.Visibilities.Reveal ]
65
- __version__ = '1.11'
65
+ __version__ = '1.12'
66
66
 
67
67
 
68
68
  standard.finalize_module(
@@ -89,8 +89,14 @@ def produce_class_construction_decorator(
89
89
  decorators: _nomina.Decorators[ __.T ] = ( ),
90
90
  **arguments: __.typx.Any,
91
91
  ) -> type[ object ]:
92
- superf = super( clscls, clscls_ ).__new__
93
- # TODO? Short-circuit if not at start of MRO.
92
+ superf = __.typx.cast(
93
+ _nomina.ClassConstructorLigation,
94
+ super( clscls, clscls_ ).__new__ )
95
+ if clscls is not clscls_:
96
+ # Parent metaclass within a hierarchy: delegate without
97
+ # duplicating preprocessing or decoration.
98
+ return superf(
99
+ clscls_, name, bases, namespace, **arguments )
94
100
  return constructor(
95
101
  clscls_, superf,
96
102
  name, bases, namespace, arguments, decorators )
@@ -107,7 +113,11 @@ def produce_class_construction_decorator(
107
113
  decorators: _nomina.Decorators[ __.T ] = ( ),
108
114
  **arguments: __.typx.Any,
109
115
  ) -> type[ object ]:
110
- # TODO? Short-circuit if not at start of MRO.
116
+ if clscls is not clscls_:
117
+ # Parent metaclass within a hierarchy: delegate without
118
+ # duplicating preprocessing or decoration.
119
+ return original(
120
+ clscls_, name, bases, namespace, **arguments )
111
121
  return constructor(
112
122
  clscls_, original,
113
123
  name, bases, namespace, arguments, decorators )
@@ -138,7 +148,11 @@ def produce_class_initialization_decorator(
138
148
  cls: type, *posargs: __.typx.Any, **nomargs: __.typx.Any
139
149
  ) -> None:
140
150
  ligation = super( clscls, cls ).__init__
141
- # TODO? Short-circuit if not at start of MRO.
151
+ if clscls is not type( cls ):
152
+ # Parent metaclass within a hierarchy: delegate without
153
+ # duplicating completion logic.
154
+ ligation( *posargs, **nomargs )
155
+ return
142
156
  initializer( cls, ligation, posargs, nomargs )
143
157
 
144
158
  clscls.__init__ = initialize_with_super
@@ -149,8 +163,12 @@ def produce_class_initialization_decorator(
149
163
  def initialize_with_original(
150
164
  cls: type, *posargs: __.typx.Any, **nomargs: __.typx.Any
151
165
  ) -> None:
166
+ if clscls is not type( cls ):
167
+ # Parent metaclass within a hierarchy: delegate without
168
+ # duplicating completion logic.
169
+ original( cls, *posargs, **nomargs )
170
+ return
152
171
  ligation = __.funct.partial( original, cls )
153
- # TODO? Short-circuit if not at start of MRO.
154
172
  initializer( cls, ligation, posargs, nomargs )
155
173
 
156
174
  clscls.__init__ = initialize_with_original
@@ -34,7 +34,7 @@ def produce_class_constructor(
34
34
  ) -> _nomina.ClassConstructor[ __.T ]:
35
35
  ''' Produces constructors for classes. '''
36
36
 
37
- def construct( # noqa: PLR0913
37
+ def construct( # noqa: PLR0913, PLR0917
38
38
  clscls: type[ __.T ],
39
39
  superf: _nomina.ClassConstructorLigation,
40
40
  name: str,
@@ -214,29 +214,52 @@ ClassInitializationCompleters: __.typx.TypeAlias = __.typx.Annotated[
214
214
  ClassConstructor: __.typx.TypeAlias = __.typx.Annotated[
215
215
  __.cabc.Callable[
216
216
  [
217
- type,
218
- ClassConstructorLigation,
219
- str,
220
- tuple[ type, ... ],
221
- dict[ str, __.typx.Any ],
222
- __.NominativeArguments,
223
- Decorators[ __.U ],
217
+ type, # metaclass
218
+ ClassConstructorLigation, # next constructor in metaclass MRO
219
+ str, # class name
220
+ tuple[ type, ... ], # bases
221
+ dict[ str, __.typx.Any ], # namespace
222
+ __.NominativeArguments, # metaclass arguments
223
+ Decorators[ __.U ], # decorators to apply after
224
224
  ],
225
225
  type
226
226
  ],
227
- __.ddoc.Doc( ''' Constructor to use with metaclass. ''' ),
227
+ __.ddoc.Doc(
228
+ ''' Constructs class, then applies hooks.
229
+
230
+ Receives the metaclass ('clscls'), the next constructor in
231
+ the metaclass MRO ('superf'), the class name, bases,
232
+ namespace, metaclass arguments, and decorators to apply
233
+ after construction. Calls 'superf' to create the class,
234
+ then applies postprocessors and decorators.
235
+
236
+ Invoked only at the most-derived metaclass of a class.
237
+ Class factory wrappers on parent metaclasses delegate
238
+ directly, so construction hooks execute exactly once per
239
+ class.
240
+ ''' ),
228
241
  ]
229
242
  ClassInitializer: __.typx.TypeAlias = __.typx.Annotated[
230
243
  __.cabc.Callable[
231
244
  [
232
- type,
233
- InitializerLigation,
234
- __.PositionalArguments,
235
- __.NominativeArguments,
245
+ type, # class
246
+ InitializerLigation, # next initializer in metaclass MRO
247
+ __.PositionalArguments, # positional arguments
248
+ __.NominativeArguments, # nominative arguments
236
249
  ],
237
250
  None
238
251
  ],
239
- __.ddoc.Doc( ''' Initializer to use with metaclass. ''' ),
252
+ __.ddoc.Doc(
253
+ ''' Initializes class, then applies hooks.
254
+
255
+ Receives the class ('cls'), the next initializer in the
256
+ metaclass MRO ('superf'), positional arguments, and
257
+ nominative arguments. Calls 'superf' first, then applies
258
+ completers, which finalize class behaviors (e.g., enable
259
+ immutability once all other initialization has occurred).
260
+
261
+ Invoked only at the most-derived metaclass of a class.
262
+ ''' ),
240
263
  ]
241
264
 
242
265
 
@@ -40,17 +40,27 @@ class Point2d(DataclassObject):
40
40
  self.hypotenuse = sqrt(x*x + y*y)
41
41
  ```
42
42
 
43
- ### `ProtocolObject`
43
+ ### `Protocol`
44
+
45
+ Protocol base class with immutability and concealment. Combines
46
+ `typing.Protocol` with standard behaviors. Immutability and concealment
47
+ are inherited by subclassed implementations. Dataclass protocol bases
48
+ (`DataclassProtocol`, `DataclassProtocolMutable`) additionally provide
49
+ automatic dataclass conversion.
44
50
 
45
- Protocol class with immutability and concealment. Combines `typing.Protocol`
46
- with standard behaviors. Immutability and concealment are inherited by
47
- subclassed implementations.
51
+ ### `AbstractObject`
52
+
53
+ Abstract base class with immutability and concealment. Combines standard
54
+ behaviors with `abc.ABCMeta` machinery: abstract method enforcement and
55
+ virtual subclass registration. Mixes with external ABC-based classes
56
+ without metaclass conflicts.
48
57
 
49
58
  ### Mutable Variants
50
59
 
51
60
  - `ObjectMutable`: Object with concealment only (no immutability)
52
61
  - `DataclassObjectMutable`: DataclassObject with concealment only
53
- - `ProtocolObjectMutable`: ProtocolObject with concealment only
62
+ - `ProtocolMutable`: Protocol with concealment only
63
+ - `DataclassProtocolMutable`: DataclassProtocol with concealment only
54
64
 
55
65
  ## Metaclasses
56
66
 
@@ -64,27 +74,35 @@ any class that uses this metaclass.
64
74
  Metaclass that automatically applies `dataclasses.dataclass` during class
65
75
  construction. Configurable for `frozen`, `slots`, etc.
66
76
 
67
- ### `Protocol`
77
+ ### `AbstractClass`
78
+
79
+ Metaclass combining `Class` with `abc.ABCMeta`. Supports abstract method
80
+ enforcement and virtual subclass registration for classes that must mix
81
+ with external ABC-based classes.
82
+
83
+ ### `ProtocolClass`
68
84
 
69
85
  Metaclass for protocol classes. Combines `typing.Protocol` with standard
70
- behaviors.
86
+ behaviors. Descends from `AbstractClass`, unifying the standard metaclass
87
+ taxonomy under `Class`. Dataclass protocol metaclasses
88
+ (`ProtocolDataclass`, `ProtocolDataclassMutable`) additionally apply
89
+ dataclass conversion.
71
90
 
72
91
  ### Mutable Variants
73
92
 
74
- - `ClassMutable`: Metaclass with concealment only
75
- - `DataclassMutable`: Dataclass metaclass with concealment only
76
- - `ProtocolMutable`: Protocol metaclass with concealment only
93
+ - `DataclassMutable`: Dataclass metaclass whose instances have mutable
94
+ attributes (concealment only)
77
95
 
78
96
  ## Decorators
79
97
 
80
- ### `class_with_standard_behaviors`
98
+ ### `with_standard_behaviors`
81
99
 
82
100
  Apply standard behaviors to an existing class.
83
101
 
84
102
  ```python
85
- from classcore.standard import class_with_standard_behaviors
103
+ from classcore.standard import with_standard_behaviors
86
104
 
87
- @class_with_standard_behaviors()
105
+ @with_standard_behaviors()
88
106
  class ExistingClass:
89
107
  x: int = 10
90
108
  ```
@@ -102,10 +120,6 @@ class Point:
102
120
  y: float
103
121
  ```
104
122
 
105
- ### `protocol_with_standard_behaviors`
106
-
107
- Create a protocol with standard behaviors applied.
108
-
109
123
  ### `class_factory`
110
124
 
111
125
  Generic factory for creating custom metaclasses with standard behaviors.
@@ -224,7 +224,7 @@ def produce_class_construction_preprocessor(
224
224
  ) -> _nomina.ClassConstructionPreprocessor[ __.U ]:
225
225
  ''' Produces construction processor which handles metaclass arguments. '''
226
226
 
227
- def preprocess( # noqa: PLR0913
227
+ def preprocess( # noqa: PLR0913, PLR0917
228
228
  clscls: type,
229
229
  name: str,
230
230
  bases: list[ type ],
@@ -32,7 +32,56 @@ _abc_class_mutables = (
32
32
  '_abc_negative_cache',
33
33
  '_abc_negative_cache_version',
34
34
  '_abc_registry',
35
+ '_is_runtime_protocol',
36
+ '__non_callable_proto_members__',
35
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
+
61
+ def _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
+
36
85
  _dynadoc_configuration = (
37
86
  _dynadoc.produce_dynadoc_configuration( table = __.fragments ) )
38
87
  _class_factory = __.funct.partial(
@@ -72,9 +121,35 @@ class Class( type ):
72
121
  return super( ).__new__( clscls, name, bases, namespace )
73
122
 
74
123
 
124
+ @_class_factory( )
125
+ class 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
+
75
150
  @_class_factory( )
76
151
  @__.typx.dataclass_transform( frozen_default = True, kw_only_default = True )
77
- class Dataclass( type ):
152
+ class Dataclass( Class ):
78
153
  ''' Metaclass for standard dataclasses. '''
79
154
 
80
155
  _dynadoc_fragments_ = (
@@ -95,7 +170,7 @@ class Dataclass( type ):
95
170
 
96
171
  @_class_factory( )
97
172
  @__.typx.dataclass_transform( kw_only_default = True )
98
- class DataclassMutable( type ):
173
+ class DataclassMutable( Dataclass ):
99
174
  ''' Metaclass for dataclasses with mutable instance attributes. '''
100
175
 
101
176
  _dynadoc_fragments_ = (
@@ -115,7 +190,7 @@ class DataclassMutable( type ):
115
190
 
116
191
 
117
192
  @_class_factory( )
118
- class ProtocolClass( type( __.typx.Protocol ) ):
193
+ class ProtocolClass( AbstractClass, type( __.typx.Protocol ) ):
119
194
  ''' Metaclass for standard protocol classes. '''
120
195
 
121
196
  _dynadoc_fragments_ = (
@@ -131,12 +206,55 @@ class ProtocolClass( type( __.typx.Protocol ) ):
131
206
  decorators: _nomina.Decorators[ __.T ] = ( ),
132
207
  **arguments: __.typx.Unpack[ ClassFactoryExtraArguments ],
133
208
  ) -> __.T:
134
- return super( ).__new__( clscls, name, bases, namespace )
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 )
135
253
 
136
254
 
137
255
  @_class_factory( )
138
256
  @__.typx.dataclass_transform( frozen_default = True, kw_only_default = True )
139
- class ProtocolDataclass( type( __.typx.Protocol ) ):
257
+ class ProtocolDataclass( ProtocolClass ):
140
258
  ''' Metaclass for standard protocol dataclasses. '''
141
259
 
142
260
  _dynadoc_fragments_ = (
@@ -157,7 +275,7 @@ class ProtocolDataclass( type( __.typx.Protocol ) ):
157
275
 
158
276
  @_class_factory( )
159
277
  @__.typx.dataclass_transform( kw_only_default = True )
160
- class ProtocolDataclassMutable( type( __.typx.Protocol ) ):
278
+ class ProtocolDataclassMutable( ProtocolDataclass ):
161
279
  ''' Metaclass for protocol dataclasses with mutable instance attributes.
162
280
  '''
163
281
 
@@ -193,6 +311,22 @@ class ObjectMutable( metaclass = Class, instances_mutables = '*' ):
193
311
  'class instance conceal' )
194
312
 
195
313
 
314
+ class 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
+
196
330
  class DataclassObject( metaclass = Dataclass ):
197
331
  ''' Standard base dataclass. '''
198
332
 
@@ -262,3 +396,41 @@ class DataclassProtocolMutable(
262
396
  'dataclass', 'protocol class',
263
397
  'class concealment', 'class protection', 'class dynadoc',
264
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
+
409
+ class _CanaryAbstractObject( AbstractObject ):
410
+ ''' Canary for abstract base classes. '''
411
+
412
+ def provide( self ) -> int:
413
+ return 42
414
+
415
+
416
+ class _CanaryProtocolUse( Protocol ):
417
+ ''' Canary for protocol definitions. '''
418
+
419
+ value: int
420
+
421
+ def greet( self ) -> str: ...
422
+
423
+
424
+ class _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( )
@@ -175,7 +175,7 @@ def apply_cfc_attributes_surveyor(
175
175
  decorator( clscls )
176
176
 
177
177
 
178
- def class_factory( # noqa: PLR0913
178
+ def class_factory( # noqa: PLR0913, PLR0917
179
179
  attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
180
180
  error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
181
181
  assigner_core: __.typx.Optional[ _nomina.AssignerCore ] = None,
@@ -220,7 +220,7 @@ def class_factory( # noqa: PLR0913
220
220
  return decorate
221
221
 
222
222
 
223
- def produce_instances_inception_decorator( # noqa: PLR0913
223
+ def produce_instances_inception_decorator( # noqa: PLR0913, PLR0917
224
224
  attributes_namer: _nomina.AttributesNamer,
225
225
  assigner_core: __.typx.Optional[ _nomina.AssignerCore ],
226
226
  deleter_core: __.typx.Optional[ _nomina.DeleterCore ],
@@ -532,7 +532,7 @@ def produce_attributes_surveillance_decorator(
532
532
 
533
533
 
534
534
  @__.typx.dataclass_transform( frozen_default = True, kw_only_default = True )
535
- def dataclass_with_standard_behaviors( # noqa: PLR0913
535
+ def dataclass_with_standard_behaviors( # noqa: PLR0913, PLR0917
536
536
  attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
537
537
  error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
538
538
  decorators: _nomina.Decorators[ __.U ] = ( ),
@@ -564,7 +564,7 @@ def dataclass_with_standard_behaviors( # noqa: PLR0913
564
564
  *decorators, _dataclass_core, *decorators_, preparers = preparers )
565
565
 
566
566
 
567
- def with_standard_behaviors( # noqa: PLR0913
567
+ def with_standard_behaviors( # noqa: PLR0913, PLR0917
568
568
  attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
569
569
  error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
570
570
  decorators: _nomina.Decorators[ __.U ] = ( ),
@@ -621,7 +621,7 @@ def _produce_instances_decoration_preparers(
621
621
  return tuple( preprocessors )
622
622
 
623
623
 
624
- def _produce_instances_decorators( # noqa: PLR0913
624
+ def _produce_instances_decorators( # noqa: PLR0913, PLR0917
625
625
  attributes_namer: _nomina.AttributesNamer,
626
626
  error_class_provider: _nomina.ErrorClassProvider,
627
627
  assigner_core: __.typx.Optional[ _nomina.AssignerCore ],
File without changes
File without changes
File without changes