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 CHANGED
@@ -1,8 +1,7 @@
1
- """A fast key-value storage library.
1
+ """Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
 
3
- Scruby is a fast key-value storage asynchronous library that provides an
4
- ordered mapping from string keys to string values.
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
- In the future, to search by value of key, the use of a quantum loop is supposed.
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, Literal, Never, TypeVar, assert_never
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.__max_num_keys = 4294967296
51
+ self.__max_branch_number = 4294967296
52
52
  case 2:
53
- self.__max_num_keys = 16777216
53
+ self.__max_branch_number = 16777216
54
54
  case 4:
55
- self.__max_num_keys = 65536
55
+ self.__max_branch_number = 65536
56
56
  case 6:
57
- self.__max_num_keys = 256
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
- key: int = 0
72
- key_as_hash: str = f"{key:08x}"[self.__hash_reduce_left :]
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
- key: int = 0
96
- key_as_hash: str = f"{key:08x}"[self.__hash_reduce_left :]
97
- separated_hash: str = "/".join(list(key_as_hash))
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, step: Literal[1, -1]) -> None:
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
- meta = await self._get_meta()
132
- meta.counter_documents += step
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
- await self._set_meta(meta)
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
- key: int,
297
+ branch_number: int,
271
298
  filter_fn: Callable,
272
- HASH_REDUCE_LEFT: str,
299
+ hash_reduce_left: str,
273
300
  db_root: str,
274
301
  class_model: T,
275
302
  ) -> dict[str, Any] | None:
276
- """Task for searching for documents.
303
+ """Task for find documents.
277
304
 
278
305
  This method is for internal use.
279
306
  """
280
- key_as_hash: str = f"{key:08x}"[HASH_REDUCE_LEFT:]
281
- separated_hash: str = "/".join(list(key_as_hash))
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
- """Find a single document matching the filter.
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
- keys: range = range(1, self.__max_num_keys)
346
+ branch_numbers: range = range(1, self.__max_branch_number)
320
347
  search_task_fn: Callable = self._task_find
321
- HASH_REDUCE_LEFT: int = self.__hash_reduce_left
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 key in keys:
352
+ for branch_number in branch_numbers:
326
353
  future = executor.submit(
327
354
  search_task_fn,
328
- key,
355
+ branch_number,
329
356
  filter_fn,
330
- HASH_REDUCE_LEFT,
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 find(
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
- """Find one or more documents matching the filter.
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
- keys: range = range(1, self.__max_num_keys)
388
+ branch_numbers: range = range(1, self.__max_branch_number)
362
389
  search_task_fn: Callable = self._task_find
363
- HASH_REDUCE_LEFT: int = self.__hash_reduce_left
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 key in keys:
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
- key,
401
+ branch_number,
375
402
  filter_fn,
376
- HASH_REDUCE_LEFT,
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
- keys: range = range(1, self.__max_num_keys)
446
+ branch_numbers: range = range(1, self.__max_branch_number)
420
447
  search_task_fn: Callable = self._task_find
421
- HASH_REDUCE_LEFT: int = self.__hash_reduce_left
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 key in keys:
453
+ for branch_number in branch_numbers:
427
454
  future = executor.submit(
428
455
  search_task_fn,
429
- key,
456
+ branch_number,
430
457
  filter_fn,
431
- HASH_REDUCE_LEFT,
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.10.4
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>A fast key-value storage library.</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
- Scruby is a fast key-value storage asynchronous library that provides an
69
- <br>
70
- ordered mapping from string keys to string values.
71
- <br>
72
- The library uses fractal-tree addressing.
73
- <br>
74
- The database consists of collections.
75
- <br>
76
- The maximum size of the one collection is 16**8=4294967296 branches,
77
- each branch can store one or more keys.
78
- <br>
79
- The value of any key in collection can be obtained in 8 steps, thereby achieving high performance.
80
- <br>
81
- In the future, to search by value of key, the use of a quantum loop is supposed.
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.find(
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,,
@@ -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,,