scruby 0.24.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 CHANGED
@@ -1,3 +1,17 @@
1
+ #
2
+ # .|'''|                        '||
3
+ # ||                             ||
4
+ # `|'''|, .|'', '||''| '||  ||`  ||''|, '||  ||`
5
+ #  .   || ||     ||     ||  ||   ||  ||  `|..||
6
+ #  |...|' `|..' .||.    `|..'|. .||..|'      ||
7
+ #                                         ,  |'
8
+ #                                          ''
9
+ #
10
+ #
11
+ # Copyright (c) 2025 Gennady Kostyunin
12
+ # Scruby is free software under terms of the MIT License.
13
+ # Repository https://github.com/kebasyaty/scruby
14
+ #
1
15
  """Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
16
 
3
17
  The library uses fractal-tree addressing and
scruby/db.py CHANGED
@@ -6,9 +6,10 @@ __all__ = ("Scruby",)
6
6
 
7
7
  import contextlib
8
8
  import logging
9
+ import re
9
10
  import zlib
10
11
  from shutil import rmtree
11
- from typing import Any, Literal, Never, TypeVar, assert_never
12
+ from typing import Any, Literal, Never, assert_never
12
13
 
13
14
  from anyio import Path
14
15
  from pydantic import BaseModel
@@ -17,8 +18,6 @@ from scruby import constants, mixins
17
18
 
18
19
  logger = logging.getLogger(__name__)
19
20
 
20
- T = TypeVar("T")
21
-
22
21
 
23
22
  class _Meta(BaseModel):
24
23
  """Metadata of Collection."""
@@ -30,8 +29,8 @@ class _Meta(BaseModel):
30
29
  counter_documents: int
31
30
 
32
31
 
33
- class Scruby[T](
34
- mixins.Keys,
32
+ class Scruby(
33
+ mixins.Docs,
35
34
  mixins.Find,
36
35
  mixins.CustomTask,
37
36
  mixins.Collection,
@@ -61,19 +60,20 @@ class Scruby[T](
61
60
  case _ as unreachable:
62
61
  msg: str = f"{unreachable} - Unacceptable value for HASH_REDUCE_LEFT."
63
62
  logger.critical(msg)
64
- assert_never(Never(unreachable))
63
+ assert_never(Never(unreachable)) # pyrefly: ignore[not-callable]
65
64
 
66
65
  @classmethod
67
- async def create(cls, class_model: T) -> Any:
66
+ async def collection(cls, class_model: Any) -> Any:
68
67
  """Get an object to access a collection.
69
68
 
70
69
  Args:
71
- class_model: Class of Model (Pydantic).
70
+ class_model: Class of Model (pydantic.BaseModel).
72
71
 
73
72
  Returns:
74
73
  Instance of Scruby for access a collection.
75
74
  """
76
75
  assert BaseModel in class_model.__bases__, "`class_model` does not contain the base class `pydantic.BaseModel`!"
76
+
77
77
  instance = cls()
78
78
  instance.__dict__["_class_model"] = class_model
79
79
  # Caching a pati for metadata.
@@ -144,22 +144,32 @@ class Scruby[T](
144
144
  meta_json = meta.model_dump_json()
145
145
  await meta_path.write_text(meta_json, "utf-8")
146
146
 
147
- async def _get_leaf_path(self, key: str) -> Path:
147
+ async def _get_leaf_path(self, key: str) -> tuple[Path, str]:
148
148
  """Asynchronous method for getting path to collection cell by key.
149
149
 
150
150
  This method is for internal use.
151
151
 
152
152
  Args:
153
- key: Key name.
153
+ key (str): Key name.
154
154
 
155
155
  Returns:
156
156
  Path to cell of collection.
