lionagi 0.13.0__py3-none-any.whl → 0.13.1__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.
@@ -0,0 +1,1071 @@
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