scruby 0.17.0__py3-none-any.whl → 0.27.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.
@@ -0,0 +1,99 @@
1
+ # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
+ # Copyright (c) 2025 Gennady Kostyunin
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+ """Methods for updating documents."""
6
+
7
+ from __future__ import annotations
8
+
9
+ __all__ = ("Update",)
10
+
11
+ import concurrent.futures
12
+ from collections.abc import Callable
13
+ from typing import Any
14
+
15
+ import orjson
16
+ from anyio import Path
17
+
18
+
19
+ class Update:
20
+ """Methods for updating documents."""
21
+
22
+ @staticmethod
23
+ async def _task_update(
24
+ branch_number: int,
25
+ filter_fn: Callable,
26
+ hash_reduce_left: str,
27
+ db_root: str,
28
+ class_model: Any,
29
+ new_data: dict[str, Any],
30
+ ) -> int:
31
+ """Task for find documents.
32
+
33
+ This method is for internal use.
34
+
35
+ Returns:
36
+ The number of updated documents.
37
+ """
38
+ branch_number_as_hash: str = f"{branch_number:08x}"[hash_reduce_left:]
39
+ separated_hash: str = "/".join(list(branch_number_as_hash))
40
+ leaf_path: Path = Path(
41
+ *(
42
+ db_root,
43
+ class_model.__name__,
44
+ separated_hash,
45
+ "leaf.json",
46
+ ),
47
+ )
48
+ counter: int = 0
49
+ if await leaf_path.exists():
50
+ data_json: bytes = await leaf_path.read_bytes()
51
+ data: dict[str, str] = orjson.loads(data_json) or {}
52
+ new_state: dict[str, str] = {}
53
+ for _, val in data.items():
54
+ doc = class_model.model_validate_json(val)
55
+ if filter_fn(doc):
56
+ for key, value in new_data.items():
57
+ doc.__dict__[key] = value
58
+ new_state[key] = doc.model_dump_json()
59
+ counter += 1
60
+ await leaf_path.write_bytes(orjson.dumps(new_state))
61
+ return counter
62
+
63
+ async def update_many(
64
+ self,
65
+ filter_fn: Callable,
66
+ new_data: dict[str, Any],
67
+ ) -> int:
68
+ """Updates one or more documents matching the filter.
69
+
70
+ The search is based on the effect of a quantum loop.
71
+ The search effectiveness depends on the number of processor threads.
72
+ Ideally, hundreds and even thousands of threads are required.
73
+
74
+ Args:
75
+ filter_fn: A function that execute the conditions of filtering.
76
+ new_data: New data for the fields that need to be updated.
77
+
78
+ Returns:
79
+ The number of updated documents.
80
+ """
81
+ branch_numbers: range = range(1, self._max_branch_number)
82
+ update_task_fn: Callable = self._task_update
83
+ hash_reduce_left: int = self._hash_reduce_left
84
+ db_root: str = self._db_root
85
+ class_model: Any = self._class_model
86
+ counter: int = 0
87
+ with concurrent.futures.ThreadPoolExecutor(self._max_workers) as executor:
88
+ for branch_number in branch_numbers:
89
+ future = executor.submit(
90
+ update_task_fn,
91
+ branch_number,
92
+ filter_fn,
93
+ hash_reduce_left,
94
+ db_root,
95
+ class_model,
96
+ new_data,
97
+ )
98
+ counter += await future.result()
99
+ return counter
scruby/settings.py ADDED
@@ -0,0 +1,44 @@
1
+ # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
+ # Copyright (c) 2025 Gennady Kostyunin
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+ """Database settings.
6
+
7
+ The module contains the following parameters:
8
+
9
+ - `DB_ROOT` - Path to root directory of database. `By default = "ScrubyDB" (in root of project)`.
10
+ - `HASH_REDUCE_LEFT` - The length of the hash reduction on the left side.
11
+ - `0` - 4294967296 branches in collection.
12
+ - `2` - 16777216 branches in collection.
13
+ - `4` - 65536 branches in collection.
14
+ - `6` - 256 branches in collection (by default).
15
+ - `MAX_WORKERS` - The maximum number of processes that can be used `By default = None`.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ __all__ = (
21
+ "DB_ROOT",
22
+ "HASH_REDUCE_LEFT",
23
+ "MAX_WORKERS",
24
+ )
25
+
26
+ from typing import Literal
27
+
28
+ # Path to root directory of database
29
+ # By default = "ScrubyDB" (in root of project).
30
+ DB_ROOT: str = "ScrubyDB"
31
+
32
+ # The length of the hash reduction on the left side.
33
+ # 0 = 4294967296 branches in collection.
34
+ # 2 = 16777216 branches in collection.
35
+ # 4 = 65536 branches in collection.
36
+ # 6 = 256 branches in collection (by default).
37
+ # Number of branches is number of requests to the hard disk during quantum operations.
38
+ # Quantum operations: find_one, find_many, count_documents, delete_many, run_custom_task.
39
+ HASH_REDUCE_LEFT: Literal[0, 2, 4, 6] = 6
40
+
41
+ # The maximum number of processes that can be used to execute the given calls.
42
+ # If None, then as many worker processes will be
43
+ # created as the machine has processors.
44
+ MAX_WORKERS: int | None = None
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scruby
3
- Version: 0.17.0
4
- Summary: A fast key-value storage library.
5
- Project-URL: Homepage, https://github.com/kebasyaty/scruby
3
+ Version: 0.27.2
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
@@ -17,6 +17,7 @@ Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Operating System :: MacOS :: MacOS X
18
18
  Classifier: Operating System :: Microsoft :: Windows
19
19
  Classifier: Operating System :: POSIX
20
+ Classifier: Operating System :: POSIX :: Linux
20
21
  Classifier: Programming Language :: Python :: 3
21
22
  Classifier: Programming Language :: Python :: 3 :: Only
22
23
  Classifier: Programming Language :: Python :: 3.12
@@ -30,7 +31,7 @@ Requires-Dist: anyio>=4.10.0
30
31
  Requires-Dist: orjson>=3.11.3
31
32
  Requires-Dist: phonenumbers>=9.0.13
32
33
  Requires-Dist: pydantic-extra-types>=2.10.5
33
- Requires-Dist: pydantic[email]>=2.11.7
34
+ Requires-Dist: pydantic[email,timezone]>=2.11.7
34
35
  Description-Content-Type: text/markdown
35
36
 
36
37
  <div align="center">
@@ -52,7 +53,7 @@ Description-Content-Type: text/markdown
52
53
  <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
54
  <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
55
  <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>
56
+ <a href="https://pyrefly.org/" alt="Types: Pyrefly"><img src="https://img.shields.io/badge/types-Pyrefly-FFB74D.svg" alt="Types: Pyrefly"></a>
56
57
  <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
58
  <a href="https://pypi.org/project/scruby"><img src="https://img.shields.io/pypi/format/scruby" alt="Format"></a>
58
59
  <a href="https://pepy.tech/projects/scruby"><img src="https://static.pepy.tech/badge/scruby" alt="PyPI Downloads"></a>
@@ -65,11 +66,11 @@ Description-Content-Type: text/markdown
65
66
  <br>
66
67
  The database consists of collections.
67
68
  <br>
68
- The maximum size of the one collection is 16\*\*8=4294967296 branches,
69
+ The maximum size of the one collection is <b>16**8=4294967296</b> branches,
69
70
  <br>
70
71
  each branch can store one or more keys.
71
72
  <br>
72
- The value of any key in collection can be obtained in 8 steps,
73
+ The value of any key in collection can be obtained maximum in <b>8</b> steps,
73
74
  <br>
74
75
  thereby achieving high performance.
75
76
  <br>
@@ -108,26 +109,33 @@ See more examples here [https://kebasyaty.github.io/scruby/latest/pages/usage/](
108
109
  import anyio
109
110
  import datetime
110
111
  from typing import Annotated
111
- from pydantic import BaseModel, EmailStr
112
+ from pydantic import BaseModel, EmailStr, Field
112
113
  from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
113
- from scruby import Scruby, constants
114
-
115
- constants.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
114
+ from scruby import Scruby, settings
116
115
 
116
+ settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
117
+ settings.HASH_REDUCE_LEFT = 6 # By default = 6
118
+ settings.MAX_WORKERS = None # By default = None
117
119
 
118
120
  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")]
121
+ """User model."""
122
+ first_name: str = Field(strict=True)
123
+ last_name: str = Field(strict=True)
124
+ birthday: datetime.datetime = Field(strict=True)
125
+ email: EmailStr = Field(strict=True)
126
+ phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")] = Field(frozen=True)
127
+ # The key is always at the bottom
128
+ key: str = Field(
129
+ strict=True,
130
+ frozen=True,
131
+ default_factory=lambda data: data["phone"],
132
+ )
125
133
 
126
134
 
127
135
  async def main() -> None:
128
136
  """Example."""
129
- # Get collection of `User`.
130
- user_coll = Scruby(User)
137
+ # Get collection `User`.
138
+ user_coll = await Scruby.collection(User)
131
139
 
132
140
  user = User(
133
141
  first_name="John",
@@ -137,9 +145,9 @@ async def main() -> None:
137
145
  phone="+447986123456",
138
146
  )
139
147
 
140
- await user_coll.add_key(user.phone, user)
148
+ await user_coll.add_doc(user)
141
149
 
142
- await user_coll.update_key(user.phone, user)
150
+ await user_coll.update_doc(user)
143
151
 
144
152
  await user_coll.get_key("+447986123456") # => user
145
153
  await user_coll.get_key("key missing") # => KeyError
@@ -153,7 +161,7 @@ async def main() -> None:
153
161
 
154
162
  # Full database deletion.
155
163
  # Hint: The main purpose is tests.
156
- await Scruby.napalm()
164
+ Scruby.napalm()
157
165
 
158
166
 
159
167
  if __name__ == "__main__":
@@ -171,63 +179,66 @@ Ideally, hundreds and even thousands of threads are required.
171
179
  import anyio
172
180
  import datetime
173
181
  from typing import Annotated
174
- from pydantic import BaseModel, EmailStr
175
- from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
176
- from scruby import Scruby, constants
182
+ from pydantic import BaseModel, Field
183
+ from scruby import Scruby, settings
177
184
  from pprint import pprint as pp
178
185
 
179
- constants.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
180
- constants.HASH_REDUCE_LEFT = 6 # 256 branches in collection
181
- # (main purpose is tests).
182
-
183
-
184
- class User(BaseModel):
185
- """Model of User."""
186
- first_name: str
187
- last_name: str
188
- birthday: datetime.datetime
189
- email: EmailStr
190
- phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")]
186
+ settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
187
+ settings.HASH_REDUCE_LEFT = 6 # By default = 6
188
+ settings.MAX_WORKERS = None # By default = None
189
+
190
+
191
+ class Phone(BaseModel):
192
+ """Phone model."""
193
+ brand: str = Field(strict=True, frozen=True)
194
+ model: str = Field(strict=True, frozen=True)
195
+ screen_diagonal: float = Field(strict=True)
196
+ matrix_type: str = Field(strict=True)
197
+ # The key is always at the bottom
198
+ key: str = Field(
199
+ strict=True,
200
+ frozen=True,
201
+ default_factory=lambda data: f"{data['brand']}:{data['model']}",
202
+ )
191
203
 
192
204
 
193
205
  async def main() -> None:
194
206
  """Example."""
195
- # Get collection of `User`.
196
- user_coll = Scruby(User)
197
-
198
- # Create user.
199
- user = User(
200
- first_name="John",
201
- last_name="Smith",
202
- birthday=datetime.datetime(1970, 1, 1),
203
- email="John_Smith@gmail.com",
204
- phone="+447986123456",
207
+ # Get collection `Phone`.
208
+ phone_coll = await Scruby.collection(Phone)
209
+
210
+ # Create phone.
211
+ phone = Phone(
212
+ brand="Samsung",
213
+ model="Galaxy A26",
214
+ screen_diagonal=6.7,
215
+ matrix_type="Super AMOLED",
205
216
  )
206
217
 
207
- # Add user to collection.
208
- await user_coll.add_key(user.phone, user)
218
+ # Add phone to collection.
219
+ await phone_coll.add_doc(phone)
209
220
 
210
- # Find user by email.
211
- user_details: User | None = user_coll.find_one(
212
- filter_fn=lambda doc: doc.email == "John_Smith@gmail.com",
221
+ # Find phone by brand.
222
+ phone_details: Phone | None = await phone_coll.find_one(
223
+ filter_fn=lambda doc: doc.brand == "Samsung",
213
224
  )
214
- if user_details is not None:
215
- pp(user_details)
225
+ if phone_details is not None:
226
+ pp(phone_details)
216
227
  else:
217
- print("No User!")
228
+ print("No Phone!")
218
229
 
219
- # Find user by birthday.
220
- user_details: User | None = user_coll.find_one(
221
- filter_fn=lambda doc: doc.birthday == datetime.datetime(1970, 1, 1),
230
+ # Find phone by model.
231
+ phone_details: Phone | None = await phone_coll.find_one(
232
+ filter_fn=lambda doc: doc.model == "Galaxy A26",
222
233
  )
223
- if user_details is not None:
224
- pp(user_details)
234
+ if phone_details is not None:
235
+ pp(phone_details)
225
236
  else:
226
- print("No User!")
237
+ print("No Phone!")
227
238
 
228
239
  # Full database deletion.
229
240
  # Hint: The main purpose is tests.
230
- await Scruby.napalm()
241
+ Scruby.napalm()
231
242
 
232
243
 
233
244
  if __name__ == "__main__":
@@ -245,53 +256,60 @@ Ideally, hundreds and even thousands of threads are required.
245
256
  import anyio
246
257
  import datetime
247
258
  from typing import Annotated
248
- from pydantic import BaseModel, EmailStr
249
- from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
250
- from scruby import Scruby, constants
259
+ from pydantic import BaseModel, Field
260
+ from scruby import Scruby, settings
251
261
  from pprint import pprint as pp
252
262
 
253
- constants.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
254
- constants.HASH_REDUCE_LEFT = 6 # 256 branches in collection
255
- # (main purpose is tests).
256
-
257
-
258
- class User(BaseModel):
259
- """Model of User."""
260
- first_name: str
261
- last_name: str
262
- birthday: datetime.datetime
263
- email: EmailStr
264
- phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")]
263
+ settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
264
+ settings.HASH_REDUCE_LEFT = 6 # By default = 6
265
+ settings.MAX_WORKERS = None # By default = None
266
+
267
+
268
+ class Car(BaseModel):
269
+ """Car model."""
270
+ brand: str = Field(strict=True, frozen=True)
271
+ model: str = Field(strict=True, frozen=True)
272
+ year: int = Field(strict=True)
273
+ power_reserve: int = Field(strict=True)
274
+ # The key is always at the bottom
275
+ key: str = Field(
276
+ strict=True,
277
+ frozen=True,
278
+ default_factory=lambda data: f"{data['brand']}:{data['model']}",
279
+ )
265
280
 
266
281
 
267
282
  async def main() -> None:
268
283
  """Example."""
269
- # Get collection of `User`.
270
- user_coll = Scruby(User)
271
-
272
- # Create users.
273
- for num in range(1, 10):
274
- user = User(
275
- first_name="John",
276
- last_name="Smith",
277
- birthday=datetime.datetime(1970, 1, num),
278
- email=f"John_Smith_{num}@gmail.com",
279
- phone=f"+44798612345{num}",
284
+ # Get collection `Car`.
285
+ car_coll = await Scruby.collection(Car)
286
+
287
+ # Create cars.
288
+ for name in range(1, 10):
289
+ car = Car(
290
+ brand="Mazda",
291
+ model=f"EZ-6 {num}",
292
+ year=2025,
293
+ power_reserve=600,
280
294
  )
281
- await user_coll.add_key(user.phone, user)
295
+ await car_coll.add_doc(car)
282
296
 
283
- # Find users by email.
284
- users: list[User] | None = user_coll.find_many(
285
- filter_fn=lambda doc: doc.email == "John_Smith_5@gmail.com" or doc.email == "John_Smith_8@gmail.com",
297
+ # Find cars by brand and year.
298
+ car_list: list[Car] | None = await car_coll.find_many(
299
+ filter_fn=lambda doc: doc.brand == "Mazda" and doc.year == 2025,
286
300
  )
287
- if users is not None:
288
- pp(users)
301
+ if car_list is not None:
302
+ pp(car_list)
289
303
  else:
290
- print("No users!")
304
+ print("No cars!")
305
+
306
+ # Get collection list.
307
+ collection_list = await Scruby.collection_list()
308
+ print(collection_list) # ["Car"]
291
309
 
292
310
  # Full database deletion.
293
311
  # Hint: The main purpose is tests.
294
- await Scruby.napalm()
312
+ Scruby.napalm()
295
313
 
296
314
 
297
315
  if __name__ == "__main__":
@@ -0,0 +1,18 @@
1
+ scruby/__init__.py,sha256=IVQbFk2jKaRnHJQys_CcYXw3bvmN4igWFAQmzLxE1k0,1283
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=jmRJHs1fcSIIFm-yz2fq81-dC3not3EW9lKzLe1Yr64,627
8
+ scruby/mixins/collection.py,sha256=coF-IOhicV_EihDwnYf6SW5Mfi3nOFR0gAhCc619NmI,1382
9
+ scruby/mixins/count.py,sha256=OyRznB7AfZcSka3uFy2miMMOt85aUmiJN1RjJlQOTPI,2059
10
+ scruby/mixins/custom_task.py,sha256=BcSJfd0eUiyQ6RgTOSsTbJZvQ5ggXtp2YueXwPWgcd8,2386
11
+ scruby/mixins/delete.py,sha256=f3xkaMOR6wBqfjee1NCcw98fU8imZRMhSjucrxFcbFU,3055
12
+ scruby/mixins/docs.py,sha256=OUByWbHfNVWJVkUrhCsJZdVqf0zez_an6Gti2n5iKnM,5671
13
+ scruby/mixins/find.py,sha256=T2MzbP_vinf5qaLm4iR5T3iMFbMQNkUbQPRx2C5e20E,5373
14
+ scruby/mixins/update.py,sha256=oAfOxIPHl3ro8Vpjnzrdj8PSzIz5dIQO_AWSni5878Y,3238
15
+ scruby-0.27.2.dist-info/METADATA,sha256=Ua5HIieo3iv00SOez0jFMjiC7G324ZtY20FH76x_Xgw,10659
16
+ scruby-0.27.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
+ scruby-0.27.2.dist-info/licenses/LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
18
+ scruby-0.27.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
scruby/constants.py DELETED
@@ -1,31 +0,0 @@
1
- """Constant variables.
2
-
3
- The module contains the following variables:
4
-
5
- - `DB_ROOT` - Path to root directory of database. `By default = "ScrubyDB"` (*in root of project*).
6
- - `HASH_REDUCE_LEFT` - The length of the hash reduction on the left side.
7
- - `0` - 4294967296 branches in collection (by default).
8
- - `2` - 16777216 branches in collectionю
9
- - `4` - 65536 branches in collectionю
10
- - `6` - 256 branches in collection (main purpose is tests).
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- __all__ = (
16
- "DB_ROOT",
17
- "HASH_REDUCE_LEFT",
18
- )
19
-
20
- from typing import Literal
21
-
22
- # Path to root directory of database
23
- # By default = "ScrubyDB" (in root of project).
24
- DB_ROOT: str = "ScrubyDB"
25
-
26
- # The length of the hash reduction on the left side.
27
- # 0 = 4294967296 branches in collection (by default).
28
- # 2 = 16777216 branches in collectionю
29
- # 4 = 65536 branches in collectionю
30
- # 6 = 256 branches in collection (main purpose is tests).
31
- HASH_REDUCE_LEFT: Literal[0, 2, 4, 6] = 0
@@ -1,10 +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=KRx7naXDNzT1WVKiYXFJ8uYxS9-aSqoiilsa4qR6y1Y,976
4
- scruby/db.py,sha256=Ml2A46bOX1P1sF4vKSyEFqklMAjoSpn1lGvj8VCtnSY,26323
5
- scruby/errors.py,sha256=nMhSLAKo7F3lTB_lgOZ5ksMuBtA1ugHqF8iyp1EY-sY,1042
6
- scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- scruby-0.17.0.dist-info/METADATA,sha256=8RMszCN_EnAzMTN3wpDrIh8BrrdlxYTX__pLyy6siz8,10079
8
- scruby-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
- scruby-0.17.0.dist-info/licenses/LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
10
- scruby-0.17.0.dist-info/RECORD,,