astreum 0.2.14__py3-none-any.whl → 0.2.16__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.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- astreum/models/merkle.py +1 -0
- astreum/models/patricia.py +178 -50
- {astreum-0.2.14.dist-info → astreum-0.2.16.dist-info}/METADATA +1 -1
- {astreum-0.2.14.dist-info → astreum-0.2.16.dist-info}/RECORD +7 -11
- astreum/_node/__init__.py +0 -447
- astreum/_node/storage/__init__.py +0 -0
- astreum/_node/storage/merkle.py +0 -224
- astreum/_node/storage/patricia.py +0 -289
- {astreum-0.2.14.dist-info → astreum-0.2.16.dist-info}/WHEEL +0 -0
- {astreum-0.2.14.dist-info → astreum-0.2.16.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.14.dist-info → astreum-0.2.16.dist-info}/top_level.txt +0 -0
astreum/models/merkle.py
CHANGED
astreum/models/patricia.py
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import blake3
|
|
2
2
|
from typing import Callable, Dict, List, Optional, Tuple
|
|
3
|
-
from
|
|
3
|
+
from ..format import encode, decode
|
|
4
4
|
|
|
5
5
|
class PatriciaNode:
|
|
6
|
+
"""
|
|
7
|
+
A node in a compressed-key Patricia trie.
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
key_len (int): Number of bits in the `key` prefix that are meaningful.
|
|
11
|
+
key (bytes): The MSB-aligned bit prefix (zero-padded in last byte).
|
|
12
|
+
value (Optional[bytes]): Stored payload (None for internal nodes).
|
|
13
|
+
child_0 (Optional[bytes]): Hash pointer for next-bit == 0.
|
|
14
|
+
child_1 (Optional[bytes]): Hash pointer for next-bit == 1.
|
|
15
|
+
"""
|
|
16
|
+
|
|
6
17
|
def __init__(
|
|
7
18
|
self,
|
|
8
19
|
key_len: int,
|
|
@@ -16,33 +27,64 @@ class PatriciaNode:
|
|
|
16
27
|
self.value = value
|
|
17
28
|
self.child_0 = child_0
|
|
18
29
|
self.child_1 = child_1
|
|
19
|
-
self._hash: bytes
|
|
30
|
+
self._hash: Optional[bytes] = None
|
|
20
31
|
|
|
21
32
|
def to_bytes(self) -> bytes:
|
|
22
|
-
|
|
33
|
+
"""
|
|
34
|
+
Serialize node fields to bytes using the shared encode format.
|
|
35
|
+
- key_len in a single byte.
|
|
36
|
+
- None pointers/values as empty bytes.
|
|
37
|
+
"""
|
|
38
|
+
key_len_b = self.key_len.to_bytes(1, "big")
|
|
39
|
+
val_b = self.value if self.value is not None else b""
|
|
40
|
+
c0_b = self.child_0 if self.child_0 is not None else b""
|
|
41
|
+
c1_b = self.child_1 if self.child_1 is not None else b""
|
|
42
|
+
return encode([key_len_b, self.key, val_b, c0_b, c1_b])
|
|
23
43
|
|
|
24
44
|
@classmethod
|
|
25
45
|
def from_bytes(cls, blob: bytes) -> "PatriciaNode":
|
|
26
|
-
|
|
46
|
+
"""
|
|
47
|
+
Deserialize a blob produced by to_bytes() back into a PatriciaNode.
|
|
48
|
+
Empty bytes are converted back to None for value/children.
|
|
49
|
+
"""
|
|
50
|
+
key_len_b, key, val_b, c0_b, c1_b = decode(blob)
|
|
51
|
+
key_len = key_len_b[0]
|
|
52
|
+
value = val_b if val_b else None
|
|
53
|
+
child_0 = c0_b if c0_b else None
|
|
54
|
+
child_1 = c1_b if c1_b else None
|
|
27
55
|
return cls(key_len, key, value, child_0, child_1)
|
|
28
|
-
|
|
56
|
+
|
|
29
57
|
def hash(self) -> bytes:
|
|
58
|
+
"""
|
|
59
|
+
Compute and cache the BLAKE3 hash of this node's serialized form.
|
|
60
|
+
"""
|
|
30
61
|
if self._hash is None:
|
|
31
62
|
self._hash = blake3.blake3(self.to_bytes()).digest()
|
|
32
63
|
return self._hash
|
|
33
64
|
|
|
34
65
|
class PatriciaTrie:
|
|
66
|
+
"""
|
|
67
|
+
A compressed-key Patricia trie supporting get and put.
|
|
68
|
+
"""
|
|
69
|
+
|
|
35
70
|
def __init__(
|
|
36
71
|
self,
|
|
37
72
|
node_get: Callable[[bytes], Optional[bytes]],
|
|
38
73
|
root_hash: Optional[bytes] = None,
|
|
39
74
|
) -> None:
|
|
75
|
+
"""
|
|
76
|
+
:param node_get: function mapping node-hash -> serialized node bytes (or None)
|
|
77
|
+
:param root_hash: optional hash of existing root node
|
|
78
|
+
"""
|
|
40
79
|
self._node_get = node_get
|
|
41
80
|
self.nodes: Dict[bytes, PatriciaNode] = {}
|
|
42
|
-
self.root_hash
|
|
81
|
+
self.root_hash = root_hash
|
|
43
82
|
|
|
44
83
|
@staticmethod
|
|
45
84
|
def _bit(buf: bytes, idx: int) -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Return the bit at position `idx` (MSB-first) from `buf`.
|
|
87
|
+
"""
|
|
46
88
|
byte_i, offset = divmod(idx, 8)
|
|
47
89
|
return ((buf[byte_i] >> (7 - offset)) & 1) == 1
|
|
48
90
|
|
|
@@ -54,15 +96,22 @@ class PatriciaTrie:
|
|
|
54
96
|
key: bytes,
|
|
55
97
|
key_bit_offset: int,
|
|
56
98
|
) -> bool:
|
|
57
|
-
|
|
99
|
+
"""
|
|
100
|
+
Check whether the `prefix_len` bits of `prefix` match
|
|
101
|
+
bits in `key` starting at `key_bit_offset`.
|
|
102
|
+
"""
|
|
103
|
+
total_bits = len(key) * 8
|
|
104
|
+
if key_bit_offset + prefix_len > total_bits:
|
|
58
105
|
return False
|
|
59
|
-
|
|
60
106
|
for i in range(prefix_len):
|
|
61
107
|
if cls._bit(prefix, i) != cls._bit(key, key_bit_offset + i):
|
|
62
108
|
return False
|
|
63
109
|
return True
|
|
64
110
|
|
|
65
111
|
def _fetch(self, h: bytes) -> Optional[PatriciaNode]:
|
|
112
|
+
"""
|
|
113
|
+
Fetch a node by hash, using in-memory cache then external node_get.
|
|
114
|
+
"""
|
|
66
115
|
node = self.nodes.get(h)
|
|
67
116
|
if node is None:
|
|
68
117
|
raw = self._node_get(h)
|
|
@@ -72,8 +121,11 @@ class PatriciaTrie:
|
|
|
72
121
|
self.nodes[h] = node
|
|
73
122
|
return node
|
|
74
123
|
|
|
75
|
-
def get(self, key: bytes) -> Optional[
|
|
76
|
-
"""
|
|
124
|
+
def get(self, key: bytes) -> Optional[bytes]:
|
|
125
|
+
"""
|
|
126
|
+
Return the stored value for `key`, or None if absent.
|
|
127
|
+
"""
|
|
128
|
+
# Empty trie?
|
|
77
129
|
if self.root_hash is None:
|
|
78
130
|
return None
|
|
79
131
|
|
|
@@ -81,40 +133,42 @@ class PatriciaTrie:
|
|
|
81
133
|
if node is None:
|
|
82
134
|
return None
|
|
83
135
|
|
|
84
|
-
key_pos = 0
|
|
136
|
+
key_pos = 0 # bit offset into key
|
|
85
137
|
|
|
86
138
|
while node is not None:
|
|
87
|
-
# 1
|
|
139
|
+
# 1) Check that this node's prefix matches the key here
|
|
88
140
|
if not self._match_prefix(node.key, node.key_len, key, key_pos):
|
|
89
141
|
return None
|
|
90
142
|
key_pos += node.key_len
|
|
91
143
|
|
|
92
|
-
# 2
|
|
93
|
-
# node actually stores a value.
|
|
144
|
+
# 2) If we've consumed all bits of the search key:
|
|
94
145
|
if key_pos == len(key) * 8:
|
|
95
|
-
|
|
146
|
+
# Return value only if this node actually stores one
|
|
147
|
+
return node.value
|
|
96
148
|
|
|
97
|
-
# 3
|
|
149
|
+
# 3) Decide which branch to follow via next bit
|
|
98
150
|
try:
|
|
99
151
|
next_bit = self._bit(key, key_pos)
|
|
100
|
-
except IndexError:
|
|
152
|
+
except IndexError:
|
|
101
153
|
return None
|
|
102
154
|
|
|
103
155
|
child_hash = node.child_1 if next_bit else node.child_0
|
|
104
|
-
if child_hash is None:
|
|
105
|
-
return None
|
|
156
|
+
if child_hash is None:
|
|
157
|
+
return None # dead end
|
|
106
158
|
|
|
107
|
-
# 4
|
|
159
|
+
# 4) Fetch child and continue descent
|
|
108
160
|
node = self._fetch(child_hash)
|
|
109
|
-
if node is None:
|
|
110
|
-
return None
|
|
161
|
+
if node is None:
|
|
162
|
+
return None # dangling pointer
|
|
111
163
|
|
|
112
|
-
key_pos += 1 #
|
|
164
|
+
key_pos += 1 # consumed routing bit
|
|
113
165
|
|
|
114
166
|
return None
|
|
115
|
-
|
|
167
|
+
|
|
116
168
|
def put(self, key: bytes, value: bytes) -> None:
|
|
117
|
-
"""
|
|
169
|
+
"""
|
|
170
|
+
Insert or update `key` with `value` in-place.
|
|
171
|
+
"""
|
|
118
172
|
total_bits = len(key) * 8
|
|
119
173
|
|
|
120
174
|
# S1 – Empty trie → create root leaf
|
|
@@ -126,7 +180,7 @@ class PatriciaTrie:
|
|
|
126
180
|
# S2 – traversal bookkeeping
|
|
127
181
|
stack: List[Tuple[PatriciaNode, bytes, int]] = [] # (parent, parent_hash, dir_bit)
|
|
128
182
|
node = self._fetch(self.root_hash)
|
|
129
|
-
assert node is not None
|
|
183
|
+
assert node is not None
|
|
130
184
|
key_pos = 0
|
|
131
185
|
|
|
132
186
|
# S4 – main descent loop
|
|
@@ -144,6 +198,7 @@ class PatriciaTrie:
|
|
|
144
198
|
self._invalidate_hash(node)
|
|
145
199
|
node.value = value
|
|
146
200
|
new_hash = node.hash()
|
|
201
|
+
self.nodes[new_hash] = node
|
|
147
202
|
self._bubble(stack, new_hash)
|
|
148
203
|
return
|
|
149
204
|
|
|
@@ -162,9 +217,11 @@ class PatriciaTrie:
|
|
|
162
217
|
# 4.8 – fetch child and continue
|
|
163
218
|
node = self._fetch(child_hash)
|
|
164
219
|
if node is None:
|
|
165
|
-
# Dangling pointer
|
|
166
|
-
|
|
220
|
+
# Dangling pointer: treat as missing child
|
|
221
|
+
parent, _, _ = stack[-1]
|
|
222
|
+
self._append_leaf(parent, next_bit, key, key_pos, value, stack[:-1])
|
|
167
223
|
return
|
|
224
|
+
|
|
168
225
|
key_pos += 1 # consumed routing bit
|
|
169
226
|
|
|
170
227
|
def _append_leaf(
|
|
@@ -176,18 +233,20 @@ class PatriciaTrie:
|
|
|
176
233
|
value: bytes,
|
|
177
234
|
stack: List[Tuple[PatriciaNode, bytes, int]],
|
|
178
235
|
) -> None:
|
|
179
|
-
# key_pos points to routing bit; leaf stores the
|
|
236
|
+
# key_pos points to routing bit; leaf stores the rest after that bit
|
|
180
237
|
tail_len = len(key) * 8 - (key_pos + 1)
|
|
181
238
|
tail_bits, tail_len = self._bit_slice(key, key_pos + 1, tail_len)
|
|
182
239
|
leaf = self._make_node(tail_bits, tail_len, value, None, None)
|
|
183
240
|
|
|
184
|
-
# attach
|
|
241
|
+
# attach to parent
|
|
185
242
|
if dir_bit:
|
|
186
243
|
parent.child_1 = leaf.hash()
|
|
187
244
|
else:
|
|
188
245
|
parent.child_0 = leaf.hash()
|
|
246
|
+
|
|
189
247
|
self._invalidate_hash(parent)
|
|
190
248
|
new_parent_hash = parent.hash()
|
|
249
|
+
self.nodes[new_parent_hash] = parent
|
|
191
250
|
self._bubble(stack, new_parent_hash)
|
|
192
251
|
|
|
193
252
|
def _split_and_insert(
|
|
@@ -198,52 +257,121 @@ class PatriciaTrie:
|
|
|
198
257
|
key_pos: int,
|
|
199
258
|
value: bytes,
|
|
200
259
|
) -> None:
|
|
201
|
-
|
|
202
|
-
# Compute LCP between node.key and remaining key bits
|
|
260
|
+
# ➊—find longest-common-prefix (lcp) as before …
|
|
203
261
|
max_lcp = min(node.key_len, len(key) * 8 - key_pos)
|
|
204
262
|
lcp = 0
|
|
205
263
|
while lcp < max_lcp and self._bit(node.key, lcp) == self._bit(key, key_pos + lcp):
|
|
206
264
|
lcp += 1
|
|
207
265
|
|
|
208
|
-
#
|
|
266
|
+
# divergence bit values (taken **before** we mutate node.key)
|
|
267
|
+
old_div_bit = self._bit(node.key, lcp)
|
|
268
|
+
new_div_bit = self._bit(key, key_pos + lcp)
|
|
269
|
+
|
|
270
|
+
# ➋—internal node that holds the common prefix
|
|
209
271
|
common_bits, common_len = self._bit_slice(node.key, 0, lcp)
|
|
210
272
|
internal = self._make_node(common_bits, common_len, None, None, None)
|
|
211
273
|
|
|
212
|
-
#
|
|
213
|
-
old_suffix_bits, old_suffix_len = self._bit_slice(
|
|
274
|
+
# ➌—trim the *existing* node’s prefix **after** the divergence bit
|
|
275
|
+
old_suffix_bits, old_suffix_len = self._bit_slice(
|
|
276
|
+
node.key,
|
|
277
|
+
lcp + 1, # start *after* divergence bit
|
|
278
|
+
node.key_len - lcp - 1 # may be zero
|
|
279
|
+
)
|
|
214
280
|
node.key = old_suffix_bits
|
|
215
281
|
node.key_len = old_suffix_len
|
|
216
|
-
self._invalidate_hash(node)
|
|
217
|
-
|
|
282
|
+
self._invalidate_hash(node)
|
|
283
|
+
new_node_hash = node.hash()
|
|
284
|
+
self.nodes[new_node_hash] = node
|
|
218
285
|
|
|
219
|
-
#
|
|
220
|
-
|
|
221
|
-
new_tail_bits,
|
|
286
|
+
# ➍—new leaf for the key being inserted (unchanged)
|
|
287
|
+
new_tail_len = len(key) * 8 - (key_pos + lcp + 1)
|
|
288
|
+
new_tail_bits, _ = self._bit_slice(key, key_pos + lcp + 1, new_tail_len)
|
|
222
289
|
leaf = self._make_node(new_tail_bits, new_tail_len, value, None, None)
|
|
223
|
-
new_div_bit = self._bit(key, key_pos + lcp)
|
|
224
290
|
|
|
225
|
-
#
|
|
291
|
+
# ➎—hang the two children off the internal node
|
|
226
292
|
if old_div_bit:
|
|
227
|
-
internal.child_1 =
|
|
228
|
-
internal.child_0 = leaf.hash()
|
|
293
|
+
internal.child_1 = new_node_hash
|
|
294
|
+
internal.child_0 = leaf.hash()
|
|
229
295
|
else:
|
|
230
|
-
internal.child_0 =
|
|
231
|
-
internal.child_1 = leaf.hash()
|
|
296
|
+
internal.child_0 = new_node_hash
|
|
297
|
+
internal.child_1 = leaf.hash()
|
|
298
|
+
|
|
299
|
+
# ➏—rehash up to the root (unchanged)
|
|
232
300
|
self._invalidate_hash(internal)
|
|
233
301
|
internal_hash = internal.hash()
|
|
302
|
+
self.nodes[internal_hash] = internal
|
|
234
303
|
|
|
235
|
-
# Rewire parent link or set as root
|
|
236
304
|
if not stack:
|
|
237
305
|
self.root_hash = internal_hash
|
|
238
306
|
return
|
|
239
307
|
|
|
240
|
-
parent,
|
|
308
|
+
parent, _, dir_bit = stack.pop()
|
|
241
309
|
if dir_bit == 0:
|
|
242
310
|
parent.child_0 = internal_hash
|
|
243
311
|
else:
|
|
244
312
|
parent.child_1 = internal_hash
|
|
245
313
|
self._invalidate_hash(parent)
|
|
246
|
-
|
|
247
|
-
|
|
314
|
+
self._bubble(stack, parent.hash())
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _make_node(
|
|
318
|
+
self,
|
|
319
|
+
prefix_bits: bytes,
|
|
320
|
+
prefix_len: int,
|
|
321
|
+
value: Optional[bytes],
|
|
322
|
+
child0: Optional[bytes],
|
|
323
|
+
child1: Optional[bytes],
|
|
324
|
+
) -> PatriciaNode:
|
|
325
|
+
node = PatriciaNode(prefix_len, prefix_bits, value, child0, child1)
|
|
326
|
+
self.nodes[node.hash()] = node
|
|
327
|
+
return node
|
|
248
328
|
|
|
329
|
+
def _invalidate_hash(self, node: PatriciaNode) -> None:
|
|
330
|
+
"""Clear cached hash so next .hash() recomputes."""
|
|
331
|
+
node._hash = None # type: ignore
|
|
249
332
|
|
|
333
|
+
def _bubble(
|
|
334
|
+
self,
|
|
335
|
+
stack: List[Tuple[PatriciaNode, bytes, int]],
|
|
336
|
+
new_hash: bytes
|
|
337
|
+
) -> None:
|
|
338
|
+
"""
|
|
339
|
+
Propagate updated child-hash `new_hash` up the ancestor stack,
|
|
340
|
+
rebasing each parent's pointer, invalidating and re-hashing.
|
|
341
|
+
"""
|
|
342
|
+
while stack:
|
|
343
|
+
parent, old_hash, dir_bit = stack.pop()
|
|
344
|
+
if dir_bit == 0:
|
|
345
|
+
parent.child_0 = new_hash
|
|
346
|
+
else:
|
|
347
|
+
parent.child_1 = new_hash
|
|
348
|
+
self._invalidate_hash(parent)
|
|
349
|
+
new_hash = parent.hash()
|
|
350
|
+
self.nodes[new_hash] = parent
|
|
351
|
+
self.root_hash = new_hash
|
|
352
|
+
|
|
353
|
+
def _bit_slice(
|
|
354
|
+
self,
|
|
355
|
+
buf: bytes,
|
|
356
|
+
start_bit: int,
|
|
357
|
+
length: int
|
|
358
|
+
) -> tuple[bytes, int]:
|
|
359
|
+
"""
|
|
360
|
+
Extract `length` bits from `buf` starting at `start_bit` (MSB-first),
|
|
361
|
+
returning (bytes, bit_len) with zero-padding.
|
|
362
|
+
"""
|
|
363
|
+
if length == 0:
|
|
364
|
+
return b"", 0
|
|
365
|
+
|
|
366
|
+
total = int.from_bytes(buf, "big")
|
|
367
|
+
bits_in_buf = len(buf) * 8
|
|
368
|
+
|
|
369
|
+
# shift so slice ends at LSB
|
|
370
|
+
shift = bits_in_buf - (start_bit + length)
|
|
371
|
+
slice_int = (total >> shift) & ((1 << length) - 1)
|
|
372
|
+
|
|
373
|
+
# left-align to MSB of first byte
|
|
374
|
+
pad = (8 - (length % 8)) % 8
|
|
375
|
+
slice_int <<= pad
|
|
376
|
+
byte_len = (length + 7) // 8
|
|
377
|
+
return slice_int.to_bytes(byte_len, "big"), length
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.16
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
astreum/__init__.py,sha256=y2Ok3EY_FstcmlVASr80lGR_0w-dH-SXDCCQFmL6uwA,28
|
|
2
2
|
astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
|
|
3
3
|
astreum/node.py,sha256=dPloCXuDyIn3-KDqxlgl3jxsonJlFMLi_quwJRsoLC8,46259
|
|
4
|
-
astreum/_node/__init__.py,sha256=7yz1YHo0DCUgUQvJf75qdUo_ocl5-XZRU-Vc2NhcvJs,18639
|
|
5
|
-
astreum/_node/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
astreum/_node/storage/merkle.py,sha256=XCQBrHbwI0FuPTCUwHOy-Kva3uWbvCdw_-13hRPf1UI,10219
|
|
7
|
-
astreum/_node/storage/patricia.py,sha256=tynxn_qETCU9X7yJdeh_0GHpC8Pzcoq4CWrSZlMUeRc,11546
|
|
8
4
|
astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
5
|
astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
|
|
10
6
|
astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
|
|
@@ -15,11 +11,11 @@ astreum/lispeum/parser.py,sha256=jQRzZYvBuSg8t_bxsbt1-WcHaR_LPveHNX7Qlxhaw-M,116
|
|
|
15
11
|
astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,1429
|
|
16
12
|
astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
13
|
astreum/models/block.py,sha256=NKYyxL6_BrtXRgcgIrnkYsobX0Z_bGqQT-fQ_09zEOo,3226
|
|
18
|
-
astreum/models/merkle.py,sha256=
|
|
19
|
-
astreum/models/patricia.py,sha256=
|
|
14
|
+
astreum/models/merkle.py,sha256=E850dITZmuQS2LmQt2yA_luy0a3QjHgSbL2ibQAmYJc,8627
|
|
15
|
+
astreum/models/patricia.py,sha256=WtfwVufZazFTOjVDnSDI0I3ghetm3GBzAwqNQevOlJ8,12974
|
|
20
16
|
astreum/models/transaction.py,sha256=Vu0cfmh80S31nEbxyJfv1dk9_zqtgGNyMdhlM0uQF4E,2611
|
|
21
|
-
astreum-0.2.
|
|
22
|
-
astreum-0.2.
|
|
23
|
-
astreum-0.2.
|
|
24
|
-
astreum-0.2.
|
|
25
|
-
astreum-0.2.
|
|
17
|
+
astreum-0.2.16.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
18
|
+
astreum-0.2.16.dist-info/METADATA,sha256=u_O4mRcn4Ve_9zpgQQIatLYzF_R2K_jY-hxLQQ1HMLg,5478
|
|
19
|
+
astreum-0.2.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
astreum-0.2.16.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
21
|
+
astreum-0.2.16.dist-info/RECORD,,
|