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.

@@ -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.10.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
@@ -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>A fast key-value storage library.</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://github.com/kebasyaty/scruby/issues"><img src="https://img.shields.io/github/issues/kebasyaty/scruby.svg" alt="GitHub issues"></a>
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://github.com/kebasyaty/scruby"><img src="https://img.shields.io/github/languages/top/kebasyaty/scruby" alt="Top"></a>
63
- <a href="https://github.com/kebasyaty/scruby"><img src="https://img.shields.io/github/repo-size/kebasyaty/scruby" alt="Size"></a>
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
- Scruby is a fast key-value storage asynchronous library that provides an
69
- <br>
70
- ordered mapping from string keys to string values.
71
- <br>
72
- The library uses fractal-tree addressing.
73
- <br>
74
- The database consists of collections.
75
- <br>
76
- The maximum size of the one collection is 16**8=4294967296 branches,
77
- each branch can store one or more keys.
78
- <br>
79
- The value of any key in collection can be obtained in 8 steps, thereby achieving high performance.
80
- <br>
81
- In the future, to search by value of key, the use of a quantum loop is supposed.
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
- """Model of User."""
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.set_key("+447986123456", user)
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
- await Scruby.napalm()
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, EmailStr
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 # 256 branches in collection
179
- # (main purpose is tests).
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 `User`.
192
- user_coll = Scruby(User)
193
-
194
- # Create user.
195
- user = User(
196
- first_name="John",
197
- last_name="Smith",
198
- birthday=datetime.datetime(1970, 1, 1),
199
- email="John_Smith@gmail.com",
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 user to collection.
204
- await user_coll.set_key("+447986123456", user)
215
+ # Add phone to collection.
216
+ await phone_coll.add_doc(phone)
205
217
 
206
- # Find user by email.
207
- user_details: User | None = user_coll.find_one(
208
- filter_fn=lambda doc: doc.email == "John_Smith@gmail.com",
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 user_details is not None:
211
- pp(user_details)
222
+ if phone_details is not None:
223
+ pp(phone_details)
212
224
  else:
213
- print("No User!")
225
+ print("No Phone!")
214
226
 
215
- # Find user by birthday.
216
- user_details: User | None = user_coll.find_one(
217
- filter_fn=lambda doc: doc.birthday == datetime.datetime(1970, 1, 1),
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 user_details is not None:
220
- pp(user_details)
231
+ if phone_details is not None:
232
+ pp(phone_details)
221
233
  else:
222
- print("No User!")
234
+ print("No Phone!")
223
235
 
224
236
  # Full database deletion.
225
237
  # Hint: The main purpose is tests.
226
- await Scruby.napalm()
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, EmailStr
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 # 256 branches in collection
250
- # (main purpose is tests).
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 `User`.
263
- user_coll = Scruby(User)
264
-
265
- # Create users.
266
- for num in range(1, 10):
267
- user = User(
268
- first_name="John",
269
- last_name="Smith",
270
- birthday=datetime.datetime(1970, 1, num),
271
- email=f"John_Smith_{num}@gmail.com",
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 db.set_key(f"+44798612345{num}", user)
291
+ await car_coll.add_doc(car)
275
292
 
276
- # Find users by email.
277
- users: list[User] | None = user_coll.find(
278
- filter_fn=lambda doc: doc.email == "John_Smith_5@gmail.com" or doc.email == "John_Smith_8@gmail.com",
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 users is not None:
281
- pp(users)
297
+ if car_list is not None:
298
+ pp(car_list)
282
299
  else:
283
- print("No users!")
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
- await Scruby.napalm()
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,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
@@ -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.
@@ -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,,