scruby 0.28.3__py3-none-any.whl → 0.31.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,4 +1,3 @@
1
- #
2
1
  # .dP"Y8  dP""b8 88""Yb 88   88 88""Yb Yb  dP
3
2
  # `Ybo." dP   `" 88__dP 88   88 88__dP  YbdP
4
3
  # o.`Y8b Yb      88"Yb  Y8   8P 88""Yb   8P
@@ -6,7 +5,6 @@
6
5
  #
7
6
  # Copyright (c) 2025 Gennady Kostyunin
8
7
  # SPDX-License-Identifier: MIT
9
- #
10
8
  """Asynchronous library for building and managing a hybrid database, by scheme of key-value.
11
9
 
12
10
  The library uses fractal-tree addressing and
@@ -28,7 +26,8 @@ from __future__ import annotations
28
26
  __all__ = (
29
27
  "settings",
30
28
  "Scruby",
29
+ "ScrubyModel",
31
30
  )
32
31
 
33
32
  from scruby import settings
34
- from scruby.db import Scruby
33
+ from scruby.db import Scruby, ScrubyModel
scruby/aggregation.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Aggregation classes."""
6
5
 
7
6
  from __future__ import annotations
scruby/db.py CHANGED
@@ -1,17 +1,20 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Creation and management of the database."""
6
5
 
7
6
  from __future__ import annotations
8
7
 
9
- __all__ = ("Scruby",)
8
+ __all__ = (
9
+ "Scruby",
10
+ "ScrubyModel",
11
+ )
10
12
 
11
13
  import contextlib
12
14
  import logging
13
15
  import re
14
16
  import zlib
17
+ from datetime import datetime
15
18
  from shutil import rmtree
16
19
  from typing import Any, Literal, Never, assert_never
17
20
 
@@ -31,6 +34,13 @@ class _Meta(BaseModel):
31
34
  counter_documents: int
32
35
 
33
36
 
37
+ class ScrubyModel(BaseModel):
38
+ """Additional fields for models."""
39
+
40
+ created_at: datetime | None = None
41
+ updated_at: datetime | None = None
42
+
43
+
34
44
  class Scruby(
35
45
  mixins.Keys,
36
46
  mixins.Find,
@@ -53,13 +63,13 @@ class Scruby(
53
63
  # The maximum number of branches.
54
64
  match self._hash_reduce_left:
55
65
  case 0:
56
- self._max_branch_number = 4294967296
66
+ self._max_number_branch = 4294967296
57
67
  case 2:
58
- self._max_branch_number = 16777216
68
+ self._max_number_branch = 16777216
59
69
  case 4:
60
- self._max_branch_number = 65536
70
+ self._max_number_branch = 65536
61
71
  case 6:
62
- self._max_branch_number = 256
72
+ self._max_number_branch = 256
63
73
  case _ as unreachable:
64
74
  msg: str = f"{unreachable} - Unacceptable value for HASH_REDUCE_LEFT."
65
75
  logging.critical(msg)
@@ -70,42 +80,67 @@ class Scruby(
70
80
  """Get an object to access a collection.
71
81
 
72
82
  Args:
73
- class_model: Class of Model (pydantic.BaseModel).
83
+ class_model (Any): Class of Model (ScrubyModel).
74
84
 
75
85
  Returns:
76
86
  Instance of Scruby for access a collection.
