scruby 0.9.3__py3-none-any.whl → 0.10.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/db.py CHANGED
@@ -11,10 +11,11 @@ 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, Never, TypeVar, assert_never
14
+ from typing import Any, Literal, Never, TypeVar, assert_never
15
15
 
16
16
  import orjson
17
17
  from anyio import Path, to_thread
18
+ from pydantic import BaseModel
18
19
 
19
20
  from scruby import constants
20
21
 
@@ -23,6 +24,12 @@ logger = logging.getLogger(__name__)
23
24
  T = TypeVar("T")
24
25
 
25
26
 
27
+ class _Meta(BaseModel):
28
+ """Metadata of Collection."""
29
+
30
+ counter_documents: int
31
+
32
+
26
33
  class Scruby[T]:
27
34
  """Creation and management of database.
28
35
 
@@ -34,6 +41,7 @@ class Scruby[T]:
34
41
  self,
35
42
  class_model: T,
36
43
  ) -> None:
44
+ self.__meta = _Meta
37
45
  self.__class_model = class_model
38
46
  self.__db_root = constants.DB_ROOT
39
47
  self.__length_reduction_hash = constants.LENGTH_REDUCTION_HASH
@@ -51,10 +59,85 @@ class Scruby[T]:
51
59
  msg: str = f"{unreachable} - Unacceptable value for LENGTH_REDUCTION_HASH."
52
60
  logger.critical(msg)
53
61
  assert_never(Never(unreachable))
62
+ # Create metadata if absent.
63
+ self._create_metadata()
64
+
65
+ def _create_metadata(self) -> None:
66
+ """Create metadata for collection if absent.
67
+
68
+ This method is for internal use.
69
+ """
70
+ key: int = 0
71
+ key_as_hash: str = f"{key:08x}"[self.__length_reduction_hash :]
72
+ separated_hash: str = "/".join(list(key_as_hash))
73
+ branch_path = SyncPath(
74
+ *(
75
+ self.__db_root,
76
+ self.__class_model.__name__,
77
+ separated_hash,
78
+ ),
79
+ )
80
+ if not branch_path.exists():
81
+ branch_path.mkdir(parents=True)
82
+ meta = _Meta(
83
+ counter_documents=0,
84
+ )
85
+ meta_json = meta.model_dump_json()
86
+ meta_path = SyncPath(*(branch_path, "meta.json"))
87
+ meta_path.write_text(meta_json, "utf-8")
88
+
89
+ async def _get_meta_path(self) -> Path:
90
+ """Asynchronous method for getting path to metadata of collection.
91
+
92
+ This method is for internal use.
93
+ """
94
+ key: int = 0
95
+ key_as_hash: str = f"{key:08x}"[self.__length_reduction_hash :]
96
+ separated_hash: str = "/".join(list(key_as_hash))
97
+ return Path(
98
+ *(
99
+ self.__db_root,
100
+ self.__class_model.__name__,
101
+ separated_hash,
102
+ "meta.json",
103
+ ),
104
+ )
105
+
106
+ async def _get_meta(self) -> _Meta:
107
+ """Asynchronous method for getting metadata of collection.
54
108
 
55
- async def get_leaf_path(self, key: str) -> Path:
109
+ This method is for internal use.
110
+ """
111
+ meta_path = await self._get_meta_path()
112
+ meta_json = await meta_path.read_text()
113
+ meta: _Meta = self.__meta.model_validate_json(meta_json)
114
+ return meta
115
+
116
+ async def _set_meta(self, meta: _Meta) -> None:
117
+ """Asynchronous method for updating metadata of collection.
118
+
119
+ This method is for internal use.
120
+ """
121
+ meta_path = await self._get_meta_path()
122
+ meta_json = meta.model_dump_json()
123
+ await meta_path.write_text(meta_json, "utf-8")
124
+
125
+ async def _counter_documents(self, step: Literal[1, -1]) -> None:
126
+ """Management of documents in metadata of collection.
127
+
128
+ This method is for internal use.
129
+ """
130
+ meta = await self._get_meta()
131
+ meta.counter_documents += step
132
+ if meta.counter_documents < 0:
133
+ meta.counter_documents = 0
134
+ await self._set_meta(meta)
135
+
136
+ async def _get_leaf_path(self, key: str) -> Path:
56
137
  """Asynchronous method for getting path to collection cell by key.
57
138
 
139
+ This method is for internal use.
140
+
58
141
  Args:
59
142
  key: Key name.
60
143
  """
@@ -95,18 +178,21 @@ class Scruby[T]:
95
178
  value: Value of key.
96
179
  """
97
180
  # The path to the database cell.
98
- leaf_path: Path = await self.get_leaf_path(key)
181
+ leaf_path: Path = await self._get_leaf_path(key)
99
182
  value_json: str = value.model_dump_json()
100
183
  # Write key-value to the database.
101
184
  if await leaf_path.exists():
102
185
  # Add new key or update existing.
103
186
  data_json: bytes = await leaf_path.read_bytes()
104
187
  data: dict = orjson.loads(data_json) or {}
188
+ if data.get(key) is None:
189
+ await self._counter_documents(1)
105
190
  data[key] = value_json
106
191
  await leaf_path.write_bytes(orjson.dumps(data))
107
192
  else:
108
193
  # Add new key to a blank leaf.
109
194
  await leaf_path.write_bytes(orjson.dumps({key: value_json}))
195
+ await self._counter_documents(1)
110
196
 
111
197
  async def get_key(self, key: str) -> T:
112
198
  """Asynchronous method for getting value of key from collection.
@@ -115,7 +201,7 @@ class Scruby[T]:
115
201
  key: Key name.
116
202
  """
