ramifice 0.5.12__py3-none-any.whl → 0.6.1__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.
@@ -1,12 +1,13 @@
1
1
  """Field of Model for upload file."""
2
2
 
3
3
  import os
4
- import shutil
5
4
  import uuid
6
5
  from base64 import b64decode
7
6
  from datetime import date
8
7
  from pathlib import Path
9
- from typing import Any
8
+
9
+ import aiofiles
10
+ from aioshutil import copyfile
10
11
 
11
12
  from ramifice.fields.general.field import Field
12
13
  from ramifice.fields.general.file_group import FileGroup
@@ -91,7 +92,7 @@ class FileField(Field, FileGroup, JsonMixin):
91
92
 
92
93
  self.value: dict[str, str | int | bool] | None = None
93
94
 
94
- def from_base64(
95
+ async def from_base64(
95
96
  self,
96
97
  base64_str: str | None = None,
97
98
  filename: str | None = None,
@@ -125,14 +126,14 @@ class FileField(Field, FileGroup, JsonMixin):
125
126
  # Create path to target directory.
126
127
  dir_target_path = f"{self.media_root}/{self.target_dir}/{date_str}"
127
128
  # Create target directory if it does not exist.
128
- if not os.path.exists(dir_target_path):
129
- os.makedirs(dir_target_path)
129
+ if not await aiofiles.os.path.exists(dir_target_path):
130
+ await aiofiles.os.makedirs(dir_target_path)
130
131
  # Create path to target file.
131
132
  f_target_path = f"{dir_target_path}/{f_uuid_name}"
132
133
  # Save file in target directory.
133
- with open(f_target_path, mode="wb") as open_f:
134
+ async with aiofiles.open(f_target_path, mode="wb") as open_f:
134
135
  f_content = b64decode(base64_str)
135
- open_f.write(f_content)
136
+ await open_f.write(f_content)
136
137
  # Add paths to target file.
137
138
  file_info["path"] = f_target_path
138
139
  file_info["url"] = f"{self.media_url}/{self.target_dir}/{date_str}/{f_uuid_name}"
@@ -141,12 +142,12 @@ class FileField(Field, FileGroup, JsonMixin):
141
142
  # Add file extension.
142
143
  file_info["extension"] = extension
143
144
  # Add file size (in bytes).
144
- file_info["size"] = os.path.getsize(f_target_path)
145
+ file_info["size"] = await aiofiles.os.path.getsize(f_target_path)
145
146
  #
146
147
  # to value.
147
148
  self.value = file_info
148
149
 
149
- def from_path(
150
+ async def from_path(
150
151
  self,
151
152
  src_path: str | None = None,
152
153
  is_delete: bool = False,
@@ -170,12 +171,12 @@ class FileField(Field, FileGroup, JsonMixin):
170
171
  # Create path to target directory.
171
172
  dir_target_path = f"{self.media_root}/{self.target_dir}/{date_str}"
172
173
  # Create target directory if it does not exist.
173
- if not os.path.exists(dir_target_path):
174
- os.makedirs(dir_target_path)
174
+ if not await aiofiles.os.path.exists(dir_target_path):
175
+ await aiofiles.os.makedirs(dir_target_path)
175
176
  # Create path to target file.
176
177
  f_target_path = f"{dir_target_path}/{f_uuid_name}"
177
178
  # Save file in target directory.
178
- shutil.copyfile(src_path, f_target_path)
179
+ await copyfile(src_path, f_target_path)
179
180
  # Add paths to target file.
180
181
  file_info["path"] = f_target_path
181
182
  file_info["url"] = f"{self.media_url}/{self.target_dir}/{date_str}/{f_uuid_name}"
@@ -184,7 +185,7 @@ class FileField(Field, FileGroup, JsonMixin):
184
185
  # Add file extension.
185
186
  file_info["extension"] = extension
186
187
  # Add file size (in bytes).
187
- file_info["size"] = os.path.getsize(f_target_path)
188
+ file_info["size"] = await aiofiles.os.path.getsize(f_target_path)
188
189
  #
189
190
  # to value.
190
191
  self.value = file_info
@@ -1,14 +1,13 @@
1
1
  """Field of Model for upload image."""
2
2
 
3
3
  import os
4
- import shutil
5
4
  import uuid
6
5
  from base64 import b64decode
7
6
  from datetime import date
8
7
  from pathlib import Path
9
- from typing import Any
10
8
 
11
- from PIL import Image
9
+ import aiofiles
10
+ from aioshutil import copyfile
12
11
 
13
12
  from ramifice.fields.general.field import Field
14
13
  from ramifice.fields.general.file_group import FileGroup
@@ -38,8 +37,6 @@ class ImageField(Field, FileGroup, JsonMixin):
38
37
  # Available 4 sizes from lg to xs or None.
39
38
  # Example: {"lg": 1200, "md": 600, "sm": 300, "xs": 150 }
40
39
  thumbnails: dict[str, int] | None = None,
41
- # True - high quality and low performance for thumbnails.
42
- high_quality: bool = False,
43
40
  ):
44
41
  if globals.DEBUG:
45
42
  if default is not None:
@@ -98,8 +95,6 @@ class ImageField(Field, FileGroup, JsonMixin):
98
95
  raise AssertionError("Parameter `target_dir` - Not а `str` type!")
99
96
  if not isinstance(accept, str):
100
97
  raise AssertionError("Parameter `accept` - Not а `str` type!")
101
- if not isinstance(high_quality, bool):
102
- raise AssertionError("Parameter `high_quality` - Not а `bool` type!")
103
98
 
104
99
  Field.__init__(
105
100
  self,
@@ -127,10 +122,8 @@ class ImageField(Field, FileGroup, JsonMixin):
127
122
  # Available 4 sizes from lg to xs or None.
128
123
  # Example: {"lg": 1200, "md": 600, "sm": 300, "xs": 150 }
129
124
  self.thumbnails = thumbnails
130
- # True is high quality and low performance.
131
- self.high_quality = high_quality
132
125
 
133
- def from_base64(
126
+ async def from_base64(
134
127
  self,
135
128
  base64_str: str | None = None,
136
129
  filename: str | None = None,
@@ -170,21 +163,15 @@ class ImageField(Field, FileGroup, JsonMixin):
170
163
  # Create path to main image.
171
164
  main_img_path = f"{imgs_dir_path}/{new_original_name}"
172
165
  # Create target directory if it does not exist.
173
- if not os.path.exists(imgs_dir_path):
174
- os.makedirs(imgs_dir_path)
166
+ if not await aiofiles.os.path.exists(imgs_dir_path):
167
+ await aiofiles.os.makedirs(imgs_dir_path)
175
168
  # Save main image in target directory.
176
- with open(main_img_path, mode="wb") as open_f:
169
+ async with aiofiles.open(main_img_path, mode="wb") as open_f:
177
170
  f_content = b64decode(base64_str)
178
- open_f.write(f_content)
171
+ await open_f.write(f_content)
179
172
  # Add paths for main image.
180
173
  img_info["path"] = main_img_path
181
174
  img_info["url"] = f"{imgs_dir_url}/{new_original_name}"
182
- # Add width and height.
183
- if self.__dict__.get("add_width_height", False):
184
- with Image.open(main_img_path) as img:
185
- width, height = img.size
186
- img_info["width"] = width
187
- img_info["height"] = height
188
175
  # Add original image name.
189
176
  img_info["name"] = filename
190
177
  # Add image extension.
@@ -199,12 +186,12 @@ class ImageField(Field, FileGroup, JsonMixin):
199
186
  # Add url path to target directory with images.
200
187
  img_info["imgs_dir_url"] = imgs_dir_url
201
188
  # Add size of main image (in bytes).
202
- img_info["size"] = os.path.getsize(main_img_path)
189
+ img_info["size"] = await aiofiles.os.path.getsize(main_img_path)
203
190
  #
204
191
  # to value.
205
192
  self.value = img_info
206
193
 
207
- def from_path(
194
+ async def from_path(
208
195
  self,
209
196
  src_path: str | None = None,
210
197
  is_delete: bool = False,
@@ -234,19 +221,13 @@ class ImageField(Field, FileGroup, JsonMixin):
234
221
  # Create path to main image.
235
222
  main_img_path = f"{imgs_dir_path}/{new_original_name}"
236
223
  # Create target directory if it does not exist.
237
- if not os.path.exists(imgs_dir_path):
238
- os.makedirs(imgs_dir_path)
224
+ if not await aiofiles.os.path.exists(imgs_dir_path):
225
+ await aiofiles.os.makedirs(imgs_dir_path)
239
226
  # Save main image in target directory.
240
- shutil.copyfile(src_path, main_img_path)
227
+ await copyfile(src_path, main_img_path)
241
228
  # Add paths for main image.
242
229
  img_info["path"] = main_img_path
243
230
  img_info["url"] = f"{imgs_dir_url}/{new_original_name}"
244
- # Add width and height.
245
- if self.__dict__.get("add_width_height", False):
246
- with Image.open(main_img_path) as img:
247
- width, height = img.size
248
- img_info["width"] = width
249
- img_info["height"] = height
250
231
  # Add original image name.
251
232
  img_info["name"] = os.path.basename(src_path)
252
233
  # Add image extension.
@@ -261,7 +242,7 @@ class ImageField(Field, FileGroup, JsonMixin):
261
242
  # Add url path to target directory with images.
262
243
  img_info["imgs_dir_url"] = imgs_dir_url
263
244
  # Add size of main image (in bytes).
264
- img_info["size"] = os.path.getsize(main_img_path)
245
+ img_info["size"] = await aiofiles.os.path.getsize(main_img_path)
265
246
  #
266
247
  # to value.
267
248
  self.value = img_info
@@ -7,8 +7,7 @@ from typing import Any
7
7
  from ramifice.commons import QCommonsMixin
8
8
  from ramifice.fields import DateTimeField, IDField
9
9
  from ramifice.models.model import Model
10
- from ramifice.models.pseudo import PseudoModel
11
- from ramifice.paladins import CheckMixin, QPaladinsMixin, ValidationMixin
10
+ from ramifice.paladins import QPaladinsMixin
12
11
  from ramifice.utils.errors import DoesNotMatchRegexError, PanicError
13
12
  from ramifice.utils.globals import REGEX
14
13
  from ramifice.utils.mixins.add_valid import AddValidMixin
@@ -20,7 +19,6 @@ def model(
20
19
  service_name: str,
21
20
  fixture_name: str | None = None,
22
21
  db_query_docs_limit: int = 1000,
23
- is_migrate_model: bool = True,
24
22
  is_create_doc: bool = True,
25
23
  is_update_doc: bool = True,
26
24
  is_delete_doc: bool = True,
@@ -32,8 +30,6 @@ def model(
32
30
  raise AssertionError("Parameter `fixture_name` - Must be `str | None` type!")
33
31
  if not isinstance(db_query_docs_limit, int):
34
32
  raise AssertionError("Parameter `db_query_docs_limit` - Must be `int` type!")
35
- if not isinstance(is_migrate_model, bool):
36
- raise AssertionError("Parameter `is_migrate_model` - Must be `bool` type!")
37
33
  if not isinstance(is_create_doc, bool):
38
34
  raise AssertionError("Parameter `is_create_doc` - Must be `bool` type!")
39
35
  if not isinstance(is_update_doc, bool):
@@ -55,53 +51,37 @@ def model(
55
51
  raise PanicError(msg)
56
52
 
57
53
  attrs = {key: val for key, val in cls.__dict__.items()}
58
- if is_migrate_model:
59
- attrs["__dict__"] = Model.__dict__["__dict__"]
60
- else:
61
- attrs["__dict__"] = PseudoModel.__dict__["__dict__"]
54
+ attrs["__dict__"] = Model.__dict__["__dict__"]
62
55
  metadata = {
63
56
  "service_name": service_name,
64
57
  "fixture_name": fixture_name,
65
58
  "db_query_docs_limit": db_query_docs_limit,
66
- "is_migrate_model": is_migrate_model,
67
- "is_create_doc": is_create_doc if is_migrate_model else False,
68
- "is_update_doc": is_update_doc if is_migrate_model else False,
69
- "is_delete_doc": is_delete_doc if is_migrate_model else False,
59
+ "is_create_doc": is_create_doc,
60
+ "is_update_doc": is_update_doc,
61
+ "is_delete_doc": is_delete_doc,
70
62
  }
71
63
  attrs["META"] = {
72
64
  **metadata,
73
- **caching(cls, service_name, is_migrate_model),
65
+ **caching(cls, service_name),
74
66
  }
75
67
 
76
- if is_migrate_model:
77
- return type(
78
- cls.__name__,
79
- (
80
- Model,
81
- QPaladinsMixin,
82
- QCommonsMixin,
83
- AddValidMixin,
84
- IndexMixin,
85
- HooksMixin,
86
- ),
87
- attrs,
88
- )
89
- else:
90
- return type(
91
- cls.__name__,
92
- (
93
- PseudoModel,
94
- ValidationMixin,
95
- CheckMixin,
96
- AddValidMixin,
97
- ),
98
- attrs,
99
- )
68
+ return type(
69
+ cls.__name__,
70
+ (
71
+ Model,
72
+ QPaladinsMixin,
73
+ QCommonsMixin,
74
+ AddValidMixin,
75
+ IndexMixin,
76
+ HooksMixin,
77
+ ),
78
+ attrs,
79
+ )
100
80
 
101
81
  return decorator
102
82
 
103
83
 
104
- def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, Any]:
84
+ def caching(cls: Any, service_name: str) -> dict[str, Any]:
105
85
  """Add additional metadata to `Model.META`."""
106
86
  metadata: dict[str, Any] = {}
107
87
  model_name: str = cls.__name__
@@ -128,11 +108,11 @@ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, An
128
108
 
129
109
  raw_model = cls()
130
110
  raw_model.fields()
131
- default_fields: dict[str, Any] = {}
132
- if is_migrate_model:
133
- default_fields["_id"] = IDField()
134
- default_fields["created_at"] = DateTimeField()
135
- default_fields["updated_at"] = DateTimeField()
111
+ default_fields: dict[str, Any] = {
112
+ "_id": IDField(),
113
+ "created_at": DateTimeField(),
114
+ "updated_at": DateTimeField(),
115
+ }
136
116
  fields = {**raw_model.__dict__, **default_fields}
137
117
  for f_name, f_data in fields.items():
138
118
  if not callable(f_data):
@@ -147,19 +127,11 @@ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, An
147
127
  #
148
128
  if not f_data.ignored:
149
129
  # Count fields for migrating.
150
- if is_migrate_model:
151
- count_fields_no_ignored += 1
130
+ count_fields_no_ignored += 1
152
131
  # Get a dictionary of field names and types.
153
132
  field_name_and_type[f_name] = f_type_str
154
133
  # Build data migration storage for dynamic fields.
155
134
  if "Dyn" in f_data.field_type:
156
- if not is_migrate_model:
157
- msg = (
158
- f"Model: `{cls.__module__}.{model_name}` > "
159
- + f"Field: `{f_name}` => "
160
- + "Dynamic field only for a migrated Model."
161
- )
162
- raise PanicError(msg)
163
135
  data_dynamic_fields[f_name] = None
164
136
  if f_data.field_type == "TextField" and f_data.multi_language:
165
137
  supported_lang_fields.append(f_name)
@@ -1,9 +1,9 @@
1
1
  """Validation of Model data before saving to the database."""
2
2
 
3
- import os
4
- import shutil
5
3
  from typing import Any
6
4
 
5
+ from aiofiles import os
6
+ from aioshutil import rmtree
7
7
  from bson.objectid import ObjectId
8
8
  from pymongo.asynchronous.collection import AsyncCollection
9
9
 
@@ -48,27 +48,15 @@ class CheckMixin(
48
48
  It is also used to verify Models that do not migrate to the database.
49
49
  """
50
50
  cls_model = self.__class__
51
- is_migrate_model: bool = cls_model.META["is_migrate_model"]
52
51
 
53
- if not is_migrate_model and is_save:
54
- msg = (
55
- f"Model: `{self.full_model_name()}` > "
56
- + "Method: `check` => "
57
- + "For a non -migrating Model, the `is_save` parameter must be equal to` False` !"
58
- )
59
- raise PanicError(msg)
60
-
61
- doc_id: ObjectId | None = None
62
- is_update: bool = False
63
- if is_migrate_model:
64
- # Get the document ID.
65
- doc_id = self._id.value
66
- # Does the document exist in the database?
67
- is_update = doc_id is not None
68
- # Create an identifier for a new document.
69
- if is_save and not is_update:
70
- doc_id = ObjectId()
71
- self._id.value = doc_id
52
+ # Get the document ID.
53
+ doc_id: ObjectId | None = self._id.value
54
+ # Does the document exist in the database?
55
+ is_update: bool = doc_id is not None
56
+ # Create an identifier for a new document.
57
+ if is_save and not is_update:
58
+ doc_id = ObjectId()
59
+ self._id.value = doc_id
72
60
 
73
61
  result_map: dict[str, Any] = {}
74
62
  # Errors from additional validation of fields.
@@ -86,7 +74,6 @@ class CheckMixin(
86
74
  "collection": collection,
87
75
  "field_data": None,
88
76
  "full_model_name": cls_model.META["full_model_name"],
89
- "is_migrate_model": is_migrate_model,
90
77
  "is_migration_process": is_migration_process,
91
78
  "curr_doc": (
92
79
  await collection.find_one({"_id": doc_id}) if is_save and is_update else None
@@ -116,9 +103,9 @@ class CheckMixin(
116
103
  elif group == "date":
117
104
  self.date_group(params)
118
105
  elif group == "img":
119
- self.img_group(params)
106
+ await self.img_group(params)
120
107
  elif group == "file":
121
- self.file_group(params)
108
+ await self.file_group(params)
122
109
  elif group == "choice":
123
110
  self.choice_group(params)
124
111
  elif group == "bool":
@@ -146,7 +133,7 @@ class CheckMixin(
146
133
  file_data = result_map.get(field_name)
147
134
  if file_data is not None:
148
135
  if file_data["is_new_file"]:
149
- os.remove(file_data["path"])
136
+ await os.remove(file_data["path"])
150
137
  field_data.value = None
151
138
  if curr_doc is not None:
152
139
  field_data.value = curr_doc[field_name]
@@ -154,7 +141,7 @@ class CheckMixin(
154
141
  img_data = result_map.get(field_name)
155
142
  if img_data is not None:
156
143
  if img_data["is_new_img"]:
157
- shutil.rmtree(img_data["imgs_dir_path"])
144
+ await rmtree(img_data["imgs_dir_path"]) # type: ignore[call-arg]
158
145
  field_data.value = None
159
146
  if curr_doc is not None:
160
147
  field_data.value = curr_doc[field_name]
@@ -1,9 +1,9 @@
1
1
  """Delete document from database."""
2
2
 
3
- import os
4
- import shutil
5
3
  from typing import Any
6
4
 
5
+ from aiofiles import os
6
+ from aioshutil import rmtree
7
7
  from pymongo.asynchronous.collection import AsyncCollection
8
8
 
9
9
  from ramifice.utils import globals
@@ -77,12 +77,12 @@ class DeleteMixin:
77
77
  if group == "file":
78
78
  file_data = mongo_doc[field_name]
79
79
  if file_data is not None and len(file_data["path"]) > 0:
80
- os.remove(file_data["path"])
80
+ await os.remove(file_data["path"])
81
81
  file_data = None
82
82
  elif group == "img":
83
83
  file_data = mongo_doc[field_name]
84
84
  if file_data is not None and len(file_data["imgs_dir_path"]) > 0:
85
- shutil.rmtree(file_data["imgs_dir_path"])
85
+ await rmtree(file_data["imgs_dir_path"]) # type: ignore[call-arg]
86
86
  file_data = None
87
87
  field_data.value = None
88
88
  # Run hook.
@@ -19,7 +19,7 @@ class FileGroupMixin:
19
19
  Supported fields: FileField
20
20
  """
21
21
 
22
- def file_group(self, params: dict[str, Any]) -> None:
22
+ async def file_group(self, params: dict[str, Any]) -> None:
23
23
  """Checking file fields."""
24
24
  field = params["field_data"]
25
25
  value = field.value or None
@@ -3,6 +3,7 @@
3
3
  Supported fields: ImageField
4
4
  """
5
5
 
6
+ import asyncio
6
7
  from typing import Any
7
8
 
8
9
  from PIL import Image
@@ -18,7 +19,7 @@ class ImgGroupMixin:
18
19
  Supported fields: ImageField
19
20
  """
20
21
 
21
- def img_group(self, params: dict[str, Any]) -> None:
22
+ async def img_group(self, params: dict[str, Any]) -> None:
22
23
  """Checking image fields."""
23
24
  field = params["field_data"]
24
25
  value = field.value or None
@@ -79,37 +80,48 @@ class ImgGroupMixin:
79
80
  # Extension to the upper register and delete the point.
80
81
  ext_upper = value["ext_upper"]
81
82
  # Get image file.
82
- with Image.open(path) as img:
83
+ with await asyncio.to_thread(Image.open, path) as img:
83
84
  width, height = img.size
84
85
  value["width"] = width
85
86
  value["height"] = height
86
- resample = (
87
- Image.Resampling.LANCZOS # High quality and low performance.
88
- if params["field_data"].high_quality
89
- else Image.Resampling.BICUBIC
90
- )
91
87
  for size_name in ["lg", "md", "sm", "xs"]:
92
88
  max_size = thumbnails.get(size_name)
93
89
  if max_size is None:
94
90
  continue
95
91
  size = max_size, max_size
96
- img.thumbnail(size=size, resample=resample)
92
+ img.thumbnail(size=size, resample=Image.Resampling.LANCZOS)
97
93
  if size_name == "lg":
98
94
  value["path_lg"] = f"{imgs_dir_path}/lg{extension}"
99
95
  value["url_lg"] = f"{imgs_dir_url}/lg{extension}"
100
- img.save(fp=value["path_lg"], format=ext_upper)
96
+ await asyncio.to_thread(
97
+ img.save,
98
+ fp=value["path_lg"],
99
+ format=ext_upper,
100
+ )
101
101
  elif size_name == "md":
102
102
  value["path_md"] = f"{imgs_dir_path}/md{extension}"
103
103
  value["url_md"] = f"{imgs_dir_url}/md{extension}"
104
- img.save(fp=value["path_md"], format=ext_upper)
104
+ await asyncio.to_thread(
105
+ img.save,
106
+ fp=value["path_md"],
107
+ format=ext_upper,
108
+ )
105
109
  elif size_name == "sm":
106
110
  value["path_sm"] = f"{imgs_dir_path}/sm{extension}"
107
111
  value["url_sm"] = f"{imgs_dir_url}/sm{extension}"
108
- img.save(fp=value["path_sm"], format=ext_upper)
112
+ await asyncio.to_thread(
113
+ img.save,
114
+ fp=value["path_sm"],
115
+ format=ext_upper,
116
+ )
109
117
  elif size_name == "xs":
110
118
  value["path_xs"] = f"{imgs_dir_path}/xs{extension}"
111
119
  value["url_xs"] = f"{imgs_dir_url}/xs{extension}"
112
- img.save(fp=value["path_xs"], format=ext_upper)
120
+ await asyncio.to_thread(
121
+ img.save,
122
+ fp=value["path_xs"],
123
+ format=ext_upper,
124
+ )
113
125
  # Insert result.
114
126
  if params["is_save"] and (value["is_new_img"] or value["save_as_is"]):
115
127
  value["is_delete"] = False
@@ -5,13 +5,26 @@ Supported fields:
5
5
  IPField | EmailField | ColorField
6
6
  """
7
7
 
8
+ import asyncio
8
9
  from typing import Any
9
10
 
10
- from email_validator import EmailNotValidError, validate_email
11
+ from email_validator import (
12
+ EmailNotValidError,
13
+ validate_email,
14
+ )
11
15
 
12
- from ramifice.paladins.tools import accumulate_error, check_uniqueness, panic_type_error
16
+ from ramifice.paladins.tools import (
17
+ accumulate_error,
18
+ check_uniqueness,
19
+ panic_type_error,
20
+ )
13
21
  from ramifice.utils import translations
14
- from ramifice.utils.tools import is_color, is_ip, is_phone, is_url
22
+ from ramifice.utils.tools import (
23
+ is_color,
24
+ is_ip,
25
+ is_phone,
26
+ is_url,
27
+ )
15
28
 
16
29
 
17
30
  class TextGroupMixin:
@@ -60,27 +73,28 @@ class TextGroupMixin:
60
73
  err_msg = translations._("Is not unique !")
61
74
  accumulate_error(err_msg, params)
62
75
  # Validation Email, Url, IP, Color, Phone.
63
- if "EmailField" == field_type:
76
+ if field_type == "EmailField":
64
77
  try:
65
- emailinfo = validate_email(
78
+ emailinfo = await asyncio.to_thread(
79
+ validate_email,
66
80
  str(value),
67
- check_deliverability=self.__class__.META["is_migrate_model"],
81
+ check_deliverability=True,
68
82
  )
69
83
  value = emailinfo.normalized
70
84
  params["field_data"].value = value
71
85
  except EmailNotValidError:
72
86
  err_msg = translations._("Invalid Email address !")
73
87
  accumulate_error(err_msg, params)
74
- elif "URLField" == field_type and not is_url(value):
88
+ elif field_type == "URLField" and not is_url(value):
75
89
  err_msg = translations._("Invalid URL address !")
76
90
  accumulate_error(err_msg, params)
77
- elif "IPField" == field_type and not is_ip(value):
91
+ elif field_type == "IPField" and not is_ip(value):
78
92
  err_msg = translations._("Invalid IP address !")
79
93
  accumulate_error(err_msg, params)
80
- elif "ColorField" == field_type and not is_color(value):
94
+ elif field_type == "ColorField" and not is_color(value):
81
95
  err_msg = translations._("Invalid Color code !")
82
96
  accumulate_error(err_msg, params)
83
- elif "PhoneField" == field_type and not is_phone(value):
97
+ elif field_type == "PhoneField" and not is_phone(value):
84
98
  err_msg = translations._("Invalid Phone number !")
85
99
  accumulate_error(err_msg, params)
86
100
  # Insert result.
@@ -58,8 +58,6 @@ async def check_uniqueness(
58
58
  is_multi_language: bool = False,
59
59
  ) -> bool:
60
60
  """Check the uniqueness of the value in the collection."""
61
- if not params["is_migrate_model"]:
62
- return True
63
61
  q_filter = None
64
62
  if is_multi_language:
65
63
  lang_filter = [{f"{field_name}.{lang}": value} for lang in translations.LANGUAGES]
@@ -31,9 +31,7 @@ class Monitor:
31
31
  globals.MONGO_CLIENT = mongo_client
32
32
  globals.MONGO_DATABASE = globals.MONGO_CLIENT[globals.DATABASE_NAME]
33
33
  # Get Model list.
34
- self.model_list: list[Any] = [
35
- cls_model for cls_model in Model.__subclasses__() if cls_model.META["is_migrate_model"]
36
- ]
34
+ self.model_list: list[Any] = Model.__subclasses__()
37
35
  # Raise the exception if there are no models for migration.
38
36
  if len(self.model_list) == 0:
39
37
  raise NoModelsForMigrationError() # type: ignore[no-untyped-call]
ramifice/utils/tools.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Global collection of auxiliary methods."""
2
2
 
3
+ import asyncio
3
4
  import ipaddress
4
5
  import math
5
6
  import os
@@ -49,10 +50,14 @@ def normal_email(email: str | None) -> str | None:
49
50
  return normal
50
51
 
51
52
 
52
- def is_email(email: str | None) -> bool:
53
+ async def is_email(email: str | None) -> bool:
53
54
  """Validate Email address."""
54
55
  try:
55
- validate_email(str(email), check_deliverability=True)
56
+ await asyncio.to_thread(
57
+ validate_email,
58
+ str(email),
59
+ check_deliverability=True,
60
+ )
56
61
  except EmailNotValidError:
57
62
  return False
58
63
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramifice
3
- Version: 0.5.12
3
+ Version: 0.6.1
4
4
  Summary: ORM-like API MongoDB for Python language.
5
5
  Project-URL: Homepage, https://github.com/kebasyaty/ramifice
6
6
  Project-URL: Documentation, https://kebasyaty.github.io/ramifice/
@@ -23,6 +23,8 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
23
23
  Classifier: Topic :: Database
24
24
  Classifier: Typing :: Typed
25
25
  Requires-Python: <4.0,>=3.12
26
+ Requires-Dist: aiofiles>=24.1.0
27
+ Requires-Dist: aioshutil>=1.5
26
28
  Requires-Dist: argon2-cffi>=25.1.0
27
29
  Requires-Dist: babel>=2.17.0
28
30
  Requires-Dist: email-validator>=2.2.0
@@ -58,6 +60,7 @@ Description-Content-Type: text/markdown
58
60
  <a href="https://pepy.tech/projects/ramifice"><img src="https://static.pepy.tech/badge/ramifice" alt="PyPI Downloads"></a>
59
61
  <a href="https://github.com/kebasyaty/ramifice/blob/main/LICENSE" alt="GitHub license"><img src="https://img.shields.io/github/license/kebasyaty/ramifice" alt="GitHub license"></a>
60
62
  <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>
63
+ <a href="https://github.com/kebasyaty/ramifice" alt="PyPI implementation"><img src="https://img.shields.io/pypi/implementation/ramifice" alt="PyPI implementation"></a>
61
64
  <a href="https://github.com/kebasyaty/ramifice" alt="GitHub repository"><img src="https://img.shields.io/badge/--ecebeb?logo=github&logoColor=000000" alt="GitHub repository"></a>
62
65
  </p>
63
66
  <p align="center">
@@ -148,16 +151,12 @@ class User:
148
151
  # ngettext = translations.ngettext
149
152
  self.avatar = ImageField(
150
153
  label=gettext("Avatar"),
151
- placeholder=gettext("Upload your photo"),
152
154
  default="public/media/default/no-photo.png",
153
155
  # Directory for images inside media directory.
154
156
  target_dir="users/avatars",
155
157
  # Available 4 sizes from lg to xs or None.
156
158
  # Hint: By default = None
157
159
  thumbnails={"lg": 512, "md": 256, "sm": 128, "xs": 64},
158
- # True - high quality and low performance for thumbnails.
159
- # Hint: By default = False
160
- high_quality=True,
161
160
  # The maximum size of the original image in bytes.
162
161
  # Hint: By default = 2 MB
163
162
  max_size=524288, # 0.5 MB = 512 KB = 524288 Bytes (in binary)
@@ -167,7 +166,6 @@ class User:
167
166
  )
168
167
  self.username = TextField(
169
168
  label=gettext("Username"),
170
- placeholder=gettext("Enter your username"),
171
169
  maxlength=150,
172
170
  required=True,
173
171
  unique=True,
@@ -177,11 +175,9 @@ class User:
177
175
  )
178
176
  self.password = PasswordField(
179
177
  label=gettext("Password"),
180
- placeholder=gettext("Enter your password"),
181
178
  )
182
179
  self.сonfirm_password = PasswordField(
183
180
  label=gettext("Confirm password"),
184
- placeholder=gettext("Repeat your password"),
185
181
  # If true, the value of this field is not saved in the database.
186
182
  ignored=True,
187
183
  )
@@ -342,14 +338,6 @@ See the documentation [here](https://kebasyaty.github.io/ramifice/ "here").
342
338
  <td align="left">1000</td>
343
339
  <td align="left">limiting query results.</td>
344
340
  </tr>
345
- <tr>
346
- <td align="left">is_migrate_model</td>
347
- <td align="left">True</td>
348
- <td align="left">
349
- Set to <b>False</b> if you do not need to migrate the Model to the database.<br>
350
- This can be use to validate a web forms - Search form, Contact form, etc.
351
- </td>
352
- </tr>
353
341
  <tr>
354
342
  <td align="left">is_create_doc</td>
355
343
  <td align="left">True</td>
@@ -380,7 +368,6 @@ See the documentation [here](https://kebasyaty.github.io/ramifice/ "here").
380
368
  service_name="ServiceName",
381
369
  fixture_name="FixtureName",
382
370
  db_query_docs_limit=1000,
383
- is_migrate_model=True,
384
371
  is_create_doc = True,
385
372
  is_update_doc = True,
386
373
  is_delete_doc = True,
@@ -570,7 +557,7 @@ if is_password("12345678"):
570
557
  ...
571
558
 
572
559
  # Validate Email address.
573
- if is_email("kebasyaty@gmail.com"):
560
+ if await is_email("kebasyaty@gmail.com"):
574
561
  ...
575
562
 
576
563
  # Normalizing email address.
@@ -25,10 +25,10 @@ ramifice/fields/color_field.py,sha256=Syam8MtB--B0HzAT3w3QuWz3pPixSMbh8xGPmbGYLC
25
25
  ramifice/fields/date_field.py,sha256=xhMRsEwk4CztDae5va2Tx0v9Oh55M4p0S4Y0qu-ZlIk,5251
26
26
  ramifice/fields/date_time_field.py,sha256=RKxXVWTWGNxTri-MDlOAZNSwgtL46m-0grh3wKX-gi8,5286
27
27
  ramifice/fields/email_field.py,sha256=qXG7zHkr5ox4CFrb6QYarBMl8gwcq8KlVFVCj-zfvs0,3477
28
- ramifice/fields/file_field.py,sha256=IysryfsXz5wcBudV_h5BspMfwtEkvLHFhPPs6VgBp4A,7992
28
+ ramifice/fields/file_field.py,sha256=YLdZvotj6ymEVRjYGU4xsiHkP_zI8krk3TuHaowvfDo,8126
29
29
  ramifice/fields/float_field.py,sha256=wadjiYakzv4uzWD8wI2E0B9w78Or4hZiAmrNXlOy8EI,4629
30
30
  ramifice/fields/id_field.py,sha256=NqdM0UJAi-iXJc7wa6CAJNG0-i4YNt08XGkHqV5ingc,4157
31
- ramifice/fields/image_field.py,sha256=EjvR_43CM_EJM4OO9VVdQGmNquKzmNsJ2OMeSIJB54k,12369
31
+ ramifice/fields/image_field.py,sha256=MfKILPPz2ye8x696eEu8loRRpIWgP4neVoZ7ucNkDIw,11545
32
32
  ramifice/fields/integer_field.py,sha256=QpLqLItG4mVMYOhpGDVFNr-wDFqeUuERtfZy7C-EslE,4605
33
33
  ramifice/fields/ip_field.py,sha256=q-2OgT4VBDo8yzrX7wwuSHwexwsM8wDwnHOyQRACItI,3386
34
34
  ramifice/fields/password_field.py,sha256=EzIsegHdsVpnj9D-cTI1Q9RBbW07qUx7I7UFcMWW8Hk,3238
@@ -44,34 +44,33 @@ ramifice/fields/general/file_group.py,sha256=T-IwFnlJAWaRcQKVn09IJCdFNGgnUfMkjLz
44
44
  ramifice/fields/general/number_group.py,sha256=LOPHbKAnIuqW4DEh2fH78w6qOQjp0OhkuP7MoikhlkA,761
45
45
  ramifice/fields/general/text_group.py,sha256=m9EnlYGwqlck-JIYOCUd0hUYAVr8jH4YCnTmm9QlCkQ,1103
46
46
  ramifice/models/__init__.py,sha256=h_QQ5rSJNZ-7kmx7wIFd8E8RmUS94b_x4jdwMbq8zm4,15
47
- ramifice/models/decorator.py,sha256=6t5-dD5aoVbBfpGwpcIkowt0Hhupy1ldtJ3EuSQGGXc,7337
47
+ ramifice/models/decorator.py,sha256=vEblLbpQQ0BkdTI2ch-BtDvCqWSEXe2aaEjxND705_Q,5954
48
48
  ramifice/models/model.py,sha256=JPEUYONRHVj7uE5vnuodc65dGHyBQCRmV8mdVUAgyXw,7118
49
- ramifice/models/pseudo.py,sha256=PEtK_ilezUlKKUyvwBZv5fc93SbZh2yoIQuc2c4D-Ko,6779
50
49
  ramifice/paladins/__init__.py,sha256=EJ6EEKXUcMG5P1bDmlEa4W3fsxSz5u6mzDG_oU1efMQ,618
51
- ramifice/paladins/check.py,sha256=CxcCiLBqNftrDksVi2XwKGWl1czTUbgXwzeIgJYgXCg,7057
52
- ramifice/paladins/delete.py,sha256=WEH0PwIdnGZpmUOLobJlZRD4iX94DOb_4ucwiGukd1o,3366
50
+ ramifice/paladins/check.py,sha256=OeEDtL3lNR94bL1shrHloAHXHZtHnl6IWzEgukD9HSU,6580
51
+ ramifice/paladins/delete.py,sha256=hnLpyMoNbgegQ_GF-A7iEHj7cbk1oSc5xyTy1BNLxsE,3426
53
52
  ramifice/paladins/password.py,sha256=pp0AVMf98w-2Ps3hHqwwIDSsXh93s-1TrBbeEI83Ly8,3041
54
53
  ramifice/paladins/refrash.py,sha256=4xojazc5BqCkE8_st11gH5tj2LpflzWVn2ZD-uNoOsg,1096
55
54
  ramifice/paladins/save.py,sha256=Z8vmqEksCK8XY27-BsgN2lyaNxLregOo5p0_XB9qUys,3895
56
- ramifice/paladins/tools.py,sha256=FYFnNMiTwyvoeBzKlpgsTx6GmFbv-m9Udpwc5Cog0DU,2713
55
+ ramifice/paladins/tools.py,sha256=zc2VXp1U_BCT4IQWopwhxJ5vqci9jX128U5_nMeHawY,2652
57
56
  ramifice/paladins/validation.py,sha256=gcEJXIEPu1g7Z54vl14YTs5rCmxOEYsgQH1usFfyy7k,1730
58
57
  ramifice/paladins/groups/__init__.py,sha256=GxJWM3IkYT3HW9pnJ1QTEjdOKJnjEoAaQ8SRukXnIW0,712
59
58
  ramifice/paladins/groups/bool_group.py,sha256=rz1XIIdhOqbCOYZT1Yl84XaJg-0vwdcqCnt0BH-xHVA,808
60
59
  ramifice/paladins/groups/choice_group.py,sha256=KmrjbuHLLl6yGbRkdoMflf1thh17GLWrcbfaqrHlDiE,1813
61
60
  ramifice/paladins/groups/date_group.py,sha256=V3MCp8eA7txVLh6xmg76G9_y62TzWIQWqH59Ip26Xag,3779
62
- ramifice/paladins/groups/file_group.py,sha256=3_U3wHm306KGw88E63W5G11U6Y4d2IrFlo8EMOFSCGY,2976
61
+ ramifice/paladins/groups/file_group.py,sha256=ySS7L5zcm2XrZRQUUOU8kgzqrucZ95UYzXD6pALWqE4,2982
63
62
  ramifice/paladins/groups/id_group.py,sha256=zN_Glu_cMGk8jMSDli3vYPvYrQXvQJNJFzb-t-qtZlQ,1290
64
- ramifice/paladins/groups/img_group.py,sha256=2VBtvItInjD1xCsAvx_7C3QJefV_0B6a8PPzpA3Ev08,5570
63
+ ramifice/paladins/groups/img_group.py,sha256=YDJTvbRoqQ2nf7-2QbIQ72I4j_c98_d5LZNrD9kCy0Q,6040
65
64
  ramifice/paladins/groups/num_group.py,sha256=sbxzTdl33TVySfaNfqMixyBkJ69v6AqEgraFUN3Kftk,2317
66
65
  ramifice/paladins/groups/pass_group.py,sha256=uuIIqMBXsYG7vTHc_AhdgWuNCivxTgQMjkEu0-ElSmY,1887
67
66
  ramifice/paladins/groups/slug_group.py,sha256=ztiA2v7e5lQYRZtlLw8WGOhSWaYQfOdZ6wkKbx3ZbTM,2329
68
- ramifice/paladins/groups/text_group.py,sha256=aMaS7jA3NBAHE6mjv5M4sJE3xOdY8mKHIPv0uMH6CrU,4382
67
+ ramifice/paladins/groups/text_group.py,sha256=cdwUZoomw61A7CmDIbds_oOaia2tD-J5LLuj5b8E8O4,4472
69
68
  ramifice/utils/__init__.py,sha256=xixHoOX4ja5jIUZemem1qn4k4aonv3G3Q76azQK_pkU,43
70
69
  ramifice/utils/errors.py,sha256=iuhq7fzpUmsOyeXeg2fJjta8yAuqlXLKsZVMpfUhtHE,1901
71
70
  ramifice/utils/fixtures.py,sha256=IG55gdFUbrWRQ2cL3Df9mYBgjVlFrlRuWB7UeK1Nzd8,3173
72
71
  ramifice/utils/globals.py,sha256=uR20um3Qg_1SG1t7WyWbpq8kQD-9Mslyr_c1yh5Hw9w,1751
73
- ramifice/utils/migration.py,sha256=cEGGSGcFGtaPUIkt_BB6tiPLFEtQtDoujV1hF413LVg,11108
74
- ramifice/utils/tools.py,sha256=1yFIOrkWToM55OpMMId9T26hZqPQWnxLU3_jQ4Ejazg,2743
72
+ ramifice/utils/migration.py,sha256=JUhYaJ61IS1W_Khof0sbqeHDQrI7-XcmTQ41PhCdjcQ,11017
73
+ ramifice/utils/tools.py,sha256=mEcLYdSM3x0Sj9d6ygYOfsOA7iXnmELoNNsCWtGIuGE,2841
75
74
  ramifice/utils/translations.py,sha256=aEkNKilD7RlJjBixqhh0cfIMw9lg13woaIwd3dyR8G4,4247
76
75
  ramifice/utils/unit.py,sha256=PPNKWYFJ8cz0nwbBPaTdL58_Nr7N0fIHFJBpKG2ZLKI,2482
77
76
  ramifice/utils/mixins/__init__.py,sha256=GrxJDsw73bEkitIh0-0BCxNnUK-N5uRXMCRlaPoaz1o,265
@@ -79,7 +78,7 @@ ramifice/utils/mixins/add_valid.py,sha256=TLOObedzXNA9eCylfAVbVCqIKE5sV-P5AdIN7a
79
78
  ramifice/utils/mixins/hooks.py,sha256=33jvJRhfnJeL2Hd_YFXk3M_7wjqHaByU2wRjKyboL6s,914
80
79
  ramifice/utils/mixins/indexing.py,sha256=Z0427HoaVRyNmSNN8Fx0mSICgAKV-gDdu3iR5qYUEbs,329
81
80
  ramifice/utils/mixins/json_converter.py,sha256=WhigXyDAV-FfILaZuwvRFRIk0D90Rv3dG5t-mv5fVyc,1107
82
- ramifice-0.5.12.dist-info/METADATA,sha256=2MQrjGd6MQddOO6PomVVgsfcCREPzHWWbriPLpMMHuQ,21454
83
- ramifice-0.5.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
84
- ramifice-0.5.12.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
85
- ramifice-0.5.12.dist-info/RECORD,,
81
+ ramifice-0.6.1.dist-info/METADATA,sha256=oINmxfbfasgZhCMYMBRTUMErLb2Exx4l5khQjgXGusY,20993
82
+ ramifice-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
83
+ ramifice-0.6.1.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
84
+ ramifice-0.6.1.dist-info/RECORD,,
ramifice/models/pseudo.py DELETED
@@ -1,181 +0,0 @@
1
- """For converting Python classes into Ramifice models."""
2
-
3
- import json
4
- import os
5
- import shutil
6
- from abc import ABCMeta, abstractmethod
7
- from typing import Any
8
-
9
- from babel.dates import format_date, format_datetime
10
- from bson.objectid import ObjectId
11
- from dateutil.parser import parse
12
-
13
- from ramifice.utils import translations
14
- from ramifice.utils.errors import PanicError
15
-
16
-
17
- class PseudoModel(metaclass=ABCMeta):
18
- """Convert the Python Class into a pseudo Model Ramifice.
19
-
20
- Used for a Model that do not migrate into the database.
21
- """
22
-
23
- META: dict[str, Any] = {}
24
-
25
- def __init__(self) -> None: # noqa: D107
26
- self.fields()
27
- self.inject()
28
-
29
- for _, f_type in self.__dict__.items():
30
- if not callable(f_type) and f_type.group == "img":
31
- f_type.__dict__["add_width_height"] = True
32
-
33
- def __del__(self) -> None: # noqa: D105
34
- # If the model is not migrated,
35
- # it must delete files and images in the destructor.
36
- for _, f_type in self.__dict__.items():
37
- if callable(f_type):
38
- continue
39
- value = f_type.value
40
- if value is not None:
41
- if f_type.group == "file":
42
- value = value.get("path")
43
- if value is not None:
44
- os.remove(value)
45
- elif f_type.group == "img":
46
- value = value.get("imgs_dir_path")
47
- if value is not None:
48
- shutil.rmtree(value)
49
-
50
- @abstractmethod
51
- def fields(self) -> None:
52
- """For adding fields."""
53
- pass
54
-
55
- def model_name(self) -> str:
56
- """Get Model name - Class name."""
57
- return self.__class__.__name__
58
-
59
- def full_model_name(self) -> str:
60
- """Get full Model name - module_name + . + ClassName."""
61
- cls = self.__class__
62
- return f"{cls.__module__}.{cls.__name__}"
63
-
64
- def inject(self) -> None:
65
- """Injecting metadata from Model.META in params of fields."""
66
- metadata = self.__class__.META
67
- if bool(metadata):
68
- field_attrs = metadata["field_attrs"]
69
- for f_name, f_type in self.__dict__.items():
70
- if callable(f_type):
71
- continue
72
- f_type.id = field_attrs[f_name]["id"]
73
- f_type.name = field_attrs[f_name]["name"]
74
- if "Dyn" in f_type.field_type:
75
- msg = (
76
- f"Model: `{metadata['full_model_name']}` > "
77
- + f"Field: `{f_name}` => "
78
- + "Dynamic field only for a migrated Model."
79
- )
80
- raise PanicError(msg)
81
-
82
- # Complect of methods for converting Model to JSON and back.
83
- # --------------------------------------------------------------------------
84
- def to_dict(self) -> dict[str, Any]:
85
- """Convert object instance to a dictionary."""
86
- json_dict: dict[str, Any] = {}
87
- for name, data in self.__dict__.items():
88
- if not callable(data):
89
- json_dict[name] = data.to_dict()
90
- return json_dict
91
-
92
- def to_json(self) -> str:
93
- """Convert object instance to a JSON string."""
94
- return json.dumps(self.to_dict())
95
-
96
- @classmethod
97
- def from_dict(cls, json_dict: dict[str, Any]) -> Any:
98
- """Convert JSON string to a object instance."""
99
- obj = cls()
100
- for name, data in json_dict.items():
101
- obj.__dict__[name] = obj.__dict__[name].__class__.from_dict(data)
102
- return obj
103
-
104
- @classmethod
105
- def from_json(cls, json_str: str) -> Any:
106
- """Convert JSON string to a object instance."""
107
- json_dict = json.loads(json_str)
108
- return cls.from_dict(json_dict)
109
-
110
- # --------------------------------------------------------------------------
111
- def to_dict_only_value(self) -> dict[str, Any]:
112
- """Convert model.field.value (only the `value` attribute) to a dictionary."""
113
- json_dict: dict[str, Any] = {}
114
- current_locale = translations.CURRENT_LOCALE
115
- for name, data in self.__dict__.items():
116
- if callable(data):
117
- continue
118
- value = data.value
119
- if value is not None:
120
- group = data.group
121
- if group == "date":
122
- value = (
123
- format_date(
124
- date=value,
125
- format="short",
126
- locale=current_locale,
127
- )
128
- if data.field_type == "DateField"
129
- else format_datetime(
130
- datetime=value,
131
- format="short",
132
- locale=current_locale,
133
- )
134
- )
135
- elif group == "id":
136
- value = str(value)
137
- elif group == "pass":
138
- value = None
139
- json_dict[name] = value
140
- return json_dict
141
-
142
- def to_json_only_value(self) -> str:
143
- """Convert model.field.value (only the `value` attribute) to a JSON string."""
144
- return json.dumps(self.to_dict_only_value())
145
-
146
- @classmethod
147
- def from_dict_only_value(cls, json_dict: dict[str, Any]) -> Any:
148
- """Convert JSON string to a object instance."""
149
- obj = cls()
150
- for name, data in obj.__dict__.items():
151
- if callable(data):
152
- continue
153
- value = json_dict.get(name)
154
- if value is not None:
155
- group = data.group
156
- if group == "date":
157
- value = parse(value)
158
- elif group == "id":
159
- value = ObjectId(value)
160
- obj.__dict__[name].value = value
161
- return obj
162
-
163
- @classmethod
164
- def from_json_only_value(cls, json_str: str) -> Any:
165
- """Convert JSON string to a object instance."""
166
- json_dict = json.loads(json_str)
167
- return cls.from_dict_only_value(json_dict)
168
-
169
- def refrash_fields_only_value(self, only_value_dict: dict[str, Any]) -> None:
170
- """Partial or complete update a `value` of fields."""
171
- for name, data in self.__dict__.items():
172
- if callable(data):
173
- continue
174
- value = only_value_dict.get(name)
175
- if value is not None:
176
- group = data.group
177
- if group == "date":
178
- value = parse(value)
179
- elif group == "id":
180
- value = ObjectId(value)
181
- self.__dict__[name].value = value