157
157
  """
158
- if len(key) == 0:
159
- logger.error("The key should not be empty.")
160
- raise KeyError("The key should not be empty.")
158
+ if not isinstance(key, str):
159
+ msg = "The key is not a string."
160
+ logger.error(msg)
161
+ raise KeyError(msg)
162
+ # Prepare key.
163
+ # Removes spaces at the beginning and end of a string.
164
+ # Replaces all whitespace characters with a single space.
165
+ prepared_key = re.sub(r"\s+", " ", key).strip().lower()
166
+ # Check the key for an empty string.
167
+ if len(prepared_key) == 0:
168
+ msg = "The key should not be empty."
169
+ logger.error(msg)
170
+ raise KeyError(msg)
161
171
  # Key to crc32 sum.
162
- key_as_hash: str = f"{zlib.crc32(key.encode('utf-8')):08x}"[self._hash_reduce_left :]
172
+ key_as_hash: str = f"{zlib.crc32(prepared_key.encode('utf-8')):08x}"[self._hash_reduce_left :]
163
173
  # Convert crc32 sum in the segment of path.
164
174
  separated_hash: str = "/".join(list(key_as_hash))
165
175
  # The path of the branch to the database.
@@ -175,7 +185,7 @@ class Scruby[T](
175
185
  await branch_path.mkdir(parents=True)
176
186
  # The path to the database cell.
177
187
  leaf_path: Path = Path(*(branch_path, "leaf.json"))
178
- return leaf_path
188
+ return (leaf_path, prepared_key)
179
189
 
180
190
  @staticmethod
181
191
  def napalm() -> None:
scruby/mixins/__init__.py CHANGED
@@ -8,7 +8,7 @@ __all__ = (
8
8
  "CustomTask",
9
9
  "Delete",
10
10
  "Find",
11
- "Keys",
11
+ "Docs",
12
12
  "Update",
13
13
  )
14
14
 
@@ -16,6 +16,6 @@ from scruby.mixins.collection import Collection
16
16
  from scruby.mixins.count import Count
17
17
  from scruby.mixins.custom_task import CustomTask
18
18
  from scruby.mixins.delete import Delete
19
+ from scruby.mixins.docs import Docs
19
20
  from scruby.mixins.find import Find
20
- from scruby.mixins.keys import Keys
21
21
  from scruby.mixins.update import Update
@@ -45,5 +45,5 @@ class Collection[T]:
45
45
  None.
46
46
  """
47
47
  target_directory = f"{constants.DB_ROOT}/{name}"
48
- await to_thread.run_sync(rmtree, target_directory)
48
+ await to_thread.run_sync(rmtree, target_directory) # pyrefly: ignore[bad-argument-type]
49
49
  return
@@ -43,7 +43,7 @@ class CustomTask[T]:
43
43
  "leaf.json",
44
44
  ),
45
45
  )
46
- docs: list[str, T] = []
46
+ docs: list[Any] = []
47
47
  if await leaf_path.exists():
48
48
  data_json: bytes = await leaf_path.read_bytes()
49
49
  data: dict[str, str] = orjson.loads(data_json) or {}
@@ -2,14 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __all__ = ("Keys",)
5
+ __all__ = ("Docs",)
6
6
 
7
7
  import logging
8
- import re
9
- from typing import TypeVar
8
+ from typing import Any
10
9
 
11
10
  import orjson
12
- from anyio import Path
13
11
 