77
87
  """
78
- assert BaseModel in class_model.__bases__, "`class_model` does not contain the base class `pydantic.BaseModel`!"
79
-
88
+ if __debug__:
89
+ # Check if the object belongs to the class `ScrubyModel`
90
+ if ScrubyModel not in class_model.__bases__:
91
+ msg = (
92
+ "Method: `collection` => argument `class_model` " + "does not contain the base class `ScrubyModel`!"
93
+ )
94
+ raise AssertionError(msg)
95
+ # Checking the model for the presence of a key.
96
+ model_fields = list(class_model.model_fields.keys())
97
+ if "key" not in model_fields:
98
+ msg = f"Model: {class_model.__name__} => The `key` field is missing!"
99
+ raise AssertionError(msg)
100
+ if "created_at" not in model_fields:
101
+ msg = f"Model: {class_model.__name__} => The `created_at` field is missing!"
102
+ raise AssertionError(msg)
103
+ if "updated_at" not in model_fields:
104
+ msg = f"Model: {class_model.__name__} => The `updated_at` field is missing!"
105
+ raise AssertionError(msg)
106
+ # Check the length of the collection name for an acceptable size.
107
+ len_db_root_absolut_path = len(str(await Path(settings.DB_ROOT).resolve()).encode("utf-8"))
108
+ len_model_name = len(class_model.__name__)
109
+ len_full_path_leaf = len_db_root_absolut_path + len_model_name + 26
110
+ if len_full_path_leaf > 255:
111
+ excess = len_full_path_leaf - 255
112
+ msg = (
113
+ f"Model: {class_model.__name__} => The collection name is too long, "
114
+ + f"it exceeds the limit of {excess} characters!"
115
+ )
116
+ raise AssertionError(msg)
117
+ # Create instance of Scruby
80
118
  instance = cls()
119
+ # Add model class to Scruby
81
120
  instance.__dict__["_class_model"] = class_model
82
- # Caching a pati for metadata.
83
- # The zero branch is reserved for metadata.
84
- branch_number: int = 0
85
- branch_number_as_hash: str = f"{branch_number:08x}"[settings.HASH_REDUCE_LEFT :]
86
- separated_hash: str = "/".join(list(branch_number_as_hash))
121
+ # Create a path for metadata.
87
122
  meta_dir_path_tuple = (
88
123
  settings.DB_ROOT,
89
124
  class_model.__name__,
90
- separated_hash,
125
+ "meta",
91
126
  )
92
127
  instance.__dict__["_meta_path"] = Path(
93
128
  *meta_dir_path_tuple,
94
129
  "meta.json",
95
130
  )
96
131
  # Create metadata for collection, if missing.
97
- branch_path = Path(*meta_dir_path_tuple)
98
- if not await branch_path.exists():
99
- await branch_path.mkdir(parents=True)
132
+ meta_dir_path = Path(*meta_dir_path_tuple)
133
+ if not await meta_dir_path.exists():
134
+ await meta_dir_path.mkdir(parents=True)
100
135
  meta = _Meta(
101
136
  db_root=settings.DB_ROOT,
102
137
  collection_name=class_model.__name__,
103
138
  hash_reduce_left=settings.HASH_REDUCE_LEFT,
104
- max_branch_number=instance.__dict__["_max_branch_number"],
139
+ max_branch_number=instance.__dict__["_max_number_branch"],
105
140
  counter_documents=0,
106
141
  )
107
142
  meta_json = meta.model_dump_json()
108
- meta_path = Path(*(branch_path, "meta.json"))
143
+ meta_path = Path(*(meta_dir_path, "meta.json"))
109
144
  await meta_path.write_text(meta_json, "utf-8")
110
145
  return instance
111
146
 
scruby/errors.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Scruby Exceptions."""
6
5
 
7
6
  from __future__ import annotations
scruby/mixins/__init__.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Mixins."""
6
5
 
7
6
  from __future__ import annotations
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Methods for working with collections."""
6
5
 
7
6
  from __future__ import annotations
scruby/mixins/count.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Methods for counting the number of documents."""
6
5
 
7
6
  from __future__ import annotations
@@ -31,18 +30,19 @@ class Count:
31
30
  ) -> int:
32
31
  """Count the number of documents a matching the filter in this collection.
33
32
 
34
- The search is based on the effect of a quantum loop.
35
- The search effectiveness depends on the number of processor threads.
33
+ Attention:
34
+ - The search is based on the effect of a quantum loop.
35
+ - The search effectiveness depends on the number of processor threads.
36
36
 
37
37
  Args:
38
- filter_fn: A function that execute the conditions of filtering.
38
+ filter_fn (Callable): A function that execute the conditions of filtering.
39
39
 
40
40
  Returns:
41
41
  The number of documents.
