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 +1 -1
- scruby/models/__init__.py +15 -0
- scruby/{models.py → models/crypt_model.py} +28 -39
- scruby/models/scruby_model.py +32 -0
- {scruby-2.7.0.dist-info → scruby-2.7.2.dist-info}/METADATA +87 -2
- {scruby-2.7.0.dist-info → scruby-2.7.2.dist-info}/RECORD +9 -7
- {scruby-2.7.0.dist-info → scruby-2.7.2.dist-info}/WHEEL +0 -0
- {scruby-2.7.0.dist-info → scruby-2.7.2.dist-info}/licenses/GPL-3.0-LICENSE +0 -0
- {scruby-2.7.0.dist-info → scruby-2.7.2.dist-info}/licenses/MIT-LICENSE +0 -0
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 = "
|
|
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
|
-
"""
|
|
1
|
+
"""Crypt Model.
|
|
2
2
|
|
|
3
|
-
|
|
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__ = ("
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
136
|
-
|
|
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
|
|
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.
|
|
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=
|
|
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
|
|
21
|
-
scruby
|
|
22
|
-
scruby
|
|
23
|
-
scruby-2.7.
|
|
24
|
-
scruby-2.7.
|
|
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
|
|
File without changes
|
|
File without changes
|