exonware-xwnode 0.0.1.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. exonware/__init__.py +14 -0
  2. exonware/xwnode/__init__.py +127 -0
  3. exonware/xwnode/base.py +676 -0
  4. exonware/xwnode/config.py +178 -0
  5. exonware/xwnode/contracts.py +730 -0
  6. exonware/xwnode/errors.py +503 -0
  7. exonware/xwnode/facade.py +460 -0
  8. exonware/xwnode/strategies/__init__.py +158 -0
  9. exonware/xwnode/strategies/advisor.py +463 -0
  10. exonware/xwnode/strategies/edges/__init__.py +32 -0
  11. exonware/xwnode/strategies/edges/adj_list.py +227 -0
  12. exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
  13. exonware/xwnode/strategies/edges/base.py +169 -0
  14. exonware/xwnode/strategies/flyweight.py +328 -0
  15. exonware/xwnode/strategies/impls/__init__.py +13 -0
  16. exonware/xwnode/strategies/impls/_base_edge.py +403 -0
  17. exonware/xwnode/strategies/impls/_base_node.py +307 -0
  18. exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
  19. exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
  20. exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
  21. exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
  22. exonware/xwnode/strategies/impls/edge_coo.py +533 -0
  23. exonware/xwnode/strategies/impls/edge_csc.py +447 -0
  24. exonware/xwnode/strategies/impls/edge_csr.py +492 -0
  25. exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
  26. exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
  27. exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
  28. exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
  29. exonware/xwnode/strategies/impls/edge_octree.py +574 -0
  30. exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
  31. exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
  32. exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
  33. exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
  34. exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
  35. exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
  36. exonware/xwnode/strategies/manager.py +775 -0
  37. exonware/xwnode/strategies/metrics.py +538 -0
  38. exonware/xwnode/strategies/migration.py +432 -0
  39. exonware/xwnode/strategies/nodes/__init__.py +50 -0
  40. exonware/xwnode/strategies/nodes/_base_node.py +307 -0
  41. exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
  42. exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
  43. exonware/xwnode/strategies/nodes/array_list.py +209 -0
  44. exonware/xwnode/strategies/nodes/base.py +247 -0
  45. exonware/xwnode/strategies/nodes/deque.py +200 -0
  46. exonware/xwnode/strategies/nodes/hash_map.py +135 -0
  47. exonware/xwnode/strategies/nodes/heap.py +307 -0
  48. exonware/xwnode/strategies/nodes/linked_list.py +232 -0
  49. exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
  50. exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
  51. exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
  52. exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
  53. exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
  54. exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
  55. exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
  56. exonware/xwnode/strategies/nodes/node_btree.py +357 -0
  57. exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
  58. exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
  59. exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
  60. exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
  61. exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
  62. exonware/xwnode/strategies/nodes/node_heap.py +191 -0
  63. exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
  64. exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
  65. exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
  66. exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
  67. exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
  68. exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
  69. exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
  70. exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
  71. exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
  72. exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
  73. exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
  74. exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
  75. exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
  76. exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
  77. exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
  78. exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
  79. exonware/xwnode/strategies/nodes/node_treap.py +387 -0
  80. exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
  81. exonware/xwnode/strategies/nodes/node_trie.py +252 -0
  82. exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
  83. exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
  84. exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
  85. exonware/xwnode/strategies/nodes/queue.py +161 -0
  86. exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
  87. exonware/xwnode/strategies/nodes/stack.py +152 -0
  88. exonware/xwnode/strategies/nodes/trie.py +274 -0
  89. exonware/xwnode/strategies/nodes/union_find.py +283 -0
  90. exonware/xwnode/strategies/pattern_detector.py +603 -0
  91. exonware/xwnode/strategies/performance_monitor.py +487 -0
  92. exonware/xwnode/strategies/queries/__init__.py +24 -0
  93. exonware/xwnode/strategies/queries/base.py +236 -0
  94. exonware/xwnode/strategies/queries/cql.py +201 -0
  95. exonware/xwnode/strategies/queries/cypher.py +181 -0
  96. exonware/xwnode/strategies/queries/datalog.py +70 -0
  97. exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
  98. exonware/xwnode/strategies/queries/eql.py +70 -0
  99. exonware/xwnode/strategies/queries/flux.py +70 -0
  100. exonware/xwnode/strategies/queries/gql.py +70 -0
  101. exonware/xwnode/strategies/queries/graphql.py +240 -0
  102. exonware/xwnode/strategies/queries/gremlin.py +181 -0
  103. exonware/xwnode/strategies/queries/hiveql.py +214 -0
  104. exonware/xwnode/strategies/queries/hql.py +70 -0
  105. exonware/xwnode/strategies/queries/jmespath.py +219 -0
  106. exonware/xwnode/strategies/queries/jq.py +66 -0
  107. exonware/xwnode/strategies/queries/json_query.py +66 -0
  108. exonware/xwnode/strategies/queries/jsoniq.py +248 -0
  109. exonware/xwnode/strategies/queries/kql.py +70 -0
  110. exonware/xwnode/strategies/queries/linq.py +238 -0
  111. exonware/xwnode/strategies/queries/logql.py +70 -0
  112. exonware/xwnode/strategies/queries/mql.py +68 -0
  113. exonware/xwnode/strategies/queries/n1ql.py +210 -0
  114. exonware/xwnode/strategies/queries/partiql.py +70 -0
  115. exonware/xwnode/strategies/queries/pig.py +215 -0
  116. exonware/xwnode/strategies/queries/promql.py +70 -0
  117. exonware/xwnode/strategies/queries/sparql.py +220 -0
  118. exonware/xwnode/strategies/queries/sql.py +275 -0
  119. exonware/xwnode/strategies/queries/xml_query.py +66 -0
  120. exonware/xwnode/strategies/queries/xpath.py +223 -0
  121. exonware/xwnode/strategies/queries/xquery.py +258 -0
  122. exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
  123. exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
  124. exonware/xwnode/strategies/registry.py +604 -0
  125. exonware/xwnode/strategies/simple.py +273 -0
  126. exonware/xwnode/strategies/utils.py +532 -0
  127. exonware/xwnode/types.py +912 -0
  128. exonware/xwnode/version.py +78 -0
  129. exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
  130. exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
  131. exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
  132. exonware_xwnode-0.0.1.12.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,353 @@