42
42
  """
43
43
  # Variable initialization
44
44
  search_task_fn: Callable = self._task_find
45
- branch_numbers: range = range(1, self._max_branch_number)
45
+ branch_numbers: range = range(self._max_number_branch)
46
46
  hash_reduce_left: int = self._hash_reduce_left
47
47
  db_root: str = self._db_root
48
48
  class_model: Any = self._class_model
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Quantum methods for running custom tasks."""
6
5
 
7
6
  from __future__ import annotations
@@ -11,64 +10,35 @@ __all__ = ("CustomTask",)
11
10
  from collections.abc import Callable
12
11
  from typing import Any
13
12
 
14
- import orjson
15
- from anyio import Path
16
-
17
13
 
18
14
  class CustomTask:
19
15
  """Quantum methods for running custom tasks."""
20
16
 
21
- @staticmethod
22
- async def _task_get_docs(
23
- branch_number: int,
24
- hash_reduce_left: int,
25
- db_root: str,
26
- class_model: Any,
27
- ) -> list[Any]:
28
- """Get documents for custom task.
29
-
30
- This method is for internal use.
31
-
32
- Returns:
33
- List of documents.
34
- """
35
- branch_number_as_hash: str = f"{branch_number:08x}"[hash_reduce_left:]
36
- separated_hash: str = "/".join(list(branch_number_as_hash))
37
- leaf_path: Path = Path(
38
- *(
39
- db_root,
40
- class_model.__name__,
41
- separated_hash,
42
- "leaf.json",
43
- ),
44
- )
45
- docs: list[Any] = []
46
- if await leaf_path.exists():
47
- data_json: bytes = await leaf_path.read_bytes()
48
- data: dict[str, str] = orjson.loads(data_json) or {}
49
- for _, val in data.items():
50
- docs.append(class_model.model_validate_json(val))
51
- return docs
52
-
53
- async def run_custom_task(self, custom_task_fn: Callable, limit_docs: int = 1000) -> Any:
17
+ async def run_custom_task(
18
+ self,
19
+ custom_task_fn: Callable,
20
+ filter_fn: Callable = lambda _: True,
21
+ **kwargs,
22
+ ) -> Any:
54
23
  """Running custom task.
55
24
 
56
- This method running a task created on the basis of a quantum loop.
57
- Effectiveness running task depends on the number of processor threads.
25
+ Attention:
26
+ - The search is based on the effect of a quantum loop.
27
+ - The search effectiveness depends on the number of processor threads.
58
28
 
59
29
  Args:
60
- custom_task_fn: A function that execute the custom task.
61
- limit_docs: Limiting the number of documents. By default = 1000.
30
+ custom_task_fn (Callable): A function that execute the custom task.
62
31
 
63
32
  Returns:
64
33
  The result of a custom task.
65
34
  """
66
- kwargs = {
67
- "get_docs_fn": self._task_get_docs,
68
- "branch_numbers": range(1, self._max_branch_number),
69
- "hash_reduce_left": self._hash_reduce_left,
70
- "db_root": self._db_root,
71
- "class_model": self._class_model,
72
- "limit_docs": limit_docs,
73
- }
74
- return await custom_task_fn(**kwargs)
35
+ return await custom_task_fn(
36
+ search_task_fn=self._task_find,
37
+ filter_fn=filter_fn,
38
+ branch_numbers=range(self._max_number_branch),
39
+ hash_reduce_left=self._hash_reduce_left,
40
+ db_root=self._db_root,
41
+ class_model=self._class_model,
42
+ max_workers=self._max_workers,
43
+ **kwargs,
44
+ )
scruby/mixins/delete.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Methods for deleting documents."""
6
5
 
7
6
  from __future__ import annotations
@@ -64,18 +63,19 @@ class Delete:
64
63
  ) -> int:
65
64
  """Delete one or more documents matching the filter.
66
65
 
67
- The search is based on the effect of a quantum loop.
68
- The search effectiveness depends on the number of processor threads.
66
+ Attention:
67
+ - The search is based on the effect of a quantum loop.
68
+ - The search effectiveness depends on the number of processor threads.
69
69
 
70
70
  Args:
71
- filter_fn: A function that execute the conditions of filtering.
71
+ filter_fn (Callable): A function that execute the conditions of filtering.
72
72
 