14
12
  from scruby.errors import (
15
13
  KeyAlreadyExistsError,
@@ -18,75 +16,80 @@ from scruby.errors import (
18
16
 
19
17
  logger = logging.getLogger(__name__)
20
18
 
21
- T = TypeVar("T")
22
19
 
20
+ class Docs:
21
+ """Methods for working with document."""
23
22
 
24
- class Keys[T]:
25
- """Methods for working with keys."""
26
-
27
- async def add_key(
28
- self,
29
- key: str,
30
- value: T,
31
- ) -> None:
32
- """Asynchronous method for adding key to collection.
23
+ async def add_doc(self, doc: Any) -> None:
24
+ """Asynchronous method for adding document to collection.
33
25
 
34
26
  Args:
35
- key: Key name. Type `str`.
36
- value: Value of key. Type `BaseModel`.
27
+ doc: Value of key. Type, derived from `BaseModel`.
37
28
 
38
29
  Returns:
39
30
  None.
40
31
  """
41
- key = re.sub(r"\s+", " ", key.strip())
32
+ # Check if the Model matches the collection
33
+ if not isinstance(doc, self._class_model):
34
+ doc_class_name = doc.__class__.__name__
35
+ collection_name = self._class_model.__name__
36
+ msg = (
37
+ f"(add_doc) Parameter `doc` => Model `{doc_class_name}` does not match collection `{collection_name}`!"
38
+ )
39
+ logger.error(msg)
40
+ raise TypeError(msg)
42
41
  # The path to cell of collection.
43
- leaf_path: Path = await self._get_leaf_path(key)
44
- value_json: str = value.model_dump_json()
42
+ leaf_path, prepared_key = await self._get_leaf_path(doc.key)
43
+ doc_json: str = doc.model_dump_json()
45
44
  # Write key-value to collection.
46
45
  if await leaf_path.exists():
47
46
  # Add new key.
48
47
  data_json: bytes = await leaf_path.read_bytes()
49
48
  data: dict = orjson.loads(data_json) or {}
50
49
  try:
51
- data[key]
50
+ data[prepared_key]
52
51
  except KeyError:
53
- data[key] = value_json
52
+ data[prepared_key] = doc_json
54
53
  await leaf_path.write_bytes(orjson.dumps(data))
55
54
  else:
56
55
  err = KeyAlreadyExistsError()
57
56
  logger.error(err.message)
58
57
  raise err
59
58
  else:
60
- # Add new key to a blank leaf.
61
- await leaf_path.write_bytes(orjson.dumps({key: value_json}))
59
+ # Add new document to a blank leaf.
60
+ await leaf_path.write_bytes(orjson.dumps({prepared_key: doc_json}))
62
61
  await self._counter_documents(1)
63
62
 
64
- async def update_key(
65
- self,
66
- key: str,
67
- value: T,
68
- ) -> None:
63
+ async def update_doc(self, doc: Any) -> None:
69
64
  """Asynchronous method for updating key to collection.
70
65
 
71
66
  Args:
72
- key: Key name. Type `str`.
73
- value: Value of key. Type `BaseModel`.
67
+ doc: Value of key. Type `BaseModel`.
74
68
 
75
69
  Returns:
76
70
  None.
77
71
  """
78
- key = re.sub(r"\s+", " ", key.strip())
72
+ # Check if the Model matches the collection
73
+ if not isinstance(doc, self._class_model):
74
+ doc_class_name = doc.__class__.__name__
75
+ collection_name = self._class_model.__name__
76
+ msg = (
77
+ f"(update_doc) Parameter `doc` => Model `{doc_class_name}` "
78
+ f"does not match collection `{collection_name}`!"
79
+ )
80
+ logger.error(msg)
81
+ raise TypeError(msg)
79
82
  # The path to cell of collection.
80
- leaf_path: Path = await self._get_leaf_path(key)
81
- value_json: str = value.model_dump_json()
83
+ leaf_path, prepared_key = await self._get_leaf_path(doc.key)
84
+ doc_json: str = doc.model_dump_json()
82
85
  # Update the existing key.
83
86
  if await leaf_path.exists():
84
87
  # Update the existing key.
85
88
  data_json: bytes = await leaf_path.read_bytes()
86
89
  data: dict = orjson.loads(data_json) or {}
87
90
  try:
88
- data[key]
89
- data[key] = value_json
91
+ data[prepared_key]
92
+ data[prepared_key] = doc_json
90
93
  await leaf_path.write_bytes(orjson.dumps(data))
91
94
  except KeyError:
92
95
  err = KeyNotExistsError()
@@ -96,7 +99,7 @@ class Keys[T]:
96
99
  logger.error("The key not exists.")
97
100
  raise KeyError()
98
101
 
99
- async def get_key(self, key: str) -> T:
102
+ async def get_key(self, key: str) -> Any:
100
103
  """Asynchronous method for getting value of key from collection.
101
104
 
102
105
  Args:
@@ -105,14 +108,13 @@ class Keys[T]:
105
108
  Returns:
106
109
  Value of key or KeyError.
107
110
  """
108
- key = re.sub(r"\s+", " ", key.strip())
109
111
  # The path to the database cell.
110
- leaf_path: Path = await self._get_leaf_path(key)
112
+ leaf_path, prepared_key = await self._get_leaf_path(key)
111
113
  # Get value of key.
112
114
  if await leaf_path.exists():
113
115
  data_json: bytes = await leaf_path.read_bytes()
114
116
  data: dict = orjson.loads(data_json) or {}
115
- obj: T = self._class_model.model_validate_json(data[key])
117
+ obj: Any = self._class_model.model_validate_json(data[prepared_key])
116
118
  return obj
117
119
  msg: str = "`get_key` - The unacceptable key value."
118
120
  logger.error(msg)
@@ -127,15 +129,14 @@ class Keys[T]:
127
129
  Returns:
128
130
  True, if the key is present.
129
131
  """
130
- key = re.sub(r"\s+", " ", key.strip())
131
132
  # Get path to cell of collection.
132
- leaf_path: Path = await self._get_leaf_path(key)
133
+ leaf_path, prepared_key = await self._get_leaf_path(key)
133
134
  # Checking whether there is a key.
134
135
  if await leaf_path.exists():
135
136
  data_json: bytes = await leaf_path.read_bytes()
136
137
  data: dict = orjson.loads(data_json) or {}
137
138
  try:
138
- data[key]
139
+ data[prepared_key]
139
140
  return True
140
141
  except KeyError:
141
142
  return False
@@ -150,14 +151,13 @@ class Keys[T]:
150
151
  Returns:
151
152
  None.
152
153
  """
153
- key = re.sub(r"\s+", " ", key.strip())
154
154
  # The path to the database cell.
155
- leaf_path: Path = await self._get_leaf_path(key)
155
+ leaf_path, prepared_key = await self._get_leaf_path(key)
156
156
  # Deleting key.
157
157
  if await leaf_path.exists():
158
158
  data_json: bytes = await leaf_path.read_bytes()
159
159
  data: dict = orjson.loads(data_json) or {}
160
- del data[key]
160
+ del data[prepared_key]
161
161
  await leaf_path.write_bytes(orjson.dumps(data))
162
162
  await self._counter_documents(-1)
163
163
  return
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scruby
3
- Version: 0.24.4
4
- Summary: A fast key-value storage library.
5
- Project-URL: Homepage, https://github.com/kebasyaty/scruby
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
@@ -30,7 +30,7 @@ Requires-Dist: anyio>=4.10.0
30
30
  Requires-Dist: orjson>=3.11.3
31
31
  Requires-Dist: phonenumbers>=9.0.13
32
32
  Requires-Dist: pydantic-extra-types>=2.10.5
33
- Requires-Dist: pydantic[email]>=2.11.7
33
+ Requires-Dist: pydantic[email,timezone]>=2.11.7
34
34
  Description-Content-Type: text/markdown
35
35
 
36
36
  <div align="center">
@@ -52,7 +52,7 @@ Description-Content-Type: text/markdown
52
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>
53
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>
54
54
  <br>
55
- <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>
56
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>
57
57
  <a href="https://pypi.org/project/scruby"><img src="https://img.shields.io/pypi/format/scruby" alt="Format"></a>
58
58
  <a href="https://pepy.tech/projects/scruby"><img src="https://static.pepy.tech/badge/scruby" alt="PyPI Downloads"></a>
@@ -65,11 +65,11 @@ Description-Content-Type: text/markdown
65
65
  <br>
66
66
  The database consists of collections.
67
67
  <br>
68
- The maximum size of the one collection is 16\*\*8=4294967296 branches,
68
+ The maximum size of the one collection is <b>16**8=4294967296</b> branches,
69
69
  <br>
70
70
  each branch can store one or more keys.
71
71
  <br>
72
- The value of any key in collection can be obtained in 8 steps,
72
+ The value of any key in collection can be obtained maximum in <b>8</b> steps,
73
73
  <br>
74
74
  thereby achieving high performance.
75
75
  <br>
@@ -108,7 +108,7 @@ See more examples here [https://kebasyaty.github.io/scruby/latest/pages/usage/](
108
108
  import anyio
109
109
  import datetime
110
110
  from typing import Annotated
111
- from pydantic import BaseModel, EmailStr
111
+ from pydantic import BaseModel, EmailStr, Field
112
112
  from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
113
113
  from scruby import Scruby, constants
114
114
 
@@ -116,18 +116,24 @@ constants.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
116
116
  constants.HASH_REDUCE_LEFT = 6 # By default = 6
117
117
 
118
118
  class User(BaseModel):
119
- """Model of User."""
120
- first_name: str
121
- last_name: str
122
- birthday: datetime.datetime
123
- email: EmailStr
124
- 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
+ )
125
131
 
126
132
 
127
133
  async def main() -> None:
128
134
  """Example."""
129
135
  # Get collection of `User`.
130
- user_coll = await Scruby.create(User)
136
+ user_coll = await Scruby.collection(User)
131
137
 
132
138
  user = User(
133
139
  first_name="John",
@@ -137,9 +143,9 @@ async def main() -> None:
137
143
  phone="+447986123456",
138
144
  )
139
145
 
140
- await user_coll.add_key(user.phone, user)
146
+ await user_coll.add_doc(user)
141
147
 
142
- await user_coll.update_key(user.phone, user)
148
+ await user_coll.update_doc(user)
143
149
 
144
150
  await user_coll.get_key("+447986123456") # => user
145
151
  await user_coll.get_key("key missing") # => KeyError
@@ -171,7 +177,7 @@ Ideally, hundreds and even thousands of threads are required.
171
177
  import anyio
172
178
  import datetime
173
179
  from typing import Annotated
174
- from pydantic import BaseModel
180
+ from pydantic import BaseModel, Field
175
181
  from scruby import Scruby, constants
176
182
  from pprint import pprint as pp
177
183
 
@@ -181,16 +187,22 @@ constants.HASH_REDUCE_LEFT = 6 # By default = 6
181
187
 
182
188
  class Phone(BaseModel):
183
189
  """Phone model."""
184
- brand: str
185
- model: str
186
- screen_diagonal: float
187
- matrix_type: str
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
+ )
188
200
 
189
201
 
190
202
  async def main() -> None:
191
203
  """Example."""
192
204
  # Get collection of `Phone`.
193
- phone_coll = await Scruby.create(Phone)
205
+ phone_coll = await Scruby.collection(Phone)
194
206
 
195
207
  # Create phone.
196
208
  phone = Phone(
@@ -201,8 +213,7 @@ async def main() -> None:
201
213
  )
202
214
 
203
215
  # Add phone to collection.
204
- key = f"{phone.brand} {phone.model}"
205
- await phone_coll.add_key(key, phone)
216
+ await phone_coll.add_doc(phone)
206
217
 
207
218
  # Find phone by brand.
208
219
  phone_details: Phone | None = await phone_coll.find_one(
@@ -242,7 +253,7 @@ Ideally, hundreds and even thousands of threads are required.
242
253
  import anyio
243
254
  import datetime
244
255
  from typing import Annotated
245
- from pydantic import BaseModel
256
+ from pydantic import BaseModel, Field
246
257
  from scruby import Scruby, constants
247
258
  from pprint import pprint as pp
248
259
 
@@ -252,16 +263,22 @@ constants.HASH_REDUCE_LEFT = 6 # By default = 6
252
263
 
253
264
  class Car(BaseModel):
254
265
  """Car model."""
255
- brand: str
256
- model: str
257
- year: int
258
- power_reserve: int
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
+ )
259
276
 
260
277
 
261
278
  async def main() -> None:
262
279
  """Example."""
263
280
  # Get collection of `Car`.
264
- car_coll = await Scruby.create(Car)
281
+ car_coll = await Scruby.collection(Car)
265
282
 
266
283
  # Create cars.
267
284
  for name in range(1, 10):
@@ -271,12 +288,11 @@ async def main() -> None:
271
288
  year=2025,
272
289
  power_reserve=600,
273
290
  )
274
- key = f"{car.brand} {car.model}"
275
- await car_coll.add_key(key, car)
291
+ await car_coll.add_doc(car)
276
292
 
277
293
  # Find cars by brand and year.
278
294
  car_list: list[Car] | None = await car_coll.find_many(
279
- filter_fn=lambda doc: doc.brand == "Mazda" or doc.year == 2025,
295
+ filter_fn=lambda doc: doc.brand == "Mazda" and doc.year == 2025,
280
296
  )
281
297
  if car_list is not None:
282
298
  pp(car_list)
@@ -285,7 +301,7 @@ async def main() -> None:
285
301
 
286
302
  # Get collection list.
287
303
  collection_list = await Scruby.collection_list()
288
- print(ucollection_list) # ["Car"]
304
+ print(collection_list) # ["Car"]
289
305
 
290
306
  # Full database deletion.
291
307
  # Hint: The main purpose is tests.
@@ -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,18 +0,0 @@
1
- scruby/__init__.py,sha256=elrW_AWMyl3kuTpEqGPaYFSpF8iVzjpivF6MxVNlqoQ,855
2
- scruby/aggregation.py,sha256=SYGcnMy2eq9vJb-pW3xR9LLAQIQ55TK-LGW_yKQ-7sU,3318
3
- scruby/constants.py,sha256=KInSZ_4dsQNXilrs7DvtQXevKEYibnNzl69a7XiWG4k,1099
4
- scruby/db.py,sha256=ggYW4dQPtr7m9-GM4QeYMMDZm5eUYN5bTAdz2Tj0hlw,5980
5
- scruby/errors.py,sha256=aj1zQlfxGwZC-bZZ07DRX2vHx31SpyWPqXHMpQ9kRVY,1124
6
- scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- scruby/mixins/__init__.py,sha256=-rRZE-JZwGmEkC0wS_X0hs8OXsEYyvgSNIfil8wmjFA,454
8
- scruby/mixins/collection.py,sha256=eMnfHFdzk7LWILMmDbzugcOYSeIKp0DlEEqCmmGQRwA,1222
9
- scruby/mixins/count.py,sha256=Wcn6CeWrYSgsTTmYQ4J-CEiM4630rUSwRP9iKwbCl6c,2193
10
- scruby/mixins/custom_task.py,sha256=Ib1G1I7NyDGbow4SeafkYd9C0r6u6EDgUK0NxjhsEa0,2297
11
- scruby/mixins/delete.py,sha256=BmfQH68iX7kzC20w16xzFcLO3uLxYKdNyqZqIbXb1M0,3240
12
- scruby/mixins/find.py,sha256=va1hTm6Poua7_TMcZW2iqI-xmL1HcCUOx8pkKvTvu6U,5063
13
- scruby/mixins/keys.py,sha256=Hbb0AX68ph--fA43AXDWoM72PzSmS48h3iVwlQwQH0c,4971
14
- scruby/mixins/update.py,sha256=A9V4PjA3INnqLTGoBxIvC8y8Wo-nLxlFejkPUhsebzQ,3428
15
- scruby-0.24.4.dist-info/METADATA,sha256=RDE0Fa_IXd2hx2MDjdsI-5iwGhZOCZ9zOqqQuOe8k5g,9643
16
- scruby-0.24.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
- scruby-0.24.4.dist-info/licenses/LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
18
- scruby-0.24.4.dist-info/RECORD,,