scruby 0.10.4__py3-none-any.whl → 0.11.1__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 scruby might be problematic. Click here for more details.
- scruby/__init__.py +5 -5
- scruby/db.py +141 -37
- {scruby-0.10.4.dist-info → scruby-0.11.1.dist-info}/METADATA +20 -17
- scruby-0.11.1.dist-info/RECORD +9 -0
- scruby-0.10.4.dist-info/RECORD +0 -9
- {scruby-0.10.4.dist-info → scruby-0.11.1.dist-info}/WHEEL +0 -0
- {scruby-0.10.4.dist-info → scruby-0.11.1.dist-info}/licenses/LICENSE +0 -0
scruby/__init__.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Asynchronous library for building and managing a hybrid database, by scheme of key-value.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The library uses fractal-tree addressing.
|
|
3
|
+
The library uses fractal-tree addressing and
|
|
4
|
+
the search for documents based on the effect of a quantum loop.
|
|
6
5
|
|
|
7
6
|
The database consists of collections.
|
|
8
7
|
The maximum size of the one collection is 16**8=4294967296 branches,
|
|
@@ -11,7 +10,8 @@ each branch can store one or more keys.
|
|
|
11
10
|
The value of any key in collection can be obtained in 8 steps,
|
|
12
11
|
thereby achieving high performance.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
The effectiveness of the search for documents based on a quantum loop,
|
|
14
|
+
requires a large number of processor threads.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
scruby/db.py
CHANGED
|
@@ -11,7 +11,7 @@ import zlib
|
|
|
11
11
|
from collections.abc import Callable
|
|
12
12
|
from pathlib import Path as SyncPath
|
|
13
13
|
from shutil import rmtree
|
|
14
|
-
from typing import Any,
|
|
14
|
+
from typing import Any, Never, TypeVar, assert_never
|
|
15
15
|
|
|
16
16
|
import orjson
|
|
17
17
|
from anyio import Path, to_thread
|
|
@@ -48,13 +48,13 @@ class Scruby[T]:
|
|
|
48
48
|
# The maximum number of keys.
|
|
49
49
|
match self.__hash_reduce_left:
|
|
50
50
|
case 0:
|
|
51
|
-
self.
|
|
51
|
+
self.__max_branch_number = 4294967296
|
|
52
52
|
case 2:
|
|
53
|
-
self.
|
|
53
|
+
self.__max_branch_number = 16777216
|
|
54
54
|
case 4:
|
|
55
|
-
self.
|
|
55
|
+
self.__max_branch_number = 65536
|
|
56
56
|
case 6:
|
|
57
|
-
self.
|
|
57
|
+
self.__max_branch_number = 256
|
|
58
58
|
case _ as unreachable:
|
|
59
59
|
msg: str = f"{unreachable} - Unacceptable value for HASH_REDUCE_LEFT."
|
|
60
60
|
logger.critical(msg)
|
|
@@ -68,8 +68,8 @@ class Scruby[T]:
|
|
|
68
68
|
|
|
69
69
|
This method is for internal use.
|
|
70
70
|
"""
|
|
71
|
-
|
|
72
|
-
key_as_hash: str = f"{
|
|
71
|
+
branch_number: int = 0
|
|
72
|
+
key_as_hash: str = f"{branch_number:08x}"[self.__hash_reduce_left :]
|
|
73
73
|
separated_hash: str = "/".join(list(key_as_hash))
|
|
74
74
|
branch_path = SyncPath(
|
|
75
75
|
*(
|
|
@@ -92,9 +92,9 @@ class Scruby[T]:
|
|
|
92
92
|
|
|
93
93
|
This method is for internal use.
|
|
94
94
|
"""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
separated_hash: str = "/".join(list(
|
|
95
|
+
branch_number: int = 0
|
|
96
|
+
branch_number_as_hash: str = f"{branch_number:08x}"[self.__hash_reduce_left :]
|
|
97
|
+
separated_hash: str = "/".join(list(branch_number_as_hash))
|
|
98
98
|
return Path(
|
|
99
99
|
*(
|
|
100
100
|
self.__db_root,
|
|
@@ -123,16 +123,43 @@ class Scruby[T]:
|
|
|
123
123
|
meta_json = meta.model_dump_json()
|
|
124
124
|
await meta_path.write_text(meta_json, "utf-8")
|
|
125
125
|
|
|
126
|
-
async def _counter_documents(self,
|
|
126
|
+
async def _counter_documents(self, number: int) -> None:
|
|
127
|
+
"""Asynchronous method for management of documents in metadata of collection.
|
|
128
|
+
|
|
129
|
+
This method is for internal use.
|
|
130
|
+
"""
|
|
131
|
+
meta_path = await self._get_meta_path()
|
|
132
|
+
meta_json = await meta_path.read_text("utf-8")
|
|
133
|
+
meta: _Meta = self.__meta.model_validate_json(meta_json)
|
|
134
|
+
meta.counter_documents += number
|
|
135
|
+
if meta.counter_documents < 0:
|
|
136
|
+
meta.counter_documents = 0
|
|
137
|
+
meta_json = meta.model_dump_json()
|
|
138
|
+
await meta_path.write_text(meta_json, "utf-8")
|
|
139
|
+
|
|
140
|
+
def _sync_counter_documents(self, number: int) -> None:
|
|
127
141
|
"""Management of documents in metadata of collection.
|
|
128
142
|
|
|
129
143
|
This method is for internal use.
|
|
130
144
|
"""
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
branch_number: int = 0
|
|
146
|
+
branch_number_as_hash: str = f"{branch_number:08x}"[self.__hash_reduce_left :]
|
|
147
|
+
separated_hash: str = "/".join(list(branch_number_as_hash))
|
|
148
|
+
meta_path = SyncPath(
|
|
149
|
+
*(
|
|
150
|
+
self.__db_root,
|
|
151
|
+
self.__class_model.__name__,
|
|
152
|
+
separated_hash,
|
|
153
|
+
"meta.json",
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
meta_json = meta_path.read_text("utf-8")
|
|
157
|
+
meta: _Meta = self.__meta.model_validate_json(meta_json)
|
|
158
|
+
meta.counter_documents += number
|
|
133
159
|
if meta.counter_documents < 0:
|
|
134
160
|
meta.counter_documents = 0
|
|
135
|
-
|
|
161
|
+
meta_json = meta.model_dump_json()
|
|
162
|
+
meta_path.write_text(meta_json, "utf-8")
|
|
136
163
|
|
|
137
164
|
async def _get_leaf_path(self, key: str) -> Path:
|
|
138
165
|
"""Asynchronous method for getting path to collection cell by key.
|
|
@@ -267,18 +294,18 @@ class Scruby[T]:
|
|
|
267
294
|
|
|
268
295
|
@staticmethod
|
|
269
296
|
def _task_find(
|
|
270
|
-
|
|
297
|
+
branch_number: int,
|
|
271
298
|
filter_fn: Callable,
|
|
272
|
-
|
|
299
|
+
hash_reduce_left: str,
|
|
273
300
|
db_root: str,
|
|
274
301
|
class_model: T,
|
|
275
302
|
) -> dict[str, Any] | None:
|
|
276
|
-
"""Task for
|
|
303
|
+
"""Task for find documents.
|
|
277
304
|
|
|
278
305
|
This method is for internal use.
|
|
279
306
|
"""
|
|
280
|
-
|
|
281
|
-
separated_hash: str = "/".join(list(
|
|
307
|
+
branch_number_as_hash: str = f"{branch_number:08x}"[hash_reduce_left:]
|
|
308
|
+
separated_hash: str = "/".join(list(branch_number_as_hash))
|
|
282
309
|
leaf_path: SyncPath = SyncPath(
|
|
283
310
|
*(
|
|
284
311
|
db_root,
|
|
@@ -302,7 +329,7 @@ class Scruby[T]:
|
|
|
302
329
|
max_workers: int | None = None,
|
|
303
330
|
timeout: float | None = None,
|
|
304
331
|
) -> T | None:
|
|
305
|
-
"""
|
|
332
|
+
"""Finds a single document matching the filter.
|
|
306
333
|
|
|
307
334
|
The search is based on the effect of a quantum loop.
|
|
308
335
|
The search effectiveness depends on the number of processor threads.
|
|
@@ -316,18 +343,18 @@ class Scruby[T]:
|
|
|
316
343
|
timeout: The number of seconds to wait for the result if the future isn't done.
|
|
317
344
|
If None, then there is no limit on the wait time.
|
|
318
345
|
"""
|
|
319
|
-
|
|
346
|
+
branch_numbers: range = range(1, self.__max_branch_number)
|
|
320
347
|
search_task_fn: Callable = self._task_find
|
|
321
|
-
|
|
348
|
+
hash_reduce_left: int = self.__hash_reduce_left
|
|
322
349
|
db_root: str = self.__db_root
|
|
323
350
|
class_model: T = self.__class_model
|
|
324
351
|
with concurrent.futures.ThreadPoolExecutor(max_workers) as executor:
|
|
325
|
-
for
|
|
352
|
+
for branch_number in branch_numbers:
|
|
326
353
|
future = executor.submit(
|
|
327
354
|
search_task_fn,
|
|
328
|
-
|
|
355
|
+
branch_number,
|
|
329
356
|
filter_fn,
|
|
330
|
-
|
|
357
|
+
hash_reduce_left,
|
|
331
358
|
db_root,
|
|
332
359
|
class_model,
|
|
333
360
|
)
|
|
@@ -336,14 +363,14 @@ class Scruby[T]:
|
|
|
336
363
|
return doc
|
|
337
364
|
return None
|
|
338
365
|
|
|
339
|
-
def
|
|
366
|
+
def find_many(
|
|
340
367
|
self,
|
|
341
368
|
filter_fn: Callable,
|
|
342
369
|
db_query_docs_limit: int = 1000,
|
|
343
370
|
max_workers: int | None = None,
|
|
344
371
|
timeout: float | None = None,
|
|
345
372
|
) -> list[T] | None:
|
|
346
|
-
"""
|
|
373
|
+
"""Finds one or more documents matching the filter.
|
|
347
374
|
|
|
348
375
|
The search is based on the effect of a quantum loop.
|
|
349
376
|
The search effectiveness depends on the number of processor threads.
|
|
@@ -358,22 +385,22 @@ class Scruby[T]:
|
|
|
358
385
|
timeout: The number of seconds to wait for the result if the future isn't done.
|
|
359
386
|
If None, then there is no limit on the wait time.
|
|
360
387
|
"""
|
|
361
|
-
|
|
388
|
+
branch_numbers: range = range(1, self.__max_branch_number)
|
|
362
389
|
search_task_fn: Callable = self._task_find
|
|
363
|
-
|
|
390
|
+
hash_reduce_left: int = self.__hash_reduce_left
|
|
364
391
|
db_root: str = self.__db_root
|
|
365
392
|
class_model: T = self.__class_model
|
|
366
393
|
counter: int = 0
|
|
367
394
|
with concurrent.futures.ThreadPoolExecutor(max_workers) as executor:
|
|
368
395
|
results = []
|
|
369
|
-
for
|
|
396
|
+
for branch_number in branch_numbers:
|
|
370
397
|
if counter == db_query_docs_limit:
|
|
371
398
|
break
|
|
372
399
|
future = executor.submit(
|
|
373
400
|
search_task_fn,
|
|
374
|
-
|
|
401
|
+
branch_number,
|
|
375
402
|
filter_fn,
|
|
376
|
-
|
|
403
|
+
hash_reduce_left,
|
|
377
404
|
db_root,
|
|
378
405
|
class_model,
|
|
379
406
|
)
|
|
@@ -416,22 +443,99 @@ class Scruby[T]:
|
|
|
416
443
|
timeout: The number of seconds to wait for the result if the future isn't done.
|
|
417
444
|
If None, then there is no limit on the wait time.
|
|
418
445
|
"""
|
|
419
|
-
|
|
446
|
+
branch_numbers: range = range(1, self.__max_branch_number)
|
|
420
447
|
search_task_fn: Callable = self._task_find
|
|
421
|
-
|
|
448
|
+
hash_reduce_left: int = self.__hash_reduce_left
|
|
422
449
|
db_root: str = self.__db_root
|
|
423
450
|
class_model: T = self.__class_model
|
|
424
451
|
counter: int = 0
|
|
425
452
|
with concurrent.futures.ThreadPoolExecutor(max_workers) as executor:
|
|
426
|
-
for
|
|
453
|
+
for branch_number in branch_numbers:
|
|
427
454
|
future = executor.submit(
|
|
428
455
|
search_task_fn,
|
|
429
|
-
|
|
456
|
+
branch_number,
|
|
430
457
|
filter_fn,
|
|
431
|
-
|
|
458
|
+
hash_reduce_left,
|
|
432
459
|
db_root,
|
|
433
460
|
class_model,
|
|
434
461
|
)
|
|
435
462
|
if future.result(timeout) is not None:
|
|
436
463
|
counter += 1
|
|
437
464
|
return counter
|
|
465
|
+
|
|
466
|
+
@staticmethod
|
|
467
|
+
def _task_delete(
|
|
468
|
+
branch_number: int,
|
|
469
|
+
filter_fn: Callable,
|
|
470
|
+
hash_reduce_left: str,
|
|
471
|
+
db_root: str,
|
|
472
|
+
class_model: T,
|
|
473
|
+
) -> int:
|
|
474
|
+
"""Task for find and delete documents.
|
|
475
|
+
|
|
476
|
+
This method is for internal use.
|
|
477
|
+
"""
|
|
478
|
+
branch_number_as_hash: str = f"{branch_number:08x}"[hash_reduce_left:]
|
|
479
|
+
separated_hash: str = "/".join(list(branch_number_as_hash))
|
|
480
|
+
leaf_path: SyncPath = SyncPath(
|
|
481
|
+
*(
|
|
482
|
+
db_root,
|
|
483
|
+
class_model.__name__,
|
|
484
|
+
separated_hash,
|
|
485
|
+
"leaf.json",
|
|
486
|
+
),
|
|
487
|
+
)
|
|
488
|
+
counter: int = 0
|
|
489
|
+
if leaf_path.exists():
|
|
490
|
+
data_json: bytes = leaf_path.read_bytes()
|
|
491
|
+
data: dict[str, str] = orjson.loads(data_json) or {}
|
|
492
|
+
new_data: dict[str, str] = {}
|
|
493
|
+
for key, val in data.items():
|
|
494
|
+
doc = class_model.model_validate_json(val)
|
|
495
|
+
if filter_fn(doc):
|
|
496
|
+
counter -= 1
|
|
497
|
+
else:
|
|
498
|
+
new_data[key] = val
|
|
499
|
+
leaf_path.write_bytes(orjson.dumps(new_data))
|
|
500
|
+
return counter
|
|
501
|
+
|
|
502
|
+
def find_many_and_delete(
|
|
503
|
+
self,
|
|
504
|
+
filter_fn: Callable,
|
|
505
|
+
max_workers: int | None = None,
|
|
506
|
+
timeout: float | None = None,
|
|
507
|
+
) -> int:
|
|
508
|
+
"""Finds one or more documents matching the filter and deletes their.
|
|
509
|
+
|
|
510
|
+
The search is based on the effect of a quantum loop.
|
|
511
|
+
The search effectiveness depends on the number of processor threads.
|
|
512
|
+
Ideally, hundreds and even thousands of threads are required.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
filter_fn: A function that execute the conditions of filtering.
|
|
516
|
+
max_workers: The maximum number of processes that can be used to
|
|
517
|
+
execute the given calls. If None or not given then as many
|
|
518
|
+
worker processes will be created as the machine has processors.
|
|
519
|
+
timeout: The number of seconds to wait for the result if the future isn't done.
|
|
520
|
+
If None, then there is no limit on the wait time.
|
|
521
|
+
"""
|
|
522
|
+
branch_numbers: range = range(1, self.__max_branch_number)
|
|
523
|
+
search_task_fn: Callable = self._task_delete
|
|
524
|
+
hash_reduce_left: int = self.__hash_reduce_left
|
|
525
|
+
db_root: str = self.__db_root
|
|
526
|
+
class_model: T = self.__class_model
|
|
527
|
+
counter: int = 0
|
|
528
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers) as executor:
|
|
529
|
+
for branch_number in branch_numbers:
|
|
530
|
+
future = executor.submit(
|
|
531
|
+
search_task_fn,
|
|
532
|
+
branch_number,
|
|
533
|
+
filter_fn,
|
|
534
|
+
hash_reduce_left,
|
|
535
|
+
db_root,
|
|
536
|
+
class_model,
|
|
537
|
+
)
|
|
538
|
+
counter += future.result(timeout)
|
|
539
|
+
if counter < 0:
|
|
540
|
+
self._sync_counter_documents(counter)
|
|
541
|
+
return abs(counter)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scruby
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: A fast key-value storage library.
|
|
5
5
|
Project-URL: Homepage, https://github.com/kebasyaty/scruby
|
|
6
6
|
Project-URL: Repository, https://github.com/kebasyaty/scruby
|
|
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
</p>
|
|
44
44
|
<p>
|
|
45
45
|
<h1>Scruby</h1>
|
|
46
|
-
<h3>
|
|
46
|
+
<h3>Asynchronous library for building and managing a hybrid database,<br>by scheme of key-value.</h3>
|
|
47
47
|
<p align="center">
|
|
48
48
|
<a href="https://github.com/kebasyaty/scruby/actions/workflows/test.yml" alt="Build Status"><img src="https://github.com/kebasyaty/scruby/actions/workflows/test.yml/badge.svg" alt="Build Status"></a>
|
|
49
49
|
<a href="https://kebasyaty.github.io/scruby/" alt="Docs"><img src="https://img.shields.io/badge/docs-available-brightgreen.svg" alt="Docs"></a>
|
|
@@ -65,20 +65,23 @@ Description-Content-Type: text/markdown
|
|
|
65
65
|
<a href="https://github.com/kebasyaty/scruby/releases/" alt="GitHub release"><img src="https://img.shields.io/github/release/kebasyaty/scruby" alt="GitHub release"></a>
|
|
66
66
|
</p>
|
|
67
67
|
<p align="center">
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
68
|
+
The library uses fractal-tree addressing and
|
|
69
|
+
<br>
|
|
70
|
+
the search for documents based on the effect of a quantum loop.
|
|
71
|
+
<br>
|
|
72
|
+
The database consists of collections.
|
|
73
|
+
<br>
|
|
74
|
+
The maximum size of the one collection is 16\*\*8=4294967296 branches,
|
|
75
|
+
<br>
|
|
76
|
+
each branch can store one or more keys.
|
|
77
|
+
<br>
|
|
78
|
+
The value of any key in collection can be obtained in 8 steps,
|
|
79
|
+
<br>
|
|
80
|
+
thereby achieving high performance.
|
|
81
|
+
<br>
|
|
82
|
+
The effectiveness of the search for documents based on a quantum loop,
|
|
83
|
+
<br>
|
|
84
|
+
requires a large number of processor threads.
|
|
82
85
|
</p>
|
|
83
86
|
</p>
|
|
84
87
|
</div>
|
|
@@ -274,7 +277,7 @@ async def main() -> None:
|
|
|
274
277
|
await db.set_key(f"+44798612345{num}", user)
|
|
275
278
|
|
|
276
279
|
# Find users by email.
|
|
277
|
-
users: list[User] | None = user_coll.
|
|
280
|
+
users: list[User] | None = user_coll.find_many(
|
|
278
281
|
filter_fn=lambda doc: doc.email == "John_Smith_5@gmail.com" or doc.email == "John_Smith_8@gmail.com",
|
|
279
282
|
)
|
|
280
283
|
if users is not None:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
scruby/__init__.py,sha256=GOVcjXmcOEDBbJQJDJlQq-x3M-VGJaMSN278EXsl2po,884
|
|
2
|
+
scruby/constants.py,sha256=3LZfcxcuRqwzoB0-iogLMjKBZRdxfWJmTbyPwVRhQgY,1007
|
|
3
|
+
scruby/db.py,sha256=Q7J4OKS2emiF0KzZClSjpBBLjohnccZ81T4pgoWNxqA,20269
|
|
4
|
+
scruby/errors.py,sha256=aHQri4LNcFVQrSHwjyzb1fL8O49SwjYEU4QgMOo4uyA,622
|
|
5
|
+
scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
scruby-0.11.1.dist-info/METADATA,sha256=Dn9WBqLzI312HlhMx4PGIVpmysCc6IUSPqXjXDuAOQY,10926
|
|
7
|
+
scruby-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
scruby-0.11.1.dist-info/licenses/LICENSE,sha256=2zZINd6m_jNYlowdQImlEizyhSui5cBAJZRhWQURcEc,1095
|
|
9
|
+
scruby-0.11.1.dist-info/RECORD,,
|
scruby-0.10.4.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
scruby/__init__.py,sha256=wFwUS1KcLxfIopXOVS8gPue9fNzIIU2cVj_RgK5drz4,849
|
|
2
|
-
scruby/constants.py,sha256=3LZfcxcuRqwzoB0-iogLMjKBZRdxfWJmTbyPwVRhQgY,1007
|
|
3
|
-
scruby/db.py,sha256=k_I2rphHG7Y5vq8oGDoimlKEwPEwmYzkqn7_DO0M6ic,15853
|
|
4
|
-
scruby/errors.py,sha256=aHQri4LNcFVQrSHwjyzb1fL8O49SwjYEU4QgMOo4uyA,622
|
|
5
|
-
scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
scruby-0.10.4.dist-info/METADATA,sha256=oJLzRBPPatu6dsq7EQyxf-UHQn4uJ1kF3C7Q-heqVNw,10819
|
|
7
|
-
scruby-0.10.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
-
scruby-0.10.4.dist-info/licenses/LICENSE,sha256=2zZINd6m_jNYlowdQImlEizyhSui5cBAJZRhWQURcEc,1095
|
|
9
|
-
scruby-0.10.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|