scruby 2.2.3__py3-none-any.whl → 2.3.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.
scruby/__init__.py CHANGED
@@ -36,5 +36,6 @@ __all__ = (
36
36
 
37
37
 
38
38
  from scruby.config import ScrubyConfig
39
- from scruby.db import Scruby, ScrubyModel
39
+ from scruby.db import Scruby
40
40
  from scruby.mixins.find import ReturnType
41
+ from scruby.model import ScrubyModel
scruby/cache.py CHANGED
@@ -12,11 +12,19 @@ from typing import Any, ClassVar, Literal, Never, assert_never, final
12
12
  import orjson
13
13
 
14
14
  from scruby.config import ScrubyConfig
15
+ from scruby.meta import Metadata
15
16
 
16
17
 
17
18
  @final
18
19
  class DocCache:
19
- """Cache documents to optimize work with the database."""
20
+ """Cache documents to optimize work with the database.
21
+
22
+ Args:
23
+ collection_name (str): Collection name.
24
+
25
+ Returns:
26
+ None.
27
+ """
20
28
 
21
29
  # Cache structure:
22
30
  # {"CollectionName": {"hash_symbol": {"hash_symbol": {"hash_symbol": {"key_name": doc}}}}
@@ -24,7 +32,14 @@ class DocCache:
24
32
 
25
33
  @classmethod
26
34
  def create_structure(cls, collection_name: str) -> None:
27
- """Create a cache structure for the collection."""
35
+ """Create structure of empty cache for collection.
36
+
37
+ Args:
38
+ collection_name (str): Collection name.
39
+
40
+ Returns:
41
+ None.
42
+ """
28
43
  if ScrubyConfig.HASH_REDUCE_LEFT == 0:
29
44
  return
30
45
 
@@ -44,29 +59,24 @@ class DocCache:
44
59
  @classmethod
45
60
  def load_cache(cls, subclasses: list[Any]) -> None:
46
61
  """Load all documents from the database into the cache."""
47
- if ScrubyConfig.HASH_REDUCE_LEFT == 0:
48
- return
49
-
50
62
  db_root: Path = Path(ScrubyConfig.db_root)
51
63
  HASH_REDUCE_LEFT: Literal[7, 6, 5, 0] = ScrubyConfig.HASH_REDUCE_LEFT
52
64
  MAX_NUMBER_BRANCH: Literal[16, 256, 4096, 4294967296] = ScrubyConfig.MAX_NUMBER_BRANCH
53
65
  branch_numbers: range = range(MAX_NUMBER_BRANCH)
54
66
 
55
- # Leave function if database does not exist
56
- if not db_root.exists():
57
- return
67
+ for subclass in subclasses:
68
+ collection_name: str = subclass.__name__
58
69
 
59
- # Get a list of created directories for collections
60
- db_directory = Path(db_root)
61
- all_entries = Path.iterdir(db_directory)
62
- directory_names: list[str] = [entry.name for entry in all_entries if entry.name != ".env.meta"]
70
+ # Create metadata for collection
71
+ Metadata.create(collection_name)
63
72
 
64
- for subclass in subclasses:
65
- collection_name = subclass.__name__
73
+ if HASH_REDUCE_LEFT == 0:
74
+ continue
66
75
 
67
- if collection_name in directory_names:
68
- cls.create_structure(collection_name)
76
+ # Create a cache structure for the collection
77
+ cls.create_structure(collection_name)
69
78
 
79
+ # Get data from database and add to cache for collection
70
80
  for branch_number in branch_numbers:
71
81
  branch_number_as_hash: str = f"{branch_number:08x}"[HASH_REDUCE_LEFT:]
72
82
  separated_hash = "/".join(list(branch_number_as_hash))
@@ -84,7 +94,7 @@ class DocCache:
84
94
  data: dict[str, str] = orjson.loads(data_json) or {}
85
95
  for key, val in data.items():
86
96
  doc = subclass.model_validate_json(val)
87
- match ScrubyConfig.HASH_REDUCE_LEFT:
97
+ match HASH_REDUCE_LEFT:
88
98
  case 7:
89
99
  cls.cache[collection_name][branch_number_as_hash[0]][key] = doc
90
100
  case 6:
scruby/db.py CHANGED
@@ -6,41 +6,22 @@
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __all__ = (
10
- "Scruby",
11
- "ScrubyModel",
12
- )
9
+ __all__ = ("Scruby",)
13
10
 
14
11
  import contextlib
15
12
  import re
16
13
  import zlib
17
- from datetime import datetime
18
14
  from shutil import rmtree
19
15
  from typing import Any, Literal, final
20
16
 
21
17
  from anyio import Path
22
- from pydantic import BaseModel
23
18
  from xloft import NamedTuple
24
19
 
25
20
  from scruby import mixins
26
21
  from scruby.cache import DocCache
27
22
  from scruby.config import ScrubyConfig
28
-
29
-
30
- class _Meta(BaseModel):
31
- """Metadata of Collection."""
32
-
33
- collection_name: str
34
- hash_reduce_left: int
35
- max_number_branch: int
36
- counter_documents: int
37
-
38
-
39
- class ScrubyModel(BaseModel):
40
- """Additional fields for models."""
41
-
42
- created_at: datetime | None = None
43
- updated_at: datetime | None = None
23
+ from scruby.meta import Meta
24
+ from scruby.model import ScrubyModel
44
25
 
45
26
 
46
27
  @final
@@ -57,97 +38,39 @@ class Scruby(
57
38
 
58
39
  def __init__( # noqa: D107
59
40
  self,
41
+ class_model: Any,
60
42
  ) -> None:
43
+ assert ScrubyModel in class_model.__bases__, (
44
+ "Scruby => Argument `class_model` does not contain the base class `ScrubyModel`."
45
+ )
46
+ assert "key" in list(class_model.model_fields.keys()), (
47
+ f"Model: {class_model.__name__} => The `key` field is missing."
48
+ )
49
+
61
50
  super().__init__()
62
- self._meta = _Meta
51
+ self._class_model = class_model
63
52
  self._db_id = ScrubyConfig.db_id
64
53
  self._db_root = ScrubyConfig.db_root
65
54
  self._hash_reduce_left = ScrubyConfig.HASH_REDUCE_LEFT
66
55
  self._max_number_branch = ScrubyConfig.MAX_NUMBER_BRANCH
67
56
  self._max_workers = ScrubyConfig.max_workers
68
-
69
- @classmethod
70
- async def collection(cls, class_model: Any) -> Any:
71
- """Asynchronous method for creating a new collection and accessing an existing collection.
72
-
73
- Args:
74
- class_model (Any): Class of Model (ScrubyModel).
75
-
76
- Returns:
77
- Instance of Scruby for access a collection.
78
- """
79
- if __debug__:
80
- # Check if the object belongs to the class `ScrubyModel`
81
- if ScrubyModel not in class_model.__bases__:
82
- msg = (
83
- "Method: `collection` => argument `class_model` " + "does not contain the base class `ScrubyModel`!"
84
- )
85
- raise AssertionError(msg)
86
- # Checking the model for the presence of a key.
87
- model_fields = list(class_model.model_fields.keys())
88
- if "key" not in model_fields:
89
- msg = f"Model: {class_model.__name__} => The `key` field is missing!"
90
- raise AssertionError(msg)
91
- if "created_at" not in model_fields:
92
- msg = f"Model: {class_model.__name__} => The `created_at` field is missing!"
93
- raise AssertionError(msg)
94
- if "updated_at" not in model_fields:
95
- msg = f"Model: {class_model.__name__} => The `updated_at` field is missing!"
96
- raise AssertionError(msg)
97
- # Check the length of the collection name for an acceptable size.
98
- len_db_root_absolut_path = len(str(await Path(ScrubyConfig.db_root).resolve()).encode("utf-8"))
99
- len_model_name = len(class_model.__name__)
100
- len_full_path_leaf = len_db_root_absolut_path + len_model_name + 26
101
- if len_full_path_leaf > 255:
102
- excess = len_full_path_leaf - 255
103
- msg = (
104
- f"Model: {class_model.__name__} => The collection name is too long, "
105
- + f"it exceeds the limit of {excess} characters!"
106
- )
107
- raise AssertionError(msg)
108
- # Create instance of Scruby
109
- instance = cls()
110
- # Add model class to Scruby
111
- instance.__dict__["_class_model"] = class_model
112
- # Create a path for metadata.
113
- meta_dir_path_tuple = (
57
+ self._meta = Meta
58
+ self._meta_path = Path(
114
59
  ScrubyConfig.db_root,
115
60
  class_model.__name__,
116
61
  "meta",
117
- )
118
- instance.__dict__["_meta_path"] = Path(
119
- *meta_dir_path_tuple,
120
62
  "meta.json",
121
63
  )
122
- # Create metadata for collection, if missing.
123
- meta_dir_path = Path(*meta_dir_path_tuple)
124
- if not await meta_dir_path.exists():
125
- # Create metadata.
126
- await meta_dir_path.mkdir(parents=True)
127
- meta = _Meta(
128
- collection_name=class_model.__name__,
129
- hash_reduce_left=instance.__dict__["_hash_reduce_left"],
130
- max_number_branch=instance.__dict__["_max_number_branch"],
131
- counter_documents=0,
132
- )
133
- # Save metadata of collection.
134
- meta_json = meta.model_dump_json()
135
- meta_path = Path(*(meta_dir_path, "meta.json"))
136
- await meta_path.write_text(meta_json, "utf-8")
137
- # Create a cache structure for the collection.
138
- if instance.__dict__["_hash_reduce_left"] != 0:
139
- DocCache.create_structure(class_model.__name__)
140
64
  # Plugins connection.
141
65
  plugin_list: dict[str, Any] = {}
142
66
  if ScrubyConfig.plugins is not None:
143
67
  for plugin in ScrubyConfig.plugins:
144
68
  name = plugin.__name__
145
69
  name = name[0].lower() + name[1:]
146
- plugin_list[name] = plugin(scruby_self=instance)
147
- instance.__dict__["plugins"] = NamedTuple(**plugin_list)
148
- return instance
70
+ plugin_list[name] = plugin(scruby_self=self)
71
+ self.plugins = NamedTuple(**plugin_list)
149
72
 
150
- async def get_meta(self) -> _Meta:
73
+ async def get_meta(self) -> Meta:
151
74
  """Asynchronous method for getting metadata of collection.
152
75
 
153
76
  This method is for internal use.
@@ -156,10 +79,10 @@ class Scruby(
156
79
  Metadata object.
157
80
  """
158
81
  meta_json = await self._meta_path.read_text()
159
- meta: _Meta = self._meta.model_validate_json(meta_json)
82
+ meta: Meta = self._meta.model_validate_json(meta_json)
160
83
  return meta
161
84
 
162
- async def _set_meta(self, meta: _Meta) -> None:
85
+ async def _set_meta(self, meta: Meta) -> None:
163
86
  """Asynchronous method for updating metadata of collection.
164
87
 
165
88
  This method is for internal use.
@@ -186,7 +109,7 @@ class Scruby(
186
109
  """
187
110
  meta_path = self._meta_path
188
111
  meta_json = await meta_path.read_text("utf-8")
189
- meta: _Meta = self._meta.model_validate_json(meta_json)
112
+ meta: Meta = self._meta.model_validate_json(meta_json)
190
113
  meta.counter_documents += step
191
114
  meta_json = meta.model_dump_json()
192
115
  await meta_path.write_text(meta_json, "utf-8")
scruby/meta.py ADDED
@@ -0,0 +1,57 @@
1
+ """Meta.
2
+
3
+ Metadata management.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ __all__ = (
9
+ "Meta",
10
+ "Metadata",
11
+ )
12
+
13
+ from pathlib import Path
14
+
15
+ from pydantic import BaseModel
16
+
17
+ from scruby.config import ScrubyConfig
18
+
19
+
20
+ class Meta(BaseModel):
21
+ """Structure of metadata for collection."""
22
+
23
+ collection_name: str
24
+ hash_reduce_left: int
25
+ max_number_branch: int
26
+ counter_documents: int
27
+
28
+
29
+ class Metadata:
30
+ """Metadata management."""
31
+
32
+ @staticmethod
33
+ def create(collection_name: str) -> None:
34
+ """Create metadata for collection.
35
+
36
+ Args:
37
+ collection_name (str): Collection name.
38
+
39
+ Returns:
40
+ None.
41
+ """
42
+ meta_dir_path = Path(
43
+ ScrubyConfig.db_root,
44
+ collection_name,
45
+ "meta",
46
+ )
47
+ if not meta_dir_path.exists():
48
+ meta_dir_path.mkdir(parents=True)
49
+ meta = Meta(
50
+ collection_name=collection_name,
51
+ hash_reduce_left=ScrubyConfig.HASH_REDUCE_LEFT,
52
+ max_number_branch=ScrubyConfig.MAX_NUMBER_BRANCH,
53
+ counter_documents=0,
54
+ )
55
+ meta_json = meta.model_dump_json()
56
+ meta_path = Path(meta_dir_path, "meta.json")
57
+ meta_path.write_text(meta_json, "utf-8")
@@ -11,10 +11,10 @@ __all__ = ("Collection",)
11
11
  from shutil import rmtree
12
12
  from typing import final
13
13
 
14
- from anyio import Path, to_thread
15
-
16
14
  from scruby.cache import DocCache
17
15
  from scruby.config import ScrubyConfig
16
+ from scruby.meta import Metadata
17
+ from scruby.model import ScrubyModel
18
18
 
19
19
 
20
20
  class Collection:
@@ -31,18 +31,15 @@ class Collection:
31
31
 
32
32
  @final
33
33
  @staticmethod
34
- async def collection_list() -> list[str] | None:
35
- """Asynchronous method for getting collection list."""
36
- db_directory = Path(ScrubyConfig.db_root)
37
- # Get all entries in the directory
38
- all_entries = Path.iterdir(db_directory)
39
- directory_names: list[str] = [entry.name async for entry in all_entries if entry.name != ".env.meta"]
40
- return directory_names or None
34
+ def collection_list() -> list[str] | None:
35
+ """Synchronous method for getting collection list."""
36
+ collections = [item.__name__ for item in ScrubyModel.__subclasses__()]
37
+ return collections or None
41
38
 
42
39
  @final
43
40
  @staticmethod
44
- async def delete_collection(collection_name: str) -> None:
45
- """Asynchronous method for deleting a collection by its name.
41
+ def clear_collection(collection_name: str) -> None:
42
+ """Synchronous method to remove all documents from a collection.
46
43
 
47
44
  Args:
48
45
  collection_name (str): Collection name.
@@ -50,8 +47,14 @@ class Collection:
50
47
  Returns:
51
48
  None.
52
49
  """
50
+ # Clear collection on file system
53
51
  target_directory = f"{ScrubyConfig.db_root}/{collection_name}"
54
- await to_thread.run_sync(rmtree, target_directory) # pyrefly: ignore [bad-argument-type, incompatible-overload-residual]
52
+ rmtree(target_directory)
53
+ # Create metadata for collection
54
+ Metadata.create(collection_name)
55
+
56
+ # Clear collection in cache
55
57
  if ScrubyConfig.HASH_REDUCE_LEFT != 0:
56
58
  del DocCache.cache[collection_name]
59
+ DocCache.create_structure(collection_name)
57
60
  return
scruby/model.py ADDED
@@ -0,0 +1,17 @@
1
+ """Model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = ("ScrubyModel",)
6
+
7
+
8
+ from datetime import datetime
9
+
10
+ from pydantic import BaseModel
11
+
12
+
13
+ class ScrubyModel(BaseModel):
14
+ """Additional fields for models."""
15
+
16
+ created_at: datetime | None = None
17
+ updated_at: datetime | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scruby
3
- Version: 2.2.3
3
+ Version: 2.3.0
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
@@ -165,7 +165,7 @@ async def main() -> None:
165
165
  Scruby.run()
166
166
 
167
167
  # Get/Create a User collection
168
- user_coll = await Scruby.collection(User)
168
+ user_coll = Scruby(User)
169
169
 
170
170
  # Create user
171
171
  user = User(
@@ -196,7 +196,7 @@ async def main() -> None:
196
196
  user_coll.collection_name() # => User
197
197
 
198
198
  # Get collection list
199
- coll_list = await Scruby.collection_list() # => ["User"]
199
+ coll_list = Scruby.collection_list() # => ["User"]
200
200
 
201
201
  # Get the number of documents in the collection from metadata
202
202
  await user_coll.estimated_document_count() # => 1
@@ -204,8 +204,8 @@ async def main() -> None:
204
204
  # Get the number of documents comparable to the filter
205
205
  user_coll.count_documents(filter_fn=lambda doc: doc.first_name == "John") == 1
206
206
 
207
- # Delete collection
208
- await Scruby.delete_collection("User")
207
+ # Clear collection
208
+ Scruby.clear_collection("User")
209
209
 
210
210
  # Full database deletion
211
211
  # Hint: The main purpose is tests
@@ -248,7 +248,7 @@ async def main() -> None:
248
248
  Scruby.run()
249
249
 
250
250
  # Get/Create a Phone collection
251
- phone_coll = await Scruby.collection(Phone)
251
+ phone_coll = Scruby(Phone)
252
252
 
253
253
  # Create phone
254
254
  phone = Phone(
@@ -325,7 +325,7 @@ async def main() -> None:
325
325
  Scruby.run()
326
326
 
327
327
  # Get/Create a Car collection
328
- car_coll = await Scruby.collection(Car)
328
+ car_coll = Scruby(Car)
329
329
 
330
330
  # Create cars
331
331
  for num in range(1, 10):
@@ -1,21 +1,23 @@
1
- scruby/__init__.py,sha256=DEtbZThadMKpKcGGjWyAFGu4IADg9ZYTb8WjfO_3Dqg,1300
1
+ scruby/__init__.py,sha256=YIR8zFx0GmS8ha96-MU9yCgKeYNIh1K8idlQckfg21E,1324
2
2
  scruby/aggregation.py,sha256=NBFxQqyRqUG2KIuD9fbl4uzSHJWTaskjiZ1YNBa-Zbo,3575
3
- scruby/cache.py,sha256=krqvIhtbvhCFDjqOg1eg2uZFa4eGDRYI9ELTe6eJbpc,3961
3
+ scruby/cache.py,sha256=wGnjuFlpA3sRGfp4vv3o3nXR_tOC8HtuzFv1TyrKEyk,3997
4
4
  scruby/config.py,sha256=INAFqNAeF8BifIywjElC97rawfTLuqpRfieUdAO7A6k,5122
5
- scruby/db.py,sha256=OtAwHtXoHaF_obqMwxn157EtVppO0tO6Dx2lWGFBEiA,9999
5
+ scruby/db.py,sha256=3erS6kDcjNy6KBx_8x58P6lI_2DzaIkYg3DK5_NQ_iY,6796
6
6
  scruby/errors.py,sha256=lTWiHzyO5Es9Nkf7quODJjONGn6ifcL95qlpA4epQQM,1386
7
+ scruby/meta.py,sha256=dfr8q3TWhVcEl-lZuH5yd9rbO9vAPBhXBO3VYCNcOWo,1283
8
+ scruby/model.py,sha256=1infUd1G-zxj5Z93mNOp1Wi2anD6TKDgu5Y0KEVcH2k,292
7
9
  scruby/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
10
  scruby/utils.py,sha256=ZwWxSyh_BAOQkXlIqXFOlX2lHVk9rfYGQiVqtTk8PpE,1865
9
11
  scruby/mixins/__init__.py,sha256=nT79e80zXliuTGhR9CFosI2-3PQUCKwXbR7wPiFwgrU,669
10
- scruby/mixins/collection.py,sha256=nTw0jnybox3iso0jzNoJiu61Hb-92zl84hdWhlWbik4,1792
12
+ scruby/mixins/collection.py,sha256=3Gn9zIT-WnSCBd-IgpE9tobRlW675YX__qaWoUOWIJM,1758
11
13
  scruby/mixins/count.py,sha256=CGpyOpsG25uC5utI2ByNxq2iTbJocj6w5tXrC1_cP40,2561
12
14
  scruby/mixins/custom_task.py,sha256=DIry4gBlTdaqZqymar-RrHSdhEiC2tn2pl9jmbk56zw,1547
13
15
  scruby/mixins/delete.py,sha256=InKVIud_ZYx9-CchUz_IygQDXMynNi4jQ0HKYeHC_R8,4328
14
16
  scruby/mixins/find.py,sha256=oEZRE6RqIBdwvNr8iSYyod8hI6ibi_egJP0pyXmJiss,9169
15
17
  scruby/mixins/keys.py,sha256=MvBy_8fQGROaQATK2qse2V8wR-xodPQG0KKZ2PK_t9U,10790
16
18
  scruby/mixins/update.py,sha256=TyxvxB-gNijlfzTmhwrq0ydvu0C3R-RihW5h5tJ96bM,4964
17
- scruby-2.2.3.dist-info/METADATA,sha256=5HaLAyfWIbGGgRBZv79_f4Q0TKTe_RsIY_2uAswYEn8,13447
18
- scruby-2.2.3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
19
- scruby-2.2.3.dist-info/licenses/GPL-3.0-LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
20
- scruby-2.2.3.dist-info/licenses/MIT-LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
21
- scruby-2.2.3.dist-info/RECORD,,
19
+ scruby-2.3.0.dist-info/METADATA,sha256=mtGCSIcs5Ah_xokLG3OnmnjDJGeDGBSFp0iNNT8DxUk,13382
20
+ scruby-2.3.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
21
+ scruby-2.3.0.dist-info/licenses/GPL-3.0-LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
22
+ scruby-2.3.0.dist-info/licenses/MIT-LICENSE,sha256=mS0Wz0yGNB63gEcWEnuIb_lldDYV0sjRaO-o_GL6CWE,1074
23
+ scruby-2.3.0.dist-info/RECORD,,
File without changes