ramifice 0.3.19__py3-none-any.whl → 0.3.21__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.
ramifice/decorators.py CHANGED
@@ -12,6 +12,7 @@ from .hooks import HooksMixin
12
12
  from .indexing import IndexMixin
13
13
  from .model import Model
14
14
  from .paladins import CheckMixin, QPaladinsMixin, ValidationMixin # type: ignore[attr-defined]
15
+ from .pseudo_model import PseudoModel
15
16
  from .store import REGEX
16
17
 
17
18
 
@@ -55,7 +56,10 @@ def model(
55
56
  raise PanicError(msg)
56
57
 
57
58
  attrs = {key: val for key, val in cls.__dict__.items()}
58
- attrs["__dict__"] = Model.__dict__["__dict__"]
59
+ if is_migrate_model:
60
+ attrs["__dict__"] = Model.__dict__["__dict__"]
61
+ else:
62
+ attrs["__dict__"] = PseudoModel.__dict__["__dict__"]
59
63
  metadata = {
60
64
  "service_name": service_name,
61
65
  "fixture_name": fixture_name,
@@ -65,7 +69,10 @@ def model(
65
69
  "is_update_doc": is_update_doc if is_migrate_model else False,
66
70
  "is_delete_doc": is_delete_doc if is_migrate_model else False,
67
71
  }
68
- attrs["META"] = {**metadata, **caching(cls, service_name)}
72
+ attrs["META"] = {
73
+ **metadata,
74
+ **caching(cls, service_name, is_migrate_model),
75
+ }
69
76
 
70
77
  if is_migrate_model:
71
78
  return type(
@@ -84,7 +91,7 @@ def model(
84
91
  return type(
85
92
  cls.__name__,
86
93
  (
87
- Model,
94
+ PseudoModel,
88
95
  ValidationMixin,
89
96
  CheckMixin,
90
97
  AddValidMixin,
@@ -95,7 +102,7 @@ def model(
95
102
  return decorator
96
103
 
97
104
 
98
- def caching(cls: Any, service_name: str) -> dict[str, Any]:
105
+ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, Any]:
99
106
  """Add additional metadata to `Model.META`."""
100
107
  metadata: dict[str, Any] = {}
101
108
  model_name = cls.__name__
@@ -117,14 +124,13 @@ def caching(cls: Any, service_name: str) -> dict[str, Any]:
117
124
  # Count fields for migrating.
118
125
  count_fields_for_migrating = 0
119
126
 
120
- old_model = cls()
121
- old_model.fields()
122
- default_fields: dict[str, Any] = {
123
- "_id": IDField(),
124
- "created_at": DateTimeField(),
125
- "updated_at": DateTimeField(),
126
- }
127
- fields = {**old_model.__dict__, **default_fields}
127
+ raw_model = cls()
128
+ raw_model.fields()
129
+ default_fields: dict[str, Any] = {"_id": IDField()}
130
+ if is_migrate_model:
131
+ default_fields["created_at"] = DateTimeField()
132
+ default_fields["updated_at"] = DateTimeField()
133
+ fields = {**raw_model.__dict__, **default_fields}
128
134
  for f_name, f_type in fields.items():
129
135
  if not callable(f_type):
130
136
  f_type_str = f_type.__class__.__name__
@@ -138,7 +144,8 @@ def caching(cls: Any, service_name: str) -> dict[str, Any]:
138
144
  #
139
145
  if not f_type.ignored:
140
146
  # Count fields for migrating.
141
- count_fields_for_migrating += 1
147
+ if is_migrate_model:
148
+ count_fields_for_migrating += 1
142
149
  # Get a dictionary of field names and types.
143
150
  field_name_and_type[f_name] = f_type_str
144
151
  # Build data migration storage for dynamic fields.
@@ -4,7 +4,7 @@ import os
4
4
  import shutil
5
5
  import uuid
6
6
  from base64 import b64decode
7
- from datetime import datetime
7
+ from datetime import date
8
8
  from pathlib import Path
9
9
  from typing import Any
10
10
 
@@ -121,7 +121,7 @@ class FileField(Field, FileGroup, JsonMixin):
121
121
  # Create new (uuid) file name.
122
122
  f_uuid_name = f"{uuid.uuid4()}{extension}"
123
123
  # Create the current date for the directory name.
124
- date_str = datetime.now().strftime("%Y-%m-%d")
124
+ date_str: str = str(date.today())
125
125
  # Create path to target directory.
126
126
  dir_target_path = f"{self.media_root}/{self.target_dir}/{date_str}"
127
127
  # Create target directory if it does not exist.
@@ -166,7 +166,7 @@ class FileField(Field, FileGroup, JsonMixin):
166
166
  # Create new (uuid) file name.
167
167
  f_uuid_name = f"{uuid.uuid4()}{extension}"
168
168
  # Create the current date for the directory name.
169
- date_str = datetime.now().strftime("%Y-%m-%d")
169
+ date_str: str = str(date.today())
170
170
  # Create path to target directory.
171
171
  dir_target_path = f"{self.media_root}/{self.target_dir}/{date_str}"
172
172
  # Create target directory if it does not exist.
@@ -4,7 +4,7 @@ import os
4
4
  import shutil
5
5
  import uuid
6
6
  from base64 import b64decode
7
- from datetime import datetime
7
+ from datetime import date
8
8
  from pathlib import Path
9
9
  from typing import Any
10
10
 
@@ -135,7 +135,6 @@ class ImageField(Field, FileGroup, JsonMixin):
135
135
  base64_str: str | None = None,
136
136
  filename: str | None = None,
137
137
  is_delete: bool = False,
138
- add_wh: bool = False,
139
138
  ) -> None:
140
139
  """Convert base64 to a image,
141
140
  get image information and save in the target directory.
@@ -159,7 +158,7 @@ class ImageField(Field, FileGroup, JsonMixin):
159
158
  if item[0] == 40:
160
159
  break
161
160
  # Create the current date for the directory name.
162
- date_str = datetime.now().strftime("%Y-%m-%d")
161
+ date_str: str = str(date.today())
163
162
  # Directory name for the original image and its thumbnails.
164
163
  general_dir = uuid.uuid4()
165
164
  # Create path to target directory with images.
@@ -181,7 +180,7 @@ class ImageField(Field, FileGroup, JsonMixin):
181
180
  img_info["path"] = main_img_path
182
181
  img_info["url"] = f"{imgs_dir_url}/{new_original_name}"
183
182
  # Add width and height.
184
- if add_wh:
183
+ if self.__dict__.get("add_width_height", False):
185
184
  with Image.open(main_img_path) as img:
186
185
  width, height = img.size
187
186
  img_info["width"] = width
@@ -209,7 +208,6 @@ class ImageField(Field, FileGroup, JsonMixin):
209
208
  self,
210
209
  src_path: str | None = None,
211
210
  is_delete: bool = False,
212
- add_wh: bool = False,
213
211
  ) -> None:
214
212
  """Get image information and copy the image to the target directory."""
215
213
  src_path = src_path or None
@@ -224,7 +222,7 @@ class ImageField(Field, FileGroup, JsonMixin):
224
222
  msg = f"The image `{src_path}` has no extension."
225
223
  raise FileHasNoExtensionError(msg)
226
224
  # Create the current date for the directory name.
227
- date_str = datetime.now().strftime("%Y-%m-%d")
225
+ date_str: str = str(date.today())
228
226
  # Directory name for the original image and its thumbnails.
229
227
  general_dir = uuid.uuid4()
230
228
  # Create path to target directory with images.
@@ -244,7 +242,7 @@ class ImageField(Field, FileGroup, JsonMixin):
244
242
  img_info["path"] = main_img_path
245
243
  img_info["url"] = f"{imgs_dir_url}/{new_original_name}"
246
244
  # Add width and height.
247
- if add_wh:
245
+ if self.__dict__.get("add_width_height", False):
248
246
  with Image.open(main_img_path) as img:
249
247
  width, height = img.size
250
248
  img_info["width"] = width
ramifice/model.py CHANGED
@@ -1,8 +1,6 @@
1
1
  """For converting Python classes into Ramifice Model."""
2
2
 
3
3
  import json
4
- import os
5
- import shutil
6
4
  from abc import ABCMeta, abstractmethod
7
5
  from typing import Any
8
6
 
@@ -15,7 +13,7 @@ from .fields import DateTimeField, IDField # type: ignore[attr-defined]
15
13
 
16
14
 
17
15
  class Model(metaclass=ABCMeta):
18
- """For converting Python classes into Ramifice Model."""
16
+ """For converting Python Class into Ramifice Model."""
19
17
 
20
18
  META: dict[str, Any] = {}
21
19
 
@@ -47,24 +45,6 @@ class Model(metaclass=ABCMeta):
47
45
  self.fields()
48
46
  self.inject()
49
47
 
50
- def __del__(self) -> None: # noqa: D105
51
- # If the model is not migrated,
52
- # it must delete files and images in the destructor.
53
- if not self.__class__.META["is_migrate_model"]:
54
- for _, f_type in self.__dict__.items():
55
- if not callable(f_type):
56
- value = f_type.value
57
- if value is None:
58
- continue
59
- if f_type.group == "file":
60
- value = value.get("path")
61
- if value is not None:
62
- os.remove(value)
63
- elif f_type.group == "img":
64
- value = value.get("imgs_dir_path")
65
- if value is not None:
66
- shutil.rmtree(value)
67
-
68
48
  @abstractmethod
69
49
  def fields(self) -> None:
70
50
  """For adding fields."""
@@ -0,0 +1,183 @@
1
+ """For converting Python classes into Ramifice Model."""
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 . import translations
14
+ from .fields import IDField # type: ignore[attr-defined]
15
+
16
+
17
+ class PseudoModel(metaclass=ABCMeta):
18
+ """For 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._id = IDField(
27
+ label="Stub",
28
+ placeholder="Stub",
29
+ hint="Stub",
30
+ hide=True,
31
+ disabled=True,
32
+ )
33
+ self.fields()
34
+ self.inject()
35
+ for _, f_type in self.__dict__.items():
36
+ if not callable(f_type):
37
+ if f_type.group == "img":
38
+ f_type.__dict__["add_width_height"] = True
39
+
40
+ def __del__(self) -> None: # noqa: D105
41
+ # If the model is not migrated,
42
+ # it must delete files and images in the destructor.
43
+ for _, f_type in self.__dict__.items():
44
+ if not callable(f_type):
45
+ value = f_type.value
46
+ if value is None:
47
+ continue
48
+ if f_type.group == "file":
49
+ value = value.get("path")
50
+ if value is not None:
51
+ os.remove(value)
52
+ elif f_type.group == "img":
53
+ value = value.get("imgs_dir_path")
54
+ if value is not None:
55
+ shutil.rmtree(value)
56
+
57
+ @abstractmethod
58
+ def fields(self) -> None:
59
+ """For adding fields."""
60
+ pass
61
+
62
+ def model_name(self) -> str:
63
+ """Get Model name - Class name."""
64
+ return self.__class__.__name__
65
+
66
+ def full_model_name(self) -> str:
67
+ """Get full Model name - module_name + . + ClassName."""
68
+ cls = self.__class__
69
+ return f"{cls.__module__}.{cls.__name__}"
70
+
71
+ def inject(self) -> None:
72
+ """Injecting metadata from Model.META in params of fields."""
73
+ metadata = self.__class__.META
74
+ if bool(metadata):
75
+ field_attrs = metadata["field_attrs"]
76
+ data_dynamic_fields = metadata["data_dynamic_fields"]
77
+ for f_name, f_type in self.__dict__.items():
78
+ if not callable(f_type):
79
+ f_type.id = field_attrs[f_name]["id"]
80
+ f_type.name = field_attrs[f_name]["name"]
81
+ if "Dyn" in f_type.field_type:
82
+ f_type.choices = data_dynamic_fields[f_name]
83
+
84
+ # Complect of methods for converting Model to JSON and back.
85
+ # --------------------------------------------------------------------------
86
+ def to_dict(self) -> dict[str, Any]:
87
+ """Convert object instance to a dictionary."""
88
+ json_dict: dict[str, Any] = {}
89
+ for name, data in self.__dict__.items():
90
+ if not callable(data):
91
+ json_dict[name] = data.to_dict()
92
+ return json_dict
93
+
94
+ def to_json(self) -> str:
95
+ """Convert object instance to a JSON string."""
96
+ return json.dumps(self.to_dict())
97
+
98
+ @classmethod
99
+ def from_dict(cls, json_dict: dict[str, Any]) -> Any:
100
+ """Convert JSON string to a object instance."""
101
+ obj = cls()
102
+ for name, data in json_dict.items():
103
+ obj.__dict__[name] = obj.__dict__[name].__class__.from_dict(data)
104
+ return obj
105
+
106
+ @classmethod
107
+ def from_json(cls, json_str: str) -> Any:
108
+ """Convert JSON string to a object instance."""
109
+ json_dict = json.loads(json_str)
110
+ return cls.from_dict(json_dict)
111
+
112
+ # --------------------------------------------------------------------------
113
+ def to_dict_only_value(self) -> dict[str, Any]:
114
+ """Convert model.field.value (only the `value` attribute) to a dictionary."""
115
+ json_dict: dict[str, Any] = {}
116
+ current_locale = translations.CURRENT_LOCALE
117
+ for name, data in self.__dict__.items():
118
+ if callable(data):
119
+ continue
120
+ value = data.value
121
+ if value is not None:
122
+ group = data.group
123
+ if group == "date":
124
+ value = (
125
+ format_date(
126
+ date=value,
127
+ format="short",
128
+ locale=current_locale,
129
+ )
130
+ if data.field_type == "DateField"
131
+ else format_datetime(
132
+ datetime=value,
133
+ format="short",
134
+ locale=current_locale,
135
+ )
136
+ )
137
+ elif group == "id":
138
+ value = str(value)
139
+ elif group == "pass":
140
+ value = None
141
+ json_dict[name] = value
142
+ return json_dict
143
+
144
+ def to_json_only_value(self) -> str:
145
+ """Convert model.field.value (only the `value` attribute) to a JSON string."""
146
+ return json.dumps(self.to_dict_only_value())
147
+
148
+ @classmethod
149
+ def from_dict_only_value(cls, json_dict: dict[str, Any]) -> Any:
150
+ """Convert JSON string to a object instance."""
151
+ obj = cls()
152
+ for name, data in obj.__dict__.items():
153
+ if callable(data):
154
+ continue
155
+ value = json_dict.get(name)
156
+ if value is not None:
157
+ group = data.group
158
+ if group == "date":
159
+ value = parse(value)
160
+ elif group == "id":
161
+ value = ObjectId(value)
162
+ obj.__dict__[name].value = value
163
+ return obj
164
+
165
+ @classmethod
166
+ def from_json_only_value(cls, json_str: str) -> Any:
167
+ """Convert JSON string to a object instance."""
168
+ json_dict = json.loads(json_str)
169
+ return cls.from_dict_only_value(json_dict)
170
+
171
+ def refrash_fields(self, only_value_dict: dict[str, Any]) -> None:
172
+ """Partial or complete update a `value` of fields."""
173
+ for name, data in self.__dict__.items():
174
+ if callable(data):
175
+ continue
176
+ value = only_value_dict.get(name)
177
+ if value is not None:
178
+ group = data.group
179
+ if group == "date":
180
+ value = parse(value)
181
+ elif group == "id":
182
+ value = ObjectId(value)
183
+ self.__dict__[name].value = value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramifice
3
- Version: 0.3.19
3
+ Version: 0.3.21
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/
@@ -1,13 +1,14 @@
1
1
  ramifice/__init__.py,sha256=ISlaL2BprlJLE_N1fvtAqGrB3Dhniy9IZGoyWEYZhRU,678
2
2
  ramifice/add_valid.py,sha256=kvpMg7snL9tor0A23XRdgwiXazRwHfb8baoJUNjM_4Y,327
3
- ramifice/decorators.py,sha256=rOM8b_5FHQ02rdxuRL-LUd5oV_MB-RDGXVfdjMUWuGY,6143
3
+ ramifice/decorators.py,sha256=X4UnHrbuMObv10dooNip3u9ihfdNvFqeMcfLNuhLlbg,6466
4
4
  ramifice/errors.py,sha256=iuhq7fzpUmsOyeXeg2fJjta8yAuqlXLKsZVMpfUhtHE,1901
5
5
  ramifice/fixtures.py,sha256=NtxOnZslYJb4yvRpZbs3ckugmTwHQFS_9iCt2zddOC0,3102
6
6
  ramifice/hooks.py,sha256=Ri-ISfMT-IHaLO2eAqg8CODCTs3HRTxSylqspUKnVf4,873
7
7
  ramifice/indexing.py,sha256=wQpX2qei5Zc7iIq5yIV93Skp8Aa8ZD0vybnEk7cRuXs,271
8
8
  ramifice/migration.py,sha256=t_Rm1OUQYrlaPQQd1uS5S7EYMvSuKUcWzi7P4JMkrOw,11114
9
9
  ramifice/mixins.py,sha256=gKLmWQ-QrGO3K5_k-h1tDa08lkCql_dte2Jy05q1wsM,1125
10
- ramifice/model.py,sha256=FnBEsKJhYHL3HZTGRC1_UaLlBh7BJS_O8qCRYCFavhg,7365
10
+ ramifice/model.py,sha256=xhLKosxnT3HkPr6j_BSkB7pvG2WNY_7uylcHo3Oq0vM,6521
11
+ ramifice/pseudo_model.py,sha256=AdETZEvffFvHogtL0NYzp1rqLaI5lq3XcVX1n5v62bw,6816
11
12
  ramifice/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
13
  ramifice/store.py,sha256=MpDEPvUvbs11FXjakNtHPm9MekIv5p1U3as2Y80lTyc,1860
13
14
  ramifice/translations.py,sha256=GNGE0ULAA0aOY5pTxUd3MQW-nVaKvp6BeXWEcsR0s0o,4048
@@ -38,10 +39,10 @@ ramifice/fields/color_field.py,sha256=sjVSoJU6TIZ66wzPx0WRl5zQsx-kINMMuxa_pSUpW6
38
39
  ramifice/fields/date_field.py,sha256=Vlaxgp3WeDNMwbnQcTGu8kyQ7gc7M-7sFBPfzu-4MVw,5205
39
40
  ramifice/fields/date_time_field.py,sha256=VpLeYJHs0df45fz4gnprtn7aipVFKGY4n02eRep7in0,5240
40
41
  ramifice/fields/email_field.py,sha256=wOM07nQ5fOSOTydQhg3b4dV_95F1bd97peQ_XAnV0MY,3720
41
- ramifice/fields/file_field.py,sha256=Ofui6MFme78v_U_pWu8chpN3FnCW5cKQmnqmJOCi2VE,7922
42
+ ramifice/fields/file_field.py,sha256=NMM9k-kVes7VarxvTQmsl2f2nvRKp-upjPHMDjNIrwY,7892
42
43
  ramifice/fields/float_field.py,sha256=4l7gzg46qQ7ZbXS6Z8GUhiUBR26rXChvO_uFx-lbUds,4555
43
44
  ramifice/fields/id_field.py,sha256=5wlY2j051TB2GPsw0iQkFiQBV_Y_o1W_QnCtBlaDuYQ,4126
44
- ramifice/fields/image_field.py,sha256=flh08BZnluez-XvL-ucIS61Km_oZO8HCA5105hpHfTE,12285
45
+ ramifice/fields/image_field.py,sha256=u4YJ3vFtGj5uhVLjgrvV9gNLwLXT4jfVPUN2mlPKHfg,12269
45
46
  ramifice/fields/integer_field.py,sha256=Gnm5IBSvWtCLGbg5pOSAdKa5xLzqXgiigVbzw8zcjas,4531
46
47
  ramifice/fields/ip_field.py,sha256=D7xR3A-O049TQ_pEEKVTLsKF5cdr5O7ULyz4Pj3-cwQ,3601
47
48
  ramifice/fields/password_field.py,sha256=5BlZZ4mXM0CAYDHguwsK8KFnvkOOmr8R0yowHByNVD4,3451
@@ -75,7 +76,7 @@ ramifice/paladins/groups/num_group.py,sha256=Jvb-lwHxapQybbLerC4t-_yO8N7Coo1fIlZ
75
76
  ramifice/paladins/groups/pass_group.py,sha256=SEKpR2voNQtmywugDXJKY4XqPTL91CrJ87h0QNMqQqs,1952
76
77
  ramifice/paladins/groups/slug_group.py,sha256=_IRil2PwpY7cH7WaExNksKz61kQjvc27blrEufgUB30,2323
77
78
  ramifice/paladins/groups/text_group.py,sha256=nYZGwAIsJD-tX8RBtFlWvngO9RU4V0CnREUhxvV2UDo,3493
78
- ramifice-0.3.19.dist-info/METADATA,sha256=uaJt29S3kOMiBjlopixQuCRIu-O8MvdAeUzQDwJHr2I,18904
79
- ramifice-0.3.19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
80
- ramifice-0.3.19.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
81
- ramifice-0.3.19.dist-info/RECORD,,
79
+ ramifice-0.3.21.dist-info/METADATA,sha256=4pWgH5EVC0dvdG2SGf4smhNUkvdXzbOEymF3YcYfvzQ,18904
80
+ ramifice-0.3.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
81
+ ramifice-0.3.21.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
82
+ ramifice-0.3.21.dist-info/RECORD,,