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,403 @@
|
|
1
|
+
"""
|
2
|
+
Abstract Edge Strategy Interface
|
3
|
+
|
4
|
+
This module defines the abstract base class that all edge strategies must implement
|
5
|
+
in the strategy system.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
from typing import Any, Dict, List, Optional, Iterator, Union, Set
|
10
|
+
from ...types import EdgeMode, EdgeTrait
|
11
|
+
|
12
|
+
|
13
|
+
class aEdgeStrategy(ABC):
|
14
|
+
"""
|
15
|
+
Abstract base class for all edge strategies.
|
16
|
+
|
17
|
+
This abstract base class defines the contract that all edge strategy
|
18
|
+
implementations must follow, ensuring consistency and interoperability.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, mode: EdgeMode, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
22
|
+
"""Initialize the abstract edge strategy."""
|
23
|
+
self.mode = mode
|
24
|
+
self.traits = traits
|
25
|
+
self.options = options
|
26
|
+
self._edges: Dict[tuple[Any, Any], Dict[str, Any]] = {}
|
27
|
+
self._vertices: Set[Any] = set()
|
28
|
+
self._edge_count = 0
|
29
|
+
|
30
|
+
# Validate traits compatibility with mode
|
31
|
+
self._validate_traits()
|
32
|
+
|
33
|
+
def _validate_traits(self) -> None:
|
34
|
+
"""Validate that the requested traits are compatible with this strategy."""
|
35
|
+
supported_traits = self.get_supported_traits()
|
36
|
+
unsupported = self.traits & ~supported_traits
|
37
|
+
if unsupported != EdgeTrait.NONE:
|
38
|
+
unsupported_names = [trait.name for trait in EdgeTrait if trait in unsupported]
|
39
|
+
raise ValueError(f"Strategy {self.mode.name} does not support traits: {unsupported_names}")
|
40
|
+
|
41
|
+
def get_supported_traits(self) -> EdgeTrait:
|
42
|
+
"""Get the traits supported by this strategy implementation."""
|
43
|
+
# Default implementation - subclasses should override
|
44
|
+
return EdgeTrait.NONE
|
45
|
+
|
46
|
+
def has_trait(self, trait: EdgeTrait) -> bool:
|
47
|
+
"""Check if this strategy has a specific trait."""
|
48
|
+
return bool(self.traits & trait)
|
49
|
+
|
50
|
+
def require_trait(self, trait: EdgeTrait, operation: str = "operation") -> None:
|
51
|
+
"""Require a specific trait for an operation."""
|
52
|
+
if not self.has_trait(trait):
|
53
|
+
from ...errors import UnsupportedCapabilityError
|
54
|
+
raise UnsupportedCapabilityError(f"{operation} requires {trait.name} capability")
|
55
|
+
|
56
|
+
# ============================================================================
|
57
|
+
# CORE OPERATIONS (Required)
|
58
|
+
# ============================================================================
|
59
|
+
|
60
|
+
@abstractmethod
|
61
|
+
def add_edge(self, u: Any, v: Any, **properties) -> None:
|
62
|
+
"""
|
63
|
+
Add an edge between vertices u and v.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
u: Source vertex
|
67
|
+
v: Target vertex
|
68
|
+
**properties: Edge properties (weight, label, etc.)
|
69
|
+
"""
|
70
|
+
pass
|
71
|
+
|
72
|
+
@abstractmethod
|
73
|
+
def remove_edge(self, u: Any, v: Any) -> bool:
|
74
|
+
"""
|
75
|
+
Remove an edge between vertices u and v.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
u: Source vertex
|
79
|
+
v: Target vertex
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
True if edge was found and removed, False otherwise
|
83
|
+
"""
|
84
|
+
pass
|
85
|
+
|
86
|
+
@abstractmethod
|
87
|
+
def has_edge(self, u: Any, v: Any) -> bool:
|
88
|
+
"""
|
89
|
+
Check if an edge exists between vertices u and v.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
u: Source vertex
|
93
|
+
v: Target vertex
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
True if edge exists, False otherwise
|
97
|
+
"""
|
98
|
+
pass
|
99
|
+
|
100
|
+
@abstractmethod
|
101
|
+
def neighbors(self, u: Any) -> Iterator[Any]:
|
102
|
+
"""
|
103
|
+
Get neighbors of vertex u.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
u: Vertex to get neighbors for
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
Iterator over neighbor vertices
|
110
|
+
"""
|
111
|
+
pass
|
112
|
+
|
113
|
+
@abstractmethod
|
114
|
+
def degree(self, u: Any) -> int:
|
115
|
+
"""
|
116
|
+
Get the degree (number of edges) of vertex u.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
u: Vertex to get degree for
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
Number of edges incident to u
|
123
|
+
"""
|
124
|
+
pass
|
125
|
+
|
126
|
+
@abstractmethod
|
127
|
+
def edges(self) -> Iterator[tuple[Any, Any, Dict[str, Any]]]:
|
128
|
+
"""
|
129
|
+
Get all edges in the graph.
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
Iterator over (u, v, properties) tuples
|
133
|
+
"""
|
134
|
+
pass
|
135
|
+
|
136
|
+
@abstractmethod
|
137
|
+
def vertices(self) -> Iterator[Any]:
|
138
|
+
"""
|
139
|
+
Get all vertices in the graph.
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
Iterator over vertices
|
143
|
+
"""
|
144
|
+
pass
|
145
|
+
|
146
|
+
@abstractmethod
|
147
|
+
def __len__(self) -> int:
|
148
|
+
"""Get the number of edges."""
|
149
|
+
pass
|
150
|
+
|
151
|
+
# ============================================================================
|
152
|
+
# CAPABILITY-BASED OPERATIONS (Optional)
|
153
|
+
# ============================================================================
|
154
|
+
|
155
|
+
def get_edge_weight(self, u: Any, v: Any) -> float:
|
156
|
+
"""
|
157
|
+
Get weight of edge (u, v) (requires WEIGHTED trait).
|
158
|
+
|
159
|
+
Args:
|
160
|
+
u: Source vertex
|
161
|
+
v: Target vertex
|
162
|
+
|
163
|
+
Returns:
|
164
|
+
Weight of the edge
|
165
|
+
|
166
|
+
Raises:
|
167
|
+
UnsupportedCapabilityError: If WEIGHTED trait not supported
|
168
|
+
"""
|
169
|
+
if EdgeTrait.WEIGHTED not in self.traits:
|
170
|
+
raise XWNodeUnsupportedCapabilityError("WEIGHTED", self.mode.name, [str(t) for t in self.traits])
|
171
|
+
|
172
|
+
# Default implementation for weighted strategies
|
173
|
+
edge_data = self._edges.get((u, v), {})
|
174
|
+
return edge_data.get('weight', 1.0)
|
175
|
+
|
176
|
+
def set_edge_weight(self, u: Any, v: Any, weight: float) -> None:
|
177
|
+
"""
|
178
|
+
Set weight of edge (u, v) (requires WEIGHTED trait).
|
179
|
+
|
180
|
+
Args:
|
181
|
+
u: Source vertex
|
182
|
+
v: Target vertex
|
183
|
+
weight: New weight value
|
184
|
+
|
185
|
+
Raises:
|
186
|
+
UnsupportedCapabilityError: If WEIGHTED trait not supported
|
187
|
+
"""
|
188
|
+
if EdgeTrait.WEIGHTED not in self.traits:
|
189
|
+
raise XWNodeUnsupportedCapabilityError("WEIGHTED", self.mode.name, [str(t) for t in self.traits])
|
190
|
+
|
191
|
+
# Default implementation for weighted strategies
|
192
|
+
if (u, v) in self._edges:
|
193
|
+
self._edges[(u, v)]['weight'] = weight
|
194
|
+
|
195
|
+
def get_edge_property(self, u: Any, v: Any, property_name: str) -> Any:
|
196
|
+
"""
|
197
|
+
Get a property of edge (u, v).
|
198
|
+
|
199
|
+
Args:
|
200
|
+
u: Source vertex
|
201
|
+
v: Target vertex
|
202
|
+
property_name: Name of the property
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
Property value, or None if not found
|
206
|
+
"""
|
207
|
+
edge_data = self._edges.get((u, v), {})
|
208
|
+
return edge_data.get(property_name)
|
209
|
+
|
210
|
+
def set_edge_property(self, u: Any, v: Any, property_name: str, value: Any) -> None:
|
211
|
+
"""
|
212
|
+
Set a property of edge (u, v).
|
213
|
+
|
214
|
+
Args:
|
215
|
+
u: Source vertex
|
216
|
+
v: Target vertex
|
217
|
+
property_name: Name of the property
|
218
|
+
value: Property value
|
219
|
+
"""
|
220
|
+
if (u, v) in self._edges:
|
221
|
+
self._edges[(u, v)][property_name] = value
|
222
|
+
|
223
|
+
def get_spatial_edges(self, bounds: Dict[str, Any]) -> List[tuple[Any, Any, Dict[str, Any]]]:
|
224
|
+
"""
|
225
|
+
Get edges within spatial bounds (requires SPATIAL trait).
|
226
|
+
|
227
|
+
Args:
|
228
|
+
bounds: Spatial bounds (e.g., {'x_min': 0, 'x_max': 10, 'y_min': 0, 'y_max': 10})
|
229
|
+
|
230
|
+
Returns:
|
231
|
+
List of (u, v, properties) tuples within bounds
|
232
|
+
|
233
|
+
Raises:
|
234
|
+
UnsupportedCapabilityError: If SPATIAL trait not supported
|
235
|
+
"""
|
236
|
+
if EdgeTrait.SPATIAL not in self.traits:
|
237
|
+
raise XWNodeUnsupportedCapabilityError("SPATIAL", self.mode.name, [str(t) for t in self.traits])
|
238
|
+
|
239
|
+
# Default implementation for spatial strategies
|
240
|
+
# This would be overridden by actual spatial implementations
|
241
|
+
return list(self.edges())
|
242
|
+
|
243
|
+
def get_temporal_edges(self, start_time: Any, end_time: Any) -> List[tuple[Any, Any, Dict[str, Any]]]:
|
244
|
+
"""
|
245
|
+
Get edges within temporal range (requires TEMPORAL trait).
|
246
|
+
|
247
|
+
Args:
|
248
|
+
start_time: Start time (inclusive)
|
249
|
+
end_time: End time (exclusive)
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
List of (u, v, properties) tuples within time range
|
253
|
+
|
254
|
+
Raises:
|
255
|
+
UnsupportedCapabilityError: If TEMPORAL trait not supported
|
256
|
+
"""
|
257
|
+
if EdgeTrait.TEMPORAL not in self.traits:
|
258
|
+
raise XWNodeUnsupportedCapabilityError("TEMPORAL", self.mode.name, [str(t) for t in self.traits])
|
259
|
+
|
260
|
+
# Default implementation for temporal strategies
|
261
|
+
result = []
|
262
|
+
for u, v, props in self.edges():
|
263
|
+
edge_time = props.get('time', 0)
|
264
|
+
if start_time <= edge_time < end_time:
|
265
|
+
result.append((u, v, props))
|
266
|
+
return result
|
267
|
+
|
268
|
+
def add_hyperedge(self, vertices: List[Any], **properties) -> str:
|
269
|
+
"""
|
270
|
+
Add a hyperedge connecting multiple vertices (requires HYPER trait).
|
271
|
+
|
272
|
+
Args:
|
273
|
+
vertices: List of vertices to connect
|
274
|
+
**properties: Hyperedge properties
|
275
|
+
|
276
|
+
Returns:
|
277
|
+
Hyperedge identifier
|
278
|
+
|
279
|
+
Raises:
|
280
|
+
UnsupportedCapabilityError: If HYPER trait not supported
|
281
|
+
"""
|
282
|
+
if EdgeTrait.HYPER not in self.traits:
|
283
|
+
raise XWNodeUnsupportedCapabilityError("HYPER", self.mode.name, [str(t) for t in self.traits])
|
284
|
+
|
285
|
+
# Default implementation for hyperedge strategies
|
286
|
+
# This would be overridden by actual hyperedge implementations
|
287
|
+
raise NotImplementedError("Hyperedge support not implemented in base class")
|
288
|
+
|
289
|
+
# ============================================================================
|
290
|
+
# GRAPH ANALYSIS OPERATIONS
|
291
|
+
# ============================================================================
|
292
|
+
|
293
|
+
def vertex_count(self) -> int:
|
294
|
+
"""Get the number of vertices."""
|
295
|
+
return len(self._vertices)
|
296
|
+
|
297
|
+
def edge_count(self) -> int:
|
298
|
+
"""Get the number of edges."""
|
299
|
+
return len(self)
|
300
|
+
|
301
|
+
def density(self) -> float:
|
302
|
+
"""Calculate graph density."""
|
303
|
+
n = self.vertex_count()
|
304
|
+
if n <= 1:
|
305
|
+
return 0.0
|
306
|
+
return self.edge_count() / (n * (n - 1))
|
307
|
+
|
308
|
+
def is_directed(self) -> bool:
|
309
|
+
"""Check if graph is directed."""
|
310
|
+
return EdgeTrait.DIRECTED in self.traits
|
311
|
+
|
312
|
+
def is_weighted(self) -> bool:
|
313
|
+
"""Check if graph is weighted."""
|
314
|
+
return EdgeTrait.WEIGHTED in self.traits
|
315
|
+
|
316
|
+
def is_spatial(self) -> bool:
|
317
|
+
"""Check if graph has spatial properties."""
|
318
|
+
return EdgeTrait.SPATIAL in self.traits
|
319
|
+
|
320
|
+
def is_temporal(self) -> bool:
|
321
|
+
"""Check if graph has temporal properties."""
|
322
|
+
return EdgeTrait.TEMPORAL in self.traits
|
323
|
+
|
324
|
+
def is_hyper(self) -> bool:
|
325
|
+
"""Check if graph supports hyperedges."""
|
326
|
+
return EdgeTrait.HYPER in self.traits
|
327
|
+
|
328
|
+
# ============================================================================
|
329
|
+
# STRATEGY METADATA
|
330
|
+
# ============================================================================
|
331
|
+
|
332
|
+
def capabilities(self) -> EdgeTrait:
|
333
|
+
"""Get the capabilities supported by this strategy."""
|
334
|
+
return self.traits
|
335
|
+
|
336
|
+
def backend_info(self) -> Dict[str, Any]:
|
337
|
+
"""Get information about the backend implementation."""
|
338
|
+
return {
|
339
|
+
"mode": self.mode.name,
|
340
|
+
"traits": str(self.traits),
|
341
|
+
"vertices": self.vertex_count(),
|
342
|
+
"edges": self.edge_count(),
|
343
|
+
"density": self.density(),
|
344
|
+
"options": self.options.copy()
|
345
|
+
}
|
346
|
+
|
347
|
+
def metrics(self) -> Dict[str, Any]:
|
348
|
+
"""Get performance metrics for this strategy."""
|
349
|
+
return {
|
350
|
+
"vertices": self.vertex_count(),
|
351
|
+
"edges": self.edge_count(),
|
352
|
+
"density": self.density(),
|
353
|
+
"mode": self.mode.name,
|
354
|
+
"traits": str(self.traits)
|
355
|
+
}
|
356
|
+
|
357
|
+
# ============================================================================
|
358
|
+
# UTILITY METHODS
|
359
|
+
# ============================================================================
|
360
|
+
|
361
|
+
def clear(self) -> None:
|
362
|
+
"""Clear all edges and vertices."""
|
363
|
+
self._edges.clear()
|
364
|
+
self._vertices.clear()
|
365
|
+
self._edge_count = 0
|
366
|
+
|
367
|
+
def add_vertex(self, v: Any) -> None:
|
368
|
+
"""Add a vertex to the graph."""
|
369
|
+
self._vertices.add(v)
|
370
|
+
|
371
|
+
def remove_vertex(self, v: Any) -> bool:
|
372
|
+
"""Remove a vertex and all its incident edges."""
|
373
|
+
if v not in self._vertices:
|
374
|
+
return False
|
375
|
+
|
376
|
+
# Remove all edges incident to v
|
377
|
+
edges_to_remove = []
|
378
|
+
for (u, w) in self._edges.keys():
|
379
|
+
if u == v or w == v:
|
380
|
+
edges_to_remove.append((u, w))
|
381
|
+
|
382
|
+
for (u, w) in edges_to_remove:
|
383
|
+
self.remove_edge(u, w)
|
384
|
+
|
385
|
+
self._vertices.remove(v)
|
386
|
+
return True
|
387
|
+
|
388
|
+
def __contains__(self, edge: tuple[Any, Any]) -> bool:
|
389
|
+
"""Check if edge exists."""
|
390
|
+
u, v = edge
|
391
|
+
return self.has_edge(u, v)
|
392
|
+
|
393
|
+
def __str__(self) -> str:
|
394
|
+
"""String representation."""
|
395
|
+
return f"{self.__class__.__name__}(mode={self.mode.name}, vertices={self.vertex_count()}, edges={self.edge_count()})"
|
396
|
+
|
397
|
+
def __repr__(self) -> str:
|
398
|
+
"""Detailed string representation."""
|
399
|
+
return f"{self.__class__.__name__}(mode={self.mode.name}, traits={self.traits}, vertices={self.vertex_count()}, edges={self.edge_count()})"
|
400
|
+
|
401
|
+
|
402
|
+
# Import here to avoid circular imports
|
403
|
+
from ...errors import XWNodeUnsupportedCapabilityError
|
@@ -0,0 +1,307 @@
|
|
1
|
+
"""
|
2
|
+
Abstract Node Strategy Interface
|
3
|
+
|
4
|
+
This module defines the abstract base class that all node strategies must implement
|
5
|
+
in the strategy system.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
from typing import Any, Dict, List, Optional, Iterator, Union
|
10
|
+
from ...types import NodeMode, NodeTrait
|
11
|
+
from ...errors import XWNodeUnsupportedCapabilityError
|
12
|
+
|
13
|
+
|
14
|
+
class aNodeStrategy(ABC):
|
15
|
+
"""
|
16
|
+
Abstract base class for all node strategies.
|
17
|
+
|
18
|
+
This abstract base class defines the contract that all node strategy
|
19
|
+
implementations must follow, ensuring consistency and interoperability.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, mode: NodeMode, traits: NodeTrait = NodeTrait.NONE, **options):
|
23
|
+
"""Initialize the abstract node strategy."""
|
24
|
+
self.mode = mode
|
25
|
+
self.traits = traits
|
26
|
+
self.options = options
|
27
|
+
self._data: Dict[str, Any] = {}
|
28
|
+
self._size = 0
|
29
|
+
|
30
|
+
# Validate traits compatibility with mode
|
31
|
+
self._validate_traits()
|
32
|
+
|
33
|
+
def _validate_traits(self) -> None:
|
34
|
+
"""Validate that the requested traits are compatible with this strategy."""
|
35
|
+
supported_traits = self.get_supported_traits()
|
36
|
+
unsupported = self.traits & ~supported_traits
|
37
|
+
if unsupported != NodeTrait.NONE:
|
38
|
+
unsupported_names = [trait.name for trait in NodeTrait if trait in unsupported]
|
39
|
+
raise ValueError(f"Strategy {self.mode.name} does not support traits: {unsupported_names}")
|
40
|
+
|
41
|
+
def get_supported_traits(self) -> NodeTrait:
|
42
|
+
"""Get the traits supported by this strategy implementation."""
|
43
|
+
# Default implementation - subclasses should override
|
44
|
+
return NodeTrait.NONE
|
45
|
+
|
46
|
+
def has_trait(self, trait: NodeTrait) -> bool:
|
47
|
+
"""Check if this strategy has a specific trait."""
|
48
|
+
return bool(self.traits & trait)
|
49
|
+
|
50
|
+
def require_trait(self, trait: NodeTrait, operation: str = "operation") -> None:
|
51
|
+
"""Require a specific trait for an operation."""
|
52
|
+
if not self.has_trait(trait):
|
53
|
+
from ...errors import UnsupportedCapabilityError
|
54
|
+
raise UnsupportedCapabilityError(f"{operation} requires {trait.name} capability")
|
55
|
+
|
56
|
+
# ============================================================================
|
57
|
+
# CORE OPERATIONS (Required)
|
58
|
+
# ============================================================================
|
59
|
+
|
60
|
+
@abstractmethod
|
61
|
+
def put(self, key: Any, value: Any = None) -> None:
|
62
|
+
"""
|
63
|
+
Store a key-value pair.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
key: The key to store
|
67
|
+
value: The value to associate with the key
|
68
|
+
"""
|
69
|
+
pass
|
70
|
+
|
71
|
+
@abstractmethod
|
72
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
73
|
+
"""
|
74
|
+
Retrieve a value by key.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
key: The key to look up
|
78
|
+
default: Default value if key not found
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
The value associated with the key, or default if not found
|
82
|
+
"""
|
83
|
+
pass
|
84
|
+
|
85
|
+
@abstractmethod
|
86
|
+
def delete(self, key: Any) -> bool:
|
87
|
+
"""
|
88
|
+
Remove a key-value pair.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
key: The key to remove
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
True if key was found and removed, False otherwise
|
95
|
+
"""
|
96
|
+
pass
|
97
|
+
|
98
|
+
@abstractmethod
|
99
|
+
def has(self, key: Any) -> bool:
|
100
|
+
"""
|
101
|
+
Check if a key exists.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
key: The key to check
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
True if key exists, False otherwise
|
108
|
+
"""
|
109
|
+
pass
|
110
|
+
|
111
|
+
@abstractmethod
|
112
|
+
def __len__(self) -> int:
|
113
|
+
"""Get the number of key-value pairs."""
|
114
|
+
pass
|
115
|
+
|
116
|
+
@abstractmethod
|
117
|
+
def keys(self) -> Iterator[Any]:
|
118
|
+
"""Get an iterator over all keys."""
|
119
|
+
pass
|
120
|
+
|
121
|
+
@abstractmethod
|
122
|
+
def values(self) -> Iterator[Any]:
|
123
|
+
"""Get an iterator over all values."""
|
124
|
+
pass
|
125
|
+
|
126
|
+
@abstractmethod
|
127
|
+
def items(self) -> Iterator[tuple[Any, Any]]:
|
128
|
+
"""Get an iterator over all key-value pairs."""
|
129
|
+
pass
|
130
|
+
|
131
|
+
# ============================================================================
|
132
|
+
# CAPABILITY-BASED OPERATIONS (Optional)
|
133
|
+
# ============================================================================
|
134
|
+
|
135
|
+
def get_ordered(self, start: Any = None, end: Any = None) -> List[tuple[Any, Any]]:
|
136
|
+
"""
|
137
|
+
Get items in order (requires ORDERED trait).
|
138
|
+
|
139
|
+
Args:
|
140
|
+
start: Start key (inclusive)
|
141
|
+
end: End key (exclusive)
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
List of (key, value) pairs in order
|
145
|
+
|
146
|
+
Raises:
|
147
|
+
UnsupportedCapabilityError: If ORDERED trait not supported
|
148
|
+
"""
|
149
|
+
if NodeTrait.ORDERED not in self.traits:
|
150
|
+
raise XWNodeUnsupportedCapabilityError("ORDERED", self.mode.name, [str(t) for t in self.traits])
|
151
|
+
|
152
|
+
# Default implementation for ordered strategies
|
153
|
+
items = list(self.items())
|
154
|
+
if start is not None:
|
155
|
+
items = [(k, v) for k, v in items if k >= start]
|
156
|
+
if end is not None:
|
157
|
+
items = [(k, v) for k, v in items if k < end]
|
158
|
+
return items
|
159
|
+
|
160
|
+
def get_with_prefix(self, prefix: str) -> List[tuple[Any, Any]]:
|
161
|
+
"""
|
162
|
+
Get items with given prefix (requires HIERARCHICAL trait).
|
163
|
+
|
164
|
+
Args:
|
165
|
+
prefix: The prefix to match
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
List of (key, value) pairs with matching prefix
|
169
|
+
|
170
|
+
Raises:
|
171
|
+
UnsupportedCapabilityError: If HIERARCHICAL trait not supported
|
172
|
+
"""
|
173
|
+
if NodeTrait.HIERARCHICAL not in self.traits:
|
174
|
+
raise XWNodeUnsupportedCapabilityError("HIERARCHICAL", self.mode.name, [str(t) for t in self.traits])
|
175
|
+
|
176
|
+
# Default implementation for hierarchical strategies
|
177
|
+
return [(k, v) for k, v in self.items() if str(k).startswith(prefix)]
|
178
|
+
|
179
|
+
def get_priority(self) -> Optional[tuple[Any, Any]]:
|
180
|
+
"""
|
181
|
+
Get highest priority item (requires PRIORITY trait).
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
(key, value) pair with highest priority, or None if empty
|
185
|
+
|
186
|
+
Raises:
|
187
|
+
UnsupportedCapabilityError: If PRIORITY trait not supported
|
188
|
+
"""
|
189
|
+
if NodeTrait.PRIORITY not in self.traits:
|
190
|
+
raise XWNodeUnsupportedCapabilityError("PRIORITY", self.mode.name, [str(t) for t in self.traits])
|
191
|
+
|
192
|
+
# Default implementation for priority strategies
|
193
|
+
if not self._data:
|
194
|
+
return None
|
195
|
+
return min(self.items(), key=lambda x: x[0])
|
196
|
+
|
197
|
+
def get_weighted(self, key: Any) -> float:
|
198
|
+
"""
|
199
|
+
Get weight for a key (requires WEIGHTED trait).
|
200
|
+
|
201
|
+
Args:
|
202
|
+
key: The key to get weight for
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
Weight value for the key
|
206
|
+
|
207
|
+
Raises:
|
208
|
+
UnsupportedCapabilityError: If WEIGHTED trait not supported
|
209
|
+
"""
|
210
|
+
if NodeTrait.WEIGHTED not in self.traits:
|
211
|
+
raise XWNodeUnsupportedCapabilityError("WEIGHTED", self.mode.name, [str(t) for t in self.traits])
|
212
|
+
|
213
|
+
# Default implementation for weighted strategies
|
214
|
+
return self._data.get(key, {}).get('weight', 1.0)
|
215
|
+
|
216
|
+
# ============================================================================
|
217
|
+
# STRATEGY METADATA
|
218
|
+
# ============================================================================
|
219
|
+
|
220
|
+
def capabilities(self) -> NodeTrait:
|
221
|
+
"""Get the capabilities supported by this strategy."""
|
222
|
+
return self.traits
|
223
|
+
|
224
|
+
def backend_info(self) -> Dict[str, Any]:
|
225
|
+
"""Get information about the backend implementation."""
|
226
|
+
return {
|
227
|
+
"mode": self.mode.name,
|
228
|
+
"traits": str(self.traits),
|
229
|
+
"size": len(self),
|
230
|
+
"options": self.options.copy()
|
231
|
+
}
|
232
|
+
|
233
|
+
def metrics(self) -> Dict[str, Any]:
|
234
|
+
"""Get performance metrics for this strategy."""
|
235
|
+
return {
|
236
|
+
"size": len(self),
|
237
|
+
"mode": self.mode.name,
|
238
|
+
"traits": str(self.traits)
|
239
|
+
}
|
240
|
+
|
241
|
+
# ============================================================================
|
242
|
+
# FACTORY METHODS
|
243
|
+
# ============================================================================
|
244
|
+
|
245
|
+
@classmethod
|
246
|
+
def create_from_data(cls, data: Any) -> 'aNodeStrategy':
|
247
|
+
"""
|
248
|
+
Create a new strategy instance from data.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
data: The data to create the strategy from
|
252
|
+
|
253
|
+
Returns:
|
254
|
+
A new strategy instance containing the data
|
255
|
+
"""
|
256
|
+
instance = cls()
|
257
|
+
if isinstance(data, dict):
|
258
|
+
for key, value in data.items():
|
259
|
+
instance.put(key, value)
|
260
|
+
elif isinstance(data, (list, tuple)):
|
261
|
+
for i, value in enumerate(data):
|
262
|
+
instance.put(i, value)
|
263
|
+
else:
|
264
|
+
# For primitive values, store as root value
|
265
|
+
instance.put('_value', data)
|
266
|
+
return instance
|
267
|
+
|
268
|
+
# ============================================================================
|
269
|
+
# UTILITY METHODS
|
270
|
+
# ============================================================================
|
271
|
+
|
272
|
+
def clear(self) -> None:
|
273
|
+
"""Clear all data."""
|
274
|
+
self._data.clear()
|
275
|
+
self._size = 0
|
276
|
+
|
277
|
+
def __contains__(self, key: Any) -> bool:
|
278
|
+
"""Check if key exists."""
|
279
|
+
return self.has(key)
|
280
|
+
|
281
|
+
def __getitem__(self, key: Any) -> Any:
|
282
|
+
"""Get value by key."""
|
283
|
+
return self.get(key)
|
284
|
+
|
285
|
+
def __setitem__(self, key: Any, value: Any) -> None:
|
286
|
+
"""Set value by key."""
|
287
|
+
self.put(key, value)
|
288
|
+
|
289
|
+
def __delitem__(self, key: Any) -> None:
|
290
|
+
"""Delete key."""
|
291
|
+
if not self.delete(key):
|
292
|
+
raise KeyError(key)
|
293
|
+
|
294
|
+
def __iter__(self) -> Iterator[Any]:
|
295
|
+
"""Iterate over keys."""
|
296
|
+
return self.keys()
|
297
|
+
|
298
|
+
def __str__(self) -> str:
|
299
|
+
"""String representation."""
|
300
|
+
return f"{self.__class__.__name__}(mode={self.mode.name}, size={len(self)})"
|
301
|
+
|
302
|
+
def __repr__(self) -> str:
|
303
|
+
"""Detailed string representation."""
|
304
|
+
return f"{self.__class__.__name__}(mode={self.mode.name}, traits={self.traits}, size={len(self)})"
|
305
|
+
|
306
|
+
|
307
|
+
|