scruby 2.7.0__py3-none-any.whl → 2.7.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.
scruby/db.py CHANGED
@@ -46,7 +46,7 @@ class Scruby(
46
46
  #
47
47
  if __debug__:
48
48
  if ScrubyModel not in class_model.__bases__:
49
- msg = "Scruby => Argument `class_model` does not contain the base class `ScrubyModel`."
49
+ msg = "Argument `class_model` does not contain the base class `ScrubyModel`."
50
50
  raise AssertionError(msg)
51
51
  if "key" not in self.key_list:
52
52
  msg = f"Model: {class_model.__name__} => The `key` field is missing."
@@ -0,0 +1,15 @@
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
+ # SPDX-License-Identifier: GPL-3.0-or-later
5
+ """Models."""
6
+
7
+ from __future__ import annotations
8
+
9
+ __all__ = (
10
+ "ScrubyModel",
11
+ "CryptModel",
12
+ )
13
+
14
+ from scruby.models.crypt_model import CryptModel
15
+ from scruby.models.scruby_model import ScrubyModel
@@ -1,42 +1,17 @@
1
- """Scruby Models.
1
+ """Crypt Model.
2
2
 
3
- The module contains the following classes:
4
-
5
- - `ScrubyModel` - A base class for creating Scruby models.
6
- - `CryptModel` - Add password support to the Scruby model.
3
+ For operations with passwords in Scruby models.
7
4
  """
8
5
 
9
6
  from __future__ import annotations
10
7
 
11
- __all__ = ("ScrubyModel",)
8
+ __all__ = ("CryptModel",)
12
9
 
13
10
 
14
- from datetime import datetime
15
11
  from typing import Annotated
16
12
 
17
13
  import bcrypt
18
- from pydantic import BaseModel, ConfigDict, Field, SecretStr
19
-
20
-
21
- class ScrubyModel(BaseModel):
22
- """A base class for creating Scruby models."""
23
-
24
- model_config = ConfigDict(strict=True)
25
-
26
- created_at: Annotated[
27
- datetime | None,
28
- Field(
29
- title="Created at",
30
- default=None,
31
- ),
32
- ]
33
- updated_at: Annotated[
34
- datetime | None,
35
- Field(
36
- title="Updated at",
37
- default=None,
38
- ),
39
- ]
14
+ from pydantic import BaseModel, Field, SecretStr
40
15
 
41
16
 
42
17
  class CryptModel(BaseModel):
@@ -59,7 +34,7 @@ class CryptModel(BaseModel):
59
34
  ]
60
35
 
61
36
  def hash_raw_password(self, password: str | SecretStr) -> str:
62
- """Takes a string and converts it to a hash.
37
+ """Takes the raw password string and converts it into a hash.
63
38
 
64
39
  Uses the bcrypt library.
65
40
 
@@ -79,7 +54,7 @@ class CryptModel(BaseModel):
79
54
  return hashed_bytes.decode("utf-8")
80
55
 
81
56
  def set_password(self, password: str | SecretStr) -> None:
82
- """Converts the value to a hash and adds it to the password field.
57
+ """Converts the raw password to a hash and adds it to the password field.
83
58
 
84
59
  Uses the bcrypt library.
85
60
 
@@ -89,10 +64,16 @@ class CryptModel(BaseModel):
89
64
  Returns:
90
65
  None
91
66
  """
92
- assert isinstance(password, (str, SecretStr)), "Valid type: str | SecretStr"
67
+ assert isinstance(password, (str, SecretStr)), "Valid type: str | SecretStr."
68
+ #
69
+ # Throw an exception if the password is present
70
+ if bool(self.password):
71
+ msg = "The password already exists. To update it, use the `update_password` method."
72
+ raise ValueError(msg)
93
73
  #
94
74
  hashed_password = self.hash_raw_password(password)
95
- # To temporarily bypass the `frozen=True` limitation
75
+ # Set user password.
76
+ # Hint: To temporarily bypass the `frozen=True` limitation.
96
77
  object.__setattr__(self, "password", hashed_password) # noqa: PLC2801
97
78
 
98
79
  def password_is_valid(self, password: str | SecretStr) -> bool:
@@ -107,7 +88,7 @@ class CryptModel(BaseModel):
107
88
  Returns:
108
89
  True if the passwords are the same and False if they are not the same.
109
90
  """
110
- assert isinstance(password, (str, SecretStr)), "Valid type: str | SecretStr"
91
+ assert isinstance(password, (str, SecretStr)), "Valid type: str | SecretStr."
111
92
  #
112
93
  if not bool(self.password):
113
94
  return False
@@ -132,15 +113,23 @@ class CryptModel(BaseModel):
132
113
  ValueError: If the current password cannot be updated because it is missing.
133
114
  ValueError: If the old password does not match the current one.
134
115
  """
135
- assert isinstance(old_password, (str, SecretStr)), "Valid type: str | SecretStr"
136
- assert isinstance(new_password, (str, SecretStr)), "Valid type: str | SecretStr"
116
+ if __debug__:
117
+ if not isinstance(old_password, (str, SecretStr)):
118
+ msg = "Argument: `old_password` => Valid type: str | SecretStr."
119
+ raise AssertionError(msg)
120
+ if not isinstance(new_password, (str, SecretStr)):
121
+ msg = "Argument: `new_password` => Valid type: str | SecretStr."
122
+ raise AssertionError(msg)
137
123
  #
138
124
  # Throw an exception if the current password is missing
139
- if self.password is None:
140
- msg = "The current password cannot be updated because it is missing"
125
+ if not bool(self.password):
126
+ msg = "The current password cannot be updated because it is missing."
141
127
  raise ValueError(msg)
142
128
  # Throw an exception if the old password does not match the current one
143
129
  if not self.password_is_valid(old_password):
144
- raise ValueError("Old password doesn't match")
130
+ raise ValueError("Old password doesn't match.")
131
+ # Pre-reset the password to default state.
132
+ # Hint: To temporarily bypass the `frozen=True` limitation.
133
+ object.__setattr__(self, "password", None) # noqa: PLC2801
145
134
  # Replace the current password with a new one
146
135
  self.set_password(new_password)
@@ -0,0 +1,32 @@
1
+ """Scruby Model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = ("ScrubyModel",)
6
+
7
+
8
+ from datetime import datetime
9
+ from typing import Annotated
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+
13
+
14
+ class ScrubyModel(BaseModel):
15
+ """A base class for creating Scruby models."""
16
+
17
+ model_config = ConfigDict(strict=True)
18
+
19
+ created_at: Annotated[
20
+ datetime | None,
21
+ Field(
22
+ title="Created at",
23
+ default=None,
24
+ ),
25
+ ]
26
+ updated_at: Annotated[
27
+ datetime | None,
28
+ Field(
29
+ title="Updated at",
30
+ default=None,
31
+ ),
32
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scruby
3
- Version: 2.7.0
3
+ Version: 2.7.2
4
4
  Summary: Asynchronous library for building and managing a hybrid database, by scheme of key-value.
5
5
  Project-URL: Bug Tracker, https://github.com/kebasyaty/scruby/issues
6
6
  Project-URL: Changelog, https://github.com/kebasyaty/scruby/blob/v2/CHANGELOG.md
@@ -147,7 +147,6 @@ from scruby import Scruby, ScrubyModel
147
147
 
148
148
  class User(ScrubyModel):
149
149
  """User model."""
150
- username: str
151
150
  first_name: str
152
151
  last_name: str
153
152
  birthday: datetime
@@ -220,6 +219,92 @@ async def main() -> None:
220
219
  Scruby.napalm()
221
220
 
222
221
 
222
+ if __name__ == "__main__":
223
+ anyio.run(main)
224
+ ```
225
+
226
+ ```python
227
+ """Operations with passwords.
228
+
229
+ To operations with a password, use only special methods.
230
+ Do not use this field directly.
231
+ """
232
+
233
+ import anyio
234
+ from datetime import datetime
235
+ from zoneinfo import ZoneInfo
236
+ from typing import Annotated
237
+ from pydantic import EmailStr, Field
238
+ from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
239
+ from scruby import CryptModel, Scruby, ScrubyModel
240
+
241
+
242
+ class User(ScrubyModel, CryptModel):
243
+ """User model."""
244
+
245
+ username: str
246
+ first_name: str
247
+ last_name: str
248
+ birthday: datetime
249
+ email: EmailStr
250
+ phone: Annotated[
251
+ PhoneNumber,
252
+ PhoneNumberValidator(number_format="E164"),
253
+ Field(strict=False),
254
+ ]
255
+ # key is always at bottom
256
+ key: Annotated[
257
+ str,
258
+ Field(
259
+ frozen=True,
260
+ default_factory=lambda data: data["username"],
261
+ ),
262
+ ]
263
+
264
+
265
+ async def main() -> None:
266
+ """Example."""
267
+ # Activate database.
268
+ Scruby.run()
269
+
270
+ # Create/get the `User` collection.
271
+ user_coll = Scruby(User)
272
+
273
+ # Create user
274
+ user = User(
275
+ username="user_1",
276
+ first_name="John",
277
+ last_name="Smith",
278
+ birthday=datetime(1970, 1, 1, tzinfo=ZoneInfo("UTC")),
279
+ email="John_Smith@gmail.com",
280
+ phone="+447986123456",
281
+ )
282
+
283
+ test_pass = "user_pass_123"
284
+
285
+ # Add user password
286
+ user.set_password(test_pass)
287
+
288
+ # Add user to collection
289
+ await user_coll.add_doc(user)
290
+
291
+ # Get user details
292
+ user_details = await user_coll.get_doc("user_1")
293
+
294
+ # Check a password
295
+ user_details.password_is_valid(test_pass) # True
296
+
297
+ # Update existing password
298
+ user_details.update_password(test_pass, new_test_pass)
299
+
300
+ # Update user data in a collection
301
+ await user_coll.update_doc(user_details)
302
+
303
+ # Full database deletion.
304
+ # Hint: The main purpose is tests.
305
+ Scruby.napalm()
306
+
307
+
223
308
  if __name__ == "__main__":
224
309
  anyio.run(main)
225
310
  ```
@@ -2,11 +2,10 @@ scruby/__init__.py,sha256=rHX2oQahEBft6ZDh1lLE2YxP5ZoMfP4m7PBBUfEGuJw,1399
2
2
  scruby/aggregation.py,sha256=NBFxQqyRqUG2KIuD9fbl4uzSHJWTaskjiZ1YNBa-Zbo,3575
3
3
  scruby/cache.py,sha256=DRcTdQbh1DhTQDGK5oC_mZrjcQg6g06vJ1e61RfNX94,3864
4
4
  scruby/config.py,sha256=clGj5_CyHEejmQUvtV0fD9PoQoPeYoh0Wge_i2BIMsg,5255
5
- scruby/db.py,sha256=QlIiGJYgbVO2jKTglvrkUQVtONLQeBpkIyeUQS2xTEU,8433
5
+ scruby/db.py,sha256=J1WldCYoWtNTo1T6QGQ4n0rVYR6D8Kv6oesOBUejn60,8423
6
6
  scruby/errors.py,sha256=lTWiHzyO5Es9Nkf7quODJjONGn6ifcL95qlpA4epQQM,1386
7
7
  scruby/meta.py,sha256=6HPgqqlKH7Vo6ibyGzxR3DzNcnc_DmjaP5q8s6kl4yA,1230
8
8
  scruby/migration.py,sha256=mmJkqVBd_eoufWD8YsRq09VCZFZzEwfxsU1gtvJnjvU,1730
9
- scruby/models.py,sha256=d5YFbfyAxIjXXlO-LJsyMTu0biJ2YqdmlTVw22QPN2Y,4437
10
9
  scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
10
  scruby/utils.py,sha256=boobO6lHOElF38fe8gRDV_gnrhuwPkP9yI8rONAJzZ4,2475
12
11
  scruby/mixins/__init__.py,sha256=nT79e80zXliuTGhR9CFosI2-3PQUCKwXbR7wPiFwgrU,669
@@ -17,8 +16,11 @@ scruby/mixins/delete.py,sha256=InKVIud_ZYx9-CchUz_IygQDXMynNi4jQ0HKYeHC_R8,4328
17
16
  scruby/mixins/find.py,sha256=YlK8L5R5qnjooDR_WDkd6ckPgE8dNTTee31S4P8-Wuo,10504
18
17
  scruby/mixins/keys.py,sha256=sd8vcM0YXouVwwgsQwtYFaV3RyAEfygpPf4a77_hN6A,11305
19
18
  scruby/mixins/update.py,sha256=TyxvxB-gNijlfzTmhwrq0ydvu0C3R-RihW5h5tJ96bM,4964
20
- scruby-2.7.0.dist-info/METADATA,sha256=PJqezaUXh9nNn5w4Stf-4caeg6WyiXKq9DNmfIm7gtY,13369
21
- scruby-2.7.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
22
- scruby-2.7.0.dist-info/licenses/GPL-3.0-LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
23
- scruby-2.7.0.dist-info/licenses/MIT-LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
24
- scruby-2.7.0.dist-info/RECORD,,
19
+ scruby/models/__init__.py,sha256=Xqk48ZYWzmrygqoi7R9O8dJdd40ochwTg-vmCDkZUY8,418
20
+ scruby/models/crypt_model.py,sha256=6ia-fZzvPh0fxDIkBXp1m3E538xtjeI0OoxaY25tnNE,4564
21
+ scruby/models/scruby_model.py,sha256=u9uAF1_aBvaooSeKPzQRmVYHPy4XWusLX85xfzc6oVM,608
22
+ scruby-2.7.2.dist-info/METADATA,sha256=2thGMIKe3ytqOQgHERbvC5ef74T_0JDozEjpJ8QgX1I,15272
23
+ scruby-2.7.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
24
+ scruby-2.7.2.dist-info/licenses/GPL-3.0-LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
+ scruby-2.7.2.dist-info/licenses/MIT-LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
26
+ scruby-2.7.2.dist-info/RECORD,,
File without changes