scruby 2.2.3__py3-none-any.whl → 2.3.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.
- scruby/__init__.py +2 -1
- scruby/cache.py +27 -17
- scruby/config.py +5 -5
- scruby/db.py +31 -103
- scruby/meta.py +57 -0
- scruby/mixins/collection.py +15 -12
- scruby/model.py +17 -0
- scruby/utils.py +65 -59
- {scruby-2.2.3.dist-info → scruby-2.3.1.dist-info}/METADATA +7 -7
- scruby-2.3.1.dist-info/RECORD +23 -0
- scruby-2.2.3.dist-info/RECORD +0 -21
- {scruby-2.2.3.dist-info → scruby-2.3.1.dist-info}/WHEEL +0 -0
- {scruby-2.2.3.dist-info → scruby-2.3.1.dist-info}/licenses/GPL-3.0-LICENSE +0 -0
- {scruby-2.2.3.dist-info → scruby-2.3.1.dist-info}/licenses/MIT-LICENSE +0 -0
scruby/__init__.py
CHANGED
scruby/cache.py
CHANGED
|
@@ -12,11 +12,19 @@ from typing import Any, ClassVar, Literal, Never, assert_never, final
|
|
|
12
12
|
import orjson
|
|
13
13
|
|
|
14
14
|
from scruby.config import ScrubyConfig
|
|
15
|
+
from scruby.meta import Metadata
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@final
|
|
18
19
|
class DocCache:
|
|
19
|
-
"""Cache documents to optimize work with the database.
|
|
20
|
+
"""Cache documents to optimize work with the database.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
collection_name (str): Collection name.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
None.
|
|
27
|
+
"""
|
|
20
28
|
|
|
21
29
|
# Cache structure:
|
|
22
30
|
# {"CollectionName": {"hash_symbol": {"hash_symbol": {"hash_symbol": {"key_name": doc}}}}
|
|
@@ -24,7 +32,14 @@ class DocCache:
|
|
|
24
32
|
|
|
25
33
|
@classmethod
|
|
26
34
|
def create_structure(cls, collection_name: str) -> None:
|
|
27
|
-
"""Create
|
|
35
|
+
"""Create structure of empty cache for collection.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
collection_name (str): Collection name.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
None.
|
|
42
|
+
"""
|
|
28
43
|
if ScrubyConfig.HASH_REDUCE_LEFT == 0:
|
|
29
44
|
return
|
|
30
45
|
|
|
@@ -44,29 +59,24 @@ class DocCache:
|
|
|
44
59
|
@classmethod
|
|
45
60
|
def load_cache(cls, subclasses: list[Any]) -> None:
|
|
46
61
|
"""Load all documents from the database into the cache."""
|
|
47
|
-
if ScrubyConfig.HASH_REDUCE_LEFT == 0:
|
|
48
|
-
return
|
|
49
|
-
|
|
50
62
|
db_root: Path = Path(ScrubyConfig.db_root)
|
|
51
63
|
HASH_REDUCE_LEFT: Literal[7, 6, 5, 0] = ScrubyConfig.HASH_REDUCE_LEFT
|
|
52
64
|
MAX_NUMBER_BRANCH: Literal[16, 256, 4096, 4294967296] = ScrubyConfig.MAX_NUMBER_BRANCH
|
|
53
65
|
branch_numbers: range = range(MAX_NUMBER_BRANCH)
|
|
54
66
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return
|
|
67
|
+
for subclass in subclasses:
|
|
68
|
+
collection_name: str = subclass.__name__
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
all_entries = Path.iterdir(db_directory)
|
|
62
|
-
directory_names: list[str] = [entry.name for entry in all_entries if entry.name != ".env.meta"]
|
|
70
|
+
# Create metadata for the collection if it is missing
|
|
71
|
+
Metadata.create(collection_name)
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
if HASH_REDUCE_LEFT == 0:
|
|
74
|
+
continue
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
# Create a cache structure for the collection
|
|
77
|
+
cls.create_structure(collection_name)
|
|
69
78
|
|
|
79
|
+
# Get data from database and add to cache for collection
|
|
70
80
|
for branch_number in branch_numbers:
|
|
71
81
|
branch_number_as_hash: str = f"{branch_number:08x}"[HASH_REDUCE_LEFT:]
|
|
72
82
|
separated_hash = "/".join(list(branch_number_as_hash))
|
|
@@ -84,7 +94,7 @@ class DocCache:
|
|
|
84
94
|
data: dict[str, str] = orjson.loads(data_json) or {}
|
|
85
95
|
for key, val in data.items():
|
|
86
96
|
doc = subclass.model_validate_json(val)
|
|
87
|
-
match
|
|
97
|
+
match HASH_REDUCE_LEFT:
|
|
88
98
|
case 7:
|
|
89
99
|
cls.cache[collection_name][branch_number_as_hash[0]][key] = doc
|
|
90
100
|
case 6:
|
scruby/config.py
CHANGED
|
@@ -27,7 +27,7 @@ import sys
|
|
|
27
27
|
from typing import Any, ClassVar, Literal, Never, assert_never, final
|
|
28
28
|
from uuid import uuid4
|
|
29
29
|
|
|
30
|
-
from scruby.utils import
|
|
30
|
+
from scruby.utils import Utils
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@final
|
|
@@ -77,10 +77,10 @@ class ScrubyConfig:
|
|
|
77
77
|
delimiter: str = "/" if cls.sys_platform != "win32" else ""
|
|
78
78
|
dotenv_path: str = f"{cls.db_root}{delimiter}.env.meta"
|
|
79
79
|
|
|
80
|
-
db_id: str | None = get_from_env(
|
|
80
|
+
db_id: str | None = Utils.get_from_env(
|
|
81
81
|
key=key,
|
|
82
82
|
dotenv_path=dotenv_path,
|
|
83
|
-
) or add_to_env(
|
|
83
|
+
) or Utils.add_to_env(
|
|
84
84
|
key=key,
|
|
85
85
|
value=str(uuid4())[:8],
|
|
86
86
|
dotenv_path=dotenv_path,
|
|
@@ -116,10 +116,10 @@ class ScrubyConfig:
|
|
|
116
116
|
delimiter: str = "/" if cls.sys_platform != "win32" else ""
|
|
117
117
|
dotenv_path: str = f"{cls.db_root}{delimiter}.env.meta"
|
|
118
118
|
|
|
119
|
-
hash_reduce_left: str | None = get_from_env(
|
|
119
|
+
hash_reduce_left: str | None = Utils.get_from_env(
|
|
120
120
|
key=key,
|
|
121
121
|
dotenv_path=dotenv_path,
|
|
122
|
-
) or add_to_env(
|
|
122
|
+
) or Utils.add_to_env(
|
|
123
123
|
key=key,
|
|
124
124
|
value=str(cls.HASH_REDUCE_LEFT),
|
|
125
125
|
dotenv_path=dotenv_path,
|
scruby/db.py
CHANGED
|
@@ -6,41 +6,22 @@
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
__all__ = (
|
|
10
|
-
"Scruby",
|
|
11
|
-
"ScrubyModel",
|
|
12
|
-
)
|
|
9
|
+
__all__ = ("Scruby",)
|
|
13
10
|
|
|
14
11
|
import contextlib
|
|
15
12
|
import re
|
|
16
13
|
import zlib
|
|
17
|
-
from datetime import datetime
|
|
18
14
|
from shutil import rmtree
|
|
19
15
|
from typing import Any, Literal, final
|
|
20
16
|
|
|
21
17
|
from anyio import Path
|
|
22
|
-
from pydantic import BaseModel
|
|
23
18
|
from xloft import NamedTuple
|
|
24
19
|
|
|
25
20
|
from scruby import mixins
|
|
26
21
|
from scruby.cache import DocCache
|
|
27
22
|
from scruby.config import ScrubyConfig
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class _Meta(BaseModel):
|
|
31
|
-
"""Metadata of Collection."""
|
|
32
|
-
|
|
33
|
-
collection_name: str
|
|
34
|
-
hash_reduce_left: int
|
|
35
|
-
max_number_branch: int
|
|
36
|
-
counter_documents: int
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class ScrubyModel(BaseModel):
|
|
40
|
-
"""Additional fields for models."""
|
|
41
|
-
|
|
42
|
-
created_at: datetime | None = None
|
|
43
|
-
updated_at: datetime | None = None
|
|
23
|
+
from scruby.meta import Meta
|
|
24
|
+
from scruby.model import ScrubyModel
|
|
44
25
|
|
|
45
26
|
|
|
46
27
|
@final
|
|
@@ -57,97 +38,40 @@ class Scruby(
|
|
|
57
38
|
|
|
58
39
|
def __init__( # noqa: D107
|
|
59
40
|
self,
|
|
41
|
+
class_model: Any,
|
|
60
42
|
) -> None:
|
|
43
|
+
if __debug__:
|
|
44
|
+
if ScrubyModel not in class_model.__bases__:
|
|
45
|
+
msg = "Scruby => Argument `class_model` does not contain the base class `ScrubyModel`."
|
|
46
|
+
raise AssertionError(msg)
|
|
47
|
+
if "key" not in list(class_model.model_fields.keys()):
|
|
48
|
+
msg = f"Model: {class_model.__name__} => The `key` field is missing."
|
|
49
|
+
raise AssertionError(msg)
|
|
50
|
+
|
|
61
51
|
super().__init__()
|
|
62
|
-
self.
|
|
52
|
+
self._class_model = class_model
|
|
63
53
|
self._db_id = ScrubyConfig.db_id
|
|
64
54
|
self._db_root = ScrubyConfig.db_root
|
|
65
55
|
self._hash_reduce_left = ScrubyConfig.HASH_REDUCE_LEFT
|
|
66
56
|
self._max_number_branch = ScrubyConfig.MAX_NUMBER_BRANCH
|
|
67
57
|
self._max_workers = ScrubyConfig.max_workers
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
async def collection(cls, class_model: Any) -> Any:
|
|
71
|
-
"""Asynchronous method for creating a new collection and accessing an existing collection.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
class_model (Any): Class of Model (ScrubyModel).
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
Instance of Scruby for access a collection.
|
|
78
|
-
"""
|
|
79
|
-
if __debug__:
|
|
80
|
-
# Check if the object belongs to the class `ScrubyModel`
|
|
81
|
-
if ScrubyModel not in class_model.__bases__:
|
|
82
|
-
msg = (
|
|
83
|
-
"Method: `collection` => argument `class_model` " + "does not contain the base class `ScrubyModel`!"
|
|
84
|
-
)
|
|
85
|
-
raise AssertionError(msg)
|
|
86
|
-
# Checking the model for the presence of a key.
|
|
87
|
-
model_fields = list(class_model.model_fields.keys())
|
|
88
|
-
if "key" not in model_fields:
|
|
89
|
-
msg = f"Model: {class_model.__name__} => The `key` field is missing!"
|
|
90
|
-
raise AssertionError(msg)
|
|
91
|
-
if "created_at" not in model_fields:
|
|
92
|
-
msg = f"Model: {class_model.__name__} => The `created_at` field is missing!"
|
|
93
|
-
raise AssertionError(msg)
|
|
94
|
-
if "updated_at" not in model_fields:
|
|
95
|
-
msg = f"Model: {class_model.__name__} => The `updated_at` field is missing!"
|
|
96
|
-
raise AssertionError(msg)
|
|
97
|
-
# Check the length of the collection name for an acceptable size.
|
|
98
|
-
len_db_root_absolut_path = len(str(await Path(ScrubyConfig.db_root).resolve()).encode("utf-8"))
|
|
99
|
-
len_model_name = len(class_model.__name__)
|
|
100
|
-
len_full_path_leaf = len_db_root_absolut_path + len_model_name + 26
|
|
101
|
-
if len_full_path_leaf > 255:
|
|
102
|
-
excess = len_full_path_leaf - 255
|
|
103
|
-
msg = (
|
|
104
|
-
f"Model: {class_model.__name__} => The collection name is too long, "
|
|
105
|
-
+ f"it exceeds the limit of {excess} characters!"
|
|
106
|
-
)
|
|
107
|
-
raise AssertionError(msg)
|
|
108
|
-
# Create instance of Scruby
|
|
109
|
-
instance = cls()
|
|
110
|
-
# Add model class to Scruby
|
|
111
|
-
instance.__dict__["_class_model"] = class_model
|
|
112
|
-
# Create a path for metadata.
|
|
113
|
-
meta_dir_path_tuple = (
|
|
58
|
+
self._meta = Meta
|
|
59
|
+
self._meta_path = Path(
|
|
114
60
|
ScrubyConfig.db_root,
|
|
115
61
|
class_model.__name__,
|
|
116
62
|
"meta",
|
|
117
|
-
)
|
|
118
|
-
instance.__dict__["_meta_path"] = Path(
|
|
119
|
-
*meta_dir_path_tuple,
|
|
120
63
|
"meta.json",
|
|
121
64
|
)
|
|
122
|
-
# Create metadata for collection, if missing.
|
|
123
|
-
meta_dir_path = Path(*meta_dir_path_tuple)
|
|
124
|
-
if not await meta_dir_path.exists():
|
|
125
|
-
# Create metadata.
|
|
126
|
-
await meta_dir_path.mkdir(parents=True)
|
|
127
|
-
meta = _Meta(
|
|
128
|
-
collection_name=class_model.__name__,
|
|
129
|
-
hash_reduce_left=instance.__dict__["_hash_reduce_left"],
|
|
130
|
-
max_number_branch=instance.__dict__["_max_number_branch"],
|
|
131
|
-
counter_documents=0,
|
|
132
|
-
)
|
|
133
|
-
# Save metadata of collection.
|
|
134
|
-
meta_json = meta.model_dump_json()
|
|
135
|
-
meta_path = Path(*(meta_dir_path, "meta.json"))
|
|
136
|
-
await meta_path.write_text(meta_json, "utf-8")
|
|
137
|
-
# Create a cache structure for the collection.
|
|
138
|
-
if instance.__dict__["_hash_reduce_left"] != 0:
|
|
139
|
-
DocCache.create_structure(class_model.__name__)
|
|
140
65
|
# Plugins connection.
|
|
141
66
|
plugin_list: dict[str, Any] = {}
|
|
142
67
|
if ScrubyConfig.plugins is not None:
|
|
143
68
|
for plugin in ScrubyConfig.plugins:
|
|
144
69
|
name = plugin.__name__
|
|
145
70
|
name = name[0].lower() + name[1:]
|
|
146
|
-
plugin_list[name] = plugin(scruby_self=
|
|
147
|
-
|
|
148
|
-
return instance
|
|
71
|
+
plugin_list[name] = plugin(scruby_self=self)
|
|
72
|
+
self.plugins = NamedTuple(**plugin_list)
|
|
149
73
|
|
|
150
|
-
async def get_meta(self) ->
|
|
74
|
+
async def get_meta(self) -> Meta:
|
|
151
75
|
"""Asynchronous method for getting metadata of collection.
|
|
152
76
|
|
|
153
77
|
This method is for internal use.
|
|
@@ -156,10 +80,10 @@ class Scruby(
|
|
|
156
80
|
Metadata object.
|
|
157
81
|
"""
|
|
158
82
|
meta_json = await self._meta_path.read_text()
|
|
159
|
-
meta:
|
|
83
|
+
meta: Meta = self._meta.model_validate_json(meta_json)
|
|
160
84
|
return meta
|
|
161
85
|
|
|
162
|
-
async def _set_meta(self, meta:
|
|
86
|
+
async def _set_meta(self, meta: Meta) -> None:
|
|
163
87
|
"""Asynchronous method for updating metadata of collection.
|
|
164
88
|
|
|
165
89
|
This method is for internal use.
|
|
@@ -186,7 +110,7 @@ class Scruby(
|
|
|
186
110
|
"""
|
|
187
111
|
meta_path = self._meta_path
|
|
188
112
|
meta_json = await meta_path.read_text("utf-8")
|
|
189
|
-
meta:
|
|
113
|
+
meta: Meta = self._meta.model_validate_json(meta_json)
|
|
190
114
|
meta.counter_documents += step
|
|
191
115
|
meta_json = meta.model_dump_json()
|
|
192
116
|
await meta_path.write_text(meta_json, "utf-8")
|
|
@@ -268,11 +192,15 @@ class Scruby(
|
|
|
268
192
|
Returns:
|
|
269
193
|
None.
|
|
270
194
|
"""
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
195
|
+
subclasses: list[Any] = ScrubyModel.__subclasses__()
|
|
196
|
+
if __debug__:
|
|
197
|
+
if len(subclasses) == 0:
|
|
198
|
+
raise AssertionError("Create least one model of document for your project.")
|
|
199
|
+
if plugins is not None:
|
|
200
|
+
for plugin in plugins:
|
|
201
|
+
if plugin.SCRUBY_VERSION != 2:
|
|
202
|
+
msg = f"Plugin {plugin.__name__} does not apply to version 2."
|
|
203
|
+
raise AssertionError(msg)
|
|
276
204
|
|
|
277
205
|
ScrubyConfig.db_root = db_root
|
|
278
206
|
ScrubyConfig.HASH_REDUCE_LEFT = hash_reduce_left
|
|
@@ -280,4 +208,4 @@ class Scruby(
|
|
|
280
208
|
ScrubyConfig.plugins = plugins
|
|
281
209
|
ScrubyConfig.init_params()
|
|
282
210
|
ScrubyConfig.check_hash_reduce_left()
|
|
283
|
-
DocCache.load_cache(
|
|
211
|
+
DocCache.load_cache(subclasses)
|
scruby/meta.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Meta.
|
|
2
|
+
|
|
3
|
+
Metadata management.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
"Meta",
|
|
10
|
+
"Metadata",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
from scruby.config import ScrubyConfig
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Meta(BaseModel):
|
|
21
|
+
"""Structure of metadata for collection."""
|
|
22
|
+
|
|
23
|
+
collection_name: str
|
|
24
|
+
hash_reduce_left: int
|
|
25
|
+
max_number_branch: int
|
|
26
|
+
counter_documents: int
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Metadata:
|
|
30
|
+
"""Metadata management."""
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def create(collection_name: str) -> None:
|
|
34
|
+
"""Create metadata for collection.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
collection_name (str): Collection name.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
None.
|
|
41
|
+
"""
|
|
42
|
+
meta_dir_path = Path(
|
|
43
|
+
ScrubyConfig.db_root,
|
|
44
|
+
collection_name,
|
|
45
|
+
"meta",
|
|
46
|
+
)
|
|
47
|
+
if not meta_dir_path.exists():
|
|
48
|
+
meta_dir_path.mkdir(parents=True)
|
|
49
|
+
meta = Meta(
|
|
50
|
+
collection_name=collection_name,
|
|
51
|
+
hash_reduce_left=ScrubyConfig.HASH_REDUCE_LEFT,
|
|
52
|
+
max_number_branch=ScrubyConfig.MAX_NUMBER_BRANCH,
|
|
53
|
+
counter_documents=0,
|
|
54
|
+
)
|
|
55
|
+
meta_json = meta.model_dump_json()
|
|
56
|
+
meta_path = Path(meta_dir_path, "meta.json")
|
|
57
|
+
meta_path.write_text(meta_json, "utf-8")
|
scruby/mixins/collection.py
CHANGED
|
@@ -11,10 +11,10 @@ __all__ = ("Collection",)
|
|
|
11
11
|
from shutil import rmtree
|
|
12
12
|
from typing import final
|
|
13
13
|
|
|
14
|
-
from anyio import Path, to_thread
|
|
15
|
-
|
|
16
14
|
from scruby.cache import DocCache
|
|
17
15
|
from scruby.config import ScrubyConfig
|
|
16
|
+
from scruby.meta import Metadata
|
|
17
|
+
from scruby.model import ScrubyModel
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class Collection:
|
|
@@ -31,18 +31,15 @@ class Collection:
|
|
|
31
31
|
|
|
32
32
|
@final
|
|
33
33
|
@staticmethod
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
all_entries = Path.iterdir(db_directory)
|
|
39
|
-
directory_names: list[str] = [entry.name async for entry in all_entries if entry.name != ".env.meta"]
|
|
40
|
-
return directory_names or None
|
|
34
|
+
def collection_list() -> list[str] | None:
|
|
35
|
+
"""Synchronous method for getting collection list."""
|
|
36
|
+
collections = [item.__name__ for item in ScrubyModel.__subclasses__()]
|
|
37
|
+
return collections or None
|
|
41
38
|
|
|
42
39
|
@final
|
|
43
40
|
@staticmethod
|
|
44
|
-
|
|
45
|
-
"""
|
|
41
|
+
def clear_collection(collection_name: str) -> None:
|
|
42
|
+
"""Synchronous method to remove all documents from a collection.
|
|
46
43
|
|
|
47
44
|
Args:
|
|
48
45
|
collection_name (str): Collection name.
|
|
@@ -50,8 +47,14 @@ class Collection:
|
|
|
50
47
|
Returns:
|
|
51
48
|
None.
|
|
52
49
|
"""
|
|
50
|
+
# Clear collection on file system
|
|
53
51
|
target_directory = f"{ScrubyConfig.db_root}/{collection_name}"
|
|
54
|
-
|
|
52
|
+
rmtree(target_directory)
|
|
53
|
+
# Create metadata for collection
|
|
54
|
+
Metadata.create(collection_name)
|
|
55
|
+
|
|
56
|
+
# Clear collection in cache
|
|
55
57
|
if ScrubyConfig.HASH_REDUCE_LEFT != 0:
|
|
56
58
|
del DocCache.cache[collection_name]
|
|
59
|
+
DocCache.create_structure(collection_name)
|
|
57
60
|
return
|
scruby/model.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = ("ScrubyModel",)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ScrubyModel(BaseModel):
|
|
14
|
+
"""Additional fields for models."""
|
|
15
|
+
|
|
16
|
+
created_at: datetime | None = None
|
|
17
|
+
updated_at: datetime | None = None
|
scruby/utils.py
CHANGED
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__all__ = (
|
|
6
|
-
"get_from_env",
|
|
7
|
-
"add_to_env",
|
|
8
|
-
)
|
|
5
|
+
__all__ = ("Utils",)
|
|
9
6
|
|
|
10
7
|
|
|
11
8
|
from pathlib import Path
|
|
@@ -13,59 +10,68 @@ from pathlib import Path
|
|
|
13
10
|
from dotenv import dotenv_values
|
|
14
11
|
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def add_to_env(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
) -> str | None:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
content = f"\n{key}={value}"
|
|
62
|
-
env_file.write(content)
|
|
13
|
+
class Utils:
|
|
14
|
+
"""Set of helper methods."""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def get_from_env(
|
|
18
|
+
key: str,
|
|
19
|
+
dotenv_path: Path | str = ".env",
|
|
20
|
+
) -> str | None:
|
|
21
|
+
"""Get value by key from .env file."""
|
|
22
|
+
assert len(key) > 0, "`get_from_env` => `key` must not be the empty string."
|
|
23
|
+
|
|
24
|
+
value: str | None = None
|
|
25
|
+
|
|
26
|
+
if isinstance(dotenv_path, str):
|
|
27
|
+
dotenv_path = Path(dotenv_path)
|
|
28
|
+
|
|
29
|
+
if dotenv_path.exists():
|
|
30
|
+
env_dict: dict[str, str | None] = dotenv_values(dotenv_path)
|
|
31
|
+
value = env_dict.get(key)
|
|
32
|
+
|
|
33
|
+
return value
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def add_to_env(
|
|
37
|
+
key: str,
|
|
38
|
+
value: str,
|
|
39
|
+
dotenv_path: Path | str = ".env",
|
|
40
|
+
) -> str | None:
|
|
41
|
+
"""Add key-value to .env file."""
|
|
42
|
+
assert len(key) > 0, "`add_to_env` => `key` must not be the empty string."
|
|
43
|
+
assert len(value) > 0, "`add_to_env` => `value` must not be the empty string."
|
|
44
|
+
|
|
45
|
+
if isinstance(dotenv_path, str):
|
|
46
|
+
assert len(dotenv_path) > 0, "`add_to_env` => `dotenv_path` must not be the empty string."
|
|
47
|
+
dotenv_path = Path(dotenv_path)
|
|
48
|
+
|
|
49
|
+
if dotenv_path.exists():
|
|
50
|
+
env_dict: dict[str, str | None] = dotenv_values(dotenv_path)
|
|
51
|
+
saved_value = env_dict.get(key)
|
|
52
|
+
if saved_value is None:
|
|
53
|
+
with dotenv_path.open("a+", encoding="utf-8") as env_file:
|
|
54
|
+
content = f"\n{key}={value}"
|
|
55
|
+
env_file.write(content)
|
|
56
|
+
else:
|
|
57
|
+
raise KeyError(f"`add_to_env` => Key `{key}` already exists.")
|
|
63
58
|
else:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
target_dir: str = "/".join(str(dotenv_path).split("/")[:-1])
|
|
60
|
+
Path(target_dir).mkdir(parents=True, exist_ok=True)
|
|
61
|
+
content = f"{key}={value}"
|
|
62
|
+
dotenv_path.write_text(data=content, encoding="utf-8")
|
|
63
|
+
|
|
64
|
+
return value
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def db_collection_list(db_root: Path | str) -> list[str] | None:
|
|
68
|
+
"""Get a list of collections from a database directory."""
|
|
69
|
+
if isinstance(db_root, str):
|
|
70
|
+
assert len(db_root) > 0, "`add_to_env` => `dotenv_path` must not be the empty string."
|
|
71
|
+
db_root = Path(db_root)
|
|
72
|
+
|
|
73
|
+
directory_names: list[str] | None = None
|
|
74
|
+
if db_root.exists():
|
|
75
|
+
all_entries = Path.iterdir(db_root)
|
|
76
|
+
directory_names = [entry.name for entry in all_entries if entry.name != ".env.meta"] or None
|
|
77
|
+
return directory_names
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scruby
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Asynchronous library for building and managing a hybrid database, by scheme of key-value.
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/kebasyaty/scruby/issues
|
|
6
6
|
Project-URL: Changelog, https://github.com/kebasyaty/scruby/blob/v2/CHANGELOG.md
|
|
@@ -165,7 +165,7 @@ async def main() -> None:
|
|
|
165
165
|
Scruby.run()
|
|
166
166
|
|
|
167
167
|
# Get/Create a User collection
|
|
168
|
-
user_coll =
|
|
168
|
+
user_coll = Scruby(User)
|
|
169
169
|
|
|
170
170
|
# Create user
|
|
171
171
|
user = User(
|
|
@@ -196,7 +196,7 @@ async def main() -> None:
|
|
|
196
196
|
user_coll.collection_name() # => User
|
|
197
197
|
|
|
198
198
|
# Get collection list
|
|
199
|
-
coll_list =
|
|
199
|
+
coll_list = Scruby.collection_list() # => ["User"]
|
|
200
200
|
|
|
201
201
|
# Get the number of documents in the collection from metadata
|
|
202
202
|
await user_coll.estimated_document_count() # => 1
|
|
@@ -204,8 +204,8 @@ async def main() -> None:
|
|
|
204
204
|
# Get the number of documents comparable to the filter
|
|
205
205
|
user_coll.count_documents(filter_fn=lambda doc: doc.first_name == "John") == 1
|
|
206
206
|
|
|
207
|
-
#
|
|
208
|
-
|
|
207
|
+
# Clear collection
|
|
208
|
+
Scruby.clear_collection("User")
|
|
209
209
|
|
|
210
210
|
# Full database deletion
|
|
211
211
|
# Hint: The main purpose is tests
|
|
@@ -248,7 +248,7 @@ async def main() -> None:
|
|
|
248
248
|
Scruby.run()
|
|
249
249
|
|
|
250
250
|
# Get/Create a Phone collection
|
|
251
|
-
phone_coll =
|
|
251
|
+
phone_coll = Scruby(Phone)
|
|
252
252
|
|
|
253
253
|
# Create phone
|
|
254
254
|
phone = Phone(
|
|
@@ -325,7 +325,7 @@ async def main() -> None:
|
|
|
325
325
|
Scruby.run()
|
|
326
326
|
|
|
327
327
|
# Get/Create a Car collection
|
|
328
|
-
car_coll =
|
|
328
|
+
car_coll = Scruby(Car)
|
|
329
329
|
|
|
330
330
|
# Create cars
|
|
331
331
|
for num in range(1, 10):
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
scruby/__init__.py,sha256=YIR8zFx0GmS8ha96-MU9yCgKeYNIh1K8idlQckfg21E,1324
|
|
2
|
+
scruby/aggregation.py,sha256=NBFxQqyRqUG2KIuD9fbl4uzSHJWTaskjiZ1YNBa-Zbo,3575
|
|
3
|
+
scruby/cache.py,sha256=j_uRBF72p7kzUK6GMHIIqb8nM2RneCod45dbbI1KTjQ,4018
|
|
4
|
+
scruby/config.py,sha256=oyBEOzmKy9vka23ZDvP8Jqu-HMYn8HCmJ0j2TfOs-0w,5127
|
|
5
|
+
scruby/db.py,sha256=oulRx20bezbv2IwcsDq6TgsXo_zQz8ZE6ohmWg4kv2k,7107
|
|
6
|
+
scruby/errors.py,sha256=lTWiHzyO5Es9Nkf7quODJjONGn6ifcL95qlpA4epQQM,1386
|
|
7
|
+
scruby/meta.py,sha256=dfr8q3TWhVcEl-lZuH5yd9rbO9vAPBhXBO3VYCNcOWo,1283
|
|
8
|
+
scruby/model.py,sha256=1infUd1G-zxj5Z93mNOp1Wi2anD6TKDgu5Y0KEVcH2k,292
|
|
9
|
+
scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
scruby/utils.py,sha256=fCBBrp-tgRKh4bukjWRUQhkOsNCxGN4lbXcymBqCsbE,2587
|
|
11
|
+
scruby/mixins/__init__.py,sha256=nT79e80zXliuTGhR9CFosI2-3PQUCKwXbR7wPiFwgrU,669
|
|
12
|
+
scruby/mixins/collection.py,sha256=3Gn9zIT-WnSCBd-IgpE9tobRlW675YX__qaWoUOWIJM,1758
|
|
13
|
+
scruby/mixins/count.py,sha256=CGpyOpsG25uC5utI2ByNxq2iTbJocj6w5tXrC1_cP40,2561
|
|
14
|
+
scruby/mixins/custom_task.py,sha256=DIry4gBlTdaqZqymar-RrHSdhEiC2tn2pl9jmbk56zw,1547
|
|
15
|
+
scruby/mixins/delete.py,sha256=InKVIud_ZYx9-CchUz_IygQDXMynNi4jQ0HKYeHC_R8,4328
|
|
16
|
+
scruby/mixins/find.py,sha256=oEZRE6RqIBdwvNr8iSYyod8hI6ibi_egJP0pyXmJiss,9169
|
|
17
|
+
scruby/mixins/keys.py,sha256=MvBy_8fQGROaQATK2qse2V8wR-xodPQG0KKZ2PK_t9U,10790
|
|
18
|
+
scruby/mixins/update.py,sha256=TyxvxB-gNijlfzTmhwrq0ydvu0C3R-RihW5h5tJ96bM,4964
|
|
19
|
+
scruby-2.3.1.dist-info/METADATA,sha256=8QxVl8_iyWBt6qwy-tx9yZRaKKtSb08qDaVy496jeLw,13382
|
|
20
|
+
scruby-2.3.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
21
|
+
scruby-2.3.1.dist-info/licenses/GPL-3.0-LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
22
|
+
scruby-2.3.1.dist-info/licenses/MIT-LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
|
|
23
|
+
scruby-2.3.1.dist-info/RECORD,,
|
scruby-2.2.3.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
scruby/__init__.py,sha256=DEtbZThadMKpKcGGjWyAFGu4IADg9ZYTb8WjfO_3Dqg,1300
|
|
2
|
-
scruby/aggregation.py,sha256=NBFxQqyRqUG2KIuD9fbl4uzSHJWTaskjiZ1YNBa-Zbo,3575
|
|
3
|
-
scruby/cache.py,sha256=krqvIhtbvhCFDjqOg1eg2uZFa4eGDRYI9ELTe6eJbpc,3961
|
|
4
|
-
scruby/config.py,sha256=INAFqNAeF8BifIywjElC97rawfTLuqpRfieUdAO7A6k,5122
|
|
5
|
-
scruby/db.py,sha256=OtAwHtXoHaF_obqMwxn157EtVppO0tO6Dx2lWGFBEiA,9999
|
|
6
|
-
scruby/errors.py,sha256=lTWiHzyO5Es9Nkf7quODJjONGn6ifcL95qlpA4epQQM,1386
|
|
7
|
-
scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
scruby/utils.py,sha256=ZwWxSyh_BAOQkXlIqXFOlX2lHVk9rfYGQiVqtTk8PpE,1865
|
|
9
|
-
scruby/mixins/__init__.py,sha256=nT79e80zXliuTGhR9CFosI2-3PQUCKwXbR7wPiFwgrU,669
|
|
10
|
-
scruby/mixins/collection.py,sha256=nTw0jnybox3iso0jzNoJiu61Hb-92zl84hdWhlWbik4,1792
|
|
11
|
-
scruby/mixins/count.py,sha256=CGpyOpsG25uC5utI2ByNxq2iTbJocj6w5tXrC1_cP40,2561
|
|
12
|
-
scruby/mixins/custom_task.py,sha256=DIry4gBlTdaqZqymar-RrHSdhEiC2tn2pl9jmbk56zw,1547
|
|
13
|
-
scruby/mixins/delete.py,sha256=InKVIud_ZYx9-CchUz_IygQDXMynNi4jQ0HKYeHC_R8,4328
|
|
14
|
-
scruby/mixins/find.py,sha256=oEZRE6RqIBdwvNr8iSYyod8hI6ibi_egJP0pyXmJiss,9169
|
|
15
|
-
scruby/mixins/keys.py,sha256=MvBy_8fQGROaQATK2qse2V8wR-xodPQG0KKZ2PK_t9U,10790
|
|
16
|
-
scruby/mixins/update.py,sha256=TyxvxB-gNijlfzTmhwrq0ydvu0C3R-RihW5h5tJ96bM,4964
|
|
17
|
-
scruby-2.2.3.dist-info/METADATA,sha256=5HaLAyfWIbGGgRBZv79_f4Q0TKTe_RsIY_2uAswYEn8,13447
|
|
18
|
-
scruby-2.2.3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
19
|
-
scruby-2.2.3.dist-info/licenses/GPL-3.0-LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
20
|
-
scruby-2.2.3.dist-info/licenses/MIT-LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
|
|
21
|
-
scruby-2.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|