scruby 0.28.3__py3-none-any.whl → 0.30.2__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 +2 -1
- scruby/db.py +55 -19
- scruby/mixins/count.py +5 -4
- scruby/mixins/custom_task.py +7 -7
- scruby/mixins/delete.py +5 -4
- scruby/mixins/find.py +8 -6
- scruby/mixins/keys.py +27 -16
- scruby/mixins/update.py +10 -7
- {scruby-0.28.3.dist-info → scruby-0.30.2.dist-info}/METADATA +33 -23
- scruby-0.30.2.dist-info/RECORD +18 -0
- scruby-0.28.3.dist-info/RECORD +0 -18
- {scruby-0.28.3.dist-info → scruby-0.30.2.dist-info}/WHEEL +0 -0
- {scruby-0.28.3.dist-info → scruby-0.30.2.dist-info}/licenses/LICENSE +0 -0
scruby/__init__.py
CHANGED
scruby/db.py
CHANGED
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
__all__ = (
|
|
9
|
+
__all__ = (
|
|
10
|
+
"Scruby",
|
|
11
|
+
"ScrubyModel",
|
|
12
|
+
)
|
|
10
13
|
|
|
11
14
|
import contextlib
|
|
12
15
|
import logging
|
|
13
16
|
import re
|
|
14
17
|
import zlib
|
|
18
|
+
from datetime import datetime
|
|
15
19
|
from shutil import rmtree
|
|
16
20
|
from typing import Any, Literal, Never, assert_never
|
|
17
21
|
|
|
@@ -31,6 +35,13 @@ class _Meta(BaseModel):
|
|
|
31
35
|
counter_documents: int
|
|
32
36
|
|
|
33
37
|
|
|
38
|
+
class ScrubyModel(BaseModel):
|
|
39
|
+
"""Additional fields for models."""
|
|
40
|
+
|
|
41
|
+
created_at: datetime | None = None
|
|
42
|
+
updated_at: datetime | None = None
|
|
43
|
+
|
|
44
|
+
|
|
34
45
|
class Scruby(
|
|
35
46
|
mixins.Keys,
|
|
36
47
|
mixins.Find,
|
|
@@ -53,13 +64,13 @@ class Scruby(
|
|
|
53
64
|
# The maximum number of branches.
|
|
54
65
|
match self._hash_reduce_left:
|
|
55
66
|
case 0:
|
|
56
|
-
self.
|
|
67
|
+
self._max_number_branch = 4294967296
|
|
57
68
|
case 2:
|
|
58
|
-
self.
|
|
69
|
+
self._max_number_branch = 16777216
|
|
59
70
|
case 4:
|
|
60
|
-
self.
|
|
71
|
+
self._max_number_branch = 65536
|
|
61
72
|
case 6:
|
|
62
|
-
self.
|
|
73
|
+
self._max_number_branch = 256
|
|
63
74
|
case _ as unreachable:
|
|
64
75
|
msg: str = f"{unreachable} - Unacceptable value for HASH_REDUCE_LEFT."
|
|
65
76
|
logging.critical(msg)
|
|
@@ -70,42 +81,67 @@ class Scruby(
|
|
|
70
81
|
"""Get an object to access a collection.
|
|
71
82
|
|
|
72
83
|
Args:
|
|
73
|
-
class_model: Class of Model (
|
|
84
|
+
class_model (Any): Class of Model (ScrubyModel).
|
|
74
85
|
|
|
75
86
|
Returns:
|
|
76
87
|
Instance of Scruby for access a collection.
|
|
77
88
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
if __debug__:
|
|
90
|
+
# Check if the object belongs to the class `ScrubyModel`
|
|
91
|
+
if ScrubyModel not in class_model.__bases__:
|
|
92
|
+
msg = (
|
|
93
|
+
"Method: `collection` => argument `class_model` " + "does not contain the base class `ScrubyModel`!"
|
|
94
|
+
)
|
|
95
|
+
raise AssertionError(msg)
|
|
96
|
+
# Checking the model for the presence of a key.
|
|
97
|
+
model_fields = list(class_model.model_fields.keys())
|
|
98
|
+
if "key" not in model_fields:
|
|
99
|
+
msg = f"Model: {class_model.__name__} => The `key` field is missing!"
|
|
100
|
+
raise AssertionError(msg)
|
|
101
|
+
if "created_at" not in model_fields:
|
|
102
|
+
msg = f"Model: {class_model.__name__} => The `created_at` field is missing!"
|
|
103
|
+
raise AssertionError(msg)
|
|
104
|
+
if "updated_at" not in model_fields:
|
|
105
|
+
msg = f"Model: {class_model.__name__} => The `updated_at` field is missing!"
|
|
106
|
+
raise AssertionError(msg)
|
|
107
|
+
# Check the length of the collection name for an acceptable size.
|
|
108
|
+
len_db_root_absolut_path = len(str(await Path(settings.DB_ROOT).resolve()).encode("utf-8"))
|
|
109
|
+
len_model_name = len(class_model.__name__)
|
|
110
|
+
len_full_path_leaf = len_db_root_absolut_path + len_model_name + 26
|
|
111
|
+
if len_full_path_leaf > 255:
|
|
112
|
+
excess = len_full_path_leaf - 255
|
|
113
|
+
msg = (
|
|
114
|
+
f"Model: {class_model.__name__} => The collection name is too long, "
|
|
115
|
+
+ f"it exceeds the limit of {excess} characters!"
|
|
116
|
+
)
|
|
117
|
+
raise AssertionError(msg)
|
|
118
|
+
# Create instance of Scruby
|
|
80
119
|
instance = cls()
|
|
120
|
+
# Add model class to Scruby
|
|
81
121
|
instance.__dict__["_class_model"] = class_model
|
|
82
|
-
#
|
|
83
|
-
# The zero branch is reserved for metadata.
|
|
84
|
-
branch_number: int = 0
|
|
85
|
-
branch_number_as_hash: str = f"{branch_number:08x}"[settings.HASH_REDUCE_LEFT :]
|
|
86
|
-
separated_hash: str = "/".join(list(branch_number_as_hash))
|
|
122
|
+
# Create a path for metadata.
|
|
87
123
|
meta_dir_path_tuple = (
|
|
88
124
|
settings.DB_ROOT,
|
|
89
125
|
class_model.__name__,
|
|
90
|
-
|
|
126
|
+
"meta",
|
|
91
127
|
)
|
|
92
128
|
instance.__dict__["_meta_path"] = Path(
|
|
93
129
|
*meta_dir_path_tuple,
|
|
94
130
|
"meta.json",
|
|
95
131
|
)
|
|
96
132
|
# Create metadata for collection, if missing.
|
|
97
|
-
|
|
98
|
-
if not await
|
|
99
|
-
await
|
|
133
|
+
meta_dir_path = Path(*meta_dir_path_tuple)
|
|
134
|
+
if not await meta_dir_path.exists():
|
|
135
|
+
await meta_dir_path.mkdir(parents=True)
|
|
100
136
|
meta = _Meta(
|
|
101
137
|
db_root=settings.DB_ROOT,
|
|
102
138
|
collection_name=class_model.__name__,
|
|
103
139
|
hash_reduce_left=settings.HASH_REDUCE_LEFT,
|
|
104
|
-
max_branch_number=instance.__dict__["
|
|
140
|
+
max_branch_number=instance.__dict__["_max_number_branch"],
|
|
105
141
|
counter_documents=0,
|
|
106
142
|
)
|
|
107
143
|
meta_json = meta.model_dump_json()
|
|
108
|
-
meta_path = Path(*(
|
|
144
|
+
meta_path = Path(*(meta_dir_path, "meta.json"))
|
|
109
145
|
await meta_path.write_text(meta_json, "utf-8")
|
|
110
146
|
return instance
|
|
111
147
|
|
scruby/mixins/count.py
CHANGED
|
@@ -31,18 +31,19 @@ class Count:
|
|
|
31
31
|
) -> int:
|
|
32
32
|
"""Count the number of documents a matching the filter in this collection.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
Attention:
|
|
35
|
+
- The search is based on the effect of a quantum loop.
|
|
36
|
+
- The search effectiveness depends on the number of processor threads.
|
|
36
37
|
|
|
37
38
|
Args:
|
|
38
|
-
filter_fn: A function that execute the conditions of filtering.
|
|
39
|
+
filter_fn (Callable): A function that execute the conditions of filtering.
|
|
39
40
|
|
|
40
41
|
Returns:
|
|
41
42
|
The number of documents.
|
|
42
43
|
"""
|
|
43
44
|
# Variable initialization
|
|
44
45
|
search_task_fn: Callable = self._task_find
|
|
45
|
-
branch_numbers: range = range(
|
|
46
|
+
branch_numbers: range = range(self._max_number_branch)
|
|
46
47
|
hash_reduce_left: int = self._hash_reduce_left
|
|
47
48
|
db_root: str = self._db_root
|
|
48
49
|
class_model: Any = self._class_model
|
scruby/mixins/custom_task.py
CHANGED
|
@@ -50,25 +50,25 @@ class CustomTask:
|
|
|
50
50
|
docs.append(class_model.model_validate_json(val))
|
|
51
51
|
return docs
|
|
52
52
|
|
|
53
|
-
async def run_custom_task(self, custom_task_fn: Callable
|
|
53
|
+
async def run_custom_task(self, custom_task_fn: Callable) -> Any:
|
|
54
54
|
"""Running custom task.
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
Attention:
|
|
57
|
+
- The search is based on the effect of a quantum loop.
|
|
58
|
+
- The search effectiveness depends on the number of processor threads.
|
|
58
59
|
|
|
59
60
|
Args:
|
|
60
|
-
custom_task_fn: A function that execute the custom task.
|
|
61
|
-
limit_docs: Limiting the number of documents. By default = 1000.
|
|
61
|
+
custom_task_fn (Callable): A function that execute the custom task.
|
|
62
62
|
|
|
63
63
|
Returns:
|
|
64
64
|
The result of a custom task.
|
|
65
65
|
"""
|
|
66
66
|
kwargs = {
|
|
67
67
|
"get_docs_fn": self._task_get_docs,
|
|
68
|
-
"branch_numbers": range(
|
|
68
|
+
"branch_numbers": range(self._max_number_branch),
|
|
69
69
|
"hash_reduce_left": self._hash_reduce_left,
|
|
70
70
|
"db_root": self._db_root,
|
|
71
71
|
"class_model": self._class_model,
|
|
72
|
-
"
|
|
72
|
+
"max_workers": self._max_workers,
|
|
73
73
|
}
|
|
74
74
|
return await custom_task_fn(**kwargs)
|
scruby/mixins/delete.py
CHANGED
|
@@ -64,18 +64,19 @@ class Delete:
|
|
|
64
64
|
) -> int:
|
|
65
65
|
"""Delete one or more documents matching the filter.
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
Attention:
|
|
68
|
+
- The search is based on the effect of a quantum loop.
|
|
69
|
+
- The search effectiveness depends on the number of processor threads.
|
|
69
70
|
|
|
70
71
|
Args:
|
|
71
|
-
filter_fn: A function that execute the conditions of filtering.
|
|
72
|
+
filter_fn (Callable): A function that execute the conditions of filtering.
|
|
72
73
|
|
|
73
74
|
Returns:
|
|
74
75
|
The number of deleted documents.
|
|
75
76
|
"""
|
|
76
77
|
# Variable initialization
|
|
77
78
|
search_task_fn: Callable = self._task_delete
|
|
78
|
-
branch_numbers: range = range(
|
|
79
|
+
branch_numbers: range = range(self._max_number_branch)
|
|
79
80
|
hash_reduce_left: int = self._hash_reduce_left
|
|
80
81
|
db_root: str = self._db_root
|
|
81
82
|
class_model: Any = self._class_model
|
scruby/mixins/find.py
CHANGED
|
@@ -61,8 +61,9 @@ class Find:
|
|
|
61
61
|
) -> Any | None:
|
|
62
62
|
"""Find one document matching the filter.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
Attention:
|
|
65
|
+
- The search is based on the effect of a quantum loop.
|
|
66
|
+
- The search effectiveness depends on the number of processor threads.
|
|
66
67
|
|
|
67
68
|
Args:
|
|
68
69
|
filter_fn (Callable): A function that execute the conditions of filtering.
|
|
@@ -72,7 +73,7 @@ class Find:
|
|
|
72
73
|
"""
|
|
73
74
|
# Variable initialization
|
|
74
75
|
search_task_fn: Callable = self._task_find
|
|
75
|
-
branch_numbers: range = range(
|
|
76
|
+
branch_numbers: range = range(self._max_number_branch)
|
|
76
77
|
hash_reduce_left: int = self._hash_reduce_left
|
|
77
78
|
db_root: str = self._db_root
|
|
78
79
|
class_model: Any = self._class_model
|
|
@@ -100,8 +101,9 @@ class Find:
|
|
|
100
101
|
) -> list[Any] | None:
|
|
101
102
|
"""Find many documents matching the filter.
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
Attention:
|
|
105
|
+
- The search is based on the effect of a quantum loop.
|
|
106
|
+
- The search effectiveness depends on the number of processor threads.
|
|
105
107
|
|
|
106
108
|
Args:
|
|
107
109
|
filter_fn (Callable): A function that execute the conditions of filtering.
|
|
@@ -117,7 +119,7 @@ class Find:
|
|
|
117
119
|
assert page_number > 0, "`find_many` => The `page_number` parameter must not be less than one."
|
|
118
120
|
# Variable initialization
|
|
119
121
|
search_task_fn: Callable = self._task_find
|
|
120
|
-
branch_numbers: range = range(
|
|
122
|
+
branch_numbers: range = range(self._max_number_branch)
|
|
121
123
|
hash_reduce_left: int = self._hash_reduce_left
|
|
122
124
|
db_root: str = self._db_root
|
|
123
125
|
class_model: Any = self._class_model
|
scruby/mixins/keys.py
CHANGED
|
@@ -9,7 +9,9 @@ from __future__ import annotations
|
|
|
9
9
|
__all__ = ("Keys",)
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
|
+
from datetime import datetime
|
|
12
13
|
from typing import Any
|
|
14
|
+
from zoneinfo import ZoneInfo
|
|
13
15
|
|
|
14
16
|
import orjson
|
|
15
17
|
|
|
@@ -26,7 +28,7 @@ class Keys:
|
|
|
26
28
|
"""Asynchronous method for adding document to collection.
|
|
27
29
|
|
|
28
30
|
Args:
|
|
29
|
-
doc: Value of key. Type, derived from `
|
|
31
|
+
doc (Any): Value of key. Type, derived from `ScrubyModel`.
|
|
30
32
|
|
|
31
33
|
Returns:
|
|
32
34
|
None.
|
|
@@ -42,6 +44,11 @@ class Keys:
|
|
|
42
44
|
raise TypeError(msg)
|
|
43
45
|
# The path to cell of collection.
|
|
44
46
|
leaf_path, prepared_key = await self._get_leaf_path(doc.key)
|
|
47
|
+
# Init a `created_at` and `updated_at` fields
|
|
48
|
+
tz = ZoneInfo("UTC")
|
|
49
|
+
doc.created_at = datetime.now(tz)
|
|
50
|
+
doc.updated_at = datetime.now(tz)
|
|
51
|
+
# Convert doc to json
|
|
45
52
|
doc_json: str = doc.model_dump_json()
|
|
46
53
|
# Write key-value to collection.
|
|
47
54
|
if await leaf_path.exists():
|
|
@@ -63,10 +70,10 @@ class Keys:
|
|
|
63
70
|
await self._counter_documents(1)
|
|
64
71
|
|
|
65
72
|
async def update_doc(self, doc: Any) -> None:
|
|
66
|
-
"""Asynchronous method for updating
|
|
73
|
+
"""Asynchronous method for updating document to collection.
|
|
67
74
|
|
|
68
75
|
Args:
|
|
69
|
-
doc: Value of key. Type `
|
|
76
|
+
doc (Any): Value of key. Type `ScrubyModel`.
|
|
70
77
|
|
|
71
78
|
Returns:
|
|
72
79
|
None.
|
|
@@ -83,6 +90,9 @@ class Keys:
|
|
|
83
90
|
raise TypeError(msg)
|
|
84
91
|
# The path to cell of collection.
|
|
85
92
|
leaf_path, prepared_key = await self._get_leaf_path(doc.key)
|
|
93
|
+
# Update a `updated_at` field
|
|
94
|
+
doc.updated_at = datetime.now(ZoneInfo("UTC"))
|
|
95
|
+
# Convert doc to json
|
|
86
96
|
doc_json: str = doc.model_dump_json()
|
|
87
97
|
# Update the existing key.
|
|
88
98
|
if await leaf_path.exists():
|
|
@@ -98,14 +108,15 @@ class Keys:
|
|
|
98
108
|
logging.error(err.message)
|
|
99
109
|
raise err from None
|
|
100
110
|
else:
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
msg: str = f"`update_doc` - The key `{doc.key}` is missing!"
|
|
112
|
+
logging.error(msg)
|
|
113
|
+
raise KeyError(msg)
|
|
103
114
|
|
|
104
|
-
async def
|
|
105
|
-
"""Asynchronous method for getting
|
|
115
|
+
async def get_doc(self, key: str) -> Any:
|
|
116
|
+
"""Asynchronous method for getting document from collection the by key.
|
|
106
117
|
|
|
107
118
|
Args:
|
|
108
|
-
key: Key name.
|
|
119
|
+
key (str): Key name.
|
|
109
120
|
|
|
110
121
|
Returns:
|
|
111
122
|
Value of key or KeyError.
|
|
@@ -118,15 +129,15 @@ class Keys:
|
|
|
118
129
|
data: dict = orjson.loads(data_json) or {}
|
|
119
130
|
obj: Any = self._class_model.model_validate_json(data[prepared_key])
|
|
120
131
|
return obj
|
|
121
|
-
msg: str = "`
|
|
132
|
+
msg: str = f"`get_doc` - The key `{key}` is missing!"
|
|
122
133
|
logging.error(msg)
|
|
123
|
-
raise KeyError()
|
|
134
|
+
raise KeyError(msg)
|
|
124
135
|
|
|
125
136
|
async def has_key(self, key: str) -> bool:
|
|
126
137
|
"""Asynchronous method for checking presence of key in collection.
|
|
127
138
|
|
|
128
139
|
Args:
|
|
129
|
-
key: Key name.
|
|
140
|
+
key (str): Key name.
|
|
130
141
|
|
|
131
142
|
Returns:
|
|
132
143
|
True, if the key is present.
|
|
@@ -144,11 +155,11 @@ class Keys:
|
|
|
144
155
|
return False
|
|
145
156
|
return False
|
|
146
157
|
|
|
147
|
-
async def
|
|
148
|
-
"""Asynchronous method for deleting
|
|
158
|
+
async def delete_doc(self, key: str) -> None:
|
|
159
|
+
"""Asynchronous method for deleting document from collection the by key.
|
|
149
160
|
|
|
150
161
|
Args:
|
|
151
|
-
key: Key name.
|
|
162
|
+
key (str): Key name.
|
|
152
163
|
|
|
153
164
|
Returns:
|
|
154
165
|
None.
|
|
@@ -163,6 +174,6 @@ class Keys:
|
|
|
163
174
|
await leaf_path.write_bytes(orjson.dumps(data))
|
|
164
175
|
await self._counter_documents(-1)
|
|
165
176
|
return
|
|
166
|
-
msg: str = "`
|
|
177
|
+
msg: str = f"`delete_doc` - The key `{key}` is missing!"
|
|
167
178
|
logging.error(msg)
|
|
168
|
-
raise KeyError()
|
|
179
|
+
raise KeyError(msg)
|
scruby/mixins/update.py
CHANGED
|
@@ -62,24 +62,27 @@ class Update:
|
|
|
62
62
|
|
|
63
63
|
async def update_many(
|
|
64
64
|
self,
|
|
65
|
-
filter_fn: Callable,
|
|
66
65
|
new_data: dict[str, Any],
|
|
66
|
+
filter_fn: Callable = lambda _: True,
|
|
67
67
|
) -> int:
|
|
68
|
-
"""Updates
|
|
68
|
+
"""Updates many documents matching the filter.
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
Attention:
|
|
71
|
+
- For a complex case, a custom task may be needed.
|
|
72
|
+
- See documentation on creating custom tasks.
|
|
73
|
+
- The search is based on the effect of a quantum loop.
|
|
74
|
+
- The search effectiveness depends on the number of processor threads.
|
|
72
75
|
|
|
73
76
|
Args:
|
|
74
|
-
filter_fn: A function that execute the conditions of filtering.
|
|
75
|
-
new_data: New data for the fields that need to be updated.
|
|
77
|
+
filter_fn (Callable): A function that execute the conditions of filtering.
|
|
78
|
+
new_data (dict[str, Any]): New data for the fields that need to be updated.
|
|
76
79
|
|
|
77
80
|
Returns:
|
|
78
81
|
The number of updated documents.
|
|
79
82
|
"""
|
|
80
83
|
# Variable initialization
|
|
81
84
|
update_task_fn: Callable = self._task_update
|
|
82
|
-
branch_numbers: range = range(
|
|
85
|
+
branch_numbers: range = range(self._max_number_branch)
|
|
83
86
|
hash_reduce_left: int = self._hash_reduce_left
|
|
84
87
|
db_root: str = self._db_root
|
|
85
88
|
class_model: Any = self._class_model
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scruby
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.30.2
|
|
4
4
|
Summary: Asynchronous library for building and managing a hybrid database, by scheme of key-value.
|
|
5
5
|
Project-URL: Homepage, https://kebasyaty.github.io/scruby/
|
|
6
6
|
Project-URL: Repository, https://github.com/kebasyaty/scruby
|
|
@@ -99,6 +99,15 @@ Online browsable documentation is available at [https://kebasyaty.github.io/scru
|
|
|
99
99
|
uv add scruby
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
## Run
|
|
103
|
+
|
|
104
|
+
```shell
|
|
105
|
+
# Run Development:
|
|
106
|
+
uv run python main.py
|
|
107
|
+
# Run Production:
|
|
108
|
+
uv run python -OOP main.py
|
|
109
|
+
```
|
|
110
|
+
|
|
102
111
|
## Usage
|
|
103
112
|
|
|
104
113
|
See more examples here [https://kebasyaty.github.io/scruby/latest/pages/usage/](https://kebasyaty.github.io/scruby/latest/pages/usage/ "Examples").
|
|
@@ -107,24 +116,25 @@ See more examples here [https://kebasyaty.github.io/scruby/latest/pages/usage/](
|
|
|
107
116
|
"""Working with keys."""
|
|
108
117
|
|
|
109
118
|
import anyio
|
|
110
|
-
import datetime
|
|
119
|
+
from datetime import datetime
|
|
120
|
+
from zoneinfo import ZoneInfo
|
|
111
121
|
from typing import Annotated
|
|
112
|
-
from pydantic import
|
|
122
|
+
from pydantic import EmailStr, Field
|
|
113
123
|
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
|
|
114
|
-
from scruby import Scruby, settings
|
|
124
|
+
from scruby import Scruby, ScrubyModel, settings
|
|
115
125
|
|
|
116
126
|
settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
|
|
117
127
|
settings.HASH_REDUCE_LEFT = 6 # By default = 6
|
|
118
128
|
settings.MAX_WORKERS = None # By default = None
|
|
119
129
|
|
|
120
|
-
class User(
|
|
130
|
+
class User(ScrubyModel):
|
|
121
131
|
"""User model."""
|
|
122
132
|
first_name: str = Field(strict=True)
|
|
123
133
|
last_name: str = Field(strict=True)
|
|
124
|
-
birthday: datetime
|
|
134
|
+
birthday: datetime = Field(strict=True)
|
|
125
135
|
email: EmailStr = Field(strict=True)
|
|
126
136
|
phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")] = Field(frozen=True)
|
|
127
|
-
#
|
|
137
|
+
# key is always at bottom
|
|
128
138
|
key: str = Field(
|
|
129
139
|
strict=True,
|
|
130
140
|
frozen=True,
|
|
@@ -140,7 +150,7 @@ async def main() -> None:
|
|
|
140
150
|
user = User(
|
|
141
151
|
first_name="John",
|
|
142
152
|
last_name="Smith",
|
|
143
|
-
birthday=datetime.datetime(1970, 1, 1),
|
|
153
|
+
birthday=datetime.datetime(1970, 1, 1, tzinfo=ZoneInfo("UTC")),
|
|
144
154
|
email="John_Smith@gmail.com",
|
|
145
155
|
phone="+447986123456",
|
|
146
156
|
)
|
|
@@ -149,15 +159,15 @@ async def main() -> None:
|
|
|
149
159
|
|
|
150
160
|
await user_coll.update_doc(user)
|
|
151
161
|
|
|
152
|
-
await user_coll.
|
|
153
|
-
await user_coll.
|
|
162
|
+
await user_coll.get_doc("+447986123456") # => user
|
|
163
|
+
await user_coll.get_doc("key missing") # => KeyError
|
|
154
164
|
|
|
155
165
|
await user_coll.has_key("+447986123456") # => True
|
|
156
166
|
await user_coll.has_key("key missing") # => False
|
|
157
167
|
|
|
158
|
-
await user_coll.
|
|
159
|
-
await user_coll.
|
|
160
|
-
await user_coll.
|
|
168
|
+
await user_coll.delete_doc("+447986123456")
|
|
169
|
+
await user_coll.delete_doc("+447986123456") # => KeyError
|
|
170
|
+
await user_coll.delete_doc("key missing") # => KeyError
|
|
161
171
|
|
|
162
172
|
# Full database deletion.
|
|
163
173
|
# Hint: The main purpose is tests.
|
|
@@ -176,10 +186,10 @@ The search effectiveness depends on the number of processor threads.
|
|
|
176
186
|
"""
|
|
177
187
|
|
|
178
188
|
import anyio
|
|
179
|
-
import datetime
|
|
189
|
+
from datetime import datetime
|
|
180
190
|
from typing import Annotated
|
|
181
|
-
from pydantic import
|
|
182
|
-
from scruby import Scruby, settings
|
|
191
|
+
from pydantic import Field
|
|
192
|
+
from scruby import Scruby, ScrubyModel, settings
|
|
183
193
|
from pprint import pprint as pp
|
|
184
194
|
|
|
185
195
|
settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
|
|
@@ -187,13 +197,13 @@ settings.HASH_REDUCE_LEFT = 6 # By default = 6
|
|
|
187
197
|
settings.MAX_WORKERS = None # By default = None
|
|
188
198
|
|
|
189
199
|
|
|
190
|
-
class Phone(
|
|
200
|
+
class Phone(ScrubyModel):
|
|
191
201
|
"""Phone model."""
|
|
192
202
|
brand: str = Field(strict=True, frozen=True)
|
|
193
203
|
model: str = Field(strict=True, frozen=True)
|
|
194
204
|
screen_diagonal: float = Field(strict=True)
|
|
195
205
|
matrix_type: str = Field(strict=True)
|
|
196
|
-
#
|
|
206
|
+
# key is always at bottom
|
|
197
207
|
key: str = Field(
|
|
198
208
|
strict=True,
|
|
199
209
|
frozen=True,
|
|
@@ -252,10 +262,10 @@ The search effectiveness depends on the number of processor threads.
|
|
|
252
262
|
"""
|
|
253
263
|
|
|
254
264
|
import anyio
|
|
255
|
-
import datetime
|
|
265
|
+
from datetime import datetime
|
|
256
266
|
from typing import Annotated
|
|
257
|
-
from pydantic import
|
|
258
|
-
from scruby import Scruby, settings
|
|
267
|
+
from pydantic import Field
|
|
268
|
+
from scruby import Scruby, ScrubyModel, settings
|
|
259
269
|
from pprint import pprint as pp
|
|
260
270
|
|
|
261
271
|
settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
|
|
@@ -263,13 +273,13 @@ settings.HASH_REDUCE_LEFT = 6 # By default = 6
|
|
|
263
273
|
settings.MAX_WORKERS = None # By default = None
|
|
264
274
|
|
|
265
275
|
|
|
266
|
-
class Car(
|
|
276
|
+
class Car(ScrubyModel):
|
|
267
277
|
"""Car model."""
|
|
268
278
|
brand: str = Field(strict=True, frozen=True)
|
|
269
279
|
model: str = Field(strict=True, frozen=True)
|
|
270
280
|
year: int = Field(strict=True)
|
|
271
281
|
power_reserve: int = Field(strict=True)
|
|
272
|
-
#
|
|
282
|
+
# key is always at bottom
|
|
273
283
|
key: str = Field(
|
|
274
284
|
strict=True,
|
|
275
285
|
frozen=True,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
scruby/__init__.py,sha256=WuIA27d3uUK0Vo0fXg92hge7i6v_2DBndhfepK16_y8,1046
|
|
2
|
+
scruby/aggregation.py,sha256=bd70J1Xye6faNHD8LS3lVQoHWKtPdPV_cqT_i7oui38,3491
|
|
3
|
+
scruby/db.py,sha256=4bSPMh0fYil0j9qhHhRu1g4U_0WaCuaGPthnmi2QpBI,8376
|
|
4
|
+
scruby/errors.py,sha256=D0jisudUsZk9iXp4nRSymaSMwyqHPVshsSlxx4HDVVk,1297
|
|
5
|
+
scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
scruby/settings.py,sha256=_uVdZIGWoi6q9zcu0c2PS51OBEBNASRRrxfzaF7Nwy0,1580
|
|
7
|
+
scruby/mixins/__init__.py,sha256=XPMjJvOZN7dLpTE1FfGMBGQ_0421HXug-0rWKMU5fRQ,627
|
|
8
|
+
scruby/mixins/collection.py,sha256=coF-IOhicV_EihDwnYf6SW5Mfi3nOFR0gAhCc619NmI,1382
|
|
9
|
+
scruby/mixins/count.py,sha256=YHjonxAtroSc5AlbDX1vpCvbe3vsTD0LfYnjEDB001A,2089
|
|
10
|
+
scruby/mixins/custom_task.py,sha256=ENr3FkCsPWRblOWv8jGMkkGKw4hvp9mMP2YjQvIeqzE,2246
|
|
11
|
+
scruby/mixins/delete.py,sha256=b7RYiTLUqVu70ep15CN1VTyKs13P7f-1YDGRc3ke-2g,3085
|
|
12
|
+
scruby/mixins/find.py,sha256=-rpILkxhfywJ5E3ceYo9dSPabma47ouceGajUVNh23Q,5396
|
|
13
|
+
scruby/mixins/keys.py,sha256=waxye5n0-oTWIhdDXGQkUTGaV9vLSCc9lCryTCuexkw,6179
|
|
14
|
+
scruby/mixins/update.py,sha256=WeNk2qZToQS3-r1_ahazBc2ErLMU9K5RRNgCxbu3JMg,3416
|
|
15
|
+
scruby-0.30.2.dist-info/METADATA,sha256=OgV4Foj3-X97NKFLpw_8Qb_osGwmYIINO2UZkgdLaY0,11032
|
|
16
|
+
scruby-0.30.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
17
|
+
scruby-0.30.2.dist-info/licenses/LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
|
|
18
|
+
scruby-0.30.2.dist-info/RECORD,,
|
scruby-0.28.3.dist-info/RECORD
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
scruby/__init__.py,sha256=bw2Le5ULYlf2nFQT_617rmEumu66Ll-QCLCxqDFERWw,1014
|
|
2
|
-
scruby/aggregation.py,sha256=bd70J1Xye6faNHD8LS3lVQoHWKtPdPV_cqT_i7oui38,3491
|
|
3
|
-
scruby/db.py,sha256=djo4JkfuKCcV3jRbd2L3mIwENS3ptqJBt7SlAuiRhGY,6794
|
|
4
|
-
scruby/errors.py,sha256=D0jisudUsZk9iXp4nRSymaSMwyqHPVshsSlxx4HDVVk,1297
|
|
5
|
-
scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
scruby/settings.py,sha256=_uVdZIGWoi6q9zcu0c2PS51OBEBNASRRrxfzaF7Nwy0,1580
|
|
7
|
-
scruby/mixins/__init__.py,sha256=XPMjJvOZN7dLpTE1FfGMBGQ_0421HXug-0rWKMU5fRQ,627
|
|
8
|
-
scruby/mixins/collection.py,sha256=coF-IOhicV_EihDwnYf6SW5Mfi3nOFR0gAhCc619NmI,1382
|
|
9
|
-
scruby/mixins/count.py,sha256=PGzRtgLvseQnHg6wt-A81s30pnsdY1d8cr-EQRnbfyU,2050
|
|
10
|
-
scruby/mixins/custom_task.py,sha256=ZhvCDiYnJ8BTIWlnRu6cTH-9G9o7dHSixjMIsxAtDpw,2316
|
|
11
|
-
scruby/mixins/delete.py,sha256=B2loiowj8ToO0euumDRxpHUVrLQx0iTcRys0jszn-rA,3046
|
|
12
|
-
scruby/mixins/find.py,sha256=gnHjnm0MZbzMHmWOBUJbMm8LZFBqdJ6yWA6Pxfap51Q,5340
|
|
13
|
-
scruby/mixins/keys.py,sha256=OUByWbHfNVWJVkUrhCsJZdVqf0zez_an6Gti2n5iKnM,5671
|
|
14
|
-
scruby/mixins/update.py,sha256=YkUiz1gcVtNXdgf7Mmda-0g3vJm3jL09v-msGy2tAWg,3229
|
|
15
|
-
scruby-0.28.3.dist-info/METADATA,sha256=DoDzTEj_wrZu3EfAhBcKBtCfbr9pb74FS3spyDpFfzY,10849
|
|
16
|
-
scruby-0.28.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
17
|
-
scruby-0.28.3.dist-info/licenses/LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
|
|
18
|
-
scruby-0.28.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|