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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
"""
|
|
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.
|
|
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.
|
|
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
|
|
@@ -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,,
|
scruby-0.9.3.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|