exonware-xwnode 0.0.1.12__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.
Files changed (132) hide show
  1. exonware/__init__.py +14 -0
  2. exonware/xwnode/__init__.py +127 -0
  3. exonware/xwnode/base.py +676 -0
  4. exonware/xwnode/config.py +178 -0
  5. exonware/xwnode/contracts.py +730 -0
  6. exonware/xwnode/errors.py +503 -0
  7. exonware/xwnode/facade.py +460 -0
  8. exonware/xwnode/strategies/__init__.py +158 -0
  9. exonware/xwnode/strategies/advisor.py +463 -0
  10. exonware/xwnode/strategies/edges/__init__.py +32 -0
  11. exonware/xwnode/strategies/edges/adj_list.py +227 -0
  12. exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
  13. exonware/xwnode/strategies/edges/base.py +169 -0
  14. exonware/xwnode/strategies/flyweight.py +328 -0
  15. exonware/xwnode/strategies/impls/__init__.py +13 -0
  16. exonware/xwnode/strategies/impls/_base_edge.py +403 -0
  17. exonware/xwnode/strategies/impls/_base_node.py +307 -0
  18. exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
  19. exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
  20. exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
  21. exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
  22. exonware/xwnode/strategies/impls/edge_coo.py +533 -0
  23. exonware/xwnode/strategies/impls/edge_csc.py +447 -0
  24. exonware/xwnode/strategies/impls/edge_csr.py +492 -0
  25. exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
  26. exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
  27. exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
  28. exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
  29. exonware/xwnode/strategies/impls/edge_octree.py +574 -0
  30. exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
  31. exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
  32. exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
  33. exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
  34. exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
  35. exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
  36. exonware/xwnode/strategies/manager.py +775 -0
  37. exonware/xwnode/strategies/metrics.py +538 -0
  38. exonware/xwnode/strategies/migration.py +432 -0
  39. exonware/xwnode/strategies/nodes/__init__.py +50 -0
  40. exonware/xwnode/strategies/nodes/_base_node.py +307 -0
  41. exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
  42. exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
  43. exonware/xwnode/strategies/nodes/array_list.py +209 -0
  44. exonware/xwnode/strategies/nodes/base.py +247 -0
  45. exonware/xwnode/strategies/nodes/deque.py +200 -0
  46. exonware/xwnode/strategies/nodes/hash_map.py +135 -0
  47. exonware/xwnode/strategies/nodes/heap.py +307 -0
  48. exonware/xwnode/strategies/nodes/linked_list.py +232 -0
  49. exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
  50. exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
  51. exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
  52. exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
  53. exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
  54. exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
  55. exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
  56. exonware/xwnode/strategies/nodes/node_btree.py +357 -0
  57. exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
  58. exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
  59. exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
  60. exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
  61. exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
  62. exonware/xwnode/strategies/nodes/node_heap.py +191 -0
  63. exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
  64. exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
  65. exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
  66. exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
  67. exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
  68. exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
  69. exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
  70. exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
  71. exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
  72. exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
  73. exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
  74. exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
  75. exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
  76. exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
  77. exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
  78. exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
  79. exonware/xwnode/strategies/nodes/node_treap.py +387 -0
  80. exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
  81. exonware/xwnode/strategies/nodes/node_trie.py +252 -0
  82. exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
  83. exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
  84. exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
  85. exonware/xwnode/strategies/nodes/queue.py +161 -0
  86. exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
  87. exonware/xwnode/strategies/nodes/stack.py +152 -0
  88. exonware/xwnode/strategies/nodes/trie.py +274 -0
  89. exonware/xwnode/strategies/nodes/union_find.py +283 -0
  90. exonware/xwnode/strategies/pattern_detector.py +603 -0
  91. exonware/xwnode/strategies/performance_monitor.py +487 -0
  92. exonware/xwnode/strategies/queries/__init__.py +24 -0
  93. exonware/xwnode/strategies/queries/base.py +236 -0
  94. exonware/xwnode/strategies/queries/cql.py +201 -0
  95. exonware/xwnode/strategies/queries/cypher.py +181 -0
  96. exonware/xwnode/strategies/queries/datalog.py +70 -0
  97. exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
  98. exonware/xwnode/strategies/queries/eql.py +70 -0
  99. exonware/xwnode/strategies/queries/flux.py +70 -0
  100. exonware/xwnode/strategies/queries/gql.py +70 -0
  101. exonware/xwnode/strategies/queries/graphql.py +240 -0
  102. exonware/xwnode/strategies/queries/gremlin.py +181 -0
  103. exonware/xwnode/strategies/queries/hiveql.py +214 -0
  104. exonware/xwnode/strategies/queries/hql.py +70 -0
  105. exonware/xwnode/strategies/queries/jmespath.py +219 -0
  106. exonware/xwnode/strategies/queries/jq.py +66 -0
  107. exonware/xwnode/strategies/queries/json_query.py +66 -0
  108. exonware/xwnode/strategies/queries/jsoniq.py +248 -0
  109. exonware/xwnode/strategies/queries/kql.py +70 -0
  110. exonware/xwnode/strategies/queries/linq.py +238 -0
  111. exonware/xwnode/strategies/queries/logql.py +70 -0
  112. exonware/xwnode/strategies/queries/mql.py +68 -0
  113. exonware/xwnode/strategies/queries/n1ql.py +210 -0
  114. exonware/xwnode/strategies/queries/partiql.py +70 -0
  115. exonware/xwnode/strategies/queries/pig.py +215 -0
  116. exonware/xwnode/strategies/queries/promql.py +70 -0
  117. exonware/xwnode/strategies/queries/sparql.py +220 -0
  118. exonware/xwnode/strategies/queries/sql.py +275 -0
  119. exonware/xwnode/strategies/queries/xml_query.py +66 -0
  120. exonware/xwnode/strategies/queries/xpath.py +223 -0
  121. exonware/xwnode/strategies/queries/xquery.py +258 -0
  122. exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
  123. exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
  124. exonware/xwnode/strategies/registry.py +604 -0
  125. exonware/xwnode/strategies/simple.py +273 -0
  126. exonware/xwnode/strategies/utils.py +532 -0
  127. exonware/xwnode/types.py +912 -0
  128. exonware/xwnode/version.py +78 -0
  129. exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
  130. exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
  131. exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
  132. exonware_xwnode-0.0.1.12.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,775 @@
