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,532 @@
|
|
1
|
+
"""
|
2
|
+
Shared utilities for XWNode strategies.
|
3
|
+
|
4
|
+
This module provides common functionality that can be used by multiple strategies
|
5
|
+
without creating cross-dependencies between strategies.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import threading
|
9
|
+
from typing import Any, Dict, List, Optional, Iterator, Tuple, Union
|
10
|
+
from collections import OrderedDict
|
11
|
+
import weakref
|
12
|
+
import time
|
13
|
+
|
14
|
+
# Use xSystem logging
|
15
|
+
from exonware.xwsystem import get_logger
|
16
|
+
|
17
|
+
logger = get_logger('xnode.strategies.utils')
|
18
|
+
|
19
|
+
|
20
|
+
# ============================================================================
|
21
|
+
# PATH PARSING UTILITIES
|
22
|
+
# ============================================================================
|
23
|
+
|
24
|
+
class PathParser:
|
25
|
+
"""Thread-safe path parser with caching for use by multiple strategies."""
|
26
|
+
|
27
|
+
def __init__(self, max_cache_size: int = 1024):
|
28
|
+
self._cache = OrderedDict()
|
29
|
+
self._max_cache_size = max_cache_size
|
30
|
+
self._lock = threading.RLock()
|
31
|
+
|
32
|
+
def parse(self, path: str) -> List[str]:
|
33
|
+
"""Parse a path string into parts."""
|
34
|
+
with self._lock:
|
35
|
+
if path in self._cache:
|
36
|
+
return self._cache[path]
|
37
|
+
|
38
|
+
parts = self._parse_path(path)
|
39
|
+
|
40
|
+
# Cache the result
|
41
|
+
if len(self._cache) >= self._max_cache_size:
|
42
|
+
self._cache.popitem(last=False)
|
43
|
+
self._cache[path] = parts
|
44
|
+
|
45
|
+
return parts
|
46
|
+
|
47
|
+
def _parse_path(self, path: str) -> List[str]:
|
48
|
+
"""Internal path parsing logic."""
|
49
|
+
if not path:
|
50
|
+
return []
|
51
|
+
|
52
|
+
# Simple dot-separated path parsing
|
53
|
+
return [part for part in path.split('.') if part]
|
54
|
+
|
55
|
+
|
56
|
+
# ============================================================================
|
57
|
+
# ADVANCED DATA STRUCTURES (Shared implementations)
|
58
|
+
# ============================================================================
|
59
|
+
|
60
|
+
class TrieNode:
|
61
|
+
"""Internal node for Trie structure - shared across strategies."""
|
62
|
+
|
63
|
+
def __init__(self):
|
64
|
+
self.children: Dict[str, 'TrieNode'] = {}
|
65
|
+
self.is_end_word: bool = False
|
66
|
+
self.value: Any = None
|
67
|
+
|
68
|
+
def __repr__(self):
|
69
|
+
return f"TrieNode(children={len(self.children)}, is_end={self.is_end_word})"
|
70
|
+
|
71
|
+
|
72
|
+
class UnionFind:
|
73
|
+
"""Union-Find (Disjoint Set) data structure - shared across strategies."""
|
74
|
+
|
75
|
+
def __init__(self):
|
76
|
+
self._parent: Dict[Any, Any] = {}
|
77
|
+
self._rank: Dict[Any, int] = {}
|
78
|
+
self._sets_count = 0
|
79
|
+
|
80
|
+
def make_set(self, x: Any) -> None:
|
81
|
+
"""Create new set with element x. O(1)"""
|
82
|
+
if x not in self._parent:
|
83
|
+
self._parent[x] = x
|
84
|
+
self._rank[x] = 0
|
85
|
+
self._sets_count += 1
|
86
|
+
|
87
|
+
def find(self, x: Any) -> Any:
|
88
|
+
"""Find root of set containing x with path compression. α(n) ≈ O(1)"""
|
89
|
+
if x not in self._parent:
|
90
|
+
raise ValueError(f"Element {x} not found in union-find structure")
|
91
|
+
|
92
|
+
# Path compression
|
93
|
+
if self._parent[x] != x:
|
94
|
+
self._parent[x] = self.find(self._parent[x])
|
95
|
+
|
96
|
+
return self._parent[x]
|
97
|
+
|
98
|
+
def union(self, x: Any, y: Any) -> None:
|
99
|
+
"""Union sets containing x and y by rank. α(n) ≈ O(1)"""
|
100
|
+
# Ensure both elements exist
|
101
|
+
self.make_set(x)
|
102
|
+
self.make_set(y)
|
103
|
+
|
104
|
+
root_x = self.find(x)
|
105
|
+
root_y = self.find(y)
|
106
|
+
|
107
|
+
if root_x == root_y:
|
108
|
+
return # Already in same set
|
109
|
+
|
110
|
+
# Union by rank
|
111
|
+
if self._rank[root_x] < self._rank[root_y]:
|
112
|
+
root_x, root_y = root_y, root_x
|
113
|
+
|
114
|
+
self._parent[root_y] = root_x
|
115
|
+
if self._rank[root_x] == self._rank[root_y]:
|
116
|
+
self._rank[root_x] += 1
|
117
|
+
|
118
|
+
self._sets_count -= 1
|
119
|
+
|
120
|
+
def connected(self, x: Any, y: Any) -> bool:
|
121
|
+
"""Check if x and y are in same set. α(n) ≈ O(1)"""
|
122
|
+
try:
|
123
|
+
return self.find(x) == self.find(y)
|
124
|
+
except ValueError:
|
125
|
+
return False
|
126
|
+
|
127
|
+
def size(self) -> int:
|
128
|
+
"""Get number of elements. O(1)"""
|
129
|
+
return len(self._parent)
|
130
|
+
|
131
|
+
def sets_count(self) -> int:
|
132
|
+
"""Get number of disjoint sets. O(1)"""
|
133
|
+
return self._sets_count
|
134
|
+
|
135
|
+
|
136
|
+
class MinHeap:
|
137
|
+
"""Min-heap implementation for priority queue operations - shared across strategies."""
|
138
|
+
|
139
|
+
def __init__(self):
|
140
|
+
self._heap: List[Tuple[float, Any]] = []
|
141
|
+
self._size = 0
|
142
|
+
|
143
|
+
def push(self, value: Any, priority: float = 0.0) -> None:
|
144
|
+
"""Push item with priority. O(log n)"""
|
145
|
+
self._heap.append((priority, value))
|
146
|
+
self._size += 1
|
147
|
+
self._heapify_up(self._size - 1)
|
148
|
+
|
149
|
+
def pop_min(self) -> Any:
|
150
|
+
"""Pop minimum priority item. O(log n)"""
|
151
|
+
if self._size == 0:
|
152
|
+
raise IndexError("Heap is empty")
|
153
|
+
|
154
|
+
min_val = self._heap[0][1]
|
155
|
+
self._heap[0] = self._heap[self._size - 1]
|
156
|
+
self._heap.pop()
|
157
|
+
self._size -= 1
|
158
|
+
|
159
|
+
if self._size > 0:
|
160
|
+
self._heapify_down(0)
|
161
|
+
|
162
|
+
return min_val
|
163
|
+
|
164
|
+
def peek_min(self) -> Any:
|
165
|
+
"""Peek at minimum without removing. O(1)"""
|
166
|
+
if self._size == 0:
|
167
|
+
raise IndexError("Heap is empty")
|
168
|
+
return self._heap[0][1]
|
169
|
+
|
170
|
+
def _heapify_up(self, index: int) -> None:
|
171
|
+
"""Move element up to maintain heap property."""
|
172
|
+
parent = (index - 1) // 2
|
173
|
+
if parent >= 0 and self._heap[index][0] < self._heap[parent][0]:
|
174
|
+
self._heap[index], self._heap[parent] = self._heap[parent], self._heap[index]
|
175
|
+
self._heapify_up(parent)
|
176
|
+
|
177
|
+
def _heapify_down(self, index: int) -> None:
|
178
|
+
"""Move element down to maintain heap property."""
|
179
|
+
smallest = index
|
180
|
+
left = 2 * index + 1
|
181
|
+
right = 2 * index + 2
|
182
|
+
|
183
|
+
if left < self._size and self._heap[left][0] < self._heap[smallest][0]:
|
184
|
+
smallest = left
|
185
|
+
|
186
|
+
if right < self._size and self._heap[right][0] < self._heap[smallest][0]:
|
187
|
+
smallest = right
|
188
|
+
|
189
|
+
if smallest != index:
|
190
|
+
self._heap[index], self._heap[smallest] = self._heap[smallest], self._heap[index]
|
191
|
+
self._heapify_down(smallest)
|
192
|
+
|
193
|
+
def size(self) -> int:
|
194
|
+
"""Get heap size. O(1)"""
|
195
|
+
return self._size
|
196
|
+
|
197
|
+
def is_empty(self) -> bool:
|
198
|
+
"""Check if heap is empty. O(1)"""
|
199
|
+
return self._size == 0
|
200
|
+
|
201
|
+
|
202
|
+
# ============================================================================
|
203
|
+
# COMMON UTILITY FUNCTIONS
|
204
|
+
# ============================================================================
|
205
|
+
|
206
|
+
def recursive_to_native(obj: Any) -> Any:
|
207
|
+
"""
|
208
|
+
Recursively convert objects to native Python types.
|
209
|
+
|
210
|
+
This is a shared utility for converting complex objects (including XWNode objects)
|
211
|
+
to native Python types for serialization and comparison.
|
212
|
+
"""
|
213
|
+
if hasattr(obj, 'to_native'):
|
214
|
+
# This is an XWNode, recursively convert it
|
215
|
+
return recursive_to_native(obj.to_native())
|
216
|
+
elif isinstance(obj, dict):
|
217
|
+
return {k: recursive_to_native(v) for k, v in obj.items()}
|
218
|
+
elif isinstance(obj, list):
|
219
|
+
return [recursive_to_native(item) for item in obj]
|
220
|
+
else:
|
221
|
+
return obj
|
222
|
+
|
223
|
+
|
224
|
+
def is_sequential_numeric_keys(data: Dict[str, Any]) -> bool:
|
225
|
+
"""
|
226
|
+
Check if dictionary keys are sequential numeric indices (for list detection).
|
227
|
+
|
228
|
+
This is useful for determining if a dict represents a list structure.
|
229
|
+
"""
|
230
|
+
if not data:
|
231
|
+
return False
|
232
|
+
|
233
|
+
keys = list(data.keys())
|
234
|
+
try:
|
235
|
+
indices = [int(k) for k in keys]
|
236
|
+
return indices == list(range(len(indices)))
|
237
|
+
except ValueError:
|
238
|
+
return False
|
239
|
+
|
240
|
+
|
241
|
+
def calculate_structural_hash(data: Dict[str, Any]) -> int:
|
242
|
+
"""
|
243
|
+
Calculate a structural hash based on keys only (not values).
|
244
|
+
|
245
|
+
This is useful for fast equality checking when values don't matter.
|
246
|
+
"""
|
247
|
+
return hash(tuple(sorted(data.keys())))
|
248
|
+
|
249
|
+
|
250
|
+
def validate_traits(supported_traits, requested_traits, strategy_name: str) -> None:
|
251
|
+
"""
|
252
|
+
Validate that requested traits are supported by a strategy.
|
253
|
+
|
254
|
+
Args:
|
255
|
+
supported_traits: Traits supported by the strategy
|
256
|
+
requested_traits: Traits requested by the user
|
257
|
+
strategy_name: Name of the strategy for error messages
|
258
|
+
"""
|
259
|
+
unsupported = requested_traits & ~supported_traits
|
260
|
+
if unsupported != 0:
|
261
|
+
unsupported_names = [trait.name for trait in unsupported]
|
262
|
+
raise ValueError(f"Strategy {strategy_name} does not support traits: {unsupported_names}")
|
263
|
+
|
264
|
+
|
265
|
+
# ============================================================================
|
266
|
+
# PERFORMANCE MONITORING UTILITIES
|
267
|
+
# ============================================================================
|
268
|
+
|
269
|
+
class PerformanceTracker:
|
270
|
+
"""Shared performance tracking utilities for strategies."""
|
271
|
+
|
272
|
+
def __init__(self):
|
273
|
+
self._access_count = 0
|
274
|
+
self._cache_hits = 0
|
275
|
+
self._cache_misses = 0
|
276
|
+
self._operation_times: Dict[str, List[float]] = {}
|
277
|
+
self._lock = threading.RLock()
|
278
|
+
|
279
|
+
def record_access(self) -> None:
|
280
|
+
"""Record a data access operation."""
|
281
|
+
with self._lock:
|
282
|
+
self._access_count += 1
|
283
|
+
|
284
|
+
def record_cache_hit(self) -> None:
|
285
|
+
"""Record a cache hit."""
|
286
|
+
with self._lock:
|
287
|
+
self._cache_hits += 1
|
288
|
+
|
289
|
+
def record_cache_miss(self) -> None:
|
290
|
+
"""Record a cache miss."""
|
291
|
+
with self._lock:
|
292
|
+
self._cache_misses += 1
|
293
|
+
|
294
|
+
def record_operation_time(self, operation: str, time_taken: float) -> None:
|
295
|
+
"""Record the time taken for an operation."""
|
296
|
+
with self._lock:
|
297
|
+
if operation not in self._operation_times:
|
298
|
+
self._operation_times[operation] = []
|
299
|
+
self._operation_times[operation].append(time_taken)
|
300
|
+
|
301
|
+
def get_metrics(self) -> Dict[str, Any]:
|
302
|
+
"""Get performance metrics."""
|
303
|
+
with self._lock:
|
304
|
+
metrics = {
|
305
|
+
'access_count': self._access_count,
|
306
|
+
'cache_hits': self._cache_hits,
|
307
|
+
'cache_misses': self._cache_misses,
|
308
|
+
}
|
309
|
+
|
310
|
+
# Calculate cache hit rate
|
311
|
+
total_cache_ops = self._cache_hits + self._cache_misses
|
312
|
+
if total_cache_ops > 0:
|
313
|
+
metrics['cache_hit_rate'] = self._cache_hits / total_cache_ops
|
314
|
+
else:
|
315
|
+
metrics['cache_hit_rate'] = 0.0
|
316
|
+
|
317
|
+
# Calculate average operation times
|
318
|
+
for operation, times in self._operation_times.items():
|
319
|
+
if times:
|
320
|
+
metrics[f'{operation}_avg_time'] = sum(times) / len(times)
|
321
|
+
metrics[f'{operation}_min_time'] = min(times)
|
322
|
+
metrics[f'{operation}_max_time'] = max(times)
|
323
|
+
|
324
|
+
return metrics
|
325
|
+
|
326
|
+
def reset(self) -> None:
|
327
|
+
"""Reset all performance counters."""
|
328
|
+
with self._lock:
|
329
|
+
self._access_count = 0
|
330
|
+
self._cache_hits = 0
|
331
|
+
self._cache_misses = 0
|
332
|
+
self._operation_times.clear()
|
333
|
+
|
334
|
+
|
335
|
+
# ============================================================================
|
336
|
+
# OBJECT POOLING UTILITIES
|
337
|
+
# ============================================================================
|
338
|
+
|
339
|
+
class ObjectPool:
|
340
|
+
"""Generic object pool for strategies that need pooling."""
|
341
|
+
|
342
|
+
def __init__(self, max_size: int = 100):
|
343
|
+
self._pool: List[Any] = []
|
344
|
+
self._max_size = max_size
|
345
|
+
self._lock = threading.RLock()
|
346
|
+
self._stats = {
|
347
|
+
'created': 0,
|
348
|
+
'reused': 0,
|
349
|
+
'pooled': 0
|
350
|
+
}
|
351
|
+
|
352
|
+
def get_object(self, factory_func, *args, **kwargs) -> Any:
|
353
|
+
"""
|
354
|
+
Get an object from the pool or create a new one.
|
355
|
+
|
356
|
+
Args:
|
357
|
+
factory_func: Function to create new objects
|
358
|
+
*args, **kwargs: Arguments for factory function
|
359
|
+
|
360
|
+
Returns:
|
361
|
+
Object from pool or newly created
|
362
|
+
"""
|
363
|
+
with self._lock:
|
364
|
+
if self._pool:
|
365
|
+
# Reuse existing object
|
366
|
+
obj = self._pool.pop()
|
367
|
+
self._stats['reused'] += 1
|
368
|
+
logger.debug(f"♻️ Reused object from pool")
|
369
|
+
return obj
|
370
|
+
else:
|
371
|
+
# Create new object
|
372
|
+
obj = factory_func(*args, **kwargs)
|
373
|
+
self._stats['created'] += 1
|
374
|
+
logger.debug(f"🆕 Created new object")
|
375
|
+
return obj
|
376
|
+
|
377
|
+
def return_object(self, obj: Any, reset_func=None) -> None:
|
378
|
+
"""
|
379
|
+
Return an object to the pool for reuse.
|
380
|
+
|
381
|
+
Args:
|
382
|
+
obj: Object to return to pool
|
383
|
+
reset_func: Optional function to reset object state
|
384
|
+
"""
|
385
|
+
with self._lock:
|
386
|
+
if len(self._pool) < self._max_size:
|
387
|
+
if reset_func:
|
388
|
+
reset_func(obj)
|
389
|
+
self._pool.append(obj)
|
390
|
+
self._stats['pooled'] += 1
|
391
|
+
logger.debug(f"🔄 Returned object to pool")
|
392
|
+
|
393
|
+
def get_stats(self) -> Dict[str, int]:
|
394
|
+
"""Get pool statistics."""
|
395
|
+
with self._lock:
|
396
|
+
stats = self._stats.copy()
|
397
|
+
stats['pool_size'] = len(self._pool)
|
398
|
+
return stats
|
399
|
+
|
400
|
+
@property
|
401
|
+
def efficiency(self) -> float:
|
402
|
+
"""Get pool efficiency (reuse rate)."""
|
403
|
+
total = self._stats['created'] + self._stats['reused']
|
404
|
+
return self._stats['reused'] / total if total > 0 else 0.0
|
405
|
+
|
406
|
+
def clear(self) -> None:
|
407
|
+
"""Clear all pooled objects."""
|
408
|
+
with self._lock:
|
409
|
+
self._pool.clear()
|
410
|
+
logger.info("🧹 Cleared object pool")
|
411
|
+
|
412
|
+
|
413
|
+
# ============================================================================
|
414
|
+
# FACTORY FUNCTIONS
|
415
|
+
# ============================================================================
|
416
|
+
|
417
|
+
def create_path_parser(max_cache_size: int = 1024) -> PathParser:
|
418
|
+
"""Create a path parser instance."""
|
419
|
+
return PathParser(max_cache_size)
|
420
|
+
|
421
|
+
|
422
|
+
def create_performance_tracker() -> PerformanceTracker:
|
423
|
+
"""Create a performance tracker instance."""
|
424
|
+
return PerformanceTracker()
|
425
|
+
|
426
|
+
|
427
|
+
def create_object_pool(max_size: int = 100) -> ObjectPool:
|
428
|
+
"""Create an object pool instance."""
|
429
|
+
return ObjectPool(max_size)
|
430
|
+
|
431
|
+
|
432
|
+
def create_basic_metrics(strategy_name: str, size: int, **additional_metrics) -> Dict[str, Any]:
|
433
|
+
"""Create basic metrics dictionary for strategies."""
|
434
|
+
metrics = {
|
435
|
+
'strategy': strategy_name,
|
436
|
+
'size': size,
|
437
|
+
'memory_usage': f"{size * 64} bytes (estimated)",
|
438
|
+
'timestamp': time.time()
|
439
|
+
}
|
440
|
+
metrics.update(additional_metrics)
|
441
|
+
return metrics
|
442
|
+
|
443
|
+
|
444
|
+
def create_basic_backend_info(strategy_name: str, backend_type: str, **additional_info) -> Dict[str, Any]:
|
445
|
+
"""Create basic backend info dictionary for strategies."""
|
446
|
+
info = {
|
447
|
+
'strategy': strategy_name,
|
448
|
+
'backend': backend_type,
|
449
|
+
'complexity': {
|
450
|
+
'get': 'O(1) average',
|
451
|
+
'put': 'O(1) average',
|
452
|
+
'has': 'O(1) average',
|
453
|
+
'remove': 'O(1) average'
|
454
|
+
}
|
455
|
+
}
|
456
|
+
info.update(additional_info)
|
457
|
+
return info
|
458
|
+
|
459
|
+
|
460
|
+
def is_list_like(keys: List[str]) -> bool:
|
461
|
+
"""Check if keys represent a list-like structure."""
|
462
|
+
if not keys:
|
463
|
+
return False
|
464
|
+
|
465
|
+
# Check if all keys are numeric and consecutive starting from 0
|
466
|
+
try:
|
467
|
+
numeric_keys = [int(key) for key in keys]
|
468
|
+
return numeric_keys == list(range(len(numeric_keys)))
|
469
|
+
except (ValueError, TypeError):
|
470
|
+
return False
|
471
|
+
|
472
|
+
|
473
|
+
def safe_to_native_conversion(data: Any) -> Any:
|
474
|
+
"""Safely convert data to native Python types, handling XWNode objects."""
|
475
|
+
if hasattr(data, 'to_native'):
|
476
|
+
# This is an XWNode, recursively convert it
|
477
|
+
return safe_to_native_conversion(data.to_native())
|
478
|
+
elif isinstance(data, dict):
|
479
|
+
return {k: safe_to_native_conversion(v) for k, v in data.items()}
|
480
|
+
elif isinstance(data, list):
|
481
|
+
return [safe_to_native_conversion(item) for item in data]
|
482
|
+
elif isinstance(data, (set, frozenset)):
|
483
|
+
return {safe_to_native_conversion(item) for item in data}
|
484
|
+
else:
|
485
|
+
return data
|
486
|
+
|
487
|
+
|
488
|
+
def create_strategy_logger(strategy_name: str):
|
489
|
+
"""Create a logger for a specific strategy."""
|
490
|
+
return get_logger(f"xnode.strategy.{strategy_name}")
|
491
|
+
|
492
|
+
|
493
|
+
def validate_strategy_options(options: Dict[str, Any], allowed_options: List[str]) -> Dict[str, Any]:
|
494
|
+
"""Validate strategy options and return only allowed ones."""
|
495
|
+
return {k: v for k, v in options.items() if k in allowed_options}
|
496
|
+
|
497
|
+
|
498
|
+
def create_size_tracker() -> Dict[str, int]:
|
499
|
+
"""Create a size tracking dictionary."""
|
500
|
+
return {'size': 0}
|
501
|
+
|
502
|
+
|
503
|
+
def update_size_tracker(tracker: Dict[str, int], delta: int) -> None:
|
504
|
+
"""Update size tracker with delta change."""
|
505
|
+
tracker['size'] = max(0, tracker['size'] + delta)
|
506
|
+
|
507
|
+
|
508
|
+
def create_access_tracker() -> Dict[str, int]:
|
509
|
+
"""Create an access tracking dictionary."""
|
510
|
+
return {
|
511
|
+
'get_count': 0,
|
512
|
+
'put_count': 0,
|
513
|
+
'delete_count': 0,
|
514
|
+
'access_count': 0
|
515
|
+
}
|
516
|
+
|
517
|
+
|
518
|
+
def record_access(tracker: Dict[str, int], operation: str) -> None:
|
519
|
+
"""Record an access operation."""
|
520
|
+
if operation in tracker:
|
521
|
+
tracker[operation] += 1
|
522
|
+
tracker['access_count'] += 1
|
523
|
+
|
524
|
+
|
525
|
+
def get_access_metrics(tracker: Dict[str, int]) -> Dict[str, Any]:
|
526
|
+
"""Get access metrics from tracker."""
|
527
|
+
return {
|
528
|
+
'total_accesses': tracker['access_count'],
|
529
|
+
'get_operations': tracker['get_count'],
|
530
|
+
'put_operations': tracker['put_count'],
|
531
|
+
'delete_operations': tracker['delete_count']
|
532
|
+
}
|