exonware-xwnode 0.0.1.22__py3-none-any.whl → 0.0.1.23__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 +1 -1
- exonware/xwnode/__init__.py +18 -5
- exonware/xwnode/add_strategy_types.py +165 -0
- exonware/xwnode/common/__init__.py +1 -1
- exonware/xwnode/common/graph/__init__.py +30 -0
- exonware/xwnode/common/graph/caching.py +131 -0
- exonware/xwnode/common/graph/contracts.py +100 -0
- exonware/xwnode/common/graph/errors.py +44 -0
- exonware/xwnode/common/graph/indexing.py +260 -0
- exonware/xwnode/common/graph/manager.py +568 -0
- exonware/xwnode/common/management/__init__.py +3 -5
- exonware/xwnode/common/management/manager.py +2 -2
- exonware/xwnode/common/management/migration.py +3 -3
- exonware/xwnode/common/monitoring/__init__.py +3 -5
- exonware/xwnode/common/monitoring/metrics.py +6 -2
- exonware/xwnode/common/monitoring/pattern_detector.py +1 -1
- exonware/xwnode/common/monitoring/performance_monitor.py +5 -1
- exonware/xwnode/common/patterns/__init__.py +3 -5
- exonware/xwnode/common/patterns/flyweight.py +5 -1
- exonware/xwnode/common/patterns/registry.py +202 -183
- exonware/xwnode/common/utils/__init__.py +25 -11
- exonware/xwnode/common/utils/simple.py +1 -1
- exonware/xwnode/config.py +3 -8
- exonware/xwnode/contracts.py +4 -105
- exonware/xwnode/defs.py +413 -159
- exonware/xwnode/edges/strategies/__init__.py +86 -4
- exonware/xwnode/edges/strategies/_base_edge.py +2 -2
- exonware/xwnode/edges/strategies/adj_list.py +287 -121
- exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
- exonware/xwnode/edges/strategies/base.py +1 -1
- exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
- exonware/xwnode/edges/strategies/bitemporal.py +520 -0
- exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
- exonware/xwnode/edges/strategies/bv_graph.py +664 -0
- exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
- exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
- exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
- exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
- exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
- exonware/xwnode/edges/strategies/edge_list.py +168 -0
- exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
- exonware/xwnode/edges/strategies/euler_tour.py +560 -0
- exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
- exonware/xwnode/edges/strategies/graphblas.py +449 -0
- exonware/xwnode/edges/strategies/hnsw.py +637 -0
- exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
- exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
- exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
- exonware/xwnode/edges/strategies/k2_tree.py +613 -0
- exonware/xwnode/edges/strategies/link_cut.py +626 -0
- exonware/xwnode/edges/strategies/multiplex.py +532 -0
- exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
- exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
- exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
- exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
- exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
- exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
- exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
- exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
- exonware/xwnode/errors.py +3 -6
- exonware/xwnode/facade.py +20 -20
- exonware/xwnode/nodes/strategies/__init__.py +29 -9
- exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
- exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
- exonware/xwnode/nodes/strategies/array_list.py +36 -3
- exonware/xwnode/nodes/strategies/art.py +581 -0
- exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
- exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
- exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
- exonware/xwnode/nodes/strategies/base.py +469 -98
- exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
- exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
- exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
- exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
- exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
- exonware/xwnode/nodes/strategies/contracts.py +1 -1
- exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
- exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
- exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
- exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
- exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
- exonware/xwnode/nodes/strategies/dawg.py +876 -0
- exonware/xwnode/nodes/strategies/deque.py +321 -153
- exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
- exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
- exonware/xwnode/nodes/strategies/hamt.py +403 -0
- exonware/xwnode/nodes/strategies/hash_map.py +354 -67
- exonware/xwnode/nodes/strategies/heap.py +105 -5
- exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
- exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
- exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
- exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
- exonware/xwnode/nodes/strategies/learned_index.py +533 -0
- exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
- exonware/xwnode/nodes/strategies/linked_list.py +316 -119
- exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
- exonware/xwnode/nodes/strategies/masstree.py +130 -0
- exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
- exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
- exonware/xwnode/nodes/strategies/queue.py +249 -120
- exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
- exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
- exonware/xwnode/nodes/strategies/rope.py +717 -0
- exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
- exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
- exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
- exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
- exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
- exonware/xwnode/nodes/strategies/stack.py +244 -112
- exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
- exonware/xwnode/nodes/strategies/t_tree.py +94 -0
- exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
- exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
- exonware/xwnode/nodes/strategies/trie.py +153 -9
- exonware/xwnode/nodes/strategies/union_find.py +111 -5
- exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
- exonware/xwnode/strategies/__init__.py +5 -51
- exonware/xwnode/version.py +3 -3
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
- exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
- exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
- exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
- exonware/xwnode/nodes/strategies/_base_node.py +0 -307
- exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
- exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
- exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
- exonware/xwnode/nodes/strategies/node_heap.py +0 -196
- exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
- exonware/xwnode/nodes/strategies/node_trie.py +0 -257
- exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
- exonware/xwnode/queries/executors/__init__.py +0 -47
- exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
- exonware/xwnode/queries/executors/array/__init__.py +0 -9
- exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
- exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
- exonware/xwnode/queries/executors/base.py +0 -257
- exonware/xwnode/queries/executors/capability_checker.py +0 -204
- exonware/xwnode/queries/executors/contracts.py +0 -166
- exonware/xwnode/queries/executors/core/__init__.py +0 -17
- exonware/xwnode/queries/executors/core/create_executor.py +0 -96
- exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
- exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
- exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
- exonware/xwnode/queries/executors/core/select_executor.py +0 -152
- exonware/xwnode/queries/executors/core/update_executor.py +0 -102
- exonware/xwnode/queries/executors/data/__init__.py +0 -13
- exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
- exonware/xwnode/queries/executors/data/load_executor.py +0 -50
- exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
- exonware/xwnode/queries/executors/data/store_executor.py +0 -50
- exonware/xwnode/queries/executors/defs.py +0 -93
- exonware/xwnode/queries/executors/engine.py +0 -221
- exonware/xwnode/queries/executors/errors.py +0 -68
- exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
- exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
- exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
- exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
- exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
- exonware/xwnode/queries/executors/graph/__init__.py +0 -15
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
- exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
- exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
- exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
- exonware/xwnode/queries/executors/projection/__init__.py +0 -9
- exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
- exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
- exonware/xwnode/queries/executors/registry.py +0 -173
- exonware/xwnode/queries/parsers/__init__.py +0 -26
- exonware/xwnode/queries/parsers/base.py +0 -86
- exonware/xwnode/queries/parsers/contracts.py +0 -46
- exonware/xwnode/queries/parsers/errors.py +0 -53
- exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
- exonware/xwnode/queries/strategies/__init__.py +0 -24
- exonware/xwnode/queries/strategies/base.py +0 -236
- exonware/xwnode/queries/strategies/cql.py +0 -201
- exonware/xwnode/queries/strategies/cypher.py +0 -181
- exonware/xwnode/queries/strategies/datalog.py +0 -70
- exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
- exonware/xwnode/queries/strategies/eql.py +0 -70
- exonware/xwnode/queries/strategies/flux.py +0 -70
- exonware/xwnode/queries/strategies/gql.py +0 -70
- exonware/xwnode/queries/strategies/graphql.py +0 -240
- exonware/xwnode/queries/strategies/gremlin.py +0 -181
- exonware/xwnode/queries/strategies/hiveql.py +0 -214
- exonware/xwnode/queries/strategies/hql.py +0 -70
- exonware/xwnode/queries/strategies/jmespath.py +0 -219
- exonware/xwnode/queries/strategies/jq.py +0 -66
- exonware/xwnode/queries/strategies/json_query.py +0 -66
- exonware/xwnode/queries/strategies/jsoniq.py +0 -248
- exonware/xwnode/queries/strategies/kql.py +0 -70
- exonware/xwnode/queries/strategies/linq.py +0 -238
- exonware/xwnode/queries/strategies/logql.py +0 -70
- exonware/xwnode/queries/strategies/mql.py +0 -68
- exonware/xwnode/queries/strategies/n1ql.py +0 -210
- exonware/xwnode/queries/strategies/partiql.py +0 -70
- exonware/xwnode/queries/strategies/pig.py +0 -215
- exonware/xwnode/queries/strategies/promql.py +0 -70
- exonware/xwnode/queries/strategies/sparql.py +0 -220
- exonware/xwnode/queries/strategies/sql.py +0 -275
- exonware/xwnode/queries/strategies/xml_query.py +0 -66
- exonware/xwnode/queries/strategies/xpath.py +0 -223
- exonware/xwnode/queries/strategies/xquery.py +0 -258
- exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
- exonware/xwnode/queries/strategies/xwquery.py +0 -456
- exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
- /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,629 @@
|
|
1
|
+
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/nodes/strategies/crdt_map.py
|
3
|
+
|
4
|
+
CRDT Map Node Strategy Implementation
|
5
|
+
|
6
|
+
This module implements the CRDT_MAP strategy for conflict-free replicated
|
7
|
+
data type with Last-Write-Wins semantics for distributed systems.
|
8
|
+
|
9
|
+
Company: eXonware.com
|
10
|
+
Author: Eng. Muhammad AlShehri
|
11
|
+
Email: connect@exonware.com
|
12
|
+
Version: 0.0.1.23
|
13
|
+
Generation Date: 12-Oct-2025
|
14
|
+
"""
|
15
|
+
|
16
|
+
import time
|
17
|
+
from typing import Any, Iterator, List, Dict, Optional, Tuple
|
18
|
+
from .base import ANodeTreeStrategy
|
19
|
+
from .contracts import NodeType
|
20
|
+
from ...defs import NodeMode, NodeTrait
|
21
|
+
from ...errors import XWNodeError, XWNodeValueError
|
22
|
+
|
23
|
+
|
24
|
+
class VectorClock:
|
25
|
+
"""
|
26
|
+
Vector clock for causality tracking.
|
27
|
+
|
28
|
+
WHY vector clocks:
|
29
|
+
- Tracks causality between operations
|
30
|
+
- Enables conflict detection
|
31
|
+
- Ensures eventual consistency
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self, replica_id: str = "default"):
|
35
|
+
"""
|
36
|
+
Initialize vector clock.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
replica_id: Unique replica identifier
|
40
|
+
"""
|
41
|
+
self.clocks: Dict[str, int] = {replica_id: 0}
|
42
|
+
self.replica_id = replica_id
|
43
|
+
|
44
|
+
def increment(self) -> None:
|
45
|
+
"""Increment this replica's clock."""
|
46
|
+
self.clocks[self.replica_id] = self.clocks.get(self.replica_id, 0) + 1
|
47
|
+
|
48
|
+
def update(self, other: 'VectorClock') -> None:
|
49
|
+
"""
|
50
|
+
Update with another vector clock.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
other: Other vector clock
|
54
|
+
|
55
|
+
WHY merge clocks:
|
56
|
+
- Takes maximum of each replica's clock
|
57
|
+
- Preserves causality information
|
58
|
+
- Enables happened-before detection
|
59
|
+
"""
|
60
|
+
for replica, clock in other.clocks.items():
|
61
|
+
self.clocks[replica] = max(self.clocks.get(replica, 0), clock)
|
62
|
+
|
63
|
+
def happens_before(self, other: 'VectorClock') -> bool:
|
64
|
+
"""
|
65
|
+
Check if this clock happened before other.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
other: Other vector clock
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
True if this happened before other
|
72
|
+
"""
|
73
|
+
# All clocks must be ≤ corresponding clocks in other
|
74
|
+
for replica, clock in self.clocks.items():
|
75
|
+
if clock > other.clocks.get(replica, 0):
|
76
|
+
return False
|
77
|
+
|
78
|
+
# At least one must be strictly less
|
79
|
+
return any(
|
80
|
+
self.clocks.get(replica, 0) < clock
|
81
|
+
for replica, clock in other.clocks.items()
|
82
|
+
)
|
83
|
+
|
84
|
+
def concurrent_with(self, other: 'VectorClock') -> bool:
|
85
|
+
"""Check if concurrent (neither happened before the other)."""
|
86
|
+
return not self.happens_before(other) and not other.happens_before(self)
|
87
|
+
|
88
|
+
def copy(self) -> 'VectorClock':
|
89
|
+
"""Create copy of vector clock."""
|
90
|
+
vc = VectorClock(self.replica_id)
|
91
|
+
vc.clocks = self.clocks.copy()
|
92
|
+
return vc
|
93
|
+
|
94
|
+
def __repr__(self) -> str:
|
95
|
+
"""String representation."""
|
96
|
+
return f"VectorClock({self.clocks})"
|
97
|
+
|
98
|
+
|
99
|
+
class CRDTEntry:
|
100
|
+
"""
|
101
|
+
CRDT map entry with timestamp and vector clock.
|
102
|
+
|
103
|
+
WHY timestamped entries:
|
104
|
+
- Enables Last-Write-Wins resolution
|
105
|
+
- Tracks causality with vector clocks
|
106
|
+
- Supports tombstones for deletions
|
107
|
+
"""
|
108
|
+
|
109
|
+
def __init__(self, value: Any, timestamp: float, vector_clock: VectorClock,
|
110
|
+
is_tombstone: bool = False):
|
111
|
+
"""
|
112
|
+
Initialize CRDT entry.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
value: Stored value
|
116
|
+
timestamp: Physical timestamp
|
117
|
+
vector_clock: Logical vector clock
|
118
|
+
is_tombstone: Whether this is a deletion marker
|
119
|
+
"""
|
120
|
+
self.value = value
|
121
|
+
self.timestamp = timestamp
|
122
|
+
self.vector_clock = vector_clock
|
123
|
+
self.is_tombstone = is_tombstone
|
124
|
+
|
125
|
+
|
126
|
+
class CRDTMapStrategy(ANodeTreeStrategy):
|
127
|
+
"""
|
128
|
+
CRDT Map strategy for conflict-free distributed data.
|
129
|
+
|
130
|
+
WHY CRDT:
|
131
|
+
- Enables offline-first applications
|
132
|
+
- Guarantees eventual consistency without coordination
|
133
|
+
- Supports multi-master replication
|
134
|
+
- Handles network partitions gracefully
|
135
|
+
- Perfect for collaborative editing and distributed databases
|
136
|
+
|
137
|
+
WHY this implementation:
|
138
|
+
- Last-Write-Wins (LWW) provides simple conflict resolution
|
139
|
+
- Vector clocks track causality for concurrent detection
|
140
|
+
- Tombstones handle deletion conflicts correctly
|
141
|
+
- Physical timestamps break ties deterministically
|
142
|
+
- Replica IDs ensure uniqueness
|
143
|
+
|
144
|
+
Time Complexity:
|
145
|
+
- Put: O(1) local operation
|
146
|
+
- Get: O(1) lookup
|
147
|
+
- Merge: O(m) where m is entries in other map
|
148
|
+
- Delete: O(1) (creates tombstone)
|
149
|
+
|
150
|
+
Space Complexity: O(n + d) where n is live entries, d is tombstones
|
151
|
+
|
152
|
+
Trade-offs:
|
153
|
+
- Advantage: Conflict-free merging (strong eventual consistency)
|
154
|
+
- Advantage: No coordination needed between replicas
|
155
|
+
- Advantage: Works offline, syncs when connected
|
156
|
+
- Limitation: Tombstones accumulate (needs garbage collection)
|
157
|
+
- Limitation: LWW may lose concurrent writes
|
158
|
+
- Limitation: Requires synchronized clocks for tie-breaking
|
159
|
+
- Compared to HashMap: Adds conflict resolution, more memory
|
160
|
+
- Compared to Operational Transform: Simpler, more robust
|
161
|
+
|
162
|
+
Best for:
|
163
|
+
- Distributed databases (Cassandra-style)
|
164
|
+
- Offline-first mobile applications
|
165
|
+
- Collaborative editing (presence, metadata)
|
166
|
+
- Multi-master replication scenarios
|
167
|
+
- Shopping carts and user preferences
|
168
|
+
- Distributed caching systems
|
169
|
+
|
170
|
+
Not recommended for:
|
171
|
+
- Single-server applications (use normal HashMap)
|
172
|
+
- When last-write semantics are unacceptable
|
173
|
+
- Financial transactions (need stronger consistency)
|
174
|
+
- When tombstone growth is problematic
|
175
|
+
- Real-time gaming (use operational transform)
|
176
|
+
|
177
|
+
Following eXonware Priorities:
|
178
|
+
1. Security: Validates replica IDs, prevents malformed merges
|
179
|
+
2. Usability: Simple put/get API, automatic conflict resolution
|
180
|
+
3. Maintainability: Clean CRDT semantics, well-documented
|
181
|
+
4. Performance: O(1) operations, efficient merging
|
182
|
+
5. Extensibility: Easy to add OR-Set, counter CRDTs
|
183
|
+
|
184
|
+
Industry Best Practices:
|
185
|
+
- Follows Shapiro et al. CRDT specification
|
186
|
+
- Implements LWW-Element-Set variant
|
187
|
+
- Uses vector clocks for causality
|
188
|
+
- Provides tombstone garbage collection
|
189
|
+
- Compatible with Riak, Cassandra approaches
|
190
|
+
"""
|
191
|
+
|
192
|
+
# Tree node type for classification
|
193
|
+
STRATEGY_TYPE: NodeType = NodeType.TREE
|
194
|
+
|
195
|
+
def __init__(self, mode: NodeMode = NodeMode.CRDT_MAP,
|
196
|
+
traits: NodeTrait = NodeTrait.NONE,
|
197
|
+
replica_id: Optional[str] = None, **options):
|
198
|
+
"""
|
199
|
+
Initialize CRDT map strategy.
|
200
|
+
|
201
|
+
Args:
|
202
|
+
mode: Node mode
|
203
|
+
traits: Node traits
|
204
|
+
replica_id: Unique replica identifier
|
205
|
+
**options: Additional options
|
206
|
+
"""
|
207
|
+
super().__init__(mode, traits, **options)
|
208
|
+
|
209
|
+
self.replica_id = replica_id or f"replica_{id(self)}"
|
210
|
+
self._entries: Dict[Any, CRDTEntry] = {}
|
211
|
+
self._vector_clock = VectorClock(self.replica_id)
|
212
|
+
self._size = 0
|
213
|
+
|
214
|
+
def get_supported_traits(self) -> NodeTrait:
|
215
|
+
"""Get supported traits."""
|
216
|
+
return NodeTrait.INDEXED | NodeTrait.PERSISTENT | NodeTrait.STREAMING
|
217
|
+
|
218
|
+
# ============================================================================
|
219
|
+
# CORE CRDT OPERATIONS
|
220
|
+
# ============================================================================
|
221
|
+
|
222
|
+
def put(self, key: Any, value: Any = None) -> None:
|
223
|
+
"""
|
224
|
+
Put value with LWW semantics.
|
225
|
+
|
226
|
+
Args:
|
227
|
+
key: Key
|
228
|
+
value: Value
|
229
|
+
|
230
|
+
WHY timestamping:
|
231
|
+
- Enables conflict resolution
|
232
|
+
- Provides total ordering
|
233
|
+
- Breaks ties deterministically
|
234
|
+
"""
|
235
|
+
# Security: Validate key
|
236
|
+
if key is None:
|
237
|
+
raise XWNodeValueError("Key cannot be None")
|
238
|
+
|
239
|
+
# Increment vector clock
|
240
|
+
self._vector_clock.increment()
|
241
|
+
|
242
|
+
# Create entry with current timestamp and vector clock
|
243
|
+
timestamp = time.time()
|
244
|
+
vector_clock = self._vector_clock.copy()
|
245
|
+
entry = CRDTEntry(value, timestamp, vector_clock, is_tombstone=False)
|
246
|
+
|
247
|
+
# Check if we need to update
|
248
|
+
if key in self._entries:
|
249
|
+
existing = self._entries[key]
|
250
|
+
|
251
|
+
# Only update if new entry wins
|
252
|
+
if self._should_replace(existing, entry):
|
253
|
+
old_tombstone = existing.is_tombstone
|
254
|
+
self._entries[key] = entry
|
255
|
+
|
256
|
+
# Update size if transitioning from tombstone to value
|
257
|
+
if old_tombstone and not entry.is_tombstone:
|
258
|
+
self._size += 1
|
259
|
+
else:
|
260
|
+
self._entries[key] = entry
|
261
|
+
self._size += 1
|
262
|
+
|
263
|
+
def _should_replace(self, existing: CRDTEntry, new: CRDTEntry) -> bool:
|
264
|
+
"""
|
265
|
+
Determine if new entry should replace existing.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
existing: Current entry
|
269
|
+
new: New entry
|
270
|
+
|
271
|
+
Returns:
|
272
|
+
True if new should replace existing
|
273
|
+
|
274
|
+
WHY LWW resolution:
|
275
|
+
- Timestamp provides total order
|
276
|
+
- Replica ID breaks ties
|
277
|
+
- Deterministic across all replicas
|
278
|
+
"""
|
279
|
+
# If vector clocks show causal ordering, use that
|
280
|
+
if new.vector_clock.happens_before(existing.vector_clock):
|
281
|
+
return False
|
282
|
+
if existing.vector_clock.happens_before(new.vector_clock):
|
283
|
+
return True
|
284
|
+
|
285
|
+
# Concurrent updates - use timestamp
|
286
|
+
if new.timestamp > existing.timestamp:
|
287
|
+
return True
|
288
|
+
elif new.timestamp < existing.timestamp:
|
289
|
+
return False
|
290
|
+
|
291
|
+
# Same timestamp - use replica ID for deterministic tie-breaking
|
292
|
+
return new.vector_clock.replica_id > existing.vector_clock.replica_id
|
293
|
+
|
294
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
295
|
+
"""
|
296
|
+
Get value.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
key: Key
|
300
|
+
default: Default value
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
Value or default (None if tombstone)
|
304
|
+
"""
|
305
|
+
if key not in self._entries:
|
306
|
+
return default
|
307
|
+
|
308
|
+
entry = self._entries[key]
|
309
|
+
|
310
|
+
# Return None for tombstones
|
311
|
+
if entry.is_tombstone:
|
312
|
+
return default
|
313
|
+
|
314
|
+
return entry.value
|
315
|
+
|
316
|
+
def has(self, key: Any) -> bool:
|
317
|
+
"""
|
318
|
+
Check if key exists (and not deleted).
|
319
|
+
|
320
|
+
Args:
|
321
|
+
key: Key
|
322
|
+
|
323
|
+
Returns:
|
324
|
+
True if exists and not tombstone
|
325
|
+
"""
|
326
|
+
if key not in self._entries:
|
327
|
+
return False
|
328
|
+
|
329
|
+
return not self._entries[key].is_tombstone
|
330
|
+
|
331
|
+
def delete(self, key: Any) -> bool:
|
332
|
+
"""
|
333
|
+
Delete key using tombstone.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
key: Key to delete
|
337
|
+
|
338
|
+
Returns:
|
339
|
+
True if deleted, False if not found
|
340
|
+
|
341
|
+
WHY tombstones:
|
342
|
+
- Deletion must be replicated
|
343
|
+
- Tombstone prevents resurrection
|
344
|
+
- Eventually garbage collected
|
345
|
+
"""
|
346
|
+
if key not in self._entries or self._entries[key].is_tombstone:
|
347
|
+
return False
|
348
|
+
|
349
|
+
# Increment vector clock
|
350
|
+
self._vector_clock.increment()
|
351
|
+
|
352
|
+
# Create tombstone entry
|
353
|
+
timestamp = time.time()
|
354
|
+
vector_clock = self._vector_clock.copy()
|
355
|
+
tombstone = CRDTEntry(None, timestamp, vector_clock, is_tombstone=True)
|
356
|
+
|
357
|
+
self._entries[key] = tombstone
|
358
|
+
self._size -= 1
|
359
|
+
|
360
|
+
return True
|
361
|
+
|
362
|
+
# ============================================================================
|
363
|
+
# CRDT MERGE OPERATION
|
364
|
+
# ============================================================================
|
365
|
+
|
366
|
+
def merge(self, other: 'CRDTMapStrategy') -> None:
|
367
|
+
"""
|
368
|
+
Merge another CRDT map into this one.
|
369
|
+
|
370
|
+
Args:
|
371
|
+
other: Other CRDT map to merge
|
372
|
+
|
373
|
+
WHY conflict-free merge:
|
374
|
+
- Applies LWW resolution to all conflicts
|
375
|
+
- Updates vector clock with merged knowledge
|
376
|
+
- Handles tombstones correctly
|
377
|
+
- Guarantees strong eventual consistency
|
378
|
+
"""
|
379
|
+
# Merge vector clocks
|
380
|
+
self._vector_clock.update(other._vector_clock)
|
381
|
+
|
382
|
+
# Merge entries
|
383
|
+
for key, other_entry in other._entries.items():
|
384
|
+
if key not in self._entries:
|
385
|
+
# New key, just add it
|
386
|
+
self._entries[key] = other_entry
|
387
|
+
if not other_entry.is_tombstone:
|
388
|
+
self._size += 1
|
389
|
+
else:
|
390
|
+
existing = self._entries[key]
|
391
|
+
|
392
|
+
# Apply LWW resolution
|
393
|
+
if self._should_replace(existing, other_entry):
|
394
|
+
old_tombstone = existing.is_tombstone
|
395
|
+
new_tombstone = other_entry.is_tombstone
|
396
|
+
|
397
|
+
self._entries[key] = other_entry
|
398
|
+
|
399
|
+
# Update size
|
400
|
+
if old_tombstone and not new_tombstone:
|
401
|
+
self._size += 1
|
402
|
+
elif not old_tombstone and new_tombstone:
|
403
|
+
self._size -= 1
|
404
|
+
|
405
|
+
def get_replica_state(self) -> Dict[str, Any]:
|
406
|
+
"""
|
407
|
+
Get current replica state for synchronization.
|
408
|
+
|
409
|
+
Returns:
|
410
|
+
Serializable replica state
|
411
|
+
"""
|
412
|
+
return {
|
413
|
+
'replica_id': self.replica_id,
|
414
|
+
'vector_clock': self._vector_clock.clocks.copy(),
|
415
|
+
'entries': {
|
416
|
+
key: {
|
417
|
+
'value': entry.value,
|
418
|
+
'timestamp': entry.timestamp,
|
419
|
+
'vector_clock': entry.vector_clock.clocks.copy(),
|
420
|
+
'is_tombstone': entry.is_tombstone
|
421
|
+
}
|
422
|
+
for key, entry in self._entries.items()
|
423
|
+
}
|
424
|
+
}
|
425
|
+
|
426
|
+
def apply_replica_state(self, state: Dict[str, Any]) -> None:
|
427
|
+
"""
|
428
|
+
Apply replica state from synchronization.
|
429
|
+
|
430
|
+
Args:
|
431
|
+
state: Replica state dictionary
|
432
|
+
|
433
|
+
WHY state application:
|
434
|
+
- Enables sync from remote replicas
|
435
|
+
- Reconstructs CRDT from serialized state
|
436
|
+
- Maintains vector clock consistency
|
437
|
+
"""
|
438
|
+
# Create temporary CRDT with remote state
|
439
|
+
temp_crdt = CRDTMapStrategy(replica_id=state['replica_id'])
|
440
|
+
temp_crdt._vector_clock.clocks = state['vector_clock'].copy()
|
441
|
+
|
442
|
+
# Reconstruct entries
|
443
|
+
for key, entry_data in state['entries'].items():
|
444
|
+
vc = VectorClock(state['replica_id'])
|
445
|
+
vc.clocks = entry_data['vector_clock'].copy()
|
446
|
+
|
447
|
+
entry = CRDTEntry(
|
448
|
+
entry_data['value'],
|
449
|
+
entry_data['timestamp'],
|
450
|
+
vc,
|
451
|
+
entry_data['is_tombstone']
|
452
|
+
)
|
453
|
+
temp_crdt._entries[key] = entry
|
454
|
+
|
455
|
+
# Merge into current state
|
456
|
+
self.merge(temp_crdt)
|
457
|
+
|
458
|
+
# ============================================================================
|
459
|
+
# GARBAGE COLLECTION
|
460
|
+
# ============================================================================
|
461
|
+
|
462
|
+
def garbage_collect_tombstones(self, age_threshold: float = 3600) -> int:
|
463
|
+
"""
|
464
|
+
Remove old tombstones to reclaim memory.
|
465
|
+
|
466
|
+
Args:
|
467
|
+
age_threshold: Minimum age in seconds for tombstone removal
|
468
|
+
|
469
|
+
Returns:
|
470
|
+
Number of tombstones removed
|
471
|
+
|
472
|
+
WHY garbage collection:
|
473
|
+
- Tombstones accumulate over time
|
474
|
+
- Safe to remove after all replicas have seen them
|
475
|
+
- Reclaims memory for deleted entries
|
476
|
+
"""
|
477
|
+
current_time = time.time()
|
478
|
+
removed = 0
|
479
|
+
|
480
|
+
to_remove = []
|
481
|
+
for key, entry in self._entries.items():
|
482
|
+
if entry.is_tombstone:
|
483
|
+
age = current_time - entry.timestamp
|
484
|
+
if age > age_threshold:
|
485
|
+
to_remove.append(key)
|
486
|
+
|
487
|
+
for key in to_remove:
|
488
|
+
del self._entries[key]
|
489
|
+
removed += 1
|
490
|
+
|
491
|
+
return removed
|
492
|
+
|
493
|
+
# ============================================================================
|
494
|
+
# STANDARD OPERATIONS
|
495
|
+
# ============================================================================
|
496
|
+
|
497
|
+
def keys(self) -> Iterator[Any]:
|
498
|
+
"""Get iterator over live keys (excluding tombstones)."""
|
499
|
+
for key, entry in self._entries.items():
|
500
|
+
if not entry.is_tombstone:
|
501
|
+
yield key
|
502
|
+
|
503
|
+
def values(self) -> Iterator[Any]:
|
504
|
+
"""Get iterator over live values."""
|
505
|
+
for entry in self._entries.values():
|
506
|
+
if not entry.is_tombstone:
|
507
|
+
yield entry.value
|
508
|
+
|
509
|
+
def items(self) -> Iterator[tuple[Any, Any]]:
|
510
|
+
"""Get iterator over live key-value pairs."""
|
511
|
+
for key, entry in self._entries.items():
|
512
|
+
if not entry.is_tombstone:
|
513
|
+
yield (key, entry.value)
|
514
|
+
|
515
|
+
def __len__(self) -> int:
|
516
|
+
"""Get number of live entries."""
|
517
|
+
return self._size
|
518
|
+
|
519
|
+
def to_native(self) -> Any:
|
520
|
+
"""Convert to native dict (live entries only)."""
|
521
|
+
return dict(self.items())
|
522
|
+
|
523
|
+
# ============================================================================
|
524
|
+
# UTILITY METHODS
|
525
|
+
# ============================================================================
|
526
|
+
|
527
|
+
def clear(self) -> None:
|
528
|
+
"""
|
529
|
+
Clear all entries with tombstones.
|
530
|
+
|
531
|
+
WHY tombstones on clear:
|
532
|
+
- Ensures deletion propagates to other replicas
|
533
|
+
- Prevents resurrection of old data
|
534
|
+
"""
|
535
|
+
# Mark all as tombstones
|
536
|
+
for key in list(self._entries.keys()):
|
537
|
+
if not self._entries[key].is_tombstone:
|
538
|
+
self.delete(key)
|
539
|
+
|
540
|
+
def is_empty(self) -> bool:
|
541
|
+
"""Check if empty (no live entries)."""
|
542
|
+
return self._size == 0
|
543
|
+
|
544
|
+
def size(self) -> int:
|
545
|
+
"""Get number of live entries."""
|
546
|
+
return self._size
|
547
|
+
|
548
|
+
def get_mode(self) -> NodeMode:
|
549
|
+
"""Get strategy mode."""
|
550
|
+
return self.mode
|
551
|
+
|
552
|
+
def get_traits(self) -> NodeTrait:
|
553
|
+
"""Get strategy traits."""
|
554
|
+
return self.traits
|
555
|
+
|
556
|
+
# ============================================================================
|
557
|
+
# STATISTICS
|
558
|
+
# ============================================================================
|
559
|
+
|
560
|
+
def get_statistics(self) -> Dict[str, Any]:
|
561
|
+
"""
|
562
|
+
Get CRDT statistics.
|
563
|
+
|
564
|
+
Returns:
|
565
|
+
Statistics dictionary
|
566
|
+
"""
|
567
|
+
live_count = sum(1 for e in self._entries.values() if not e.is_tombstone)
|
568
|
+
tombstone_count = sum(1 for e in self._entries.values() if e.is_tombstone)
|
569
|
+
|
570
|
+
return {
|
571
|
+
'replica_id': self.replica_id,
|
572
|
+
'live_entries': live_count,
|
573
|
+
'tombstones': tombstone_count,
|
574
|
+
'total_entries': len(self._entries),
|
575
|
+
'vector_clock_size': len(self._vector_clock.clocks),
|
576
|
+
'vector_clock': self._vector_clock.clocks.copy()
|
577
|
+
}
|
578
|
+
|
579
|
+
# ============================================================================
|
580
|
+
# COMPATIBILITY METHODS
|
581
|
+
# ============================================================================
|
582
|
+
|
583
|
+
def find(self, key: Any) -> Optional[Any]:
|
584
|
+
"""Find value by key."""
|
585
|
+
return self.get(key)
|
586
|
+
|
587
|
+
def insert(self, key: Any, value: Any = None) -> None:
|
588
|
+
"""Insert key-value pair."""
|
589
|
+
self.put(key, value)
|
590
|
+
|
591
|
+
def __str__(self) -> str:
|
592
|
+
"""String representation."""
|
593
|
+
stats = self.get_statistics()
|
594
|
+
return (f"CRDTMapStrategy(replica={self.replica_id}, live={stats['live_entries']}, "
|
595
|
+
f"tombstones={stats['tombstones']})")
|
596
|
+
|
597
|
+
def __repr__(self) -> str:
|
598
|
+
"""Detailed representation."""
|
599
|
+
return f"CRDTMapStrategy(mode={self.mode.name}, replica={self.replica_id}, size={self._size}, traits={self.traits})"
|
600
|
+
|
601
|
+
# ============================================================================
|
602
|
+
# FACTORY METHOD
|
603
|
+
# ============================================================================
|
604
|
+
|
605
|
+
@classmethod
|
606
|
+
def create_from_data(cls, data: Any, replica_id: Optional[str] = None) -> 'CRDTMapStrategy':
|
607
|
+
"""
|
608
|
+
Create CRDT map from data.
|
609
|
+
|
610
|
+
Args:
|
611
|
+
data: Dictionary or iterable
|
612
|
+
replica_id: Unique replica ID
|
613
|
+
|
614
|
+
Returns:
|
615
|
+
New CRDTMapStrategy instance
|
616
|
+
"""
|
617
|
+
instance = cls(replica_id=replica_id)
|
618
|
+
|
619
|
+
if isinstance(data, dict):
|
620
|
+
for key, value in data.items():
|
621
|
+
instance.put(key, value)
|
622
|
+
elif isinstance(data, (list, tuple)):
|
623
|
+
for i, value in enumerate(data):
|
624
|
+
instance.put(i, value)
|
625
|
+
else:
|
626
|
+
instance.put('value', data)
|
627
|
+
|
628
|
+
return instance
|
629
|
+
|
@@ -8,11 +8,11 @@ worst-case lookup time with efficient space utilization.
|
|
8
8
|
from typing import Any, Iterator, List, Dict, Optional, Tuple
|
9
9
|
import hashlib
|
10
10
|
import random
|
11
|
-
from .
|
11
|
+
from .base import ANodeStrategy
|
12
12
|
from ...defs import NodeMode, NodeTrait
|
13
13
|
|
14
14
|
|
15
|
-
class
|
15
|
+
class CuckooHashStrategy(ANodeStrategy):
|
16
16
|
"""
|
17
17
|
Cuckoo Hash node strategy for guaranteed O(1) worst-case lookups.
|
18
18
|
|
@@ -1,6 +1,13 @@
|
|
1
1
|
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/nodes/strategies/data_interchange_optimized.py
|
3
|
+
|
2
4
|
DATA_INTERCHANGE_OPTIMIZED Node Strategy Implementation
|
3
5
|
|
6
|
+
Status: Production Ready ✅
|
7
|
+
True Purpose: Ultra-lightweight data interchange with COW and object pooling
|
8
|
+
Complexity: O(1) operations with minimal overhead
|
9
|
+
Production Features: ✓ COW Semantics, ✓ Object Pooling, ✓ Structural Hashing, ✓ __slots__
|
10
|
+
|
4
11
|
Ultra-lightweight strategy specifically optimized for data interchange patterns:
|
5
12
|
- Copy-on-write semantics for data interchange
|
6
13
|
- Object pooling support for factory patterns
|
@@ -8,22 +15,31 @@ Ultra-lightweight strategy specifically optimized for data interchange patterns:
|
|
8
15
|
- Minimal metadata overhead
|
9
16
|
- Zero graph features for maximum performance
|
10
17
|
- __slots__ optimization for memory efficiency
|
18
|
+
|
19
|
+
Company: eXonware.com
|
20
|
+
Author: Eng. Muhammad AlShehri
|
21
|
+
Email: connect@exonware.com
|
22
|
+
Version: 0.0.1.23
|
23
|
+
Generation Date: October 12, 2025
|
11
24
|
"""
|
12
25
|
|
13
26
|
import weakref
|
14
27
|
from typing import Any, Iterator, Dict, List, Optional
|
15
|
-
from .
|
28
|
+
from .base import ANodeStrategy
|
16
29
|
from ...defs import NodeMode, NodeTrait
|
17
30
|
from ...errors import XWNodeUnsupportedCapabilityError
|
18
31
|
|
32
|
+
# Import contracts
|
33
|
+
from .contracts import NodeType
|
34
|
+
|
19
35
|
# Import shared utilities
|
20
|
-
from
|
36
|
+
from ...common.utils import (
|
21
37
|
recursive_to_native, is_sequential_numeric_keys,
|
22
38
|
calculate_structural_hash, create_performance_tracker
|
23
39
|
)
|
24
40
|
|
25
41
|
|
26
|
-
class DataInterchangeOptimizedStrategy(
|
42
|
+
class DataInterchangeOptimizedStrategy(ANodeStrategy):
|
27
43
|
"""
|
28
44
|
Ultra-lightweight node strategy optimized for data interchange patterns.
|
29
45
|
|
@@ -48,7 +64,7 @@ class DataInterchangeOptimizedStrategy(aNodeStrategy):
|
|
48
64
|
def __init__(self, traits: NodeTrait = NodeTrait.INDEXED, **options):
|
49
65
|
"""Initialize the xData-optimized strategy."""
|
50
66
|
# Initialize parent without calling super() to avoid dict overhead
|
51
|
-
self.mode = NodeMode.
|
67
|
+
self.mode = NodeMode.DATA_INTERCHANGE_OPTIMIZED # Dedicated mode for data interchange
|
52
68
|
self.traits = traits
|
53
69
|
self.options = options
|
54
70
|
|
@@ -371,3 +387,4 @@ def create_data_interchange_optimized_strategy(**options) -> DataInterchangeOpti
|
|
371
387
|
data_interchange_options.update(options)
|
372
388
|
|
373
389
|
return DataInterchangeOptimizedStrategy(NodeTrait.INDEXED, **data_interchange_options)
|
390
|
+
|