117
203
  # The path to the database cell.
118
- leaf_path: Path = await self.get_leaf_path(key)
204
+ leaf_path: Path = await self._get_leaf_path(key)
119
205
  # Get value of key.
120
206
  if await leaf_path.exists():
121
207
  data_json: bytes = await leaf_path.read_bytes()
@@ -133,7 +219,7 @@ class Scruby[T]:
133
219
  key: Key name.
134
220
  """
135
221
  # The path to the database cell.
136
- leaf_path: Path = await self.get_leaf_path(key)
222
+ leaf_path: Path = await self._get_leaf_path(key)
137
223
  # Checking whether there is a key.
138
224
  if await leaf_path.exists():
139
225
  data_json: bytes = await leaf_path.read_bytes()
@@ -152,13 +238,14 @@ class Scruby[T]:
152
238
  key: Key name.
153
239
  """
154
240
  # The path to the database cell.
155
- leaf_path: Path = await self.get_leaf_path(key)
241
+ leaf_path: Path = await self._get_leaf_path(key)
156
242
  # Deleting key.
157
243
  if await leaf_path.exists():
158
244
  data_json: bytes = await leaf_path.read_bytes()
159
245
  data: dict = orjson.loads(data_json) or {}
160
246
  del data[key]
161
247
  await leaf_path.write_bytes(orjson.dumps(data))
248
+ await self._counter_documents(-1)
162
249
  return
163
250
  msg: str = "`delete_key` - The unacceptable key value."
164
251
  logger.error(msg)
@@ -178,14 +265,17 @@ class Scruby[T]:
178
265
  return
179
266
 
180
267
  @staticmethod
181
- def search_task(
268
+ def _search_task(
182
269
  key: int,
183
270
  filter_fn: Callable,
184
271
  length_reduction_hash: str,
185
272
  db_root: str,
186
273
  class_model: T,
187
274
  ) -> dict[str, Any] | None:
188
- """Search task."""
275
+ """Task for searching for documents.
276
+
277
+ This method is for internal use.
278
+ """
189
279
  key_as_hash: str = f"{key:08x}"[length_reduction_hash:]
190
280
  separated_hash: str = "/".join(list(key_as_hash))
191
281
  leaf_path: SyncPath = SyncPath(
@@ -226,7 +316,7 @@ class Scruby[T]:
226
316
  If None, then there is no limit on the wait time.
227
317
  """
228
318
  keys: range = range(1, self.__max_num_keys)
229
- search_task_fn: Callable = self.search_task
319
+ search_task_fn: Callable = self._search_task
230
320
  length_reduction_hash: int = self.__length_reduction_hash
231
321
  db_root: str = self.__db_root
232
322
  class_model: T = self.__class_model
@@ -268,7 +358,7 @@ class Scruby[T]:
268
358
  If None, then there is no limit on the wait time.
269
359
  """
270
360
  keys: range = range(1, self.__max_num_keys)
271
- search_task_fn: Callable = self.search_task
361
+ search_task_fn: Callable = self._search_task
272
362
  length_reduction_hash: int = self.__length_reduction_hash
273
363
  db_root: str = self.__db_root
274
364
  class_model: T = self.__class_model
@@ -299,3 +389,8 @@ class Scruby[T]:
299
389
  def collection_full_name(self) -> str:
300
390
  """Get full name of collection."""
301
391
  return f"{self.__db_root}/{self.__class_model.__name__}"
392
+
393
+ async def estimated_document_count(self) -> int:
394
+ """Get an estimate of the number of documents in this collection using collection metadata."""
395
+ meta = await self._get_meta()
396
+ return meta.counter_documents
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scruby
3
- Version: 0.9.3
3
+ Version: 0.10.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
@@ -0,0 +1,8 @@
1
+ scruby/__init__.py,sha256=wFwUS1KcLxfIopXOVS8gPue9fNzIIU2cVj_RgK5drz4,849
2
+ scruby/constants.py,sha256=GbB-O0qaVdi5EHUp-zRAppFXLR-oHxpXUFVAOCpS0C8,1022
3
+ scruby/db.py,sha256=d4fVb4JhGaBU_YPxByT156UGHQPTwzSizBqOpCNhW8Y,14159
4
+ scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ scruby-0.10.1.dist-info/METADATA,sha256=k2AaJn-JijNceNIB7s13D1cB3jr4p4mW6R1z7IuXp4g,10829
6
+ scruby-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ scruby-0.10.1.dist-info/licenses/LICENSE,sha256=2zZINd6m_jNYlowdQImlEizyhSui5cBAJZRhWQURcEc,1095
8
+ scruby-0.10.1.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- scruby/__init__.py,sha256=wFwUS1KcLxfIopXOVS8gPue9fNzIIU2cVj_RgK5drz4,849
2
- scruby/constants.py,sha256=GbB-O0qaVdi5EHUp-zRAppFXLR-oHxpXUFVAOCpS0C8,1022
3
- scruby/db.py,sha256=reYCZduh4GAycS17oPw8BWoFd-M9A8N7BW_uPFwUd_w,10941
4
- scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- scruby-0.9.3.dist-info/METADATA,sha256=YBlk8IR3bDVRV-XKLKEHkpd3Bir97OF0wyOe0jFDpR0,10828
6
- scruby-0.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- scruby-0.9.3.dist-info/licenses/LICENSE,sha256=2zZINd6m_jNYlowdQImlEizyhSui5cBAJZRhWQURcEc,1095
8
- scruby-0.9.3.dist-info/RECORD,,