73
73
  Returns:
74
74
  The number of deleted documents.
75
75
  """
76
76
  # Variable initialization
77
77
  search_task_fn: Callable = self._task_delete
78
- branch_numbers: range = range(1, self._max_branch_number)
78
+ branch_numbers: range = range(self._max_number_branch)
79
79
  hash_reduce_left: int = self._hash_reduce_left
80
80
  db_root: str = self._db_root
81
81
  class_model: Any = self._class_model
scruby/mixins/find.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Quantum methods for searching documents."""
6
5
 
7
6
  from __future__ import annotations
@@ -34,7 +33,6 @@ class Find:
34
33
  Returns:
35
34
  List of documents or None.
36
35
  """
37
- # Variable initialization
38
36
  branch_number_as_hash: str = f"{branch_number:08x}"[hash_reduce_left:]
39
37
  separated_hash: str = "/".join(list(branch_number_as_hash))
40
38
  leaf_path: Path = Path(
@@ -61,8 +59,9 @@ class Find:
61
59
  ) -> Any | None:
62
60
  """Find one document matching the filter.
63
61
 
64
- The search is based on the effect of a quantum loop.
65
- The search effectiveness depends on the number of processor threads.
62
+ Attention:
63
+ - The search is based on the effect of a quantum loop.
64
+ - The search effectiveness depends on the number of processor threads.
66
65
 
67
66
  Args:
68
67
  filter_fn (Callable): A function that execute the conditions of filtering.
@@ -72,7 +71,7 @@ class Find:
72
71
  """
73
72
  # Variable initialization
74
73
  search_task_fn: Callable = self._task_find
75
- branch_numbers: range = range(1, self._max_branch_number)
74
+ branch_numbers: range = range(self._max_number_branch)
76
75
  hash_reduce_left: int = self._hash_reduce_left
77
76
  db_root: str = self._db_root
78
77
  class_model: Any = self._class_model
@@ -100,14 +99,15 @@ class Find:
100
99
  ) -> list[Any] | None:
101
100
  """Find many documents matching the filter.
102
101
 
103
- The search is based on the effect of a quantum loop.
104
- The search effectiveness depends on the number of processor threads.
102
+ Attention:
103
+ - The search is based on the effect of a quantum loop.
104
+ - The search effectiveness depends on the number of processor threads.
105
105
 
106
106
  Args:
107
107
  filter_fn (Callable): A function that execute the conditions of filtering.
108
108
  By default it searches for all documents.
109
109
  limit_docs (int): Limiting the number of documents. By default = 1000.
110
- page_number (int): For pagination output. By default = 1.
110
+ page_number (int): For pagination. By default = 1.
111
111
  Number of documents per page = limit_docs.
112
112
 
113
113
  Returns:
@@ -117,7 +117,7 @@ class Find:
117
117
  assert page_number > 0, "`find_many` => The `page_number` parameter must not be less than one."
118
118
  # Variable initialization
119
119
  search_task_fn: Callable = self._task_find
120
- branch_numbers: range = range(1, self._max_branch_number)
120
+ branch_numbers: range = range(self._max_number_branch)
121
121
  hash_reduce_left: int = self._hash_reduce_left
122
122
  db_root: str = self._db_root
123
123
  class_model: Any = self._class_model
scruby/mixins/keys.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Methods for working with keys."""
6
5
 
7
6
  from __future__ import annotations
@@ -9,7 +8,9 @@ from __future__ import annotations
9
8
  __all__ = ("Keys",)
10
9
 
11
10
  import logging
11
+ from datetime import datetime
12
12
  from typing import Any
13
+ from zoneinfo import ZoneInfo
13
14
 
14
15
  import orjson
15
16
 
@@ -26,7 +27,7 @@ class Keys:
26
27
  """Asynchronous method for adding document to collection.
27
28
 
28
29
  Args:
29
- doc: Value of key. Type, derived from `BaseModel`.
30
+ doc (Any): Value of key. Type, derived from `ScrubyModel`.
30
31
 
31
32
  Returns:
32
33
  None.
