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.
- exonware/__init__.py +14 -0
- exonware/xwnode/__init__.py +127 -0
- exonware/xwnode/base.py +676 -0
- exonware/xwnode/config.py +178 -0
- exonware/xwnode/contracts.py +730 -0
- exonware/xwnode/errors.py +503 -0
- exonware/xwnode/facade.py +460 -0
- exonware/xwnode/strategies/__init__.py +158 -0
- exonware/xwnode/strategies/advisor.py +463 -0
- exonware/xwnode/strategies/edges/__init__.py +32 -0
- exonware/xwnode/strategies/edges/adj_list.py +227 -0
- exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
- exonware/xwnode/strategies/edges/base.py +169 -0
- exonware/xwnode/strategies/flyweight.py +328 -0
- exonware/xwnode/strategies/impls/__init__.py +13 -0
- exonware/xwnode/strategies/impls/_base_edge.py +403 -0
- exonware/xwnode/strategies/impls/_base_node.py +307 -0
- exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
- exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
- exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
- exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
- exonware/xwnode/strategies/impls/edge_coo.py +533 -0
- exonware/xwnode/strategies/impls/edge_csc.py +447 -0
- exonware/xwnode/strategies/impls/edge_csr.py +492 -0
- exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
- exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
- exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
- exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
- exonware/xwnode/strategies/impls/edge_octree.py +574 -0
- exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
- exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
- exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
- exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
- exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
- exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
- exonware/xwnode/strategies/manager.py +775 -0
- exonware/xwnode/strategies/metrics.py +538 -0
- exonware/xwnode/strategies/migration.py +432 -0
- exonware/xwnode/strategies/nodes/__init__.py +50 -0
- exonware/xwnode/strategies/nodes/_base_node.py +307 -0
- exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
- exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
- exonware/xwnode/strategies/nodes/array_list.py +209 -0
- exonware/xwnode/strategies/nodes/base.py +247 -0
- exonware/xwnode/strategies/nodes/deque.py +200 -0
- exonware/xwnode/strategies/nodes/hash_map.py +135 -0
- exonware/xwnode/strategies/nodes/heap.py +307 -0
- exonware/xwnode/strategies/nodes/linked_list.py +232 -0
- exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
- exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
- exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
- exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
- exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
- exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
- exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
- exonware/xwnode/strategies/nodes/node_btree.py +357 -0
- exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
- exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
- exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
- exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
- exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
- exonware/xwnode/strategies/nodes/node_heap.py +191 -0
- exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
- exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
- exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
- exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
- exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
- exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
- exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
- exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
- exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
- exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
- exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
- exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
- exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
- exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
- exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
- exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
- exonware/xwnode/strategies/nodes/node_treap.py +387 -0
- exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
- exonware/xwnode/strategies/nodes/node_trie.py +252 -0
- exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
- exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
- exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
- exonware/xwnode/strategies/nodes/queue.py +161 -0
- exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
- exonware/xwnode/strategies/nodes/stack.py +152 -0
- exonware/xwnode/strategies/nodes/trie.py +274 -0
- exonware/xwnode/strategies/nodes/union_find.py +283 -0
- exonware/xwnode/strategies/pattern_detector.py +603 -0
- exonware/xwnode/strategies/performance_monitor.py +487 -0
- exonware/xwnode/strategies/queries/__init__.py +24 -0
- exonware/xwnode/strategies/queries/base.py +236 -0
- exonware/xwnode/strategies/queries/cql.py +201 -0
- exonware/xwnode/strategies/queries/cypher.py +181 -0
- exonware/xwnode/strategies/queries/datalog.py +70 -0
- exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
- exonware/xwnode/strategies/queries/eql.py +70 -0
- exonware/xwnode/strategies/queries/flux.py +70 -0
- exonware/xwnode/strategies/queries/gql.py +70 -0
- exonware/xwnode/strategies/queries/graphql.py +240 -0
- exonware/xwnode/strategies/queries/gremlin.py +181 -0
- exonware/xwnode/strategies/queries/hiveql.py +214 -0
- exonware/xwnode/strategies/queries/hql.py +70 -0
- exonware/xwnode/strategies/queries/jmespath.py +219 -0
- exonware/xwnode/strategies/queries/jq.py +66 -0
- exonware/xwnode/strategies/queries/json_query.py +66 -0
- exonware/xwnode/strategies/queries/jsoniq.py +248 -0
- exonware/xwnode/strategies/queries/kql.py +70 -0
- exonware/xwnode/strategies/queries/linq.py +238 -0
- exonware/xwnode/strategies/queries/logql.py +70 -0
- exonware/xwnode/strategies/queries/mql.py +68 -0
- exonware/xwnode/strategies/queries/n1ql.py +210 -0
- exonware/xwnode/strategies/queries/partiql.py +70 -0
- exonware/xwnode/strategies/queries/pig.py +215 -0
- exonware/xwnode/strategies/queries/promql.py +70 -0
- exonware/xwnode/strategies/queries/sparql.py +220 -0
- exonware/xwnode/strategies/queries/sql.py +275 -0
- exonware/xwnode/strategies/queries/xml_query.py +66 -0
- exonware/xwnode/strategies/queries/xpath.py +223 -0
- exonware/xwnode/strategies/queries/xquery.py +258 -0
- exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
- exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
- exonware/xwnode/strategies/registry.py +604 -0
- exonware/xwnode/strategies/simple.py +273 -0
- exonware/xwnode/strategies/utils.py +532 -0
- exonware/xwnode/types.py +912 -0
- exonware/xwnode/version.py +78 -0
- exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
- exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
- exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
- 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
|
+
)
|