1
+ """
2
+ Adjacency List Edge Strategy Implementation
3
+
4
+ This module implements the ADJ_LIST strategy for sparse graph representation
5
+ with efficient edge addition and neighbor queries.
6
+ """
7
+
8
+ from typing import Any, Iterator, Dict, List, Set, Optional, Tuple
9
+ from collections import defaultdict
10
+ from ._base_edge import aEdgeStrategy
11
+ from ...types import EdgeMode, EdgeTrait
12
+
13
+
14
+ class xAdjListStrategy(aEdgeStrategy):
15
+ """
16
+ Adjacency List edge strategy for sparse graph representation.
17
+
18
+ Provides O(1) edge addition and O(degree) neighbor queries,
19
+ ideal for sparse graphs where most vertices have few connections.
20
+ """
21
+
22
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
23
+ """Initialize the Adjacency List strategy."""
24
+ super().__init__(EdgeMode.ADJ_LIST, traits, **options)
25
+
26
+ self.is_directed = options.get('directed', True)
27
+ self.allow_self_loops = options.get('self_loops', True)
28
+ self.allow_multi_edges = options.get('multi_edges', False)
29
+
30
+ # Core storage: vertex -> list of (neighbor, edge_data)
31
+ self._outgoing: Dict[str, List[Tuple[str, Dict[str, Any]]]] = defaultdict(list)
32
+ self._incoming: Dict[str, List[Tuple[str, Dict[str, Any]]]] = defaultdict(list) if self.is_directed else None
33
+
34
+ # Vertex set for fast membership testing
35
+ self._vertices: Set[str] = set()
36
+
37
+ # Edge properties storage
38
+ self._edge_count = 0
39
+ self._edge_id_counter = 0
40
+
41
+ def get_supported_traits(self) -> EdgeTrait:
42
+ """Get the traits supported by the adjacency list strategy."""
43
+ return (EdgeTrait.SPARSE | EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.MULTI)
44
+
45
+ # ============================================================================
46
+ # CORE EDGE OPERATIONS
47
+ # ============================================================================
48
+
49
+ def add_edge(self, source: str, target: str, **properties) -> str:
50
+ """Add an edge between source and target vertices."""
51
+ # Validate self-loops
52
+ if source == target and not self.allow_self_loops:
53
+ raise ValueError(f"Self-loops not allowed: {source} -> {target}")
54
+
55
+ # Check for existing edge if multi-edges not allowed
56
+ if not self.allow_multi_edges and self.has_edge(source, target):
57
+ raise ValueError(f"Multi-edges not allowed: {source} -> {target}")
58
+
59
+ # Generate edge ID
60
+ edge_id = f"edge_{self._edge_id_counter}"
61
+ self._edge_id_counter += 1
62
+
63
+ # Create edge data
64
+ edge_data = {
65
+ 'id': edge_id,
66
+ 'source': source,
67
+ 'target': target,
68
+ 'properties': properties.copy()
69
+ }
70
+
71
+ # Add vertices to vertex set
72
+ self._vertices.add(source)
73
+ self._vertices.add(target)
74
+
75
+ # Add to outgoing adjacency list
76
+ self._outgoing[source].append((target, edge_data))
77
+
78
+ # Add to incoming adjacency list (if directed)
79
+ if self.is_directed and self._incoming is not None:
80
+ self._incoming[target].append((source, edge_data))
81
+ elif not self.is_directed:
82
+ # For undirected graphs, add reverse edge (unless it's a self-loop)
83
+ if source != target:
84
+ self._outgoing[target].append((source, edge_data))
85
+
86
+ self._edge_count += 1
87
+ return edge_id
88
+
89
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
90
+ """Remove edge(s) between source and target."""
91
+ if source not in self._outgoing:
92
+ return False
93
+
94
+ removed = False
95
+
96
+ # Remove from outgoing list
97
+ original_length = len(self._outgoing[source])
98
+ if edge_id:
99
+ # Remove specific edge by ID
100
+ self._outgoing[source] = [
101
+ (neighbor, data) for neighbor, data in self._outgoing[source]
102
+ if not (neighbor == target and data['id'] == edge_id)
103
+ ]
104
+ else:
105
+ # Remove all edges to target
106
+ self._outgoing[source] = [
107
+ (neighbor, data) for neighbor, data in self._outgoing[source]
108
+ if neighbor != target
109
+ ]
110
+
111
+ removed = len(self._outgoing[source]) < original_length
112
+
113
+ if removed:
114
+ self._edge_count -= (original_length - len(self._outgoing[source]))
115
+
116
+ # Remove from incoming list (if directed)
117
+ if self.is_directed and self._incoming is not None and target in self._incoming:
118
+ if edge_id:
119
+ self._incoming[target] = [
120
+ (neighbor, data) for neighbor, data in self._incoming[target]
121
+ if not (neighbor == source and data['id'] == edge_id)
122
+ ]
123
+ else:
124
+ self._incoming[target] = [
125
+ (neighbor, data) for neighbor, data in self._incoming[target]
126
+ if neighbor != source
127
+ ]
128
+ elif not self.is_directed and source != target:
129
+ # For undirected graphs, remove reverse edge
130
+ if target in self._outgoing:
131
+ if edge_id:
132
+ self._outgoing[target] = [
133
+ (neighbor, data) for neighbor, data in self._outgoing[target]
134
+ if not (neighbor == source and data['id'] == edge_id)
135
+ ]
136
+ else:
137
+ self._outgoing[target] = [
138
+ (neighbor, data) for neighbor, data in self._outgoing[target]
139
+ if neighbor != source
140
+ ]
141
+
142
+ return removed
143
+
144
+ def has_edge(self, source: str, target: str) -> bool:
145
+ """Check if edge exists between source and target."""
146
+ if source not in self._outgoing:
147
+ return False
148
+
149
+ return any(neighbor == target for neighbor, _ in self._outgoing[source])
150
+
151
+ def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
152
+ """Get edge data between source and target."""
153
+ if source not in self._outgoing:
154
+ return None
155
+
156
+ for neighbor, data in self._outgoing[source]:
157
+ if neighbor == target:
158
+ return data
159
+
160
+ return None
161
+
162
+ def neighbors(self, vertex: str, direction: str = 'out') -> Iterator[str]:
163
+ """Get neighbors of a vertex."""
164
+ if direction == 'out':
165
+ if vertex in self._outgoing:
166
+ for neighbor, _ in self._outgoing[vertex]:
167
+ yield neighbor
168
+ elif direction == 'in':
169
+ if self.is_directed and self._incoming is not None and vertex in self._incoming:
170
+ for neighbor, _ in self._incoming[vertex]:
171
+ yield neighbor
172
+ elif not self.is_directed:
173
+ # For undirected graphs, incoming = outgoing
174
+ if vertex in self._outgoing:
175
+ for neighbor, _ in self._outgoing[vertex]:
176
+ yield neighbor
177
+ elif direction == 'both':
178
+ # Get all neighbors (both in and out)
179
+ seen = set()
180
+ for neighbor in self.neighbors(vertex, 'out'):
181
+ if neighbor not in seen:
182
+ seen.add(neighbor)
183
+ yield neighbor
184
+ for neighbor in self.neighbors(vertex, 'in'):
185
+ if neighbor not in seen:
186
+ seen.add(neighbor)
187
+ yield neighbor
188
+
189
+ def degree(self, vertex: str, direction: str = 'out') -> int:
190
+ """Get degree of a vertex."""
191
+ if direction == 'out':
192
+ return len(self._outgoing.get(vertex, []))
193
+ elif direction == 'in':
194
+ if self.is_directed and self._incoming is not None:
195
+ return len(self._incoming.get(vertex, []))
196
+ elif not self.is_directed:
197
+ return len(self._outgoing.get(vertex, []))
198
+ else:
199
+ return 0
200
+ elif direction == 'both':
201
+ out_degree = self.degree(vertex, 'out')
202
+ in_degree = self.degree(vertex, 'in')
203
+ # For undirected graphs, avoid double counting
204
+ return out_degree if not self.is_directed else out_degree + in_degree
205
+
206
+ def edges(self, data: bool = False) -> Iterator[tuple]:
207
+ """Get all edges in the graph."""
208
+ seen_edges = set()
209
+
210
+ for source, adj_list in self._outgoing.items():
211
+ for target, edge_data in adj_list:
212
+ edge_key = (source, target, edge_data['id'])
213
+
214
+ if edge_key not in seen_edges:
215
+ seen_edges.add(edge_key)
216
+
217
+ if data:
218
+ yield (source, target, edge_data)
219
+ else:
220
+ yield (source, target)
221
+
222
+ def vertices(self) -> Iterator[str]:
223
+ """Get all vertices in the graph."""
224
+ return iter(self._vertices)
225
+
226
+ def __len__(self) -> int:
227
+ """Get the number of edges."""
228
+ return self._edge_count
229
+
230
+ def vertex_count(self) -> int:
231
+ """Get the number of vertices."""
232
+ return len(self._vertices)
233
+
234
+ def clear(self) -> None:
235
+ """Clear all edges and vertices."""
236
+ self._outgoing.clear()
237
+ if self._incoming is not None:
238
+ self._incoming.clear()
239
+ self._vertices.clear()
240
+ self._edge_count = 0
241
+ self._edge_id_counter = 0
242
+
243
+ def add_vertex(self, vertex: str) -> None:
244
+ """Add a vertex to the graph."""
245
+ self._vertices.add(vertex)
246
+ # Initialize adjacency lists if not present
247
+ if vertex not in self._outgoing:
248
+ self._outgoing[vertex] = []
249
+ if self.is_directed and self._incoming is not None and vertex not in self._incoming:
250
+ self._incoming[vertex] = []
251
+
252
+ def remove_vertex(self, vertex: str) -> bool:
253
+ """Remove a vertex and all its edges."""
254
+ if vertex not in self._vertices:
255
+ return False
256
+
257
+ # Remove all outgoing edges
258
+ edges_removed = len(self._outgoing.get(vertex, []))
259
+ self._edge_count -= edges_removed
260
+
261
+ # Remove all incoming edges
262
+ for source in list(self._outgoing.keys()):
263
+ if source != vertex:
264
+ original_length = len(self._outgoing[source])
265
+ self._outgoing[source] = [
266
+ (neighbor, data) for neighbor, data in self._outgoing[source]
267
+ if neighbor != vertex
268
+ ]
269
+ self._edge_count -= (original_length - len(self._outgoing[source]))
270
+
271
+ # Clean up adjacency lists
272
+ if vertex in self._outgoing:
273
+ del self._outgoing[vertex]
274
+ if self._incoming is not None and vertex in self._incoming:
275
+ del self._incoming[vertex]
276
+
277
+ # Remove from vertex set
278
+ self._vertices.remove(vertex)
279
+
280
+ return True
281
+
282
+ # ============================================================================
283
+ # ADVANCED OPERATIONS
284
+ # ============================================================================
285
+
286
+ def get_subgraph(self, vertices: Set[str]) -> 'xAdjListStrategy':
287
+ """Extract subgraph containing only specified vertices."""
288
+ subgraph = xAdjListStrategy(
289
+ traits=self._traits,
290
+ directed=self.is_directed,
291
+ self_loops=self.allow_self_loops,
292
+ multi_edges=self.allow_multi_edges
293
+ )
294
+
295
+ # Add vertices
296
+ for vertex in vertices:
297
+ if vertex in self._vertices:
298
+ subgraph.add_vertex(vertex)
299
+
300
+ # Add edges
301
+ for source, target, edge_data in self.edges(data=True):
302
+ if source in vertices and target in vertices:
303
+ subgraph.add_edge(source, target, **edge_data['properties'])
304
+
305
+ return subgraph
306
+
307
+ def get_edge_list(self) -> List[Tuple[str, str, Dict[str, Any]]]:
308
+ """Get all edges as a list."""
309
+ return list(self.edges(data=True))
310
+
311
+ def get_adjacency_dict(self) -> Dict[str, List[str]]:
312
+ """Get adjacency representation as a dictionary."""
313
+ return {
314
+ vertex: [neighbor for neighbor, _ in adj_list]
315
+ for vertex, adj_list in self._outgoing.items()
316
+ }
317
+
318
+ # ============================================================================
319
+ # PERFORMANCE CHARACTERISTICS
320
+ # ============================================================================
321
+
322
+ @property
323
+ def backend_info(self) -> Dict[str, Any]:
324
+ """Get backend implementation info."""
325
+ return {
326
+ 'strategy': 'adjacency_list',
327
+ 'backend': 'Python defaultdict + lists',
328
+ 'directed': self.is_directed,
329
+ 'multi_edges': self.allow_multi_edges,
330
+ 'self_loops': self.allow_self_loops,
331
+ 'complexity': {
332
+ 'add_edge': 'O(1)',
333
+ 'remove_edge': 'O(degree)',
334
+ 'has_edge': 'O(degree)',
335
+ 'neighbors': 'O(degree)',
336
+ 'space': 'O(V + E)'
337
+ }
338
+ }
339
+
340
+ @property
341
+ def metrics(self) -> Dict[str, Any]:
342
+ """Get performance metrics."""
343
+ avg_degree = self._edge_count / max(1, len(self._vertices)) if self._vertices else 0
344
+ density = self._edge_count / max(1, len(self._vertices) * (len(self._vertices) - 1)) if len(self._vertices) > 1 else 0
345
+
346
+ return {
347
+ 'vertices': len(self._vertices),
348
+ 'edges': self._edge_count,
349
+ 'average_degree': round(avg_degree, 2),
350
+ 'density': round(density, 4),
351
+ 'memory_usage': f"{len(self._vertices) * 48 + self._edge_count * 32} bytes (estimated)",
352
+ 'sparsity': f"{(1 - density) * 100:.1f}%"
353
+ }