classcore 1.11__tar.gz → 1.13__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 (29) hide show
  1. {classcore-1.11 → classcore-1.13}/PKG-INFO +4 -6
  2. {classcore-1.11 → classcore-1.13}/pyproject.toml +13 -8
  3. {classcore-1.11 → classcore-1.13}/sources/classcore/README.md +27 -7
  4. {classcore-1.11 → classcore-1.13}/sources/classcore/__/imports.py +1 -0
  5. classcore-1.13/sources/classcore/__/nomina.py +95 -0
  6. {classcore-1.11 → classcore-1.13}/sources/classcore/__init__.py +1 -1
  7. {classcore-1.11 → classcore-1.13}/sources/classcore/decorators.py +23 -5
  8. {classcore-1.11 → classcore-1.13}/sources/classcore/factories.py +15 -6
  9. {classcore-1.11 → classcore-1.13}/sources/classcore/nomina.py +36 -13
  10. {classcore-1.11 → classcore-1.13}/sources/classcore/standard/README.md +31 -17
  11. classcore-1.13/sources/classcore/standard/__.py +106 -0
  12. {classcore-1.11 → classcore-1.13}/sources/classcore/standard/__init__.py +1 -0
  13. {classcore-1.11 → classcore-1.13}/sources/classcore/standard/behaviors.py +159 -65
  14. {classcore-1.11 → classcore-1.13}/sources/classcore/standard/classes.py +202 -16
  15. {classcore-1.11 → classcore-1.13}/sources/classcore/standard/decorators.py +37 -7
  16. classcore-1.13/sources/classcore/standard/explanations.py +253 -0
  17. {classcore-1.11 → classcore-1.13}/sources/classcore/standard/nomina.py +4 -0
  18. {classcore-1.11 → classcore-1.13}/sources/classcore/utilities.py +28 -3
  19. classcore-1.11/sources/classcore/__/nomina.py +0 -36
  20. classcore-1.11/sources/classcore/standard/__.py +0 -42
  21. {classcore-1.11 → classcore-1.13}/.gitignore +0 -0
  22. {classcore-1.11 → classcore-1.13}/LICENSE.txt +0 -0
  23. {classcore-1.11 → classcore-1.13}/README.rst +0 -0
  24. {classcore-1.11 → classcore-1.13}/sources/classcore/__/__init__.py +0 -0
  25. {classcore-1.11 → classcore-1.13}/sources/classcore/__/doctab.py +0 -0
  26. {classcore-1.11 → classcore-1.13}/sources/classcore/exceptions.py +0 -0
  27. {classcore-1.11 → classcore-1.13}/sources/classcore/py.typed +0 -0
  28. {classcore-1.11 → classcore-1.13}/sources/classcore/standard/dynadoc.py +0 -0
  29. {classcore-1.11 → classcore-1.13}/sources/classcore/standard/modules.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.13
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
@@ -15,15 +15,13 @@ Classifier: Development Status :: 5 - Production/Stable
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: License :: OSI Approved :: Apache Software License
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3.11
20
18
  Classifier: Programming Language :: Python :: 3.12
21
19
  Classifier: Programming Language :: Python :: 3.13
22
20
  Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: Python :: 3.15
23
22
  Classifier: Programming Language :: Python :: Implementation :: CPython
24
- Classifier: Programming Language :: Python :: Implementation :: PyPy
25
23
  Classifier: Topic :: Software Development
26
- Requires-Python: >=3.10
24
+ Requires-Python: >=3.12
27
25
  Requires-Dist: dynadoc~=1.4
28
26
  Requires-Dist: typing-extensions
29
27
  Description-Content-Type: text/x-rst
@@ -13,7 +13,7 @@ description = 'Foundational class factories and decorators.'
13
13
  dynamic = [ 'version' ]
14
14
  license = 'Apache-2.0'
15
15
  readme = { 'file' = 'README.rst', 'content-type' = 'text/x-rst' }