1
+ """
2
+ #exonware/xwnode/src/exonware/xwnode/strategies/manager.py
3
+
4
+ Enhanced Strategy Manager
5
+
6
+ This module provides the enhanced StrategyManager class that integrates:
7
+ - Flyweight pattern for memory optimization
8
+ - Data pattern detection for intelligent strategy selection
9
+ - Performance monitoring for optimization recommendations
10
+ - Lazy materialization and strategy management
11
+
12
+ Company: eXonware.com
13
+ Author: Eng. Muhammad AlShehri
14
+ Email: connect@exonware.com
15
+ Version: 0.0.1.12
16
+ Generation Date: 07-Sep-2025
17
+ """
18
+
19
+ import time
20
+ import threading
21
+ from typing import Dict, Optional, Any, Union, List
22
+ from exonware.xwsystem import get_logger
23
+
24
+ logger = get_logger(__name__)
25
+ from ..types import NodeMode, EdgeMode, NodeTrait, EdgeTrait
26
+ from .registry import get_registry
27
+ from .advisor import get_advisor
28
+ from .migration import get_migrator
29
+ from .flyweight import get_flyweight
30
+ from .pattern_detector import get_detector, DataProfile
31
+ from .performance_monitor import get_monitor, OperationType
32
+ from ..errors import (
33
+ XWNodeStrategyError, XWNodeError,
34
+ XWNodeStrategyError, XWNodeUnsupportedCapabilityError
35
+ )
36
+
37
+
38
+
39
+ class StrategyManager:
40
+ """
41
+ Enhanced strategy manager with integrated optimization components.
42
+
43
+ This class handles:
44
+ - Lazy materialization of strategies with flyweight optimization
45
+ - Intelligent pattern detection for AUTO mode selection
46
+ - Performance monitoring and optimization recommendations
47
+ - Strategy migration and validation
48
+ - Memory-efficient strategy management
49
+ """
50
+
51
+ def __init__(self,
52
+ node_mode: NodeMode = NodeMode.AUTO,
53
+ edge_mode: EdgeMode = EdgeMode.AUTO,
54
+ node_traits: NodeTrait = NodeTrait.NONE,
55
+ edge_traits: EdgeTrait = EdgeTrait.NONE,
56
+ **options):
57
+ """Initialize the enhanced strategy manager."""
58
+ self._node_mode_requested = node_mode
59
+ self._edge_mode_requested = edge_mode
60
+ self._node_traits = node_traits
61
+ self._edge_traits = edge_traits
62
+ self._options = options
63
+
64
+ # Lazy materialization state
65
+ self._node_strategy = None
66
+ self._edge_strategy = None
67
+ self._node_locked = False
68
+ self._edge_locked = False
69
+
70
+ # Performance tracking
71
+ self._node_metrics = {"operations": 0, "last_migration": None}
72
+ self._edge_metrics = {"operations": 0, "last_migration": None}
73
+
74
+ # Thread safety
75
+ self._lock = threading.RLock()
76
+
77
+ # Get global instances
78
+ self._registry = get_registry()
79
+ self._advisor = get_advisor()
80
+ self._migrator = get_migrator()
81
+
82
+ # Enhanced components
83
+ self._flyweight = get_flyweight()
84
+ self._detector = get_detector()
85
+ self._monitor = get_monitor()
86
+
87
+ # Strategy IDs for monitoring
88
+ self._node_strategy_id = f"node_{id(self)}"
89
+ self._edge_strategy_id = f"edge_{id(self)}"
90
+
91
+ edge_name = edge_mode.name if edge_mode is not None else "None"
92
+ logger.debug(f"🔧 Enhanced StrategyManager initialized: node={node_mode.name}, edge={edge_name}")
93
+
94
+ def _materialize_node_strategy(self) -> None:
95
+ """Lazily materialize the node strategy with enhanced optimization."""
96
+ if self._node_strategy is not None:
97
+ return
98
+
99
+ with self._lock:
100
+ if self._node_strategy is not None: # Double-check
101
+ return
102
+
103
+ start_time = time.time()
104
+
105
+ # Determine actual mode (AUTO or explicit)
106
+ if self._node_mode_requested == NodeMode.AUTO:
107
+ actual_mode = self._select_node_mode_enhanced()
108
+ else:
109
+ actual_mode = self._node_mode_requested
110
+
111
+ # Create strategy instance using flyweight pattern
112
+ try:
113
+ strategy_class = self._registry.get_node_strategy_class(actual_mode)
114
+ self._node_strategy = self._flyweight.get_node_strategy(
115
+ strategy_class,
116
+ mode=actual_mode,
117
+ traits=self._node_traits,
118
+ **self._options
119
+ )
120
+ self._node_locked = True
121
+
122
+ # Record strategy creation
123
+ creation_time = time.time() - start_time
124
+ self._monitor.record_operation(
125
+ self._node_strategy_id,
126
+ OperationType.CREATE,
127
+ creation_time,
128
+ error=False
129
+ )
130
+
131
+ logger.info(f"🎯 Materialized node strategy: {actual_mode.name} (flyweight optimized)")
132
+
133
+ except XWNodeStrategyError as e:
134
+ logger.error(f"❌ Requested node strategy '{actual_mode.name}' is not available")
135
+ self._monitor.record_operation(
136
+ self._node_strategy_id,
137
+ OperationType.CREATE,
138
+ time.time() - start_time,
139
+ error=True
140
+ )
141
+ raise XWNodeError(
142
+ message=f"Requested node strategy '{actual_mode.name}' is not available. "
143
+ f"Available strategies: {', '.join([mode.name for mode in self._registry.list_node_modes()])}",
144
+ cause=e
145
+ )
146
+ except Exception as e:
147
+ logger.error(f"❌ Failed to materialize node strategy {actual_mode.name}: {e}")
148
+ self._monitor.record_operation(
149
+ self._node_strategy_id,
150
+ OperationType.CREATE,
151
+ time.time() - start_time,
152
+ error=True
153
+ )
154
+ raise XWNodeError(message=f"Failed to materialize strategy '{actual_mode.name}': {e}", cause=e)
155
+
156
+ def _materialize_edge_strategy(self) -> None:
157
+ """Lazily materialize the edge strategy."""
158
+ if self._edge_strategy is not None:
159
+ return
160
+
161
+ # If edge mode is None (disabled for DATA_INTERCHANGE_OPTIMIZED), skip materialization
162
+ if self._edge_mode_requested is None:
163
+ logger.debug("🚫 Edge strategy disabled (None mode)")
164
+ return
165
+
166
+ with self._lock:
167
+ if self._edge_strategy is not None: # Double-check
168
+ return
169
+
170
+ # Determine actual mode (AUTO or explicit)
171
+ if self._edge_mode_requested == EdgeMode.AUTO:
172
+ actual_mode = self._select_edge_mode()
173
+ else:
174
+ actual_mode = self._edge_mode_requested
175
+
176
+ # Create strategy instance - no fallbacks allowed
177
+ try:
178
+ self._edge_strategy = self._registry.get_edge_strategy(
179
+ actual_mode,
180
+ traits=self._edge_traits,
181
+ **self._options
182
+ )
183
+ self._edge_locked = True
184
+
185
+ logger.info(f"🎯 Materialized edge strategy: {actual_mode.name}")
186
+
187
+ except XWNodeStrategyError as e:
188
+ logger.error(f"❌ Requested edge strategy '{actual_mode.name}' is not available")
189
+ raise XWNodeError(
190
+ message=f"Requested edge strategy '{actual_mode.name}' is not available. "
191
+ f"Available strategies: {', '.join([mode.name for mode in self._registry.list_edge_modes()])}",
192
+ cause=e
193
+ )
194
+ except Exception as e:
195
+ logger.error(f"❌ Failed to materialize edge strategy {actual_mode.name}: {e}")
196
+ raise XWNodeError(message=f"Failed to materialize edge strategy '{actual_mode.name}': {e}", cause=e)
197
+
198
+ def _select_node_mode_enhanced(self) -> NodeMode:
199
+ """
200
+ Enhanced node mode selection using pattern detection.
201
+
202
+ Returns:
203
+ Optimal node mode based on data analysis
204
+ """
205
+ # Analyze data if available
206
+ initial_data = self._options.get('initial_data')
207
+ if initial_data is not None:
208
+ try:
209
+ # Create data profile
210
+ profile = self._detector.analyze_data(initial_data, **self._options)
211
+
212
+ # Get recommendation
213
+ recommendation = self._detector.recommend_node_strategy(profile, **self._options)
214
+
215
+ logger.debug(f"🤖 Pattern-based recommendation: {recommendation.mode.name} "
216
+ f"(confidence: {recommendation.confidence:.2f})")
217
+
218
+ return recommendation.mode
219
+
220
+ except Exception as e:
221
+ logger.warning(f"⚠️ Pattern detection failed, falling back to heuristic selection: {e}")
222
+
223
+ # Fallback to original heuristic selection
224
+ return self._select_node_mode()
225
+
226
+ def _select_node_mode(self) -> NodeMode:
227
+ """Select optimal node mode using advisor heuristics."""
228
+ # Quick data type-based selection for common cases
229
+ is_dict = self._options.get('is_dict', False)
230
+ is_list = self._options.get('is_list', False)
231
+ has_numeric_indices = self._options.get('has_numeric_indices', False)
232
+ is_string_keys = self._options.get('is_string_keys', False)
233
+ initial_size = self._options.get('initial_size', 0)
234
+ initial_data = self._options.get('initial_data')
235
+
236
+ # Check if this is a leaf/primitive value that will use "value" key
237
+ # These should use TREE_GRAPH_HYBRID to maintain backward compatibility
238
+ # But fall back to HASH_MAP if TREE_GRAPH_HYBRID is not available
239
+ if ((initial_data is not None and not isinstance(initial_data, (dict, list))) or
240
+ (initial_data is None)) and not is_dict and not is_list:
241
+ # Use TREE_GRAPH_HYBRID for XWNode compatibility
242
+ from .impls.node_tree_graph_hybrid import TreeGraphHybridStrategy
243
+ return NodeMode.TREE_GRAPH_HYBRID
244
+
245
+ # Fast path for simple data type patterns
246
+ if is_list or has_numeric_indices:
247
+ # Data that looks like a list/array
248
+ if initial_size < 100:
249
+ return NodeMode.ARRAY_LIST
250
+ else:
251
+ return NodeMode.ARRAY_LIST # Could be enhanced with other list strategies later
252
+ elif is_dict and is_string_keys:
253
+ # String-keyed dictionary - check for prefix patterns
254
+ keys = list(self._options.get('initial_data', {}).keys()) if 'initial_data' in self._options else []
255
+ if len(keys) > 3 and any(key.startswith(other) for key in keys for other in keys if key != other):
256
+ return NodeMode.TRIE # Looks like prefix-heavy data
257
+ else:
258
+ return NodeMode.HASH_MAP # Regular dictionary
259
+ elif is_dict:
260
+ # Non-string keys or mixed keys
261
+ return NodeMode.HASH_MAP
262
+
263
+ # Build data profile for advisor for complex cases
264
+ data_profile = {
265
+ 'size': self._options.get('size', initial_size),
266
+ 'operations': self._node_metrics.get('operations', {}),
267
+ 'persistent': self._options.get('persistent', False),
268
+ 'write_heavy': self._options.get('write_heavy', False),
269
+ 'connectivity': self._options.get('connectivity', False),
270
+ 'streaming': self._options.get('streaming', False),
271
+ 'frequency': self._options.get('frequency', False),
272
+ 'binary': self._options.get('binary', False),
273
+ 'updates': self._options.get('updates', False),
274
+ }
275
+
276
+ # Get recommendation from advisor
277
+ recommendation = self._advisor.suggest_node_strategy(data_profile)
278
+
279
+ logger.debug(f"🤖 Node mode recommendation: {recommendation.mode.name} "
280
+ f"({recommendation.estimated_gain_percent:.1f}% gain)")
281
+
282
+ return recommendation.mode
283
+
284
+ def _select_edge_mode(self) -> EdgeMode:
285
+ """Select optimal edge mode using advisor heuristics."""
286
+ # Build graph profile for advisor
287
+ graph_profile = {
288
+ 'vertices': self._options.get('vertices', 0),
289
+ 'edges': self._options.get('edges', 0),
290
+ 'spatial': self._options.get('spatial', False),
291
+ 'temporal': self._options.get('temporal', False),
292
+ 'hyper': self._options.get('hyper', False),
293
+ 'undirected': self._options.get('undirected', False),
294
+ 'high_churn': self._options.get('high_churn', False),
295
+ 'dimensions': self._options.get('dimensions', 2),
296
+ }
297
+
298
+ # Get recommendation from advisor
299
+ recommendation = self._advisor.suggest_edge_strategy(graph_profile)
300
+
301
+ logger.debug(f"🤖 Edge mode recommendation: {recommendation.mode.name} "
302
+ f"({recommendation.estimated_gain_percent:.1f}% gain)")
303
+
304
+ return recommendation.mode
305
+
306
+ def get_node_strategy(self) -> Any:
307
+ """Get the node strategy instance (materializes if needed)."""
308
+ self._materialize_node_strategy()
309
+ return self._node_strategy
310
+
311
+ def get_edge_strategy(self) -> Any:
312
+ """Get the edge strategy instance (materializes if needed)."""
313
+ if self._edge_mode_requested is None:
314
+ return None # Edge strategy disabled
315
+ self._materialize_edge_strategy()
316
+ return self._edge_strategy
317
+
318
+ def rebuild_node_strategy(self, mode: NodeMode, *, allow_loss: bool = False) -> 'StrategyManager':
319
+ """
320
+ Rebuild node strategy with new mode.
321
+
322
+ Args:
323
+ mode: New node mode
324
+ allow_loss: Whether to allow data loss during migration
325
+
326
+ Returns:
327
+ Self for chaining
328
+
329
+ Raises:
330
+ IllegalMigrationError: If migration is not allowed
331
+ """
332
+ with self._lock:
333
+ if self._node_locked and not allow_loss:
334
+ # Check if migration would cause data loss
335
+ current_mode = self._get_current_node_mode()
336
+ if current_mode and self._would_lose_data(current_mode, mode, is_node=True):
337
+ raise XWNodeStrategyError(
338
+ current_mode.name, mode.name,
339
+ "Migration would cause data loss. Use allow_loss=True to override."
340
+ )
341
+
342
+ # Save data from current strategy before rebuilding
343
+ old_data = None
344
+ if self._node_strategy is not None:
345
+ try:
346
+ old_data = self._node_strategy.to_native()
347
+ except Exception as e:
348
+ logger.warning(f"⚠️ Failed to extract data during migration: {e}")
349
+
350
+ # Rebuild strategy
351
+ self._node_strategy = None
352
+ self._node_locked = False
353
+ self._node_mode_requested = mode
354
+
355
+ # Materialize new strategy
356
+ self._materialize_node_strategy()
357
+
358
+ # Restore data to new strategy
359
+ if old_data is not None and self._node_strategy is not None:
360
+ try:
361
+ if isinstance(old_data, dict):
362
+ for key, value in old_data.items():
363
+ self._node_strategy.put(key, value)
364
+ elif isinstance(old_data, list):
365
+ for i, value in enumerate(old_data):
366
+ self._node_strategy.put(str(i), value)
367
+ else:
368
+ # For leaf nodes, store as value
369
+ self._node_strategy.put("value", old_data)
370
+ except Exception as e:
371
+ logger.warning(f"⚠️ Failed to restore data during migration: {e}")
372
+
373
+ # Update metrics
374
+ self._node_metrics["last_migration"] = time.time()
375
+
376
+ logger.info(f"🔄 Rebuilt node strategy to {mode.name}")
377
+ return self
378
+
379
+ def rebuild_edge_strategy(self, mode: EdgeMode, *, allow_loss: bool = False) -> 'StrategyManager':
380
+ """
381
+ Rebuild edge strategy with new mode.
382
+
383
+ Args:
384
+ mode: New edge mode
385
+ allow_loss: Whether to allow data loss during migration
386
+
387
+ Returns:
388
+ Self for chaining
389
+
390
+ Raises:
391
+ IllegalMigrationError: If migration is not allowed
392
+ """
393
+ with self._lock:
394
+ if self._edge_locked and not allow_loss:
395
+ # Check if migration would cause data loss
396
+ current_mode = self._get_current_edge_mode()
397
+ if current_mode and self._would_lose_data(current_mode, mode, is_node=False):
398
+ raise XWNodeStrategyError(
399
+ current_mode.name, mode.name,
400
+ "Migration would cause data loss. Use allow_loss=True to override."
401
+ )
402
+
403
+ # Rebuild strategy
404
+ self._edge_strategy = None
405
+ self._edge_locked = False
406
+ self._edge_mode_requested = mode
407
+
408
+ # Materialize new strategy
409
+ self._materialize_edge_strategy()
410
+
411
+ # Update metrics
412
+ self._edge_metrics["last_migration"] = time.time()
413
+
414
+ logger.info(f"🔄 Rebuilt edge strategy to {mode.name}")
415
+ return self
416
+
417
+ def _get_current_node_mode(self) -> Optional[NodeMode]:
418
+ """Get current node mode if materialized."""
419
+ if self._node_strategy is None:
420
+ return None
421
+
422
+ # Try to get mode from strategy
423
+ try:
424
+ return self._node_strategy.mode
425
+ except AttributeError:
426
+ return self._node_mode_requested
427
+
428
+ def _get_current_edge_mode(self) -> Optional[EdgeMode]:
429
+ """Get current edge mode if materialized."""
430
+ if self._edge_strategy is None:
431
+ return None
432
+
433
+ # Try to get mode from strategy
434
+ try:
435
+ return self._edge_strategy.mode
436
+ except AttributeError:
437
+ return self._edge_mode_requested
438
+
439
+ def _would_lose_data(self, from_mode: Union[NodeMode, EdgeMode],
440
+ to_mode: Union[NodeMode, EdgeMode], is_node: bool) -> bool:
441
+ """Check if migration would cause data loss."""
442
+ # Get recommendation to assess data loss risk
443
+ if is_node:
444
+ recommendation = self._advisor.suggest_node_strategy({}, from_mode)
445
+ else:
446
+ recommendation = self._advisor.suggest_edge_strategy({}, from_mode)
447
+
448
+ return recommendation.data_loss_risk
449
+
450
+ def record_operation(self, operation: str, duration: float,
451
+ memory_usage: float = 0.0, is_node: bool = True) -> None:
452
+ """Record operation for performance monitoring."""
453
+ strategy_id = f"{self._get_current_node_mode().name if is_node else self._get_current_edge_mode().name}"
454
+
455
+ # Record with enhanced monitor
456
+ operation_type = OperationType(operation.lower()) if hasattr(OperationType, operation.upper()) else OperationType.GET
457
+ monitor_id = self._node_strategy_id if is_node else self._edge_strategy_id
458
+
459
+ self._monitor.record_operation(
460
+ strategy_id=monitor_id,
461
+ operation=operation_type,
462
+ duration=duration,
463
+ memory_usage=memory_usage,
464
+ error=False
465
+ )
466
+
467
+ # Also record with advisor for backward compatibility
468
+ self._advisor.record_operation(
469
+ strategy_id=strategy_id,
470
+ operation=operation,
471
+ duration=duration,
472
+ memory_usage=memory_usage,
473
+ is_node=is_node
474
+ )
475
+
476
+ # Update local metrics
477
+ if is_node:
478
+ self._node_metrics["operations"] += 1
479
+ else:
480
+ self._edge_metrics["operations"] += 1
481
+
482
+ def get_performance_profile(self, is_node: bool = True) -> Dict[str, Any]:
483
+ """Get performance profile for current strategy."""
484
+ strategy_id = f"{self._get_current_node_mode().name if is_node else self._get_current_edge_mode().name}"
485
+ return self._advisor.get_performance_profile(strategy_id, is_node)
486
+
487
+ def get_enhanced_performance_summary(self) -> Dict[str, Any]:
488
+ """Get comprehensive performance summary with all metrics."""
489
+ return {
490
+ 'flyweight_stats': self._flyweight.get_stats(),
491
+ 'monitor_summary': self._monitor.get_performance_summary(),
492
+ 'detector_stats': self._detector.get_stats(),
493
+ 'strategy_info': self.get_strategy_info()
494
+ }
495
+
496
+ def get_optimization_recommendations(self) -> Dict[str, Any]:
497
+ """Get optimization recommendations for current strategies."""
498
+ recommendations = {}
499
+
500
+ # Get node strategy recommendations
501
+ if self._node_strategy is not None:
502
+ node_recommendations = self._monitor.generate_recommendations(self._node_strategy_id)
503
+ recommendations['node'] = [
504
+ {
505
+ 'type': rec.recommendation_type,
506
+ 'confidence': rec.confidence,
507
+ 'reasoning': rec.reasoning,
508
+ 'estimated_improvement': rec.estimated_improvement,
509
+ 'alternative': rec.alternative_strategy
510
+ }
511
+ for rec in node_recommendations
512
+ ]
513
+
514
+ # Get edge strategy recommendations
515
+ if self._edge_strategy is not None:
516
+ edge_recommendations = self._monitor.generate_recommendations(self._edge_strategy_id)
517
+ recommendations['edge'] = [
518
+ {
519
+ 'type': rec.recommendation_type,
520
+ 'confidence': rec.confidence,
521
+ 'reasoning': rec.reasoning,
522
+ 'estimated_improvement': rec.estimated_improvement,
523
+ 'alternative': rec.alternative_strategy
524
+ }
525
+ for rec in edge_recommendations
526
+ ]
527
+
528
+ return recommendations
529
+
530
+ def analyze_data_patterns(self, data: Any, **context: Any) -> DataProfile:
531
+ """Analyze data patterns for strategy optimization."""
532
+ return self._detector.analyze_data(data, **context)
533
+
534
+ def get_flyweight_stats(self) -> Dict[str, Any]:
535
+ """Get flyweight pattern statistics."""
536
+ return self._flyweight.get_stats()
537
+
538
+ def clear_flyweight_cache(self) -> None:
539
+ """Clear flyweight cache to free memory."""
540
+ self._flyweight.clear_cache()
541
+ logger.info("🧹 Cleared flyweight cache")
542
+
543
+ def get_monitor_history(self, limit: int = 100) -> List[Dict[str, Any]]:
544
+ """Get recent operation history from monitor."""
545
+ return self._monitor.get_operation_history(limit)
546
+
547
+ def get_strategy_info(self) -> Dict[str, Any]:
548
+ """Get comprehensive strategy information."""
549
+ with self._lock:
550
+ return {
551
+ "node": {
552
+ "requested_mode": self._node_mode_requested.name,
553
+ "current_mode": self._get_current_node_mode().name if self._node_strategy else None,
554
+ "materialized": self._node_strategy is not None,
555
+ "locked": self._node_locked,
556
+ "traits": str(self._node_traits),
557
+ "metrics": self._node_metrics.copy(),
558
+ "performance": self.get_performance_profile(is_node=True) if self._node_strategy else None
559
+ },
560
+ "edge": {
561
+ "requested_mode": self._edge_mode_requested.name,
562
+ "current_mode": self._get_current_edge_mode().name if self._edge_strategy else None,
563
+ "materialized": self._edge_strategy is not None,
564
+ "locked": self._edge_locked,
565
+ "traits": str(self._edge_traits),
566
+ "metrics": self._edge_metrics.copy(),
567
+ "performance": self.get_performance_profile(is_node=False) if self._edge_strategy else None
568
+ },
569
+ "options": self._options.copy()
570
+ }
571
+
572
+ def check_capability(self, capability: Union[NodeTrait, EdgeTrait], is_node: bool = True) -> bool:
573
+ """Check if current strategy supports a capability."""
574
+ if is_node:
575
+ return capability in self._node_traits
576
+ else:
577
+ return capability in self._edge_traits
578
+
579
+ def require_capability(self, capability: Union[NodeTrait, EdgeTrait], is_node: bool = True) -> None:
580
+ """Require a capability, raising error if not supported."""
581
+ if not self.check_capability(capability, is_node):
582
+ current_mode = self._get_current_node_mode() if is_node else self._get_current_edge_mode()
583
+ current_traits = self._node_traits if is_node else self._edge_traits
584
+
585
+ raise XWNodeUnsupportedCapabilityError(
586
+ capability=str(capability),
587
+ strategy=current_mode.name if current_mode else "unmaterialized",
588
+ available_capabilities=[str(trait) for trait in current_traits]
589
+ )
590
+
591
+ # ============================================================================
592
+ # STRATEGY MIGRATION
593
+ # ============================================================================
594
+
595
+ def migrate_node_strategy(self, target_mode: NodeMode,
596
+ target_traits: NodeTrait = NodeTrait.NONE,
597
+ **options) -> None:
598
+ """Migrate the current node strategy to a new mode."""
599
+ with self._lock:
600
+ if self._node_strategy is None:
601
+ # No current strategy, just update the requested mode
602
+ self._node_mode_requested = target_mode
603
+ self._node_traits = target_traits
604
+ return
605
+
606
+ logger.info(f"🔄 Migrating node strategy: {self._get_current_node_mode().name} → {target_mode.name}")
607
+
608
+ # Use migrator to perform the migration
609
+ new_strategy = self._migrator.execute_node_migration(
610
+ self._node_strategy, target_mode, target_traits, **options
611
+ )
612
+
613
+ # Update manager state
614
+ self._node_strategy = new_strategy
615
+ self._node_mode_requested = target_mode
616
+ self._node_traits = target_traits
617
+
618
+ logger.info(f"✅ Node strategy migration completed")
619
+
620
+ def migrate_edge_strategy(self, target_mode: EdgeMode,
621
+ target_traits: EdgeTrait = EdgeTrait.NONE,
622
+ **options) -> None:
623
+ """Migrate the current edge strategy to a new mode."""
624
+ with self._lock:
625
+ if self._edge_strategy is None:
626
+ # No current strategy, just update the requested mode
627
+ self._edge_mode_requested = target_mode
628
+ self._edge_traits = target_traits
629
+ return
630
+
631
+ logger.info(f"🔄 Migrating edge strategy: {self._get_current_edge_mode().name} → {target_mode.name}")
632
+
633
+ # Use migrator to perform the migration
634
+ new_strategy = self._migrator.execute_edge_migration(
635
+ self._edge_strategy, target_mode, target_traits, **options
636
+ )
637
+
638
+ # Update manager state
639
+ self._edge_strategy = new_strategy
640
+ self._edge_mode_requested = target_mode
641
+ self._edge_traits = target_traits
642
+
643
+ logger.info(f"✅ Edge strategy migration completed")
644
+
645
+ def plan_node_migration(self, target_mode: NodeMode,
646
+ target_traits: NodeTrait = NodeTrait.NONE) -> Any:
647
+ """Plan a migration for the current node strategy."""
648
+ current_mode = self._get_current_node_mode()
649
+ current_traits = self._node_traits
650
+ data_size = len(self._node_strategy) if self._node_strategy else 0
651
+
652
+ return self._migrator.plan_node_migration(
653
+ current_mode, target_mode, current_traits, target_traits, data_size
654
+ )
655
+
656
+ def plan_edge_migration(self, target_mode: EdgeMode,
657
+ target_traits: EdgeTrait = EdgeTrait.NONE) -> Any:
658
+ """Plan a migration for the current edge strategy."""
659
+ current_mode = self._get_current_edge_mode()
660
+ current_traits = self._edge_traits
661
+ edge_count = len(self._edge_strategy) if self._edge_strategy else 0
662
+ vertex_count = self._edge_strategy.vertex_count() if self._edge_strategy else 0
663
+
664
+ return self._migrator.plan_edge_migration(
665
+ current_mode, target_mode, current_traits, target_traits, edge_count, vertex_count
666
+ )
667
+
668
+ # ============================================================================
669
+ # PERFORMANCE MONITORING
670
+ # ============================================================================
671
+
672
+ def get_performance_report(self) -> Dict[str, Any]:
673
+ """Get basic performance report for this strategy manager."""
674
+ node_mode = self._get_current_node_mode()
675
+ edge_mode = self._get_current_edge_mode()
676
+
677
+ return {
678
+ 'current_node_strategy': node_mode.name if node_mode else "unknown",
679
+ 'current_edge_strategy': edge_mode.name if edge_mode else "unknown",
680
+ 'node_strategy_active': self._node_strategy is not None,
681
+ 'edge_strategy_active': self._edge_strategy is not None
682
+ }
683
+
684
+ def copy(self) -> 'StrategyManager':
685
+ """Create a deep copy of this strategy manager."""
686
+ new_manager = StrategyManager(
687
+ node_mode=self._node_mode_requested,
688
+ edge_mode=self._edge_mode_requested,
689
+ node_traits=self._node_traits,
690
+ edge_traits=self._edge_traits,
691
+ **self._options
692
+ )
693
+
694
+ # Copy the materialized strategies if they exist
695
+ if self._node_strategy is not None:
696
+ new_manager._node_strategy = self._node_strategy
697
+ new_manager._node_locked = True
698
+
699
+ if self._edge_strategy is not None:
700
+ new_manager._edge_strategy = self._edge_strategy
701
+ new_manager._edge_locked = True
702
+
703
+ return new_manager
704
+
705
+ def create_node_strategy(self, data: Any) -> Any:
706
+ """Create a node strategy from data and return the internal representation."""
707
+ self._materialize_node_strategy()
708
+
709
+ if self._node_strategy is not None:
710
+ return self._node_strategy.create_from_data(data)
711
+ else:
712
+ # This should never happen if _materialize_node_strategy() worked correctly
713
+ raise XWNodeError(
714
+ message="Node strategy materialization failed but no exception was raised"
715
+ )
716
+
717
+ def create_reference_strategy(self, uri: str, reference_type: str = "generic", metadata: Optional[Dict[str, Any]] = None) -> Any:
718
+ """Create a reference strategy."""
719
+ self._materialize_node_strategy()
720
+
721
+ if self._node_strategy is not None:
722
+ # Use the strategy's reference creation method if available
723
+ if hasattr(self._node_strategy, 'create_reference'):
724
+ return self._node_strategy.create_reference(uri, reference_type, metadata or {})
725
+ else:
726
+ # Create a reference using the strategy's factory
727
+ reference_data = {
728
+ 'uri': uri,
729
+ 'reference_type': reference_type,
730
+ 'metadata': metadata or {},
731
+ '_type': 'reference'
732
+ }
733
+ return self._node_strategy.create_from_data(reference_data)
734
+ else:
735
+ # This should never happen if _materialize_node_strategy() worked correctly
736
+ raise XWNodeError(
737
+ message="Node strategy materialization failed but no exception was raised"
738
+ )
739
+
740
+ def create_object_strategy(self, uri: str, object_type: str, mime_type: Optional[str] = None, size: Optional[int] = None, metadata: Optional[Dict[str, Any]] = None) -> Any:
741
+ """Create an object strategy."""
742
+ self._materialize_node_strategy()
743
+
744
+ if self._node_strategy is not None:
745
+ # Use the strategy's object creation method if available
746
+ if hasattr(self._node_strategy, 'create_object'):
747
+ return self._node_strategy.create_object(uri, object_type, mime_type, size, metadata or {})
748
+ else:
749
+ # Create an object using the strategy's factory
750
+ object_data = {
751
+ 'uri': uri,
752
+ 'object_type': object_type,
753
+ 'mime_type': mime_type,
754
+ 'size': size,
755
+ 'metadata': metadata or {},
756
+ '_type': 'object'
757
+ }
758
+ return self._node_strategy.create_from_data(object_data)
759
+ else:
760
+ # This should never happen if _materialize_node_strategy() worked correctly
761
+ raise XWNodeError(
762
+ message="Node strategy materialization failed but no exception was raised"
763
+ )
764
+
765
+ def create_edge_strategy(self) -> Any:
766
+ """Create an edge strategy."""
767
+ self._materialize_edge_strategy()
768
+
769
+ if self._edge_strategy is not None:
770
+ return self._edge_strategy
771
+ else:
772
+ # This should never happen if _materialize_edge_strategy() worked correctly
773
+ raise XWNodeError(
774
+ message="Edge strategy materialization failed but no exception was raised"
775
+ )