@@ -42,6 +43,11 @@ class Keys:
42
43
  raise TypeError(msg)
43
44
  # The path to cell of collection.
44
45
  leaf_path, prepared_key = await self._get_leaf_path(doc.key)
46
+ # Init a `created_at` and `updated_at` fields
47
+ tz = ZoneInfo("UTC")
48
+ doc.created_at = datetime.now(tz)
49
+ doc.updated_at = datetime.now(tz)
50
+ # Convert doc to json
45
51
  doc_json: str = doc.model_dump_json()
46
52
  # Write key-value to collection.
47
53
  if await leaf_path.exists():
@@ -63,10 +69,10 @@ class Keys:
63
69
  await self._counter_documents(1)
64
70
 
65
71
  async def update_doc(self, doc: Any) -> None:
66
- """Asynchronous method for updating key to collection.
72
+ """Asynchronous method for updating document to collection.
67
73
 
68
74
  Args:
69
- doc: Value of key. Type `BaseModel`.
75
+ doc (Any): Value of key. Type `ScrubyModel`.
70
76
 
71
77
  Returns:
72
78
  None.
@@ -83,6 +89,9 @@ class Keys:
83
89
  raise TypeError(msg)
84
90
  # The path to cell of collection.
85
91
  leaf_path, prepared_key = await self._get_leaf_path(doc.key)
92
+ # Update a `updated_at` field
93
+ doc.updated_at = datetime.now(ZoneInfo("UTC"))
94
+ # Convert doc to json
86
95
  doc_json: str = doc.model_dump_json()
87
96
  # Update the existing key.
88
97
  if await leaf_path.exists():
@@ -98,14 +107,15 @@ class Keys:
98
107
  logging.error(err.message)
99
108
  raise err from None
100
109
  else:
101
- logging.error("The key not exists.")
102
- raise KeyError()
110
+ msg: str = f"`update_doc` - The key `{doc.key}` is missing!"
111
+ logging.error(msg)
112
+ raise KeyError(msg)
103
113
 
104
- async def get_key(self, key: str) -> Any:
105
- """Asynchronous method for getting value of key from collection.
114
+ async def get_doc(self, key: str) -> Any:
115
+ """Asynchronous method for getting document from collection the by key.
106
116
 
107
117
  Args:
108
- key: Key name.
118
+ key (str): Key name.
109
119
 
110
120
  Returns:
111
121
  Value of key or KeyError.
@@ -118,15 +128,15 @@ class Keys:
118
128
  data: dict = orjson.loads(data_json) or {}
119
129
  obj: Any = self._class_model.model_validate_json(data[prepared_key])
120
130
  return obj
121
- msg: str = "`get_key` - The unacceptable key value."
131
+ msg: str = f"`get_doc` - The key `{key}` is missing!"
122
132
  logging.error(msg)
123
- raise KeyError()
133
+ raise KeyError(msg)
124
134
 
125
135
  async def has_key(self, key: str) -> bool:
126
136
  """Asynchronous method for checking presence of key in collection.
127
137
 
128
138
  Args:
129
- key: Key name.
139
+ key (str): Key name.
130
140
 
131
141
  Returns:
132
142
  True, if the key is present.
@@ -144,11 +154,11 @@ class Keys:
144
154
  return False
145
155
  return False
146
156
 
147
- async def delete_key(self, key: str) -> None:
148
- """Asynchronous method for deleting key from collection.
157
+ async def delete_doc(self, key: str) -> None:
158
+ """Asynchronous method for deleting document from collection the by key.
149
159
 
150
160
  Args:
151
- key: Key name.
161
+ key (str): Key name.
152
162
 
153
163
  Returns:
154
164
  None.
@@ -163,6 +173,6 @@ class Keys:
163
173
  await leaf_path.write_bytes(orjson.dumps(data))
164
174
  await self._counter_documents(-1)
165
175
  return
166
- msg: str = "`delete_key` - The unacceptable key value."
176
+ msg: str = f"`delete_doc` - The key `{key}` is missing!"
167
177
  logging.error(msg)
168
- raise KeyError()
178
+ raise KeyError(msg)
scruby/mixins/update.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Methods for updating documents."""
6
5
 
