scruby 0.10.4__py3-none-any.whl → 0.26.0__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 +43 -29
- scruby/aggregation.py +148 -0
- scruby/constants.py +33 -31
- scruby/db.py +204 -437
- scruby/errors.py +41 -23
- scruby/mixins/__init__.py +21 -0
- scruby/mixins/collection.py +49 -0
- scruby/mixins/count.py +64 -0
- scruby/mixins/custom_task.py +76 -0
- scruby/mixins/delete.py +101 -0
- scruby/mixins/docs.py +166 -0
- scruby/mixins/find.py +146 -0
- scruby/mixins/update.py +104 -0
- {scruby-0.10.4.dist-info → scruby-0.26.0.dist-info}/METADATA +127 -105
- scruby-0.26.0.dist-info/RECORD +18 -0
- {scruby-0.10.4.dist-info → scruby-0.26.0.dist-info}/WHEEL +1 -1
- {scruby-0.10.4.dist-info → scruby-0.26.0.dist-info}/licenses/LICENSE +21 -21
- scruby-0.10.4.dist-info/RECORD +0 -9
scruby/mixins/update.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Methods for updating documents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = ("Update",)
|
|
6
|
+
|
|
7
|
+
import concurrent.futures
|
|
8
|
+
import logging
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Any, TypeVar
|
|
11
|
+
|
|
12
|
+
import orjson
|
|
13
|
+
from anyio import Path
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
T = TypeVar("T")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Update[T]:
|
|
21
|
+
"""Methods for updating documents."""
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
async def _task_update(
|
|
25
|
+
branch_number: int,
|
|
26
|
+
filter_fn: Callable,
|
|
27
|
+
hash_reduce_left: str,
|
|
28
|
+
db_root: str,
|
|
29
|
+
class_model: T,
|
|
30
|
+
new_data: dict[str, Any],
|
|
31
|
+
) -> int:
|
|
32
|
+
"""Task for find documents.
|
|
33
|
+
|
|
34
|
+
This method is for internal use.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The number of updated documents.
|
|
38
|
+
"""
|
|
39
|
+
branch_number_as_hash: str = f"{branch_number:08x}"[hash_reduce_left:]
|
|
40
|
+
separated_hash: str = "/".join(list(branch_number_as_hash))
|
|
41
|
+
leaf_path: Path = Path(
|
|
42
|
+
*(
|
|
43
|
+
db_root,
|
|
44
|
+
class_model.__name__,
|
|
45
|
+
separated_hash,
|
|
46
|
+
"leaf.json",
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
counter: int = 0
|
|
50
|
+
if await leaf_path.exists():
|
|
51
|
+
data_json: bytes = await leaf_path.read_bytes()
|
|
52
|
+
data: dict[str, str] = orjson.loads(data_json) or {}
|
|
53
|
+
new_state: dict[str, str] = {}
|
|
54
|
+
for _, val in data.items():
|
|
55
|
+
doc = class_model.model_validate_json(val)
|
|
56
|
+
if filter_fn(doc):
|
|
57
|
+
for key, value in new_data.items():
|
|
58
|
+
doc.__dict__[key] = value
|
|
59
|
+
new_state[key] = doc.model_dump_json()
|
|
60
|
+
counter += 1
|
|
61
|
+
await leaf_path.write_bytes(orjson.dumps(new_state))
|
|
62
|
+
return counter
|
|
63
|
+
|
|
64
|
+
async def update_many(
|
|
65
|
+
self,
|
|
66
|
+
filter_fn: Callable,
|
|
67
|
+
new_data: dict[str, Any],
|
|
68
|
+
max_workers: int | None = None,
|
|
69
|
+
) -> int:
|
|
70
|
+
"""Updates one or more documents matching the filter.
|
|
71
|
+
|
|
72
|
+
The search is based on the effect of a quantum loop.
|
|
73
|
+
The search effectiveness depends on the number of processor threads.
|
|
74
|
+
Ideally, hundreds and even thousands of threads are required.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
filter_fn: A function that execute the conditions of filtering.
|
|
78
|
+
new_data: New data for the fields that need to be updated.
|
|
79
|
+
max_workers: The maximum number of processes that can be used to
|
|
80
|
+
execute the given calls. If None or not given then as many
|
|
81
|
+
worker processes will be created as the machine has processors.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The number of updated documents.
|
|
85
|
+
"""
|
|
86
|
+
branch_numbers: range = range(1, self._max_branch_number)
|
|
87
|
+
update_task_fn: Callable = self._task_update
|
|
88
|
+
hash_reduce_left: int = self._hash_reduce_left
|
|
89
|
+
db_root: str = self._db_root
|
|
90
|
+
class_model: T = self._class_model
|
|
91
|
+
counter: int = 0
|
|
92
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers) as executor:
|
|
93
|
+
for branch_number in branch_numbers:
|
|
94
|
+
future = executor.submit(
|
|
95
|
+
update_task_fn,
|
|
96
|
+
branch_number,
|
|
97
|
+
filter_fn,
|
|
98
|
+
hash_reduce_left,
|
|
99
|
+
db_root,
|
|
100
|
+
class_model,
|
|
101
|
+
new_data,
|
|
102
|
+
)
|
|
103
|
+
counter += await future.result()
|
|
104
|
+
return counter
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scruby
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
5
|
-
Project-URL: Homepage, https://github.
|
|
3
|
+
Version: 0.26.0
|
|
4
|
+
Summary: Asynchronous library for building and managing a hybrid database, by scheme of key-value.
|
|
5
|
+
Project-URL: Homepage, https://kebasyaty.github.io/scruby/
|
|
6
6
|
Project-URL: Repository, https://github.com/kebasyaty/scruby
|
|
7
7
|
Project-URL: Source, https://github.com/kebasyaty/scruby
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/kebasyaty/scruby/issues
|
|
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
25
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
26
|
Classifier: Topic :: Database
|
|
26
27
|
Classifier: Typing :: Typed
|
|
@@ -29,7 +30,7 @@ Requires-Dist: anyio>=4.10.0
|
|
|
29
30
|
Requires-Dist: orjson>=3.11.3
|
|
30
31
|
Requires-Dist: phonenumbers>=9.0.13
|
|
31
32
|
Requires-Dist: pydantic-extra-types>=2.10.5
|
|
32
|
-
Requires-Dist: pydantic[email]>=2.11.7
|
|
33
|
+
Requires-Dist: pydantic[email,timezone]>=2.11.7
|
|
33
34
|
Description-Content-Type: text/markdown
|
|
34
35
|
|
|
35
36
|
<div align="center">
|
|
@@ -43,7 +44,7 @@ Description-Content-Type: text/markdown
|
|
|
43
44
|
</p>
|
|
44
45
|
<p>
|
|
45
46
|
<h1>Scruby</h1>
|
|
46
|
-
<h3>
|
|
47
|
+
<h3>Asynchronous library for building and managing a hybrid database,<br>by scheme of key-value.</h3>
|
|
47
48
|
<p align="center">
|
|
48
49
|
<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
50
|
<a href="https://kebasyaty.github.io/scruby/" alt="Docs"><img src="https://img.shields.io/badge/docs-available-brightgreen.svg" alt="Docs"></a>
|
|
@@ -51,34 +52,30 @@ Description-Content-Type: text/markdown
|
|
|
51
52
|
<a href="https://pypi.python.org/pypi/scruby/" alt="PyPI status"><img src="https://img.shields.io/pypi/status/scruby.svg" alt="PyPI status"></a>
|
|
52
53
|
<a href="https://pypi.python.org/pypi/scruby/" alt="PyPI version fury.io"><img src="https://badge.fury.io/py/scruby.svg" alt="PyPI version fury.io"></a>
|
|
53
54
|
<br>
|
|
54
|
-
<a href="https://
|
|
55
|
-
<a href="https://pepy.tech/projects/scruby"><img src="https://static.pepy.tech/badge/scruby" alt="PyPI Downloads"></a>
|
|
56
|
-
<a href="https://github.com/kebasyaty/scruby/blob/main/LICENSE" alt="GitHub license"><img src="https://img.shields.io/github/license/kebasyaty/scruby" alt="GitHub license"></a>
|
|
57
|
-
<a href="https://mypy-lang.org/" alt="Types: Mypy"><img src="https://img.shields.io/badge/types-Mypy-202235.svg?color=0c7ebf" alt="Types: Mypy"></a>
|
|
55
|
+
<a href="https://pyrefly.org/" alt="Types: Pyrefly"><img src="https://img.shields.io/badge/types-Pyrefly-FFB74D.svg" alt="Types: Pyrefly"></a>
|
|
58
56
|
<a href="https://docs.astral.sh/ruff/" alt="Code style: Ruff"><img src="https://img.shields.io/badge/code%20style-Ruff-FDD835.svg" alt="Code style: Ruff"></a>
|
|
59
|
-
<a href="https://github.com/kebasyaty/scruby" alt="PyPI implementation"><img src="https://img.shields.io/pypi/implementation/scruby" alt="PyPI implementation"></a>
|
|
60
|
-
<br>
|
|
61
57
|
<a href="https://pypi.org/project/scruby"><img src="https://img.shields.io/pypi/format/scruby" alt="Format"></a>
|
|
62
|
-
<a href="https://
|
|
63
|
-
<a href="https://github.com/kebasyaty/scruby"><img src="https://img.shields.io/github/
|
|
64
|
-
<a href="https://github.com/kebasyaty/scruby"><img src="https://img.shields.io/github/last-commit/kebasyaty/scruby/main" alt="Last commit"></a>
|
|
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>
|
|
58
|
+
<a href="https://pepy.tech/projects/scruby"><img src="https://static.pepy.tech/badge/scruby" alt="PyPI Downloads"></a>
|
|
59
|
+
<a href="https://github.com/kebasyaty/scruby/blob/main/LICENSE" alt="GitHub license"><img src="https://img.shields.io/github/license/kebasyaty/scruby" alt="GitHub license"></a>
|
|
66
60
|
</p>
|
|
67
61
|
<p align="center">
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
62
|
+
The library uses fractal-tree addressing and
|
|
63
|
+
<br>
|
|
64
|
+
the search for documents based on the effect of a quantum loop.
|
|
65
|
+
<br>
|
|
66
|
+
The database consists of collections.
|
|
67
|
+
<br>
|
|
68
|
+
The maximum size of the one collection is <b>16**8=4294967296</b> branches,
|
|
69
|
+
<br>
|
|
70
|
+
each branch can store one or more keys.
|
|
71
|
+
<br>
|
|
72
|
+
The value of any key in collection can be obtained maximum in <b>8</b> steps,
|
|
73
|
+
<br>
|
|
74
|
+
thereby achieving high performance.
|
|
75
|
+
<br>
|
|
76
|
+
The effectiveness of the search for documents based on a quantum loop,
|
|
77
|
+
<br>
|
|
78
|
+
requires a large number of processor threads.
|
|
82
79
|
</p>
|
|
83
80
|
</p>
|
|
84
81
|
</div>
|
|
@@ -111,24 +108,32 @@ See more examples here [https://kebasyaty.github.io/scruby/latest/pages/usage/](
|
|
|
111
108
|
import anyio
|
|
112
109
|
import datetime
|
|
113
110
|
from typing import Annotated
|
|
114
|
-
from pydantic import BaseModel, EmailStr
|
|
111
|
+
from pydantic import BaseModel, EmailStr, Field
|
|
115
112
|
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
|
|
116
113
|
from scruby import Scruby, constants
|
|
117
114
|
|
|
118
115
|
constants.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
|
|
116
|
+
constants.HASH_REDUCE_LEFT = 6 # By default = 6
|
|
119
117
|
|
|
120
118
|
class User(BaseModel):
|
|
121
|
-
"""
|
|
122
|
-
first_name: str
|
|
123
|
-
last_name: str
|
|
124
|
-
birthday: datetime.datetime
|
|
125
|
-
email: EmailStr
|
|
126
|
-
phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")]
|
|
119
|
+
"""User model."""
|
|
120
|
+
first_name: str = Field(strict=True)
|
|
121
|
+
last_name: str = Field(strict=True)
|
|
122
|
+
birthday: datetime.datetime = Field(strict=True)
|
|
123
|
+
email: EmailStr = Field(strict=True)
|
|
124
|
+
phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")] = Field(frozen=True)
|
|
125
|
+
# The key is always at the bottom
|
|
126
|
+
key: str = Field(
|
|
127
|
+
strict=True,
|
|
128
|
+
frozen=True,
|
|
129
|
+
default_factory=lambda data: data["phone"],
|
|
130
|
+
)
|
|
131
|
+
|
|
127
132
|
|
|
128
133
|
async def main() -> None:
|
|
129
134
|
"""Example."""
|
|
130
135
|
# Get collection of `User`.
|
|
131
|
-
user_coll = Scruby(User)
|
|
136
|
+
user_coll = await Scruby.collection(User)
|
|
132
137
|
|
|
133
138
|
user = User(
|
|
134
139
|
first_name="John",
|
|
@@ -138,7 +143,9 @@ async def main() -> None:
|
|
|
138
143
|
phone="+447986123456",
|
|
139
144
|
)
|
|
140
145
|
|
|
141
|
-
await user_coll.
|
|
146
|
+
await user_coll.add_doc(user)
|
|
147
|
+
|
|
148
|
+
await user_coll.update_doc(user)
|
|
142
149
|
|
|
143
150
|
await user_coll.get_key("+447986123456") # => user
|
|
144
151
|
await user_coll.get_key("key missing") # => KeyError
|
|
@@ -152,7 +159,8 @@ async def main() -> None:
|
|
|
152
159
|
|
|
153
160
|
# Full database deletion.
|
|
154
161
|
# Hint: The main purpose is tests.
|
|
155
|
-
|
|
162
|
+
Scruby.napalm()
|
|
163
|
+
|
|
156
164
|
|
|
157
165
|
if __name__ == "__main__":
|
|
158
166
|
anyio.run(main)
|
|
@@ -169,61 +177,66 @@ Ideally, hundreds and even thousands of threads are required.
|
|
|
169
177
|
import anyio
|
|
170
178
|
import datetime
|
|
171
179
|
from typing import Annotated
|
|
172
|
-
from pydantic import BaseModel,
|
|
173
|
-
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
|
|
180
|
+
from pydantic import BaseModel, Field
|
|
174
181
|
from scruby import Scruby, constants
|
|
175
182
|
from pprint import pprint as pp
|
|
176
183
|
|
|
177
184
|
constants.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
|
|
178
|
-
constants.HASH_REDUCE_LEFT = 6 #
|
|
179
|
-
|
|
185
|
+
constants.HASH_REDUCE_LEFT = 6 # By default = 6
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class Phone(BaseModel):
|
|
189
|
+
"""Phone model."""
|
|
190
|
+
brand: str = Field(strict=True, frozen=True)
|
|
191
|
+
model: str = Field(strict=True, frozen=True)
|
|
192
|
+
screen_diagonal: float = Field(strict=True)
|
|
193
|
+
matrix_type: str = Field(strict=True)
|
|
194
|
+
# The key is always at the bottom
|
|
195
|
+
key: str = Field(
|
|
196
|
+
strict=True,
|
|
197
|
+
frozen=True,
|
|
198
|
+
default_factory=lambda data: f"{data['brand']}:{data['model']}",
|
|
199
|
+
)
|
|
180
200
|
|
|
181
|
-
class User(BaseModel):
|
|
182
|
-
"""Model of User."""
|
|
183
|
-
first_name: str
|
|
184
|
-
last_name: str
|
|
185
|
-
birthday: datetime.datetime
|
|
186
|
-
email: EmailStr
|
|
187
|
-
phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")]
|
|
188
201
|
|
|
189
202
|
async def main() -> None:
|
|
190
203
|
"""Example."""
|
|
191
|
-
# Get collection of `
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
# Create
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
phone="+447986123456",
|
|
204
|
+
# Get collection of `Phone`.
|
|
205
|
+
phone_coll = await Scruby.collection(Phone)
|
|
206
|
+
|
|
207
|
+
# Create phone.
|
|
208
|
+
phone = Phone(
|
|
209
|
+
brand="Samsung",
|
|
210
|
+
model="Galaxy A26",
|
|
211
|
+
screen_diagonal=6.7,
|
|
212
|
+
matrix_type="Super AMOLED",
|
|
201
213
|
)
|
|
202
214
|
|
|
203
|
-
# Add
|
|
204
|
-
await
|
|
215
|
+
# Add phone to collection.
|
|
216
|
+
await phone_coll.add_doc(phone)
|
|
205
217
|
|
|
206
|
-
# Find
|
|
207
|
-
|
|
208
|
-
filter_fn=lambda doc: doc.
|
|
218
|
+
# Find phone by brand.
|
|
219
|
+
phone_details: Phone | None = await phone_coll.find_one(
|
|
220
|
+
filter_fn=lambda doc: doc.brand == "Samsung",
|
|
209
221
|
)
|
|
210
|
-
if
|
|
211
|
-
pp(
|
|
222
|
+
if phone_details is not None:
|
|
223
|
+
pp(phone_details)
|
|
212
224
|
else:
|
|
213
|
-
print("No
|
|
225
|
+
print("No Phone!")
|
|
214
226
|
|
|
215
|
-
# Find
|
|
216
|
-
|
|
217
|
-
filter_fn=lambda doc: doc.
|
|
227
|
+
# Find phone by model.
|
|
228
|
+
phone_details: Phone | None = await phone_coll.find_one(
|
|
229
|
+
filter_fn=lambda doc: doc.model == "Galaxy A26",
|
|
218
230
|
)
|
|
219
|
-
if
|
|
220
|
-
pp(
|
|
231
|
+
if phone_details is not None:
|
|
232
|
+
pp(phone_details)
|
|
221
233
|
else:
|
|
222
|
-
print("No
|
|
234
|
+
print("No Phone!")
|
|
223
235
|
|
|
224
236
|
# Full database deletion.
|
|
225
237
|
# Hint: The main purpose is tests.
|
|
226
|
-
|
|
238
|
+
Scruby.napalm()
|
|
239
|
+
|
|
227
240
|
|
|
228
241
|
if __name__ == "__main__":
|
|
229
242
|
anyio.run(main)
|
|
@@ -240,51 +253,60 @@ Ideally, hundreds and even thousands of threads are required.
|
|
|
240
253
|
import anyio
|
|
241
254
|
import datetime
|
|
242
255
|
from typing import Annotated
|
|
243
|
-
from pydantic import BaseModel,
|
|
244
|
-
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
|
|
256
|
+
from pydantic import BaseModel, Field
|
|
245
257
|
from scruby import Scruby, constants
|
|
246
258
|
from pprint import pprint as pp
|
|
247
259
|
|
|
248
260
|
constants.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
|
|
249
|
-
constants.HASH_REDUCE_LEFT = 6 #
|
|
250
|
-
|
|
261
|
+
constants.HASH_REDUCE_LEFT = 6 # By default = 6
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class Car(BaseModel):
|
|
265
|
+
"""Car model."""
|
|
266
|
+
brand: str = Field(strict=True, frozen=True)
|
|
267
|
+
model: str = Field(strict=True, frozen=True)
|
|
268
|
+
year: int = Field(strict=True)
|
|
269
|
+
power_reserve: int = Field(strict=True)
|
|
270
|
+
# The key is always at the bottom
|
|
271
|
+
key: str = Field(
|
|
272
|
+
strict=True,
|
|
273
|
+
frozen=True,
|
|
274
|
+
default_factory=lambda data: f"{data['brand']}:{data['model']}",
|
|
275
|
+
)
|
|
251
276
|
|
|
252
|
-
class User(BaseModel):
|
|
253
|
-
"""Model of User."""
|
|
254
|
-
first_name: str
|
|
255
|
-
last_name: str
|
|
256
|
-
birthday: datetime.datetime
|
|
257
|
-
email: EmailStr
|
|
258
|
-
phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")]
|
|
259
277
|
|
|
260
278
|
async def main() -> None:
|
|
261
279
|
"""Example."""
|
|
262
|
-
# Get collection of `
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
# Create
|
|
266
|
-
for
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
phone=f"+44798612345{num}",
|
|
280
|
+
# Get collection of `Car`.
|
|
281
|
+
car_coll = await Scruby.collection(Car)
|
|
282
|
+
|
|
283
|
+
# Create cars.
|
|
284
|
+
for name in range(1, 10):
|
|
285
|
+
car = Car(
|
|
286
|
+
brand="Mazda",
|
|
287
|
+
model=f"EZ-6 {num}",
|
|
288
|
+
year=2025,
|
|
289
|
+
power_reserve=600,
|
|
273
290
|
)
|
|
274
|
-
await
|
|
291
|
+
await car_coll.add_doc(car)
|
|
275
292
|
|
|
276
|
-
# Find
|
|
277
|
-
|
|
278
|
-
filter_fn=lambda doc: doc.
|
|
293
|
+
# Find cars by brand and year.
|
|
294
|
+
car_list: list[Car] | None = await car_coll.find_many(
|
|
295
|
+
filter_fn=lambda doc: doc.brand == "Mazda" and doc.year == 2025,
|
|
279
296
|
)
|
|
280
|
-
if
|
|
281
|
-
pp(
|
|
297
|
+
if car_list is not None:
|
|
298
|
+
pp(car_list)
|
|
282
299
|
else:
|
|
283
|
-
print("No
|
|
300
|
+
print("No cars!")
|
|
301
|
+
|
|
302
|
+
# Get collection list.
|
|
303
|
+
collection_list = await Scruby.collection_list()
|
|
304
|
+
print(collection_list) # ["Car"]
|
|
284
305
|
|
|
285
306
|
# Full database deletion.
|
|
286
307
|
# Hint: The main purpose is tests.
|
|
287
|
-
|
|
308
|
+
Scruby.napalm()
|
|
309
|
+
|
|
288
310
|
|
|
289
311
|
if __name__ == "__main__":
|
|
290
312
|
anyio.run(main)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
scruby/__init__.py,sha256=iYjvi002DeRh-U_ND2cCOHlbX2xwxN8IIhsposeotNw,1504
|
|
2
|
+
scruby/aggregation.py,sha256=SYGcnMy2eq9vJb-pW3xR9LLAQIQ55TK-LGW_yKQ-7sU,3318
|
|
3
|
+
scruby/constants.py,sha256=KInSZ_4dsQNXilrs7DvtQXevKEYibnNzl69a7XiWG4k,1099
|
|
4
|
+
scruby/db.py,sha256=06GjnhN9lKvZo585nxKFd4z8Ox858Ep08c7eCbMA99k,6462
|
|
5
|
+
scruby/errors.py,sha256=aj1zQlfxGwZC-bZZ07DRX2vHx31SpyWPqXHMpQ9kRVY,1124
|
|
6
|
+
scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
scruby/mixins/__init__.py,sha256=w1Be13FHAGSkdRfXcmoZ-eDn5Q8cFsPRAV7k1tXkIwY,454
|
|
8
|
+
scruby/mixins/collection.py,sha256=kqUgzJbgG9pTZhlP7OD5DsOaArRzu0fl6fVibLAdNtk,1260
|
|
9
|
+
scruby/mixins/count.py,sha256=Wcn6CeWrYSgsTTmYQ4J-CEiM4630rUSwRP9iKwbCl6c,2193
|
|
10
|
+
scruby/mixins/custom_task.py,sha256=DL-pQZninz7CJUyRYlVV7SNPC60qMD3ZQyMLnC3zVTM,2294
|
|
11
|
+
scruby/mixins/delete.py,sha256=BmfQH68iX7kzC20w16xzFcLO3uLxYKdNyqZqIbXb1M0,3240
|
|
12
|
+
scruby/mixins/docs.py,sha256=UHawXUjIkDBtik6MIQwbPF3DZKSOG8WI4Da9_i_-9R4,5533
|
|
13
|
+
scruby/mixins/find.py,sha256=va1hTm6Poua7_TMcZW2iqI-xmL1HcCUOx8pkKvTvu6U,5063
|
|
14
|
+
scruby/mixins/update.py,sha256=A9V4PjA3INnqLTGoBxIvC8y8Wo-nLxlFejkPUhsebzQ,3428
|
|
15
|
+
scruby-0.26.0.dist-info/METADATA,sha256=CluDLzRgB952ZDbbxdsXuHGE598BzIiF5eYXpGaNdj4,10483
|
|
16
|
+
scruby-0.26.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
17
|
+
scruby-0.26.0.dist-info/licenses/LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
|
|
18
|
+
scruby-0.26.0.dist-info/RECORD,,
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Gennady Kostyunin
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Gennady Kostyunin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
scruby-0.10.4.dist-info/RECORD
DELETED
|
@@ -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,,
|