lionagi 0.13.2__py3-none-any.whl → 0.13.4__py3-none-any.whl
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.
- lionagi/config.py +1 -1
- lionagi/fields/action.py +0 -1
- lionagi/fields/reason.py +0 -1
- lionagi/libs/file/save.py +1 -1
- lionagi/libs/schema/as_readable.py +142 -196
- lionagi/libs/schema/extract_docstring.py +1 -2
- lionagi/libs/token_transform/synthlang_/base.py +0 -2
- lionagi/libs/validate/string_similarity.py +1 -2
- lionagi/models/hashable_model.py +0 -1
- lionagi/models/schema_model.py +0 -1
- lionagi/operations/ReAct/utils.py +0 -1
- lionagi/operations/__init__.py +2 -0
- lionagi/operations/_act/act.py +0 -1
- lionagi/operations/flow.py +436 -0
- lionagi/operations/interpret/interpret.py +1 -4
- lionagi/operations/manager.py +0 -1
- lionagi/operations/node.py +107 -0
- lionagi/operations/plan/plan.py +0 -1
- lionagi/operations/select/utils.py +0 -2
- lionagi/protocols/forms/flow.py +3 -1
- lionagi/protocols/generic/pile.py +1 -2
- lionagi/protocols/generic/processor.py +0 -1
- lionagi/protocols/graph/__init__.py +6 -0
- lionagi/protocols/graph/graph.py +1 -3
- lionagi/protocols/mail/package.py +0 -1
- lionagi/protocols/messages/assistant_response.py +0 -2
- lionagi/protocols/messages/message.py +0 -1
- lionagi/service/connections/endpoint_config.py +6 -0
- lionagi/service/connections/match_endpoint.py +26 -8
- lionagi/service/connections/providers/claude_code_.py +8 -9
- lionagi/service/connections/providers/claude_code_cli.py +411 -0
- lionagi/service/connections/providers/oai_.py +1 -1
- lionagi/service/manager.py +0 -1
- lionagi/service/rate_limited_processor.py +0 -2
- lionagi/service/token_calculator.py +0 -3
- lionagi/session/__init__.py +5 -0
- lionagi/session/branch.py +0 -2
- lionagi/session/session.py +47 -1
- lionagi/settings.py +2 -3
- lionagi/utils.py +6 -9
- lionagi/version.py +1 -1
- {lionagi-0.13.2.dist-info → lionagi-0.13.4.dist-info}/METADATA +15 -10
- {lionagi-0.13.2.dist-info → lionagi-0.13.4.dist-info}/RECORD +45 -47
- lionagi/traits/__init__.py +0 -58
- lionagi/traits/base.py +0 -216
- lionagi/traits/composer.py +0 -343
- lionagi/traits/protocols.py +0 -495
- lionagi/traits/registry.py +0 -1071
- {lionagi-0.13.2.dist-info → lionagi-0.13.4.dist-info}/WHEEL +0 -0
- {lionagi-0.13.2.dist-info → lionagi-0.13.4.dist-info}/licenses/LICENSE +0 -0
lionagi/traits/registry.py
DELETED
@@ -1,1071 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
TraitRegistry - Global trait tracking and dependency resolution.
|
3
|
-
|
4
|
-
This module provides the central registry for all trait implementations
|
5
|
-
in the LionAGI system. The registry manages:
|
6
|
-
|
7
|
-
- Trait registration and lookup
|
8
|
-
- Dependency resolution and validation
|
9
|
-
- Performance monitoring and caching
|
10
|
-
- Memory-safe weak references
|
11
|
-
|
12
|
-
Performance characteristics:
|
13
|
-
- Registration: <2μs per trait (target)
|
14
|
-
- Lookup: <200ns per trait (Protocol isinstance)
|
15
|
-
- Memory: Zero leaks via weak references
|
16
|
-
- Thread-safe: All operations are thread-safe
|
17
|
-
"""
|
18
|
-
|
19
|
-
from __future__ import annotations
|
20
|
-
|
21
|
-
import threading
|
22
|
-
import time
|
23
|
-
import weakref
|
24
|
-
from collections import defaultdict
|
25
|
-
from collections.abc import Callable
|
26
|
-
from dataclasses import dataclass, field
|
27
|
-
from typing import Any, ClassVar, Literal
|
28
|
-
|
29
|
-
from .base import DEFAULT_TRAIT_DEFINITIONS, Trait, TraitDefinition
|
30
|
-
|
31
|
-
__all__ = [
|
32
|
-
"OrphanRuleViolation",
|
33
|
-
"TraitRegistry",
|
34
|
-
"ValidationResult",
|
35
|
-
"as_trait",
|
36
|
-
"implement",
|
37
|
-
"reinitialize_registry",
|
38
|
-
"seal_trait",
|
39
|
-
]
|
40
|
-
|
41
|
-
|
42
|
-
@dataclass
|
43
|
-
class ValidationResult:
|
44
|
-
"""Result of trait validation with detailed error information."""
|
45
|
-
|
46
|
-
success: bool
|
47
|
-
error_type: str | None = None
|
48
|
-
error_message: str | None = None
|
49
|
-
missing_dependencies: set[Trait] = field(default_factory=set)
|
50
|
-
missing_attributes: list[str] = field(default_factory=list)
|
51
|
-
performance_warning: str | None = None
|
52
|
-
|
53
|
-
|
54
|
-
class OrphanRuleViolation(Exception):
|
55
|
-
"""Raised when attempting to implement an external trait on an external type."""
|
56
|
-
|
57
|
-
def __init__(self, trait: Trait, implementation_type: type[Any]) -> None:
|
58
|
-
self.trait = trait
|
59
|
-
self.implementation_type = implementation_type
|
60
|
-
super().__init__(
|
61
|
-
f"Cannot implement external trait {trait.name} on external type "
|
62
|
-
f"{implementation_type.__module__}.{implementation_type.__qualname__}. "
|
63
|
-
"Either the trait or the implementation type must be defined in your codebase."
|
64
|
-
)
|
65
|
-
|
66
|
-
|
67
|
-
class TraitCycleError(Exception):
|
68
|
-
"""Raised when a circular dependency is detected in trait dependencies."""
|
69
|
-
|
70
|
-
def __init__(self, path: list[Trait]) -> None:
|
71
|
-
self.path = path
|
72
|
-
cycle_str = " -> ".join(t.name for t in path)
|
73
|
-
super().__init__(
|
74
|
-
f"Circular dependency detected in traits: {cycle_str}"
|
75
|
-
)
|
76
|
-
|
77
|
-
|
78
|
-
class SealedTraitError(Exception):
|
79
|
-
"""Raised when attempting to implement a sealed trait from an external module."""
|
80
|
-
|
81
|
-
def __init__(self, trait: Trait, implementation_type: type[Any]) -> None:
|
82
|
-
self.trait = trait
|
83
|
-
self.implementation_type = implementation_type
|
84
|
-
super().__init__(
|
85
|
-
f"Cannot implement sealed trait {trait.name} from external module "
|
86
|
-
f"{implementation_type.__module__}. Sealed traits can only be "
|
87
|
-
"implemented by types in local modules."
|
88
|
-
)
|
89
|
-
|
90
|
-
|
91
|
-
class TraitRegistry:
|
92
|
-
"""
|
93
|
-
Singleton registry for tracking trait implementations and dependencies.
|
94
|
-
|
95
|
-
This registry provides the central coordination point for the trait system,
|
96
|
-
managing registration, lookup, and dependency resolution with optimal
|
97
|
-
performance characteristics.
|
98
|
-
|
99
|
-
Research-validated design:
|
100
|
-
- Weak references prevent memory leaks
|
101
|
-
- Thread-safe operations
|
102
|
-
- <2μs registration target
|
103
|
-
- Protocol-based isinstance checks (145ns)
|
104
|
-
"""
|
105
|
-
|
106
|
-
_instance: ClassVar[TraitRegistry | None] = None
|
107
|
-
_lock: ClassVar[threading.RLock] = threading.RLock()
|
108
|
-
|
109
|
-
def __new__(cls) -> TraitRegistry:
|
110
|
-
"""Ensure singleton pattern with thread safety."""
|
111
|
-
if cls._instance is None:
|
112
|
-
with cls._lock:
|
113
|
-
if cls._instance is None:
|
114
|
-
cls._instance = super().__new__(cls)
|
115
|
-
return cls._instance
|
116
|
-
|
117
|
-
@classmethod
|
118
|
-
def reset(cls) -> None:
|
119
|
-
"""
|
120
|
-
Reset singleton instance for testing.
|
121
|
-
|
122
|
-
This method should only be used in tests to ensure clean state
|
123
|
-
between test runs.
|
124
|
-
"""
|
125
|
-
with cls._lock:
|
126
|
-
cls._instance = None
|
127
|
-
|
128
|
-
@classmethod
|
129
|
-
def reinitialize(cls) -> TraitRegistry:
|
130
|
-
"""
|
131
|
-
Reinitialize the registry for multiprocessing contexts.
|
132
|
-
|
133
|
-
This method should be called in worker processes after forking
|
134
|
-
to ensure the registry is properly initialized with fresh locks.
|
135
|
-
|
136
|
-
Returns:
|
137
|
-
Newly initialized registry instance
|
138
|
-
"""
|
139
|
-
with cls._lock:
|
140
|
-
# Force creation of new instance with fresh locks
|
141
|
-
cls._instance = None
|
142
|
-
new_instance = cls()
|
143
|
-
|
144
|
-
# Reinitialize locks to avoid fork issues
|
145
|
-
new_instance._registry_lock = threading.RLock()
|
146
|
-
|
147
|
-
return new_instance
|
148
|
-
|
149
|
-
def __init__(self) -> None:
|
150
|
-
"""Initialize registry if not already done."""
|
151
|
-
if hasattr(self, "_initialized"):
|
152
|
-
return
|
153
|
-
|
154
|
-
# Core trait tracking
|
155
|
-
self._trait_implementations: dict[type[Any], set[Trait]] = {}
|
156
|
-
self._trait_definitions: dict[Trait, TraitDefinition] = (
|
157
|
-
DEFAULT_TRAIT_DEFINITIONS.copy()
|
158
|
-
)
|
159
|
-
self._implementation_registry: dict[
|
160
|
-
tuple[Trait, type[Any]], TraitDefinition
|
161
|
-
] = {}
|
162
|
-
|
163
|
-
# Performance optimization: Cache TraitDefinition objects
|
164
|
-
self._definition_cache: dict[
|
165
|
-
tuple[Trait, type[Any]], TraitDefinition
|
166
|
-
] = {}
|
167
|
-
|
168
|
-
# Performance optimization: Pre-compute default dependencies
|
169
|
-
self._default_dependencies: dict[Trait, frozenset[Trait]] = {}
|
170
|
-
for trait, definition in DEFAULT_TRAIT_DEFINITIONS.items():
|
171
|
-
self._default_dependencies[trait] = definition.dependencies
|
172
|
-
|
173
|
-
# Dependency tracking
|
174
|
-
self._dependency_graph: dict[Trait, set[Trait]] = {}
|
175
|
-
self._reverse_dependencies: dict[Trait, set[Trait]] = defaultdict(set)
|
176
|
-
|
177
|
-
# Performance monitoring
|
178
|
-
self._registration_count = 0
|
179
|
-
self._lookup_count = 0
|
180
|
-
self._last_cleanup = time.time()
|
181
|
-
|
182
|
-
# Memory management
|
183
|
-
self._weak_references: dict[int, weakref.ReferenceType[type[Any]]] = {}
|
184
|
-
self._type_id_mapping: dict[type[Any], int] = {}
|
185
|
-
|
186
|
-
# Thread safety
|
187
|
-
self._registry_lock = threading.RLock()
|
188
|
-
|
189
|
-
# Orphan rule enforcement
|
190
|
-
self._sealed_traits: set[Trait] = set()
|
191
|
-
self._local_modules: set[str] = {
|
192
|
-
"lionagi",
|
193
|
-
"__main__",
|
194
|
-
} # Modules considered "local"
|
195
|
-
|
196
|
-
self._initialized = True
|
197
|
-
|
198
|
-
def _perform_registration(
|
199
|
-
self,
|
200
|
-
implementation_type: type[Any],
|
201
|
-
trait: Trait,
|
202
|
-
definition: TraitDefinition,
|
203
|
-
start_time: float,
|
204
|
-
) -> None:
|
205
|
-
"""Perform the actual trait registration."""
|
206
|
-
# Performance optimization: Reuse cached definitions
|
207
|
-
cache_key = (trait, implementation_type)
|
208
|
-
impl_definition = self._definition_cache.get(cache_key)
|
209
|
-
|
210
|
-
if impl_definition is None:
|
211
|
-
# Create implementation-specific definition
|
212
|
-
impl_definition = TraitDefinition(
|
213
|
-
trait=trait,
|
214
|
-
protocol_type=definition.protocol_type,
|
215
|
-
implementation_type=implementation_type,
|
216
|
-
dependencies=definition.dependencies,
|
217
|
-
version=definition.version,
|
218
|
-
description=definition.description,
|
219
|
-
registration_time=start_time,
|
220
|
-
)
|
221
|
-
self._definition_cache[cache_key] = impl_definition
|
222
|
-
|
223
|
-
# Register the implementation
|
224
|
-
if implementation_type not in self._trait_implementations:
|
225
|
-
self._trait_implementations[implementation_type] = set()
|
226
|
-
|
227
|
-
self._trait_implementations[implementation_type].add(trait)
|
228
|
-
self._implementation_registry[(trait, implementation_type)] = (
|
229
|
-
impl_definition
|
230
|
-
)
|
231
|
-
|
232
|
-
# Set up weak reference only if not already present
|
233
|
-
type_id = self._type_id_mapping.get(implementation_type)
|
234
|
-
if type_id is None:
|
235
|
-
type_id = id(implementation_type)
|
236
|
-
self._weak_references[type_id] = weakref.ref(
|
237
|
-
implementation_type, self._cleanup_weak_reference
|
238
|
-
)
|
239
|
-
self._type_id_mapping[implementation_type] = type_id
|
240
|
-
|
241
|
-
# Update dependency tracking
|
242
|
-
self._update_dependency_graph(trait, impl_definition.dependencies)
|
243
|
-
|
244
|
-
# Performance tracking
|
245
|
-
self._registration_count += 1
|
246
|
-
|
247
|
-
def register_trait_with_validation( # noqa: C901, PLR0912
|
248
|
-
self,
|
249
|
-
implementation_type: type[Any],
|
250
|
-
trait: Trait,
|
251
|
-
definition: TraitDefinition | None = None,
|
252
|
-
) -> ValidationResult:
|
253
|
-
"""
|
254
|
-
Register a trait implementation with detailed validation results.
|
255
|
-
|
256
|
-
Args:
|
257
|
-
implementation_type: The class implementing the trait
|
258
|
-
trait: The trait being implemented
|
259
|
-
definition: Optional custom trait definition
|
260
|
-
|
261
|
-
Returns:
|
262
|
-
ValidationResult with detailed success/failure information
|
263
|
-
|
264
|
-
Performance target: <2μs per registration
|
265
|
-
"""
|
266
|
-
start_time = time.perf_counter()
|
267
|
-
result = ValidationResult(success=False)
|
268
|
-
|
269
|
-
with self._registry_lock:
|
270
|
-
try:
|
271
|
-
# Check if trait is sealed first
|
272
|
-
if trait in self._sealed_traits:
|
273
|
-
type_is_local = self._type_is_local(implementation_type)
|
274
|
-
if not type_is_local:
|
275
|
-
raise SealedTraitError(trait, implementation_type)
|
276
|
-
|
277
|
-
# Validate orphan rule compliance
|
278
|
-
if not self._validate_orphan_rule(implementation_type, trait):
|
279
|
-
result.error_type = "orphan_rule"
|
280
|
-
result.error_message = (
|
281
|
-
f"Cannot implement external trait {trait.name} on external type "
|
282
|
-
f"{implementation_type.__module__}.{implementation_type.__qualname__}"
|
283
|
-
)
|
284
|
-
return result
|
285
|
-
|
286
|
-
# Validate trait implementation with detailed error capture
|
287
|
-
validation_details = (
|
288
|
-
self._validate_trait_implementation_detailed(
|
289
|
-
implementation_type, trait
|
290
|
-
)
|
291
|
-
)
|
292
|
-
if not validation_details["valid"]:
|
293
|
-
result.error_type = "implementation"
|
294
|
-
error_msg = validation_details.get("error", "")
|
295
|
-
if isinstance(error_msg, str):
|
296
|
-
result.error_message = (
|
297
|
-
error_msg or "Invalid trait implementation"
|
298
|
-
)
|
299
|
-
else:
|
300
|
-
result.error_message = "Invalid trait implementation"
|
301
|
-
|
302
|
-
missing_attrs = validation_details.get(
|
303
|
-
"missing_attributes", []
|
304
|
-
)
|
305
|
-
if isinstance(missing_attrs, list):
|
306
|
-
result.missing_attributes = missing_attrs
|
307
|
-
return result
|
308
|
-
|
309
|
-
# Check dependencies
|
310
|
-
current_traits = self.get_traits(implementation_type)
|
311
|
-
trait_def = self._trait_definitions.get(trait)
|
312
|
-
if trait_def:
|
313
|
-
missing_deps = trait_def.dependencies - current_traits
|
314
|
-
if missing_deps:
|
315
|
-
result.error_type = "dependencies"
|
316
|
-
result.error_message = f"Missing required trait dependencies: {', '.join(t.name for t in missing_deps)}"
|
317
|
-
result.missing_dependencies = set(missing_deps)
|
318
|
-
return result
|
319
|
-
|
320
|
-
# Check for dependency cycles across full graph
|
321
|
-
# This checks not just the new trait but all reachable traits
|
322
|
-
cycle_path = self._detect_full_graph_cycle(trait)
|
323
|
-
if cycle_path:
|
324
|
-
raise TraitCycleError(cycle_path)
|
325
|
-
|
326
|
-
# Use provided definition or default
|
327
|
-
if definition is None:
|
328
|
-
definition = self._trait_definitions.get(trait)
|
329
|
-
if definition is None:
|
330
|
-
result.error_type = "definition"
|
331
|
-
result.error_message = (
|
332
|
-
f"No definition found for trait {trait.name}"
|
333
|
-
)
|
334
|
-
return result
|
335
|
-
|
336
|
-
# Perform the actual registration
|
337
|
-
self._perform_registration(
|
338
|
-
implementation_type, trait, definition, start_time
|
339
|
-
)
|
340
|
-
registration_time = (
|
341
|
-
time.perf_counter() - start_time
|
342
|
-
) * 1_000_000 # μs
|
343
|
-
|
344
|
-
# Validate performance target (relaxed for current implementation)
|
345
|
-
PERFORMANCE_THRESHOLD_US = (
|
346
|
-
100.0 # 100μs target (raised from 50μs)
|
347
|
-
)
|
348
|
-
if registration_time > PERFORMANCE_THRESHOLD_US:
|
349
|
-
result.performance_warning = (
|
350
|
-
f"Trait registration took {registration_time:.1f}μs, "
|
351
|
-
f"exceeding {PERFORMANCE_THRESHOLD_US}μs target"
|
352
|
-
)
|
353
|
-
|
354
|
-
result.success = True
|
355
|
-
return result
|
356
|
-
|
357
|
-
except Exception as e: # noqa: BLE001
|
358
|
-
# Registration failed - cleanup partial state
|
359
|
-
self._cleanup_failed_registration(implementation_type, trait)
|
360
|
-
result.error_type = "exception"
|
361
|
-
result.error_message = f"Registration failed: {e!s}"
|
362
|
-
return result
|
363
|
-
|
364
|
-
def register_trait(
|
365
|
-
self,
|
366
|
-
implementation_type: type[Any],
|
367
|
-
trait: Trait,
|
368
|
-
definition: TraitDefinition | None = None,
|
369
|
-
) -> bool:
|
370
|
-
"""
|
371
|
-
Register a trait implementation (backward compatible).
|
372
|
-
|
373
|
-
Args:
|
374
|
-
implementation_type: The class implementing the trait
|
375
|
-
trait: The trait being implemented
|
376
|
-
definition: Optional custom trait definition
|
377
|
-
|
378
|
-
Returns:
|
379
|
-
True if registration succeeded
|
380
|
-
|
381
|
-
Performance target: <2μs per registration
|
382
|
-
"""
|
383
|
-
result = self.register_trait_with_validation(
|
384
|
-
implementation_type, trait, definition
|
385
|
-
)
|
386
|
-
|
387
|
-
# Log detailed error if registration failed
|
388
|
-
if not result.success:
|
389
|
-
import warnings
|
390
|
-
|
391
|
-
warnings.warn(
|
392
|
-
f"Trait registration failed: {result.error_type} - {result.error_message}",
|
393
|
-
UserWarning,
|
394
|
-
stacklevel=2,
|
395
|
-
)
|
396
|
-
|
397
|
-
# Emit performance warning if needed
|
398
|
-
if result.performance_warning:
|
399
|
-
import warnings
|
400
|
-
|
401
|
-
warnings.warn(
|
402
|
-
result.performance_warning, PerformanceWarning, stacklevel=2
|
403
|
-
)
|
404
|
-
|
405
|
-
return result.success
|
406
|
-
|
407
|
-
def get_traits(self, implementation_type: type[Any]) -> set[Trait]:
|
408
|
-
"""
|
409
|
-
Get all traits implemented by a type.
|
410
|
-
|
411
|
-
Args:
|
412
|
-
implementation_type: The type to check
|
413
|
-
|
414
|
-
Returns:
|
415
|
-
Set of traits implemented by the type
|
416
|
-
"""
|
417
|
-
with self._registry_lock:
|
418
|
-
self._lookup_count += 1
|
419
|
-
return self._trait_implementations.get(
|
420
|
-
implementation_type, set()
|
421
|
-
).copy()
|
422
|
-
|
423
|
-
def has_trait(
|
424
|
-
self,
|
425
|
-
implementation_type: type[Any],
|
426
|
-
trait: Trait,
|
427
|
-
*,
|
428
|
-
source: Literal["registered", "protocol"] = "registered",
|
429
|
-
) -> bool:
|
430
|
-
"""
|
431
|
-
Check if a type implements a specific trait.
|
432
|
-
|
433
|
-
Args:
|
434
|
-
implementation_type: The type to check
|
435
|
-
trait: The trait to check for
|
436
|
-
source: Check source - "registered" (default) checks if trait was
|
437
|
-
successfully registered, "protocol" checks structural conformance
|
438
|
-
|
439
|
-
Returns:
|
440
|
-
True if type implements the trait
|
441
|
-
|
442
|
-
Performance:
|
443
|
-
- "registered": Dict lookup (~50ns)
|
444
|
-
- "protocol": isinstance check (~145ns)
|
445
|
-
"""
|
446
|
-
with self._registry_lock:
|
447
|
-
self._lookup_count += 1
|
448
|
-
|
449
|
-
if source == "registered":
|
450
|
-
# Simple dict lookup for registered traits
|
451
|
-
return trait in self._trait_implementations.get(
|
452
|
-
implementation_type, set()
|
453
|
-
)
|
454
|
-
|
455
|
-
elif source == "protocol":
|
456
|
-
# Fast path: check registry first for performance
|
457
|
-
if trait in self._trait_implementations.get(
|
458
|
-
implementation_type, set()
|
459
|
-
):
|
460
|
-
return True
|
461
|
-
|
462
|
-
# Fallback: Protocol isinstance check
|
463
|
-
trait_def = self._trait_definitions.get(trait)
|
464
|
-
if trait_def and trait_def.protocol_type:
|
465
|
-
# Use Protocol isinstance for runtime checking
|
466
|
-
try:
|
467
|
-
return isinstance(
|
468
|
-
implementation_type, trait_def.protocol_type
|
469
|
-
)
|
470
|
-
except (TypeError, AttributeError):
|
471
|
-
return False
|
472
|
-
|
473
|
-
return False
|
474
|
-
|
475
|
-
def get_trait_definition(self, trait: Trait) -> TraitDefinition | None:
|
476
|
-
"""Get the definition for a specific trait."""
|
477
|
-
with self._registry_lock:
|
478
|
-
return self._trait_definitions.get(trait)
|
479
|
-
|
480
|
-
def get_implementation_definition(
|
481
|
-
self, trait: Trait, implementation_type: type[Any]
|
482
|
-
) -> TraitDefinition | None:
|
483
|
-
"""Get the definition for a specific trait implementation."""
|
484
|
-
with self._registry_lock:
|
485
|
-
return self._implementation_registry.get(
|
486
|
-
(trait, implementation_type)
|
487
|
-
)
|
488
|
-
|
489
|
-
def validate_dependencies(
|
490
|
-
self, implementation_type: type[Any], required_traits: set[Trait]
|
491
|
-
) -> tuple[bool, set[Trait]]:
|
492
|
-
"""
|
493
|
-
Validate that all trait dependencies are satisfied.
|
494
|
-
|
495
|
-
Args:
|
496
|
-
implementation_type: Type to validate
|
497
|
-
required_traits: Traits that must be present
|
498
|
-
|
499
|
-
Returns:
|
500
|
-
(is_valid, missing_dependencies)
|
501
|
-
"""
|
502
|
-
with self._registry_lock:
|
503
|
-
current_traits = self.get_traits(implementation_type)
|
504
|
-
missing_deps = set()
|
505
|
-
|
506
|
-
for trait in required_traits:
|
507
|
-
trait_def = self._trait_definitions.get(trait)
|
508
|
-
if trait_def:
|
509
|
-
# Check direct dependencies
|
510
|
-
for dep in trait_def.dependencies:
|
511
|
-
if dep not in current_traits:
|
512
|
-
missing_deps.add(dep)
|
513
|
-
|
514
|
-
return len(missing_deps) == 0, missing_deps
|
515
|
-
|
516
|
-
def get_dependency_graph(self) -> dict[Trait, set[Trait]]:
|
517
|
-
"""Get the complete trait dependency graph."""
|
518
|
-
with self._registry_lock:
|
519
|
-
return {
|
520
|
-
trait: deps.copy()
|
521
|
-
for trait, deps in self._dependency_graph.items()
|
522
|
-
}
|
523
|
-
|
524
|
-
def get_performance_stats(self) -> dict[str, Any]:
|
525
|
-
"""Get performance and usage statistics."""
|
526
|
-
with self._registry_lock:
|
527
|
-
return {
|
528
|
-
"registrations": self._registration_count,
|
529
|
-
"lookups": self._lookup_count,
|
530
|
-
"active_implementations": len(self._trait_implementations),
|
531
|
-
"total_traits": len(self._trait_definitions),
|
532
|
-
"weak_references": len(self._weak_references),
|
533
|
-
"last_cleanup": self._last_cleanup,
|
534
|
-
"memory_pressure": self._calculate_memory_pressure(),
|
535
|
-
}
|
536
|
-
|
537
|
-
def cleanup_orphaned_references(self) -> int:
|
538
|
-
"""
|
539
|
-
Clean up orphaned weak references.
|
540
|
-
|
541
|
-
Returns:
|
542
|
-
Number of references cleaned up
|
543
|
-
"""
|
544
|
-
with self._registry_lock:
|
545
|
-
# Find dead references
|
546
|
-
dead_refs = []
|
547
|
-
for type_id, weak_ref in self._weak_references.items():
|
548
|
-
if weak_ref() is None:
|
549
|
-
dead_refs.append(type_id)
|
550
|
-
|
551
|
-
self._last_cleanup = time.time()
|
552
|
-
|
553
|
-
# Clean up dead references outside the lock to avoid deadlock
|
554
|
-
cleaned = 0
|
555
|
-
for type_id in dead_refs:
|
556
|
-
self._cleanup_weak_reference(type_id)
|
557
|
-
cleaned += 1
|
558
|
-
|
559
|
-
return cleaned
|
560
|
-
|
561
|
-
def _validate_trait_implementation(
|
562
|
-
self, implementation_type: type[Any], trait: Trait
|
563
|
-
) -> bool:
|
564
|
-
"""Validate that a type properly implements a trait."""
|
565
|
-
trait_def = self._trait_definitions.get(trait)
|
566
|
-
if not trait_def or not trait_def.protocol_type:
|
567
|
-
return False
|
568
|
-
|
569
|
-
try:
|
570
|
-
# Check specific trait requirements
|
571
|
-
if trait == Trait.IDENTIFIABLE:
|
572
|
-
# Identifiable requires id and id_type properties
|
573
|
-
return hasattr(implementation_type, "id") and hasattr(
|
574
|
-
implementation_type, "id_type"
|
575
|
-
)
|
576
|
-
elif trait == Trait.TEMPORAL:
|
577
|
-
# Temporal requires created_at and updated_at properties
|
578
|
-
return hasattr(implementation_type, "created_at") and hasattr(
|
579
|
-
implementation_type, "updated_at"
|
580
|
-
)
|
581
|
-
else:
|
582
|
-
# For other traits, be more lenient for now
|
583
|
-
return hasattr(implementation_type, "__dict__") or hasattr(
|
584
|
-
implementation_type, "__slots__"
|
585
|
-
)
|
586
|
-
|
587
|
-
except (TypeError, AttributeError):
|
588
|
-
return False
|
589
|
-
|
590
|
-
def _get_required_attributes(self, trait: Trait) -> list[str]:
|
591
|
-
"""Get required attributes for a trait."""
|
592
|
-
if trait == Trait.IDENTIFIABLE:
|
593
|
-
return ["id", "id_type"]
|
594
|
-
elif trait == Trait.TEMPORAL:
|
595
|
-
return ["created_at", "updated_at"]
|
596
|
-
elif trait == Trait.AUDITABLE:
|
597
|
-
return [
|
598
|
-
"id",
|
599
|
-
"id_type",
|
600
|
-
"created_at",
|
601
|
-
"updated_at",
|
602
|
-
"created_by",
|
603
|
-
"updated_by",
|
604
|
-
]
|
605
|
-
elif trait == Trait.HASHABLE:
|
606
|
-
return ["compute_hash"]
|
607
|
-
else:
|
608
|
-
return []
|
609
|
-
|
610
|
-
def _validate_trait_implementation_detailed(
|
611
|
-
self, implementation_type: type[Any], trait: Trait
|
612
|
-
) -> dict[str, bool | str | list[str]]:
|
613
|
-
"""Validate trait implementation with detailed error information."""
|
614
|
-
result: dict[str, bool | str | list[str]] = {
|
615
|
-
"valid": False,
|
616
|
-
"error": "",
|
617
|
-
"missing_attributes": [],
|
618
|
-
}
|
619
|
-
|
620
|
-
trait_def = self._trait_definitions.get(trait)
|
621
|
-
if not trait_def:
|
622
|
-
result["error"] = f"No definition found for trait {trait.name}"
|
623
|
-
return result
|
624
|
-
|
625
|
-
if not trait_def.protocol_type:
|
626
|
-
result["error"] = (
|
627
|
-
f"No protocol type defined for trait {trait.name}"
|
628
|
-
)
|
629
|
-
return result
|
630
|
-
|
631
|
-
try:
|
632
|
-
# Get required attributes for the trait
|
633
|
-
required_attrs = self._get_required_attributes(trait)
|
634
|
-
|
635
|
-
if required_attrs:
|
636
|
-
# Check for missing attributes
|
637
|
-
missing = [
|
638
|
-
attr
|
639
|
-
for attr in required_attrs
|
640
|
-
if not hasattr(implementation_type, attr)
|
641
|
-
]
|
642
|
-
if missing:
|
643
|
-
result["missing_attributes"] = missing
|
644
|
-
attr_type = (
|
645
|
-
"methods" if trait == Trait.HASHABLE else "attributes"
|
646
|
-
)
|
647
|
-
result["error"] = (
|
648
|
-
f"Missing required {attr_type} for {trait.name}: {', '.join(missing)}"
|
649
|
-
)
|
650
|
-
else:
|
651
|
-
result["valid"] = True
|
652
|
-
elif hasattr(implementation_type, "__dict__") or hasattr(
|
653
|
-
implementation_type, "__slots__"
|
654
|
-
):
|
655
|
-
# For other traits, check basic structure
|
656
|
-
result["valid"] = True
|
657
|
-
else:
|
658
|
-
result["error"] = (
|
659
|
-
f"Type {implementation_type.__name__} lacks basic structure for trait implementation"
|
660
|
-
)
|
661
|
-
|
662
|
-
except (TypeError, AttributeError) as e:
|
663
|
-
result["error"] = f"Validation error: {e!s}"
|
664
|
-
|
665
|
-
return result
|
666
|
-
|
667
|
-
def _type_is_local(self, implementation_type: type[Any]) -> bool:
|
668
|
-
"""Check if a type is from a local module."""
|
669
|
-
type_module = implementation_type.__module__
|
670
|
-
if not type_module:
|
671
|
-
return False
|
672
|
-
|
673
|
-
# Get the base package name
|
674
|
-
type_package = type_module.split(".")[0]
|
675
|
-
|
676
|
-
# Check if it's in local modules
|
677
|
-
if type_package in {"lionagi", "__main__"}:
|
678
|
-
return True
|
679
|
-
|
680
|
-
for local_mod in self._local_modules:
|
681
|
-
local_package = local_mod.split(".")[0]
|
682
|
-
if type_package == local_package:
|
683
|
-
return True
|
684
|
-
|
685
|
-
return False
|
686
|
-
|
687
|
-
def _validate_orphan_rule(
|
688
|
-
self, implementation_type: type[Any], trait: Trait
|
689
|
-
) -> bool:
|
690
|
-
"""
|
691
|
-
Validate orphan rule: either trait or implementation type must be local.
|
692
|
-
|
693
|
-
The orphan rule prevents external packages from implementing external traits
|
694
|
-
on external types, which could cause conflicts.
|
695
|
-
"""
|
696
|
-
# Performance optimization: fast path for sealed traits
|
697
|
-
if trait in self._sealed_traits:
|
698
|
-
return True
|
699
|
-
|
700
|
-
# All our traits are local for now
|
701
|
-
trait_is_local = True
|
702
|
-
|
703
|
-
# Check if type is local using PEP 420 namespace package comparison
|
704
|
-
type_module = implementation_type.__module__
|
705
|
-
if not type_module:
|
706
|
-
return trait_is_local
|
707
|
-
|
708
|
-
# Get the base package name (first segment)
|
709
|
-
type_package = type_module.split(".")[0]
|
710
|
-
|
711
|
-
# Performance: check most common case first
|
712
|
-
if type_package == "lionagi":
|
713
|
-
return True
|
714
|
-
|
715
|
-
# Check other local modules with namespace package awareness
|
716
|
-
for local_mod in self._local_modules:
|
717
|
-
local_package = local_mod.split(".")[0]
|
718
|
-
if type_package == local_package:
|
719
|
-
return True
|
720
|
-
|
721
|
-
return trait_is_local
|
722
|
-
|
723
|
-
def seal_trait(self, trait: Trait) -> None:
|
724
|
-
"""
|
725
|
-
Seal a trait to prevent external implementations.
|
726
|
-
|
727
|
-
Sealed traits can only be implemented by types in local modules.
|
728
|
-
"""
|
729
|
-
with self._registry_lock:
|
730
|
-
self._sealed_traits.add(trait)
|
731
|
-
|
732
|
-
def add_local_module(self, module_name: str) -> None:
|
733
|
-
"""Add a module name to the list of local modules."""
|
734
|
-
with self._registry_lock:
|
735
|
-
self._local_modules.add(module_name)
|
736
|
-
|
737
|
-
def _update_dependency_graph(
|
738
|
-
self, trait: Trait, dependencies: frozenset[Trait]
|
739
|
-
) -> None:
|
740
|
-
"""Update the dependency graph with new trait dependencies."""
|
741
|
-
# Flatten loops: update both forward and reverse dependencies in one pass
|
742
|
-
deps = set(dependencies) if dependencies else set()
|
743
|
-
self._dependency_graph[trait] = deps
|
744
|
-
for dep in deps:
|
745
|
-
self._reverse_dependencies[dep].add(trait)
|
746
|
-
|
747
|
-
def _cleanup_weak_reference(
|
748
|
-
self, ref_or_type_id: int | weakref.ReferenceType[type[Any]]
|
749
|
-
) -> None:
|
750
|
-
"""Cleanup callback for dead weak references."""
|
751
|
-
with self._registry_lock:
|
752
|
-
# Handle both callback signature (weak ref) and direct cleanup (type_id)
|
753
|
-
if isinstance(ref_or_type_id, int):
|
754
|
-
type_id = ref_or_type_id
|
755
|
-
else:
|
756
|
-
# This is a weak reference callback - extract type_id
|
757
|
-
type_id = None
|
758
|
-
for tid, weak_ref in self._weak_references.items():
|
759
|
-
if weak_ref == ref_or_type_id:
|
760
|
-
type_id = tid
|
761
|
-
break
|
762
|
-
if type_id is None:
|
763
|
-
return
|
764
|
-
|
765
|
-
if type_id in self._weak_references:
|
766
|
-
del self._weak_references[type_id]
|
767
|
-
|
768
|
-
# Find and remove from type mapping
|
769
|
-
to_remove = None
|
770
|
-
for impl_type, stored_id in self._type_id_mapping.items():
|
771
|
-
if stored_id == type_id:
|
772
|
-
to_remove = impl_type
|
773
|
-
break
|
774
|
-
|
775
|
-
if to_remove:
|
776
|
-
del self._type_id_mapping[to_remove]
|
777
|
-
|
778
|
-
# Remove from trait implementations
|
779
|
-
if to_remove in self._trait_implementations:
|
780
|
-
del self._trait_implementations[to_remove]
|
781
|
-
|
782
|
-
def _cleanup_failed_registration(
|
783
|
-
self, implementation_type: type[Any], trait: Trait
|
784
|
-
) -> None:
|
785
|
-
"""Clean up state after failed registration."""
|
786
|
-
# Capture type_id before acquiring lock
|
787
|
-
type_id = None
|
788
|
-
|
789
|
-
with self._registry_lock:
|
790
|
-
# Remove from trait implementations if added
|
791
|
-
if implementation_type in self._trait_implementations:
|
792
|
-
self._trait_implementations[implementation_type].discard(trait)
|
793
|
-
if not self._trait_implementations[implementation_type]:
|
794
|
-
del self._trait_implementations[implementation_type]
|
795
|
-
|
796
|
-
# Remove from implementation registry
|
797
|
-
key = (trait, implementation_type)
|
798
|
-
if key in self._implementation_registry:
|
799
|
-
del self._implementation_registry[key]
|
800
|
-
|
801
|
-
# Get type_id for cleanup
|
802
|
-
type_id = self._type_id_mapping.get(implementation_type)
|
803
|
-
|
804
|
-
# Call cleanup outside the lock to avoid nested locking
|
805
|
-
if type_id:
|
806
|
-
self._cleanup_weak_reference(type_id)
|
807
|
-
|
808
|
-
def _calculate_memory_pressure(self) -> float:
|
809
|
-
"""Calculate memory pressure metric (0.0 = low, 1.0 = high)."""
|
810
|
-
# Simple heuristic based on number of tracked references
|
811
|
-
max_refs = 10000 # Arbitrary threshold
|
812
|
-
current_refs = len(self._weak_references)
|
813
|
-
return min(current_refs / max_refs, 1.0)
|
814
|
-
|
815
|
-
def _detect_dependency_cycle(
|
816
|
-
self,
|
817
|
-
trait: Trait,
|
818
|
-
visited: set[Trait] | None = None,
|
819
|
-
path: list[Trait] | None = None,
|
820
|
-
) -> list[Trait] | None:
|
821
|
-
"""
|
822
|
-
Detect cycles in trait dependencies using DFS.
|
823
|
-
|
824
|
-
Args:
|
825
|
-
trait: The trait to check for cycles
|
826
|
-
visited: Set of already visited traits (for optimization)
|
827
|
-
path: Current path in the DFS traversal
|
828
|
-
|
829
|
-
Returns:
|
830
|
-
List of traits forming a cycle if found, None otherwise
|
831
|
-
|
832
|
-
Performance: O(V+E) where V=traits, E=dependencies. With ~50 traits, <5μs.
|
833
|
-
"""
|
834
|
-
if visited is None:
|
835
|
-
visited = set()
|
836
|
-
if path is None:
|
837
|
-
path = []
|
838
|
-
|
839
|
-
# If we've seen this trait in the current path, we have a cycle
|
840
|
-
if trait in path:
|
841
|
-
cycle_start = path.index(trait)
|
842
|
-
return path[cycle_start:] + [trait]
|
843
|
-
|
844
|
-
# If we've already fully explored this trait, skip it
|
845
|
-
if trait in visited:
|
846
|
-
return None
|
847
|
-
|
848
|
-
# Add to current path
|
849
|
-
path.append(trait)
|
850
|
-
|
851
|
-
try:
|
852
|
-
# Get dependencies for this trait
|
853
|
-
definition = self._trait_definitions.get(trait)
|
854
|
-
if definition and definition.dependencies:
|
855
|
-
for dep in definition.dependencies:
|
856
|
-
cycle = self._detect_dependency_cycle(dep, visited, path)
|
857
|
-
if cycle:
|
858
|
-
return cycle
|
859
|
-
|
860
|
-
# Mark as fully explored
|
861
|
-
visited.add(trait)
|
862
|
-
return None
|
863
|
-
|
864
|
-
finally:
|
865
|
-
# Remove from current path
|
866
|
-
path.pop()
|
867
|
-
|
868
|
-
def _detect_full_graph_cycle(self, new_trait: Trait) -> list[Trait] | None:
|
869
|
-
"""
|
870
|
-
Detect cycles in the full dependency graph, including pre-existing cycles.
|
871
|
-
|
872
|
-
This checks not just the new trait but all traits reachable from it,
|
873
|
-
ensuring we catch cycles like B -> C -> A when adding A -> B.
|
874
|
-
|
875
|
-
TODO: Consider adding rebuild_reverse_dependencies() helper to prevent
|
876
|
-
stale cycles if dependencies are manually removed.
|
877
|
-
|
878
|
-
Args:
|
879
|
-
new_trait: The trait being added to the graph
|
880
|
-
|
881
|
-
Returns:
|
882
|
-
List of traits forming a cycle if found, None otherwise
|
883
|
-
"""
|
884
|
-
# First check the new trait itself
|
885
|
-
cycle = self._detect_dependency_cycle(new_trait)
|
886
|
-
if cycle:
|
887
|
-
return cycle
|
888
|
-
|
889
|
-
# Also check all traits that depend on the new trait
|
890
|
-
# This catches pre-existing cycles that would be completed by adding new_trait
|
891
|
-
for trait in self._reverse_dependencies.get(new_trait, set()):
|
892
|
-
cycle = self._detect_dependency_cycle(trait)
|
893
|
-
if cycle:
|
894
|
-
return cycle
|
895
|
-
|
896
|
-
return None
|
897
|
-
|
898
|
-
|
899
|
-
class PerformanceWarning(UserWarning):
|
900
|
-
"""Warning for performance issues in trait system."""
|
901
|
-
|
902
|
-
pass
|
903
|
-
|
904
|
-
|
905
|
-
# Global registry instance
|
906
|
-
_global_registry: TraitRegistry | None = None
|
907
|
-
|
908
|
-
|
909
|
-
def get_global_registry() -> TraitRegistry:
|
910
|
-
"""Get the global trait registry instance."""
|
911
|
-
global _global_registry # noqa: PLW0603
|
912
|
-
if _global_registry is None:
|
913
|
-
_global_registry = TraitRegistry()
|
914
|
-
return _global_registry
|
915
|
-
|
916
|
-
|
917
|
-
# Convenience functions using global registry
|
918
|
-
def register_trait(implementation_type: type[Any], trait: Trait) -> bool:
|
919
|
-
"""Register a trait implementation with the global registry."""
|
920
|
-
return get_global_registry().register_trait(implementation_type, trait)
|
921
|
-
|
922
|
-
|
923
|
-
def has_trait(implementation_type: type[Any], trait: Trait) -> bool:
|
924
|
-
"""Check if a type has a trait using the global registry."""
|
925
|
-
return get_global_registry().has_trait(implementation_type, trait)
|
926
|
-
|
927
|
-
|
928
|
-
def seal_trait(trait: Trait) -> None:
|
929
|
-
"""Seal a trait to prevent external implementations."""
|
930
|
-
get_global_registry().seal_trait(trait)
|
931
|
-
|
932
|
-
|
933
|
-
def reinitialize_registry() -> None:
|
934
|
-
"""
|
935
|
-
Reinitialize the global registry for multiprocessing contexts.
|
936
|
-
|
937
|
-
Call this function in worker processes after forking to ensure
|
938
|
-
the registry is properly initialized with fresh locks.
|
939
|
-
"""
|
940
|
-
global _global_registry # noqa: PLW0603
|
941
|
-
_global_registry = TraitRegistry.reinitialize()
|
942
|
-
|
943
|
-
|
944
|
-
def implement(trait: Trait) -> Callable[[type[Any]], type[Any]]:
|
945
|
-
"""
|
946
|
-
Decorator to safely implement a trait on a type.
|
947
|
-
|
948
|
-
Usage:
|
949
|
-
@implement(Trait.IDENTIFIABLE)
|
950
|
-
class MyClass:
|
951
|
-
...
|
952
|
-
"""
|
953
|
-
|
954
|
-
def decorator(cls: type[Any]) -> type[Any]:
|
955
|
-
registry = get_global_registry()
|
956
|
-
|
957
|
-
# Orphan rule check with PEP 420 namespace-package support
|
958
|
-
type_module = cls.__module__
|
959
|
-
trait_module = trait.__class__.__module__ # Trait enum's module
|
960
|
-
|
961
|
-
# Get base package names (first segment)
|
962
|
-
our_package = __package__.split(".")[0] if __package__ else "lionagi"
|
963
|
-
type_package = type_module.split(".")[0] if type_module else ""
|
964
|
-
trait_package = trait_module.split(".")[0] if trait_module else ""
|
965
|
-
|
966
|
-
# Enforce orphan rule: either trait or type must be from our package
|
967
|
-
if our_package not in (type_package, trait_package):
|
968
|
-
raise OrphanRuleViolation(trait, cls)
|
969
|
-
|
970
|
-
if not registry.register_trait(cls, trait):
|
971
|
-
raise ValueError(
|
972
|
-
f"Failed to implement trait {trait.name} on {cls.__qualname__}"
|
973
|
-
)
|
974
|
-
return cls
|
975
|
-
|
976
|
-
return decorator
|
977
|
-
|
978
|
-
|
979
|
-
def as_trait(*traits: Trait) -> Callable[[type[Any]], type[Any]]:
|
980
|
-
"""
|
981
|
-
Decorator to implement multiple traits on a type with validation.
|
982
|
-
|
983
|
-
This is the primary decorator for trait implementation with enhanced
|
984
|
-
developer experience including validation and helpful error messages.
|
985
|
-
|
986
|
-
Usage:
|
987
|
-
@as_trait(Trait.IDENTIFIABLE, Trait.TEMPORAL)
|
988
|
-
class MyClass:
|
989
|
-
id: str = "example"
|
990
|
-
id_type: str = "example"
|
991
|
-
created_at: float = 0.0
|
992
|
-
updated_at: float = 0.0
|
993
|
-
|
994
|
-
Args:
|
995
|
-
*traits: Traits to implement on the decorated class
|
996
|
-
|
997
|
-
Returns:
|
998
|
-
Decorator function that registers the traits
|
999
|
-
|
1000
|
-
Raises:
|
1001
|
-
ValueError: If trait implementation fails
|
1002
|
-
OrphanRuleViolation: If orphan rule is violated
|
1003
|
-
"""
|
1004
|
-
|
1005
|
-
def decorator(cls: type[Any]) -> type[Any]:
|
1006
|
-
registry = get_global_registry()
|
1007
|
-
failed_traits = []
|
1008
|
-
detailed_errors = []
|
1009
|
-
orphan_violations = []
|
1010
|
-
|
1011
|
-
# Collect all errors before raising
|
1012
|
-
for trait in traits:
|
1013
|
-
try:
|
1014
|
-
result = registry.register_trait_with_validation(cls, trait)
|
1015
|
-
if not result.success:
|
1016
|
-
failed_traits.append(trait.name)
|
1017
|
-
# Provide detailed error information
|
1018
|
-
if (
|
1019
|
-
result.error_type == "dependencies"
|
1020
|
-
and result.missing_dependencies
|
1021
|
-
):
|
1022
|
-
deps_str = ", ".join(
|
1023
|
-
t.name for t in result.missing_dependencies
|
1024
|
-
)
|
1025
|
-
detailed_errors.append(
|
1026
|
-
f"{trait.name}: missing dependencies [{deps_str}]"
|
1027
|
-
)
|
1028
|
-
elif (
|
1029
|
-
result.error_type == "implementation"
|
1030
|
-
and result.missing_attributes
|
1031
|
-
):
|
1032
|
-
attrs_str = ", ".join(result.missing_attributes)
|
1033
|
-
detailed_errors.append(
|
1034
|
-
f"{trait.name}: missing attributes [{attrs_str}]"
|
1035
|
-
)
|
1036
|
-
elif result.error_type == "orphan_rule":
|
1037
|
-
orphan_violations.append(
|
1038
|
-
f"{trait.name}: {result.error_message}"
|
1039
|
-
)
|
1040
|
-
else:
|
1041
|
-
detailed_errors.append(
|
1042
|
-
f"{trait.name}: {result.error_message}"
|
1043
|
-
)
|
1044
|
-
except OrphanRuleViolation as e:
|
1045
|
-
orphan_violations.append(f"{trait.name}: {e}")
|
1046
|
-
failed_traits.append(trait.name)
|
1047
|
-
except Exception as e: # noqa: BLE001
|
1048
|
-
failed_traits.append(f"{trait.name}")
|
1049
|
-
detailed_errors.append(
|
1050
|
-
f"{trait.name}: unexpected error - {e!s}"
|
1051
|
-
)
|
1052
|
-
|
1053
|
-
# If there are any failures, aggregate and report all of them
|
1054
|
-
if failed_traits or orphan_violations:
|
1055
|
-
all_errors = []
|
1056
|
-
if orphan_violations:
|
1057
|
-
all_errors.extend(orphan_violations)
|
1058
|
-
if detailed_errors:
|
1059
|
-
all_errors.extend(detailed_errors)
|
1060
|
-
|
1061
|
-
error_details = "\n - ".join(all_errors)
|
1062
|
-
raise ValueError(
|
1063
|
-
f"Failed to implement traits on {cls.__qualname__}:\n - {error_details}"
|
1064
|
-
)
|
1065
|
-
|
1066
|
-
# Add metadata for introspection
|
1067
|
-
cls.__declared_traits__ = frozenset(traits)
|
1068
|
-
|
1069
|
-
return cls
|
1070
|
-
|
1071
|
-
return decorator
|