7
6
  from __future__ import annotations
@@ -62,24 +61,27 @@ class Update:
62
61
 
63
62
  async def update_many(
64
63
  self,
65
- filter_fn: Callable,
66
64
  new_data: dict[str, Any],
65
+ filter_fn: Callable = lambda _: True,
67
66
  ) -> int:
68
- """Updates one or more documents matching the filter.
67
+ """Updates many documents matching the filter.
69
68
 
70
- The search is based on the effect of a quantum loop.
71
- The search effectiveness depends on the number of processor threads.
69
+ Attention:
70
+ - For a complex case, a custom task may be needed.
71
+ - See documentation on creating custom tasks.
72
+ - The search is based on the effect of a quantum loop.
73
+ - The search effectiveness depends on the number of processor threads.
72
74
 
73
75
  Args:
74
- filter_fn: A function that execute the conditions of filtering.
75
- new_data: New data for the fields that need to be updated.
76
+ filter_fn (Callable): A function that execute the conditions of filtering.
77
+ new_data (dict[str, Any]): New data for the fields that need to be updated.
76
78
 
77
79
  Returns:
78
80
  The number of updated documents.
79
81
  """
80
82
  # Variable initialization
81
83
  update_task_fn: Callable = self._task_update
82
- branch_numbers: range = range(1, self._max_branch_number)
84
+ branch_numbers: range = range(self._max_number_branch)
83
85
  hash_reduce_left: int = self._hash_reduce_left
84
86
  db_root: str = self._db_root
85
87
  class_model: Any = self._class_model
scruby/settings.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Scruby - Asynchronous library for building and managing a hybrid database, by scheme of key-value.
2
2
  # Copyright (c) 2025 Gennady Kostyunin
3
3
  # SPDX-License-Identifier: MIT
4
- #
5
4
  """Database settings.
6
5
 
7
6
  The module contains the following parameters:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scruby
3
- Version: 0.28.3
3
+ Version: 0.31.0
4
4
  Summary: Asynchronous library for building and managing a hybrid database, by scheme of key-value.
5
5
  Project-URL: Homepage, https://kebasyaty.github.io/scruby/
6
6
  Project-URL: Repository, https://github.com/kebasyaty/scruby
