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
exonware/xwnode/base.py
ADDED
@@ -0,0 +1,676 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
#exonware/xwnode/src/exonware/xwnode/base.py
|
4
|
+
|
5
|
+
Abstract base classes for XWNode.
|
6
|
+
|
7
|
+
This module contains the abstract base classes that provide core functionality
|
8
|
+
for all XWNode implementations.
|
9
|
+
"""
|
10
|
+
|
11
|
+
import threading
|
12
|
+
import copy
|
13
|
+
from abc import ABC
|
14
|
+
from typing import Any, Iterator, Union, Optional, List, Dict, Callable
|
15
|
+
from collections import OrderedDict
|
16
|
+
|
17
|
+
# Core XWNode imports - strategy-agnostic
|
18
|
+
from .errors import (
|
19
|
+
XWNodeTypeError, XWNodePathError, XWNodeSecurityError, XWNodeValueError, XWNodeLimitError
|
20
|
+
)
|
21
|
+
from .config import get_config
|
22
|
+
from .contracts import iNodeFacade, iNodeStrategy, iEdge, iEdgeStrategy, iQuery, iQueryResult, iQueryEngine
|
23
|
+
|
24
|
+
# System-level imports - standard imports (no defensive code!)
|
25
|
+
from exonware.xwsystem.security import get_resource_limits
|
26
|
+
from exonware.xwsystem.validation import validate_untrusted_data
|
27
|
+
from exonware.xwsystem.monitoring import create_component_metrics
|
28
|
+
from exonware.xwsystem.threading import ThreadSafeFactory, create_thread_safe_cache
|
29
|
+
from exonware.xwsystem.patterns import CircuitBreaker
|
30
|
+
from exonware.xwsystem import get_logger
|
31
|
+
|
32
|
+
logger = get_logger('xwnode.base')
|
33
|
+
|
34
|
+
# Metrics setup
|
35
|
+
_metrics = create_component_metrics('xwnode_base')
|
36
|
+
measure_operation = _metrics['measure_operation']
|
37
|
+
record_cache_hit = _metrics['record_cache_hit']
|
38
|
+
record_cache_miss = _metrics['record_cache_miss']
|
39
|
+
|
40
|
+
# Thread-safe cache for path parsing
|
41
|
+
_path_cache = create_thread_safe_cache(max_size=1024)
|
42
|
+
|
43
|
+
# Circuit breaker for strategy operations
|
44
|
+
_strategy_circuit_breaker = CircuitBreaker(
|
45
|
+
failure_threshold=5,
|
46
|
+
recovery_timeout=30,
|
47
|
+
expected_exception=Exception
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
class PathParser:
|
52
|
+
"""Thread-safe path parser with caching."""
|
53
|
+
|
54
|
+
def __init__(self, max_cache_size: int = 1024):
|
55
|
+
self._cache = OrderedDict()
|
56
|
+
self._max_cache_size = max_cache_size
|
57
|
+
self._lock = threading.RLock()
|
58
|
+
|
59
|
+
def parse(self, path: str) -> List[str]:
|
60
|
+
"""Parse a path string into parts."""
|
61
|
+
with self._lock:
|
62
|
+
if path in self._cache:
|
63
|
+
record_cache_hit()
|
64
|
+
return self._cache[path]
|
65
|
+
|
66
|
+
record_cache_miss()
|
67
|
+
parts = self._parse_path(path)
|
68
|
+
|
69
|
+
# Cache the result
|
70
|
+
if len(self._cache) >= self._max_cache_size:
|
71
|
+
self._cache.popitem(last=False)
|
72
|
+
self._cache[path] = parts
|
73
|
+
|
74
|
+
return parts
|
75
|
+
|
76
|
+
def _parse_path(self, path: str) -> List[str]:
|
77
|
+
"""Internal path parsing logic."""
|
78
|
+
if not path:
|
79
|
+
return []
|
80
|
+
|
81
|
+
parts = []
|
82
|
+
current = ""
|
83
|
+
in_brackets = False
|
84
|
+
in_quotes = False
|
85
|
+
quote_char = None
|
86
|
+
|
87
|
+
for char in path:
|
88
|
+
if in_quotes:
|
89
|
+
if char == quote_char:
|
90
|
+
in_quotes = False
|
91
|
+
quote_char = None
|
92
|
+
else:
|
93
|
+
current += char
|
94
|
+
elif char in ['"', "'"]:
|
95
|
+
in_quotes = True
|
96
|
+
quote_char = char
|
97
|
+
elif char == '[':
|
98
|
+
if current:
|
99
|
+
parts.append(current)
|
100
|
+
current = ""
|
101
|
+
in_brackets = True
|
102
|
+
current += char
|
103
|
+
elif char == ']':
|
104
|
+
current += char
|
105
|
+
in_brackets = False
|
106
|
+
elif char == '.' and not in_brackets:
|
107
|
+
if current:
|
108
|
+
parts.append(current)
|
109
|
+
current = ""
|
110
|
+
else:
|
111
|
+
current += char
|
112
|
+
|
113
|
+
if current:
|
114
|
+
parts.append(current)
|
115
|
+
|
116
|
+
return parts
|
117
|
+
|
118
|
+
def clear_cache(self):
|
119
|
+
"""Clear the path cache."""
|
120
|
+
with self._lock:
|
121
|
+
self._cache.clear()
|
122
|
+
|
123
|
+
|
124
|
+
class GlobalPathCache:
|
125
|
+
"""Global cache for path lookups."""
|
126
|
+
|
127
|
+
def __init__(self, max_size: int = 512):
|
128
|
+
self._cache = OrderedDict()
|
129
|
+
self._max_size = max_size
|
130
|
+
self._lock = threading.RLock()
|
131
|
+
self._stats = {'hits': 0, 'misses': 0}
|
132
|
+
|
133
|
+
def get(self, node_id: int, path: str) -> Optional[Any]:
|
134
|
+
"""Get cached result for node and path."""
|
135
|
+
key = (node_id, path)
|
136
|
+
with self._lock:
|
137
|
+
if key in self._cache:
|
138
|
+
self._stats['hits'] += 1
|
139
|
+
return self._cache[key]
|
140
|
+
self._stats['misses'] += 1
|
141
|
+
return None
|
142
|
+
|
143
|
+
def put(self, node_id: int, path: str, result: Any):
|
144
|
+
"""Cache result for node and path."""
|
145
|
+
key = (node_id, path)
|
146
|
+
with self._lock:
|
147
|
+
if len(self._cache) >= self._max_size:
|
148
|
+
self._cache.popitem(last=False)
|
149
|
+
self._cache[key] = result
|
150
|
+
|
151
|
+
def clear(self):
|
152
|
+
"""Clear the cache."""
|
153
|
+
with self._lock:
|
154
|
+
self._cache.clear()
|
155
|
+
|
156
|
+
def stats(self) -> Dict[str, int]:
|
157
|
+
"""Get cache statistics."""
|
158
|
+
with self._lock:
|
159
|
+
return self._stats.copy()
|
160
|
+
|
161
|
+
|
162
|
+
# Global instances
|
163
|
+
_path_parser = None
|
164
|
+
_global_path_cache = None
|
165
|
+
|
166
|
+
def get_path_parser() -> PathParser:
|
167
|
+
"""Get the global path parser instance."""
|
168
|
+
global _path_parser
|
169
|
+
if _path_parser is None:
|
170
|
+
_path_parser = PathParser()
|
171
|
+
return _path_parser
|
172
|
+
|
173
|
+
def get_global_path_cache() -> GlobalPathCache:
|
174
|
+
"""Get the global path cache instance."""
|
175
|
+
global _global_path_cache
|
176
|
+
if _global_path_cache is None:
|
177
|
+
_global_path_cache = GlobalPathCache()
|
178
|
+
return _global_path_cache
|
179
|
+
|
180
|
+
|
181
|
+
class XWNodeBase(iNodeFacade):
|
182
|
+
"""
|
183
|
+
Abstract base class for all XWNode implementations.
|
184
|
+
|
185
|
+
This class provides the core functionality that all XWNode implementations
|
186
|
+
must have, working through the iNodeStrategy interface.
|
187
|
+
"""
|
188
|
+
|
189
|
+
__slots__ = ('_strategy', '_hash_cache', '_type_cache')
|
190
|
+
|
191
|
+
def __init__(self, strategy: iNodeStrategy):
|
192
|
+
"""Initialize with a strategy implementation."""
|
193
|
+
self._strategy = strategy
|
194
|
+
self._hash_cache = None
|
195
|
+
self._type_cache = None
|
196
|
+
|
197
|
+
@classmethod
|
198
|
+
def from_native(cls, data: Any) -> 'XWNodeBase':
|
199
|
+
"""Create XWNodeBase from native data."""
|
200
|
+
# For now, we'll use a simple hash map strategy
|
201
|
+
# In the full implementation, this would use the strategy manager
|
202
|
+
from .strategies.simple import SimpleNodeStrategy
|
203
|
+
strategy = SimpleNodeStrategy.create_from_data(data)
|
204
|
+
return cls(strategy)
|
205
|
+
|
206
|
+
def get(self, path: str, default: Any = None) -> Optional['XWNodeBase']:
|
207
|
+
"""Get a node by path."""
|
208
|
+
try:
|
209
|
+
result_strategy = self._strategy.get(path, default)
|
210
|
+
if result_strategy is None:
|
211
|
+
return None
|
212
|
+
return XWNodeBase(result_strategy)
|
213
|
+
except Exception:
|
214
|
+
return None
|
215
|
+
|
216
|
+
def set(self, path: str, value: Any, in_place: bool = True) -> 'XWNodeBase':
|
217
|
+
"""Set a value at path."""
|
218
|
+
new_strategy = self._strategy.put(path, value)
|
219
|
+
if in_place:
|
220
|
+
self._strategy = new_strategy
|
221
|
+
return self
|
222
|
+
else:
|
223
|
+
return XWNodeBase(new_strategy)
|
224
|
+
|
225
|
+
def delete(self, path: str, in_place: bool = True) -> 'XWNodeBase':
|
226
|
+
"""Delete a node at path."""
|
227
|
+
success = self._strategy.delete(path)
|
228
|
+
return self
|
229
|
+
|
230
|
+
def exists(self, path: str) -> bool:
|
231
|
+
"""Check if path exists."""
|
232
|
+
return self._strategy.exists(path)
|
233
|
+
|
234
|
+
def find(self, path: str, in_place: bool = False) -> Optional['XWNodeBase']:
|
235
|
+
"""Find a node by path."""
|
236
|
+
return self.get(path)
|
237
|
+
|
238
|
+
def to_native(self) -> Any:
|
239
|
+
"""Convert to native Python object."""
|
240
|
+
return self._strategy.to_native()
|
241
|
+
|
242
|
+
def copy(self) -> 'XWNodeBase':
|
243
|
+
"""Create a deep copy."""
|
244
|
+
return XWNodeBase(self._strategy.create_from_data(self._strategy.to_native()))
|
245
|
+
|
246
|
+
def count(self, path: str = ".") -> int:
|
247
|
+
"""Count nodes at path."""
|
248
|
+
if path == ".":
|
249
|
+
return len(self._strategy)
|
250
|
+
node = self.get(path)
|
251
|
+
return len(node._strategy) if node else 0
|
252
|
+
|
253
|
+
def flatten(self, separator: str = ".") -> Dict[str, Any]:
|
254
|
+
"""Flatten to dictionary."""
|
255
|
+
result = {}
|
256
|
+
|
257
|
+
def _flatten(node_strategy, prefix=""):
|
258
|
+
if node_strategy.is_leaf:
|
259
|
+
result[prefix or "root"] = node_strategy.value
|
260
|
+
elif node_strategy.is_dict:
|
261
|
+
for key in node_strategy.keys():
|
262
|
+
child = node_strategy.get(key)
|
263
|
+
new_prefix = f"{prefix}{separator}{key}" if prefix else key
|
264
|
+
_flatten(child, new_prefix)
|
265
|
+
elif node_strategy.is_list:
|
266
|
+
for i in range(len(node_strategy)):
|
267
|
+
child = node_strategy.get(str(i))
|
268
|
+
new_prefix = f"{prefix}{separator}{i}" if prefix else str(i)
|
269
|
+
_flatten(child, new_prefix)
|
270
|
+
|
271
|
+
_flatten(self._strategy)
|
272
|
+
return result
|
273
|
+
|
274
|
+
def merge(self, other: 'XWNodeBase', strategy: str = "replace") -> 'XWNodeBase':
|
275
|
+
"""Merge with another node."""
|
276
|
+
# Simple implementation - just replace
|
277
|
+
return XWNodeBase(self._strategy.create_from_data(other.to_native()))
|
278
|
+
|
279
|
+
def diff(self, other: 'XWNodeBase') -> Dict[str, Any]:
|
280
|
+
"""Get differences with another node."""
|
281
|
+
return {"changed": True} # Simple implementation
|
282
|
+
|
283
|
+
def transform(self, transformer: callable) -> 'XWNodeBase':
|
284
|
+
"""Transform using a function."""
|
285
|
+
transformed_data = transformer(self.to_native())
|
286
|
+
return XWNodeBase(self._strategy.create_from_data(transformed_data))
|
287
|
+
|
288
|
+
def select(self, *paths: str) -> Dict[str, 'XWNodeBase']:
|
289
|
+
"""Select multiple paths."""
|
290
|
+
result = {}
|
291
|
+
for path in paths:
|
292
|
+
node = self.get(path)
|
293
|
+
if node:
|
294
|
+
result[path] = node
|
295
|
+
return result
|
296
|
+
|
297
|
+
# Container methods
|
298
|
+
def __len__(self) -> int:
|
299
|
+
"""Get length."""
|
300
|
+
return len(self._strategy)
|
301
|
+
|
302
|
+
def __iter__(self) -> Iterator['XWNodeBase']:
|
303
|
+
"""Iterate over children."""
|
304
|
+
for child_strategy in self._strategy:
|
305
|
+
yield XWNodeBase(child_strategy)
|
306
|
+
|
307
|
+
def __getitem__(self, key: Union[str, int]) -> 'XWNodeBase':
|
308
|
+
"""Get child by key or index."""
|
309
|
+
child_strategy = self._strategy[key]
|
310
|
+
return XWNodeBase(child_strategy)
|
311
|
+
|
312
|
+
def __setitem__(self, key: Union[str, int], value: Any) -> None:
|
313
|
+
"""Set child by key or index."""
|
314
|
+
self._strategy[key] = value
|
315
|
+
|
316
|
+
def __contains__(self, key: Union[str, int]) -> bool:
|
317
|
+
"""Check if key exists."""
|
318
|
+
return key in self._strategy
|
319
|
+
|
320
|
+
# Type checking properties
|
321
|
+
@property
|
322
|
+
def is_leaf(self) -> bool:
|
323
|
+
"""Check if this is a leaf node."""
|
324
|
+
return self._strategy.is_leaf
|
325
|
+
|
326
|
+
@property
|
327
|
+
def is_list(self) -> bool:
|
328
|
+
"""Check if this is a list node."""
|
329
|
+
return self._strategy.is_list
|
330
|
+
|
331
|
+
@property
|
332
|
+
def is_dict(self) -> bool:
|
333
|
+
"""Check if this is a dict node."""
|
334
|
+
return self._strategy.is_dict
|
335
|
+
|
336
|
+
@property
|
337
|
+
def type(self) -> str:
|
338
|
+
"""Get the type of this node."""
|
339
|
+
return self._strategy.type
|
340
|
+
|
341
|
+
@property
|
342
|
+
def value(self) -> Any:
|
343
|
+
"""Get the value of this node."""
|
344
|
+
return self._strategy.value
|
345
|
+
|
346
|
+
|
347
|
+
class aEdge(iEdge):
|
348
|
+
"""Abstract base class for edge implementations."""
|
349
|
+
|
350
|
+
def __init__(self, strategy: iEdgeStrategy):
|
351
|
+
self._strategy = strategy
|
352
|
+
|
353
|
+
def add_edge(self, source: str, target: str, edge_type: str = "default",
|
354
|
+
weight: float = 1.0, properties: Optional[Dict[str, Any]] = None,
|
355
|
+
is_bidirectional: bool = False, edge_id: Optional[str] = None) -> str:
|
356
|
+
"""Add an edge between source and target with advanced properties."""
|
357
|
+
return self._strategy.add_edge(source, target, edge_type, weight, properties, is_bidirectional, edge_id)
|
358
|
+
|
359
|
+
def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
|
360
|
+
"""Remove an edge between source and target."""
|
361
|
+
return self._strategy.remove_edge(source, target, edge_id)
|
362
|
+
|
363
|
+
def has_edge(self, source: str, target: str) -> bool:
|
364
|
+
"""Check if edge exists between source and target."""
|
365
|
+
return self._strategy.has_edge(source, target)
|
366
|
+
|
367
|
+
def get_neighbors(self, node: str, edge_type: Optional[str] = None, direction: str = "outgoing") -> List[str]:
|
368
|
+
"""Get neighbors of a node with optional filtering."""
|
369
|
+
return self._strategy.get_neighbors(node, edge_type, direction)
|
370
|
+
|
371
|
+
def get_edges(self, edge_type: Optional[str] = None, direction: str = "both") -> List[Dict[str, Any]]:
|
372
|
+
"""Get all edges with metadata."""
|
373
|
+
return self._strategy.get_edges(edge_type, direction)
|
374
|
+
|
375
|
+
def get_edge_data(self, source: str, target: str, edge_id: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
376
|
+
"""Get edge data/properties."""
|
377
|
+
return self._strategy.get_edge_data(source, target, edge_id)
|
378
|
+
|
379
|
+
def shortest_path(self, source: str, target: str, edge_type: Optional[str] = None) -> List[str]:
|
380
|
+
"""Find shortest path between nodes."""
|
381
|
+
return self._strategy.shortest_path(source, target, edge_type)
|
382
|
+
|
383
|
+
def find_cycles(self, start_node: str, edge_type: Optional[str] = None, max_depth: int = 10) -> List[List[str]]:
|
384
|
+
"""Find cycles in the graph."""
|
385
|
+
return self._strategy.find_cycles(start_node, edge_type, max_depth)
|
386
|
+
|
387
|
+
def traverse_graph(self, start_node: str, strategy: str = "bfs", max_depth: int = 100,
|
388
|
+
edge_type: Optional[str] = None) -> Iterator[str]:
|
389
|
+
"""Traverse the graph with cycle detection."""
|
390
|
+
return self._strategy.traverse_graph(start_node, strategy, max_depth, edge_type)
|
391
|
+
|
392
|
+
def is_connected(self, source: str, target: str, edge_type: Optional[str] = None) -> bool:
|
393
|
+
"""Check if nodes are connected."""
|
394
|
+
return self._strategy.is_connected(source, target, edge_type)
|
395
|
+
|
396
|
+
def __len__(self) -> int:
|
397
|
+
"""Get number of edges."""
|
398
|
+
return len(self._strategy)
|
399
|
+
|
400
|
+
def __iter__(self) -> Iterator[Dict[str, Any]]:
|
401
|
+
"""Iterate over edges with full metadata."""
|
402
|
+
return iter(self._strategy)
|
403
|
+
|
404
|
+
def to_native(self) -> Any:
|
405
|
+
"""Convert to native Python object."""
|
406
|
+
return self._strategy.to_native()
|
407
|
+
|
408
|
+
def copy(self) -> 'aEdge':
|
409
|
+
"""Create a deep copy."""
|
410
|
+
return aEdge(copy.deepcopy(self._strategy))
|
411
|
+
|
412
|
+
|
413
|
+
class aQuery(iQuery):
|
414
|
+
"""Abstract base class for query implementations."""
|
415
|
+
|
416
|
+
def __init__(self, node: XWNodeBase, engine: iQueryEngine):
|
417
|
+
self._node = node
|
418
|
+
self._engine = engine
|
419
|
+
|
420
|
+
def query(self, query_string: str, query_type: str = "hybrid", **kwargs) -> iQueryResult:
|
421
|
+
"""Execute a query."""
|
422
|
+
context = {"node": self._node, "type": query_type, **kwargs}
|
423
|
+
return self._engine.execute_query(query_string, context)
|
424
|
+
|
425
|
+
def find_nodes(self, predicate: Callable[[XWNodeBase], bool], max_results: Optional[int] = None) -> iQueryResult:
|
426
|
+
"""Find nodes matching predicate."""
|
427
|
+
# Simple implementation
|
428
|
+
results = []
|
429
|
+
count = 0
|
430
|
+
|
431
|
+
def _search(node):
|
432
|
+
nonlocal count
|
433
|
+
if max_results and count >= max_results:
|
434
|
+
return
|
435
|
+
if predicate(node):
|
436
|
+
results.append(node)
|
437
|
+
count += 1
|
438
|
+
|
439
|
+
for child in node:
|
440
|
+
_search(child)
|
441
|
+
|
442
|
+
_search(self._node)
|
443
|
+
return SimpleQueryResult(results)
|
444
|
+
|
445
|
+
def find_by_path(self, path_pattern: str) -> iQueryResult:
|
446
|
+
"""Find nodes by path pattern."""
|
447
|
+
# Simple implementation - exact match
|
448
|
+
node = self._node.get(path_pattern)
|
449
|
+
return SimpleQueryResult([node] if node else [])
|
450
|
+
|
451
|
+
def find_by_value(self, value: Any, exact_match: bool = True) -> iQueryResult:
|
452
|
+
"""Find nodes by value."""
|
453
|
+
results = []
|
454
|
+
|
455
|
+
def _search(node):
|
456
|
+
if exact_match:
|
457
|
+
if node.value == value:
|
458
|
+
results.append(node)
|
459
|
+
else:
|
460
|
+
if str(value) in str(node.value):
|
461
|
+
results.append(node)
|
462
|
+
|
463
|
+
for child in node:
|
464
|
+
_search(child)
|
465
|
+
|
466
|
+
_search(self._node)
|
467
|
+
return SimpleQueryResult(results)
|
468
|
+
|
469
|
+
def count_nodes(self, predicate: Optional[Callable[[XWNodeBase], bool]] = None) -> int:
|
470
|
+
"""Count nodes matching predicate."""
|
471
|
+
if predicate is None:
|
472
|
+
return self._node.count()
|
473
|
+
|
474
|
+
count = 0
|
475
|
+
def _count(node):
|
476
|
+
nonlocal count
|
477
|
+
if predicate(node):
|
478
|
+
count += 1
|
479
|
+
for child in node:
|
480
|
+
_count(child)
|
481
|
+
|
482
|
+
_count(self._node)
|
483
|
+
return count
|
484
|
+
|
485
|
+
# Simplified implementations for other methods
|
486
|
+
def select(self, selector: str, **kwargs) -> List[XWNodeBase]:
|
487
|
+
return []
|
488
|
+
|
489
|
+
def filter(self, condition: str, **kwargs) -> List[XWNodeBase]:
|
490
|
+
return []
|
491
|
+
|
492
|
+
def where(self, condition: str) -> List[XWNodeBase]:
|
493
|
+
return []
|
494
|
+
|
495
|
+
def sort(self, key: str = None, reverse: bool = False) -> List[XWNodeBase]:
|
496
|
+
return []
|
497
|
+
|
498
|
+
def limit(self, count: int) -> List[XWNodeBase]:
|
499
|
+
return []
|
500
|
+
|
501
|
+
def skip(self, count: int) -> List[XWNodeBase]:
|
502
|
+
return []
|
503
|
+
|
504
|
+
def first(self) -> Optional[XWNodeBase]:
|
505
|
+
return None
|
506
|
+
|
507
|
+
def last(self) -> Optional[XWNodeBase]:
|
508
|
+
return None
|
509
|
+
|
510
|
+
def group_by(self, key: str) -> Dict[str, List[XWNodeBase]]:
|
511
|
+
return {}
|
512
|
+
|
513
|
+
def distinct(self, key: str = None) -> List[XWNodeBase]:
|
514
|
+
return []
|
515
|
+
|
516
|
+
def clear_query_cache(self):
|
517
|
+
pass
|
518
|
+
|
519
|
+
def get_query_stats(self) -> Dict[str, Any]:
|
520
|
+
return {}
|
521
|
+
|
522
|
+
|
523
|
+
class SimpleQueryResult(iQueryResult):
|
524
|
+
"""Simple implementation of query results."""
|
525
|
+
|
526
|
+
def __init__(self, nodes: List[XWNodeBase]):
|
527
|
+
self._nodes = nodes
|
528
|
+
self._metadata = {}
|
529
|
+
|
530
|
+
@property
|
531
|
+
def nodes(self) -> List[XWNodeBase]:
|
532
|
+
return self._nodes
|
533
|
+
|
534
|
+
@property
|
535
|
+
def metadata(self) -> Dict[str, Any]:
|
536
|
+
return self._metadata
|
537
|
+
|
538
|
+
def first(self) -> Optional[XWNodeBase]:
|
539
|
+
return self._nodes[0] if self._nodes else None
|
540
|
+
|
541
|
+
def count(self) -> int:
|
542
|
+
return len(self._nodes)
|
543
|
+
|
544
|
+
def filter(self, predicate: Callable[[XWNodeBase], bool]) -> 'SimpleQueryResult':
|
545
|
+
filtered = [node for node in self._nodes if predicate(node)]
|
546
|
+
return SimpleQueryResult(filtered)
|
547
|
+
|
548
|
+
def limit(self, limit: int) -> 'SimpleQueryResult':
|
549
|
+
return SimpleQueryResult(self._nodes[:limit])
|
550
|
+
|
551
|
+
def offset(self, offset: int) -> 'SimpleQueryResult':
|
552
|
+
return SimpleQueryResult(self._nodes[offset:])
|
553
|
+
|
554
|
+
|
555
|
+
class aQueryResult(iQueryResult):
|
556
|
+
"""Abstract base class for query results."""
|
557
|
+
pass
|
558
|
+
|
559
|
+
|
560
|
+
class aQueryEngine(iQueryEngine):
|
561
|
+
"""Abstract base class for query engines with multi-language support."""
|
562
|
+
|
563
|
+
def __init__(self):
|
564
|
+
self._parsers = {}
|
565
|
+
self._register_default_parsers()
|
566
|
+
|
567
|
+
def _register_default_parsers(self):
|
568
|
+
"""Register default query language parsers."""
|
569
|
+
# JSONPath-style queries
|
570
|
+
self._parsers['jsonpath'] = self._parse_jsonpath
|
571
|
+
self._parsers['xpath'] = self._parse_xpath
|
572
|
+
self._parsers['css'] = self._parse_css_selector
|
573
|
+
self._parsers['jq'] = self._parse_jq
|
574
|
+
self._parsers['sql'] = self._parse_sql_like
|
575
|
+
self._parsers['mongo'] = self._parse_mongodb
|
576
|
+
self._parsers['graphql'] = self._parse_graphql
|
577
|
+
# Default hybrid parser
|
578
|
+
self._parsers['hybrid'] = self._parse_hybrid
|
579
|
+
|
580
|
+
def register_parser(self, language: str, parser_func: Callable):
|
581
|
+
"""Register a custom query language parser."""
|
582
|
+
self._parsers[language] = parser_func
|
583
|
+
|
584
|
+
def detect_query_language(self, query_string: str) -> str:
|
585
|
+
"""Auto-detect query language from query string."""
|
586
|
+
query = query_string.strip()
|
587
|
+
|
588
|
+
# GraphQL detection
|
589
|
+
if query.startswith('{') and ('query' in query or 'mutation' in query):
|
590
|
+
return 'graphql'
|
591
|
+
|
592
|
+
# SQL-like detection
|
593
|
+
if any(keyword in query.upper() for keyword in ['SELECT', 'FROM', 'WHERE', 'JOIN']):
|
594
|
+
return 'sql'
|
595
|
+
|
596
|
+
# MongoDB detection
|
597
|
+
if query.startswith('{') and any(op in query for op in ['$match', '$group', '$sort', '$project']):
|
598
|
+
return 'mongo'
|
599
|
+
|
600
|
+
# XPath detection
|
601
|
+
if query.startswith('/') or query.startswith('//') or '//' in query:
|
602
|
+
return 'xpath'
|
603
|
+
|
604
|
+
# CSS selector detection
|
605
|
+
if any(sel in query for sel in ['.', '#', '[', ':', '>']):
|
606
|
+
return 'css'
|
607
|
+
|
608
|
+
# jq detection
|
609
|
+
if query.startswith('.') or any(func in query for func in ['map', 'select', 'group_by']):
|
610
|
+
return 'jq'
|
611
|
+
|
612
|
+
# JSONPath detection
|
613
|
+
if query.startswith('$') or '..' in query:
|
614
|
+
return 'jsonpath'
|
615
|
+
|
616
|
+
# Default to hybrid
|
617
|
+
return 'hybrid'
|
618
|
+
|
619
|
+
@measure_operation('query_execute')
|
620
|
+
def execute_query(self, query_string: str, context: Dict[str, Any]) -> iQueryResult:
|
621
|
+
"""Execute query with auto-detection or explicit language."""
|
622
|
+
query_type = context.get('query_type', self.detect_query_language(query_string))
|
623
|
+
|
624
|
+
if query_type not in self._parsers:
|
625
|
+
logger.warning(f"Unknown query language: {query_type}, falling back to hybrid")
|
626
|
+
query_type = 'hybrid'
|
627
|
+
|
628
|
+
try:
|
629
|
+
return self._parsers[query_type](query_string, context)
|
630
|
+
except Exception as e:
|
631
|
+
logger.error(f"Query execution failed for {query_type}: {e}")
|
632
|
+
# Fallback to hybrid parser
|
633
|
+
if query_type != 'hybrid':
|
634
|
+
return self._parsers['hybrid'](query_string, context)
|
635
|
+
raise
|
636
|
+
|
637
|
+
def _parse_jsonpath(self, query: str, context: Dict[str, Any]) -> iQueryResult:
|
638
|
+
"""Parse JSONPath-style queries."""
|
639
|
+
# Implementation would use jsonpath library
|
640
|
+
logger.debug(f"Parsing JSONPath query: {query}")
|
641
|
+
return SimpleQueryResult([])
|
642
|
+
|
643
|
+
def _parse_xpath(self, query: str, context: Dict[str, Any]) -> iQueryResult:
|
644
|
+
"""Parse XPath-style queries."""
|
645
|
+
logger.debug(f"Parsing XPath query: {query}")
|
646
|
+
return SimpleQueryResult([])
|
647
|
+
|
648
|
+
def _parse_css_selector(self, query: str, context: Dict[str, Any]) -> iQueryResult:
|
649
|
+
"""Parse CSS selector-style queries."""
|
650
|
+
logger.debug(f"Parsing CSS selector query: {query}")
|
651
|
+
return SimpleQueryResult([])
|
652
|
+
|
653
|
+
def _parse_jq(self, query: str, context: Dict[str, Any]) -> iQueryResult:
|
654
|
+
"""Parse jq-style queries."""
|
655
|
+
logger.debug(f"Parsing jq query: {query}")
|
656
|
+
return SimpleQueryResult([])
|
657
|
+
|
658
|
+
def _parse_sql_like(self, query: str, context: Dict[str, Any]) -> iQueryResult:
|
659
|
+
"""Parse SQL-like queries."""
|
660
|
+
logger.debug(f"Parsing SQL-like query: {query}")
|
661
|
+
return SimpleQueryResult([])
|
662
|
+
|
663
|
+
def _parse_mongodb(self, query: str, context: Dict[str, Any]) -> iQueryResult:
|
664
|
+
"""Parse MongoDB-style queries."""
|
665
|
+
logger.debug(f"Parsing MongoDB query: {query}")
|
666
|
+
return SimpleQueryResult([])
|
667
|
+
|
668
|
+
def _parse_graphql(self, query: str, context: Dict[str, Any]) -> iQueryResult:
|
669
|
+
"""Parse GraphQL-style queries."""
|
670
|
+
logger.debug(f"Parsing GraphQL query: {query}")
|
671
|
+
return SimpleQueryResult([])
|
672
|
+
|
673
|
+
def _parse_hybrid(self, query: str, context: Dict[str, Any]) -> iQueryResult:
|
674
|
+
"""Parse hybrid/default queries."""
|
675
|
+
logger.debug(f"Parsing hybrid query: {query}")
|
676
|
+
return SimpleQueryResult([])
|