16
- requires-python = '>= 3.10'
16
+ requires-python = '>= 3.12'
17
17
  dependencies = [
18
18
  'dynadoc ~= 1.4',
19
19
  'typing-extensions',
@@ -26,13 +26,11 @@ classifiers = [ # https://pypi.org/classifiers
26
26
  'License :: OSI Approved :: Apache Software License',
27
27
  'Programming Language :: Python :: 3 :: Only',
28
28
  # --- BEGIN: Injected by Copier ---
29
- 'Programming Language :: Python :: 3.10',
30
- 'Programming Language :: Python :: 3.11',
31
29
  'Programming Language :: Python :: 3.12',
32
30
  'Programming Language :: Python :: 3.13',
33
31
  'Programming Language :: Python :: 3.14',
32
+ 'Programming Language :: Python :: 3.15',
34
33
  'Programming Language :: Python :: Implementation :: CPython',
35
- 'Programming Language :: Python :: Implementation :: PyPy',
36
34
  # --- END: Injected by Copier ---
37
35
  'Topic :: Software Development',
38
36
  ]
@@ -91,7 +89,7 @@ strict-naming = false
91
89
  # --- BEGIN: Injected by Copier ---
92
90
  # --- END: Injected by Copier ---
93
91
  [tool.hatch.envs.default]
94
- python = '3.10'
92
+ python = '3.12'
95
93
  [tool.hatch.envs.develop]
96
94
  description = ''' Development environment. '''
97
95
  builder = true
@@ -127,6 +125,12 @@ docsgen = [
127
125
  """sphinx-build -a -d .auxiliary/caches/sphinx --quiet \
128
126
  documentation .auxiliary/artifacts/sphinx-html""",
129
127
  ]
128
+ fragments = [
129
+ """python .auxiliary/scripts/validate-fragments.py""",
130
+ ]
131
+ module-sizes = [
132
+ """python .auxiliary/scripts/validate-module-sizes.py""",
133
+ ]
130
134
  linters = [
131
135
  """ruff check --quiet sources documentation tests""",
132
136
  # """vibelinter check""",
@@ -134,6 +138,8 @@ linters = [
134
138
  # --- END: Injected by Copier ---
135
139
  """isort --check-only --diff sources tests""",
136
140
  """pyright sources""",
141
+ 'fragments',
142
+ 'module-sizes',
137
143
  ]
138
144
  packagers = [
139
145
  """hatch build""",
@@ -170,12 +176,10 @@ description = ''' Quality assurance environment. '''
170
176
  template = 'develop'
171
177
  [[tool.hatch.envs.qa.matrix]]
172
178
  python = [
173
- '3.10',
174
- '3.11',
175
179
  '3.12',
176
180
  '3.13',
177
181
  '3.14',
178
- 'pypy3.11',
182
+ '3.15',
179
183
  ]
180
184
  # [tool.hatch.metadata]
181
185
  # allow-direct-references = true
@@ -255,6 +259,7 @@ cache_dir = '.auxiliary/caches/pytest'
255
259
  markers = [
256
260
  "slow: long-running tests",
257
261
  ]
262
+ asyncio_default_fixture_loop_scope = "function"
258
263
 
259
264
  [tool.ruff]
260
265
  #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
@@ -296,6 +297,7 @@ sources/
296
297
  ├── classes.py # Standard metaclasses and base classes
297
298
  ├── decorators.py # Standard class decorators
298
299
  ├── dynadoc.py # Documentation configuration
300
+ ├── explanations.py # Attribute behavior explanations
299
301
  ├── modules.py # Module reclassification utilities
300
302
  └── nomina.py # Standard-specific naming types
301
303
  ```
@@ -377,20 +379,38 @@ sources/
377
379
  - `survey_visible_attributes`: Filter attributes for `dir()`
378
380
  - Verifier processing for names, regexes, and predicates
379
381
 
382
+ `explanations.py`
383
+ : Explanations of attribute behavior decisions:
384
+
385
+ - `explain_attribute`: Decision trace for one attribute of one target
386
+ - `AttributeExplanation` and the `Verdict` records (`AssignVerdict`,
387
+ `DeleteVerdict`, `SurveyVerdict`) with derived permissibility
388
+ - The closed `Decision` hierarchy: `PermitByInapplicability`,
389
+ `PermitByOmni`, `PermitByNames`, `PermitByPredicate`,
390
+ `PermitByRegex`, `Prohibit`, with typed payloads
391
+ - Internal marking via metaclass-carried contributions: the namer
392
+ detector for framework names, the abstract-base wiring for stdlib
393
+ ABC machinery (only where that machinery operates), and downstream
394
+ factory namers' detectors for downstream names
395
+
380
396
  `classes.py`
381
397
  : Standard metaclasses and base classes:
382
398
 
383
399
  - `Class` / `Object`: Standard class with full behaviors
400
+ - `AbstractClass` / `AbstractObject`: Abstract class with ABC machinery
401
+ (abstract method enforcement, virtual subclass registration); mixes
402
+ with external `abc.ABCMeta`-based classes
384
403
  - `Dataclass` / `DataclassObject`: Auto-dataclass with behaviors
385
- - `Protocol` / `ProtocolObject`: Protocol with behaviors
404
+ - `ProtocolClass` / `Protocol`, `ProtocolMutable`,
405
+ `DataclassProtocol`, `DataclassProtocolMutable`: Protocol classes
406
+ with behaviors
386
407
  - Mutable variants for each
387
408
 
388
409
  `decorators.py`
389
410
  : Standard class decorators:
390
411
 
391
- - `class_with_standard_behaviors`: Apply behaviors to existing class
412
+ - `with_standard_behaviors`: Apply behaviors to existing class
392
413
  - `dataclass_with_standard_behaviors`: Create dataclass with behaviors
393
- - `protocol_with_standard_behaviors`: Create protocol with behaviors
394
414
  - `class_factory`: Generic factory for creating custom metaclasses
395
415
 
396
416
  `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
@@ -0,0 +1,95 @@
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
+ ''' Common names and type aliases. '''
22
+
23
+
24
+ from . import imports as __
25
+
26
+
27
+ ComparisonResult: __.typx.TypeAlias = bool | __.types.NotImplementedType
28
+ NominativeArguments: __.typx.TypeAlias = __.cabc.Mapping[ str, __.typx.Any ]
29
+ PositionalArguments: __.typx.TypeAlias = __.cabc.Sequence[ __.typx.Any ]
30
+
31
+
32
+ package_name = __name__.split( '.', maxsplit = 1 )[ 0 ]
33
+
34
+
35
+ _levels = frozenset( ( 'class', 'classes', 'instance', 'instances' ) )
36
+ _cores = frozenset( (
37
+ 'behaviors',
38
+ 'construction_arguments',
39
+ 'dynadoc_configuration',
40
+ 'in_progress',
41
+ 'mutables_names', 'mutables_predicates', 'mutables_regexes',
42
+ 'visibles_names', 'visibles_predicates', 'visibles_regexes',
43
+ 'assigner_core', 'deleter_core', 'surveyor_core' ) )
44
+ _hex_digits = frozenset( '0123456789abcdef' )
45
+ _digest_length = 64
46
+ _mangled_cores = frozenset( ( 'class_behaviors', 'class_in_progress' ) )
47
+
48
+
49
+ class AttrnameCalculator:
50
+ ''' Calculates framework attribute names from level and core.
51
+
52
+ Reference implementation of the attribute namer convention:
53
+ call instances to produce names, and consult ``is_internal_name``
54
+ to detect names this framework generated. Downstream packages
55
+ may subclass (or otherwise provide callables exposing the same
56
+ optional detector) to contribute their own internal names.
57
+ '''
58
+
59
+ def is_internal_name( self, name: str ) -> bool:
60
+ prefix = f"_{package_name}_"
61
+ if not name.startswith( prefix ):
62
+ return False
63
+ body = name[ len( prefix ): ]
64
+ # Static stems: _{package}_{level}_{core}_, trailing underscore.
65
+ # The class-level behaviors and in_progress stems are stored
66
+ # mangled only, so they are excluded from static acceptance.
67
+ if body.endswith( '_' ):
68
+ level, _, core = body[ :-1 ].partition( '_' )
69
+ if level in _levels and core in _cores:
70
+ return not (
71
+ 'class' == level
72
+ and f"{level}_{core}" in _mangled_cores )
73
+ return False
74
+ # Mangled forms: one of the mangled cores + 64 lowercase hex,
75
+ # no trailing underscore. The mangling accessors append digests
76
+ # to these stems only.
77
+ for mangled_core in _mangled_cores:
78
+ mangled_prefix = f"{mangled_core}_"
79
+ if body.startswith( mangled_prefix ):
80
+ suffix = body[ len( mangled_prefix ): ]
81
+ return (
82
+ _digest_length == len( suffix )
83
+ and all( c in _hex_digits for c in suffix ) )
84
+ return False
85
+
86
+ def __call__( self, level: str, core: str ) -> str:
87
+ return f"_{package_name}_{level}_{core}_"
88
+
89
+
90
+ calculate_attrname = AttrnameCalculator( )
91
+
92
+
93
+ def calculate_contribution_name( ) -> str:
94
+ ''' Returns metaclass attribute name holding internal-name detectors. '''
95
+ return f"_{package_name}_internal_names_"
@@ -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.13'
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,
@@ -54,12 +54,20 @@ def produce_class_constructor(
54
54
  # Some decorators create new classes, which invokes this method again.
55
55
  # Short-circuit to prevent recursive decoration and other tangles.
56
56
  progress_name = attributes_namer( 'class', 'in_progress' )
57
- progress_name_m = _utilities.mangle_name( cls, progress_name )
58
- in_progress = getattr( cls, progress_name_m, False )
57
+ progress_names = _utilities.survey_mangled_names( cls, progress_name )
58
+ in_progress = any(
59
+ getattr( cls, key, False ) for key in progress_names )
59
60
  if in_progress: return cls
61
+ progress_name_m = _utilities.mangle_name( cls, progress_name )
60
62
  setattr( cls, progress_name_m, True )
61
63
  for postprocessor in postprocessors: postprocessor( cls, decorators_ )
62
64
  cls = _decorators.apply_decorators( cls, decorators_ )
65
+ # Decoration may have replaced the class. Clear every marker
66
+ # variant, including ones inherited from the original namespace,
67
+ # so initialization completes regardless of qualname restoration.
68
+ progress_names = _utilities.survey_mangled_names( cls, progress_name )
69
+ for progress_name_ in progress_names:
70
+ setattr( cls, progress_name_, False )
63
71
  setattr( cls, progress_name_m, False )
64
72
  return cls
65
73
 
@@ -81,10 +89,11 @@ def produce_class_initializer(
81
89
  ''' Initializes class, applying hooks. '''
82
90
  superf( *posargs, **nomargs )
83
91
  progress_name = attributes_namer( 'class', 'in_progress' )
84
- progress_name_m = _utilities.mangle_name( cls, progress_name )
85
- in_progress = getattr( cls, progress_name_m, False )
92
+ progress_names = _utilities.survey_mangled_names( cls, progress_name )
93
+ in_progress = any(
94
+ getattr( cls, key, False ) for key in progress_names )
86
95
  if in_progress: return # If non-empty, then not top-level.
87
- delattr( cls, progress_name_m )
96
+ for progress_name_m in progress_names: delattr( cls, progress_name_m )
88
97
  for completer in completers: completer( cls )
89
98
 
90
99
  return initialize
@@ -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.
@@ -0,0 +1,106 @@
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
+ ''' Common constants, imports, and utilities. '''
22
+
23
+ # ruff: noqa: F403, F405
24
+
25
+
26
+ from ..__ import *
27
+ from ..nomina import is_public_identifier
28
+
29
+
30
+ def provide_error_class( name: str ) -> type[ Exception ]:
31
+ ''' Produces error class for this package. '''
32
+ match name:
33
+ case 'AttributeImmutability':
34
+ from ..exceptions import AttributeImmutability as error
35
+ case _:
36
+ from ..exceptions import ErrorProvideFailure
37
+ raise ErrorProvideFailure( name, reason = 'Does not exist.' )
38
+ return error
39
+
40
+
41
+ mutables_default = ( )
42
+ visibles_default = ( is_public_identifier, )
43
+
44
+ # Standard library ABC machinery attribute names which the class-level
45
+ # immutability behavior permits. The C _abc implementation writes these
46
+ # cache attributes through direct dict access, so they must remain
47
+ # assignable on standard classes. Also used to mark such names as
48
+ # internal in attribute explanations.
49
+ abc_class_mutables = (
50
+ '_abc_cache',
51
+ '_abc_negative_cache',
52
+ '_abc_negative_cache_version',
53
+ '_abc_registry',
54
+ )
55
+ # Standard library typing machinery attribute names which the
56
+ # class-level immutability behavior permits on protocol classes.
57
+ # 'typing' sets these protocol bookkeeping attributes during class
58
+ # decoration: '_is_runtime_protocol' and '__non_callable_proto_members__'
59
+ # in the 'runtime_checkable' decorator, and
60
+ # '__typing_is_deprecated_inherited_runtime_protocol__' (CPython 3.15+)
61
+ # during class decoration and in 'runtime_checkable'. Without this
62
+ # exemption, decorating a runtime protocol which inherits runtime
63
+ # checkability fails. Also used to mark such names as internal in
64
+ # attribute explanations.
65
+ typing_class_mutables = (
66
+ '_is_runtime_protocol',
67
+ '__non_callable_proto_members__',
68
+ '__typing_is_deprecated_inherited_runtime_protocol__', # Python 3.15+
69
+ )
70
+
71
+
72
+ def is_same_detector(
73
+ detector: cabc.Callable[ [ str ], bool ],
74
+ other: cabc.Callable[ [ str ], bool ], /,
75
+ ) -> bool:
76
+ ''' Returns whether two detector references are the same detector.
77
+
78
+ Bound methods are the same detector only when both the
79
+ underlying function and the bound instance are identical, so
80
+ distinct namer instances with distinct configured behavior keep
81
+ separate contributions. Plain callables compare by identity.
82
+ '''
83
+ if detector is other: return True
84
+ function = getattr( detector, '__func__', None )
85
+ other_function = getattr( other, '__func__', None )
86
+ if function is None or other_function is None: return False
87
+ return (
88
+ function is other_function
89
+ and getattr( detector, '__self__', None )
90
+ is getattr( other, '__self__', None ) )
91
+
92
+
93
+ def augment_internal_names(
94
+ cls: type, /, detector: cabc.Callable[ [ str ], bool ]
95
+ ) -> None:
96
+ ''' Adds an internal-name detector to existing contributions.
97
+
98
+ Private wiring point for machinery mixins: the abstract-base
99
+ metaclass adds the stdlib machinery set after factory
100
+ decoration, without expanding public factory arguments.
101
+ '''
102
+ detectors = getattr( cls, calculate_contribution_name( ), ( ) )
103
+ if any( is_same_detector( detector, d ) for d in detectors ): return
104
+ setattr(
105
+ cls, calculate_contribution_name( ),
106
+ ( *detectors, detector ) )
@@ -49,4 +49,5 @@ from . import nomina
49
49
 
50
50
  from .classes import *
51
51
  from .decorators import *
52
+ from .explanations import *
52
53
  from .modules import *