@@ -99,6 +99,15 @@ Online browsable documentation is available at [https://kebasyaty.github.io/scru
99
99
  uv add scruby
100
100
  ```
101
101
 
102
+ ## Run
103
+
104
+ ```shell
105
+ # Run Development:
106
+ uv run python main.py
107
+ # Run Production:
108
+ uv run python -OOP main.py
109
+ ```
110
+
102
111
  ## Usage
103
112
 
104
113
  See more examples here [https://kebasyaty.github.io/scruby/latest/pages/usage/](https://kebasyaty.github.io/scruby/latest/pages/usage/ "Examples").
@@ -107,24 +116,25 @@ See more examples here [https://kebasyaty.github.io/scruby/latest/pages/usage/](
107
116
  """Working with keys."""
108
117
 
109
118
  import anyio
110
- import datetime
119
+ from datetime import datetime
120
+ from zoneinfo import ZoneInfo
111
121
  from typing import Annotated
112
- from pydantic import BaseModel, EmailStr, Field
122
+ from pydantic import EmailStr, Field
113
123
  from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
114
- from scruby import Scruby, settings
124
+ from scruby import Scruby, ScrubyModel, settings
115
125
 
116
126
  settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
117
127
  settings.HASH_REDUCE_LEFT = 6 # By default = 6
118
128
  settings.MAX_WORKERS = None # By default = None
119
129
 
120
- class User(BaseModel):
130
+ class User(ScrubyModel):
121
131
  """User model."""
122
132
  first_name: str = Field(strict=True)
123
133
  last_name: str = Field(strict=True)
124
- birthday: datetime.datetime = Field(strict=True)
134
+ birthday: datetime = Field(strict=True)
125
135
  email: EmailStr = Field(strict=True)
126
136
  phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")] = Field(frozen=True)
127
- # The key is always at the bottom
137
+ # key is always at bottom
128
138
  key: str = Field(
129
139
  strict=True,
130
140
  frozen=True,
@@ -140,7 +150,7 @@ async def main() -> None:
140
150
  user = User(
141
151
  first_name="John",
142
152
  last_name="Smith",
143
- birthday=datetime.datetime(1970, 1, 1),
153
+ birthday=datetime(1970, 1, 1, tzinfo=ZoneInfo("UTC")),
144
154
  email="John_Smith@gmail.com",
145
155
  phone="+447986123456",
146
156
  )
@@ -149,15 +159,15 @@ async def main() -> None:
149
159
 
150
160
  await user_coll.update_doc(user)
151
161
 
152
- await user_coll.get_key("+447986123456") # => user
153
- await user_coll.get_key("key missing") # => KeyError
162
+ await user_coll.get_doc("+447986123456") # => user
163
+ await user_coll.get_doc("key missing") # => KeyError
154
164
 
155
165
  await user_coll.has_key("+447986123456") # => True
156
166
  await user_coll.has_key("key missing") # => False
157
167
 
158
- await user_coll.delete_key("+447986123456")
159
- await user_coll.delete_key("+447986123456") # => KeyError
160
- await user_coll.delete_key("key missing") # => KeyError
168
+ await user_coll.delete_doc("+447986123456")
169
+ await user_coll.delete_doc("+447986123456") # => KeyError
170
+ await user_coll.delete_doc("key missing") # => KeyError
161
171
 
162
172
  # Full database deletion.
163
173
  # Hint: The main purpose is tests.
@@ -176,10 +186,9 @@ The search effectiveness depends on the number of processor threads.
176
186
  """
177
187
 
178
188
  import anyio
179
- import datetime
180
189
  from typing import Annotated
181
- from pydantic import BaseModel, Field
182
- from scruby import Scruby, settings
190
+ from pydantic import Field
191
+ from scruby import Scruby, ScrubyModel, settings
183
192
  from pprint import pprint as pp
184
193
 
185
194
  settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
@@ -187,13 +196,13 @@ settings.HASH_REDUCE_LEFT = 6 # By default = 6
187
196
  settings.MAX_WORKERS = None # By default = None
188
197
 
189
198
 
190
- class Phone(BaseModel):
199
+ class Phone(ScrubyModel):
191
200
  """Phone model."""
192
201
  brand: str = Field(strict=True, frozen=True)
193
202
  model: str = Field(strict=True, frozen=True)
194
203
  screen_diagonal: float = Field(strict=True)
195
204
  matrix_type: str = Field(strict=True)
196
- # The key is always at the bottom
205
+ # key is always at bottom
197
206
  key: str = Field(
198
207
  strict=True,
199
208
  frozen=True,
@@ -252,10 +261,9 @@ The search effectiveness depends on the number of processor threads.
252
261
  """
253
262
 
254
263
  import anyio
255
- import datetime
256
264
  from typing import Annotated
257
- from pydantic import BaseModel, Field
258
- from scruby import Scruby, settings
265
+ from pydantic import Field
266
+ from scruby import Scruby, ScrubyModel, settings
259
267
  from pprint import pprint as pp
260
268
 
261
269
  settings.DB_ROOT = "ScrubyDB" # By default = "ScrubyDB"
@@ -263,13 +271,13 @@ settings.HASH_REDUCE_LEFT = 6 # By default = 6
263
271
  settings.MAX_WORKERS = None # By default = None
264
272
 
265
273
 
266
- class Car(BaseModel):
274
+ class Car(ScrubyModel):
267
275
  """Car model."""
268
276
  brand: str = Field(strict=True, frozen=True)
269
277
  model: str = Field(strict=True, frozen=True)
270
278
  year: int = Field(strict=True)
271
279
  power_reserve: int = Field(strict=True)
272
- # The key is always at the bottom
280
+ # key is always at bottom
273
281
  key: str = Field(
274
282
  strict=True,
275
283
  frozen=True,
@@ -0,0 +1,18 @@
1
+ scruby/__init__.py,sha256=m5VkmOTqVzfHsLb7eIfGTdBpWPwEayrRDpAilUuksGc,1042
2
+ scruby/aggregation.py,sha256=ooDN2KShP_JwIpIqxz7wL6gtBdd4oCSFk_3BhnD2o50,3489
3
+ scruby/db.py,sha256=EVrMfHYWZr9ZEyU7b2fMBqSKeh0fEJnjbihQhMNVJak,8374
4
+ scruby/errors.py,sha256=F51l5eyJuuMrwPeiIy6IGc1GqMsrGuS6l_B0e0Y6xWs,1295
5
+ scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ scruby/settings.py,sha256=g2AYH8QorjoTYxMceWotFL9X3F2mQnKDl9dlgmSLa5c,1578
7
+ scruby/mixins/__init__.py,sha256=9geOyHcfWV4hilYs76whiEUOK2aZtgeC0fg2K0qvUOk,625
8
+ scruby/mixins/collection.py,sha256=SZ2yvPPPPu_Cg9pfqu_RM0YB6hkhikwmuE13sATeUtE,1380
9
+ scruby/mixins/count.py,sha256=gYX7ttHjHINZjSWa6FjzMZnqwEQO9kj5Ehl7Hkcs-2E,2087
10
+ scruby/mixins/custom_task.py,sha256=tHZSo6IdZL5Yu46S-c7Ac3ImxGhwzPoQm4Dp4O8bqLs,1320
11
+ scruby/mixins/delete.py,sha256=ng0xXM9mKQy5iq04WCyDm2AseETzEV7PgQOeDzUe-uU,3083
12
+ scruby/mixins/find.py,sha256=5X0PNvLtsPq8s9J-lNFrhT9Pmqn4qSYUmSsV9yCl3VE,5353
13
+ scruby/mixins/keys.py,sha256=ybCHefGgxXNCYMKxgRhlXXW1CLcv2BGKff3S8E1y2JI,6177
14
+ scruby/mixins/update.py,sha256=PWrF1lvuMOHIlsrfXMB6jfCFEGbpqXor-CzH5X0b3VE,3414
15
+ scruby-0.31.0.dist-info/METADATA,sha256=f1Ad9XPgW1euUdbxe0SbxpKQ0qjiybSMADCu_e4QxPw,10963
16
+ scruby-0.31.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
+ scruby-0.31.0.dist-info/licenses/LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
18
+ scruby-0.31.0.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- scruby/__init__.py,sha256=bw2Le5ULYlf2nFQT_617rmEumu66Ll-QCLCxqDFERWw,1014
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=XPMjJvOZN7dLpTE1FfGMBGQ_0421HXug-0rWKMU5fRQ,627
8
- scruby/mixins/collection.py,sha256=coF-IOhicV_EihDwnYf6SW5Mfi3nOFR0gAhCc619NmI,1382
9
- scruby/mixins/count.py,sha256=PGzRtgLvseQnHg6wt-A81s30pnsdY1d8cr-EQRnbfyU,2050
10
- scruby/mixins/custom_task.py,sha256=ZhvCDiYnJ8BTIWlnRu6cTH-9G9o7dHSixjMIsxAtDpw,2316
11
- scruby/mixins/delete.py,sha256=B2loiowj8ToO0euumDRxpHUVrLQx0iTcRys0jszn-rA,3046
12
- scruby/mixins/find.py,sha256=gnHjnm0MZbzMHmWOBUJbMm8LZFBqdJ6yWA6Pxfap51Q,5340
13
- scruby/mixins/keys.py,sha256=OUByWbHfNVWJVkUrhCsJZdVqf0zez_an6Gti2n5iKnM,5671
14
- scruby/mixins/update.py,sha256=YkUiz1gcVtNXdgf7Mmda-0g3vJm3jL09v-msGy2tAWg,3229
15
- scruby-0.28.3.dist-info/METADATA,sha256=DoDzTEj_wrZu3EfAhBcKBtCfbr9pb74FS3spyDpFfzY,10849
16
- scruby-0.28.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
- scruby-0.28.3.dist-info/licenses/LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
18
- scruby-0.28.3.dist-info/RECORD,,