ramifice 0.3.20__py3-none-any.whl → 0.3.22__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/__init__.py CHANGED
@@ -1,10 +1,10 @@
1
- # Copyright 2018-present MongoDB, Inc.
1
+ # Copyright 2022-present MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
5
5
  # You may obtain a copy of the License at
6
6
  #
7
- # http://www.apache.org/licenses/LICENSE-2.0
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
8
  #
9
9
  # Unless required by applicable law or agreed to in writing, software
10
10
  # distributed under the License is distributed on an "AS IS" BASIS,
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,
@@ -67,7 +71,7 @@ def model(
67
71
  }
68
72
  attrs["META"] = {
69
73
  **metadata,
70
- **caching(cls, service_name),
74
+ **caching(cls, service_name, is_migrate_model),
71
75
  }
72
76
 
73
77
  if is_migrate_model:
@@ -87,7 +91,7 @@ def model(
87
91
  return type(
88
92
  cls.__name__,
89
93
  (
90
- Model,
94
+ PseudoModel,
91
95
  ValidationMixin,
92
96
  CheckMixin,
93
97
  AddValidMixin,
@@ -98,7 +102,7 @@ def model(
98
102
  return decorator
99
103
 
100
104
 
101
- 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]:
102
106
  """Add additional metadata to `Model.META`."""
103
107
  metadata: dict[str, Any] = {}
104
108
  model_name = cls.__name__
@@ -118,15 +122,14 @@ def caching(cls: Any, service_name: str) -> dict[str, Any]:
118
122
  # Count all fields.
119
123
  count_all_fields = 0
120
124
  # Count fields for migrating.
121
- count_fields_for_migrating = 0
125
+ count_fields_no_ignored = 0
122
126
 
123
127
  raw_model = cls()
124
128
  raw_model.fields()
125
- default_fields: dict[str, Any] = {
126
- "_id": IDField(),
127
- "created_at": DateTimeField(),
128
- "updated_at": DateTimeField(),
129
- }
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()
130
133
  fields = {**raw_model.__dict__, **default_fields}
131
134
  for f_name, f_type in fields.items():
132
135
  if not callable(f_type):
@@ -141,17 +144,25 @@ def caching(cls: Any, service_name: str) -> dict[str, Any]:
141
144
  #
142
145
  if not f_type.ignored:
143
146
  # Count fields for migrating.
144
- count_fields_for_migrating += 1
147
+ if is_migrate_model:
148
+ count_fields_no_ignored += 1
145
149
  # Get a dictionary of field names and types.
146
150
  field_name_and_type[f_name] = f_type_str
147
151
  # Build data migration storage for dynamic fields.
148
152
  if "Dyn" in f_type.field_type:
153
+ if not is_migrate_model:
154
+ msg = (
155
+ f"Model: `{cls.__module__}.{model_name}` > "
156
+ + f"Field: `{f_name}` => "
157
+ + "Dynamic field only for a migrated Model."
158
+ )
159
+ raise PanicError(msg)
149
160
  data_dynamic_fields[f_name] = None
150
161
 
151
162
  metadata["field_name_and_type"] = field_name_and_type
152
163
  metadata["field_attrs"] = field_attrs
153
164
  metadata["data_dynamic_fields"] = data_dynamic_fields
154
165
  metadata["count_all_fields"] = count_all_fields
155
- metadata["count_fields_for_migrating"] = count_fields_for_migrating
166
+ metadata["count_fields_no_ignored"] = count_fields_no_ignored
156
167
 
157
168
  return metadata
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
 
@@ -46,29 +44,6 @@ class Model(metaclass=ABCMeta):
46
44
  )
47
45
  self.fields()
48
46
  self.inject()
49
- if not self.__class__.META["is_migrate_model"]:
50
- for _, f_type in self.__dict__.items():
51
- if not callable(f_type):
52
- if f_type.group == "img":
53
- f_type.__dict__["add_width_height"] = True
54
-
55
- def __del__(self) -> None: # noqa: D105
56
- # If the model is not migrated,
57
- # it must delete files and images in the destructor.
58
- if not self.__class__.META["is_migrate_model"]:
59
- for _, f_type in self.__dict__.items():
60
- if not callable(f_type):
61
- value = f_type.value
62
- if value is None:
63
- continue
64
- if f_type.group == "file":
65
- value = value.get("path")
66
- if value is not None:
67
- os.remove(value)
68
- elif f_type.group == "img":
69
- value = value.get("imgs_dir_path")
70
- if value is not None:
71
- shutil.rmtree(value)
72
47
 
73
48
  @abstractmethod
74
49
  def fields(self) -> None:
@@ -45,11 +45,12 @@ class CheckMixin(
45
45
  It is also used to verify Models that do not migrate to the database.
46
46
  """
47
47
  cls_model = self.__class__
48
- if not cls_model.META["is_migrate_model"] and is_save: # type: ignore[attr-defined]
48
+ is_migrate_model: bool = cls_model.META["is_migrate_model"] # type: ignore[attr-defined]
49
+ if not is_migrate_model and is_save:
49
50
  msg = (
50
51
  f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
51
52
  + "Method: `check` => "
52
- + "For a non-migrating Model, the `is_save` parameter cannot be equal to `True`!"
53
+ + "For a non -migrating Model, the `is_save` parameter must be equal to` False` !"
53
54
  )
54
55
  raise PanicError(msg)
55
56
  # Get the document ID.
@@ -78,6 +79,7 @@ class CheckMixin(
78
79
  "collection": collection,
79
80
  "field_data": None,
80
81
  "full_model_name": cls_model.META["full_model_name"], # type: ignore[attr-defined]
82
+ "is_migrate_model": is_migrate_model,
81
83
  }
82
84
  #
83
85
  # Run checking fields.
@@ -23,7 +23,7 @@ class BoolGroupMixin:
23
23
  value = field.value
24
24
 
25
25
  if not isinstance(value, (bool, type(None))):
26
- panic_type_error(params["full_model_name"], "bool | None", params)
26
+ panic_type_error("bool | None", params)
27
27
 
28
28
  if not params["is_update"] and value is None:
29
29
  value = field.default
@@ -32,14 +32,14 @@ class ChoiceGroupMixin:
32
32
  if value is None:
33
33
  if field.required:
34
34
  err_msg = translations._("Required field !")
35
- accumulate_error(params["full_model_name"], err_msg, params)
35
+ accumulate_error(err_msg, params)
36
36
  if params["is_save"]:
37
37
  params["result_map"][field.name] = None
38
38
  return
39
39
  # Does the field value match the possible options in choices.
40
40
  if not field.has_value():
41
41
  err_msg = translations._("Your choice does not match the options offered !")
42
- accumulate_error(params["full_model_name"], err_msg, params)
42
+ accumulate_error(err_msg, params)
43
43
  # Insert result.
44
44
  if params["is_save"]:
45
45
  params["result_map"][field.name] = value
@@ -27,12 +27,12 @@ class DateGroupMixin:
27
27
  value = field.value or field.default or None
28
28
 
29
29
  if not isinstance(value, (datetime, type(None))):
30
- panic_type_error(params["full_model_name"], "datetime | None", params)
30
+ panic_type_error("datetime | None", params)
31
31
 
32
32
  if value is None:
33
33
  if field.required:
34
34
  err_msg = translations._("Required field !")
35
- accumulate_error(params["full_model_name"], err_msg, params)
35
+ accumulate_error(err_msg, params)
36
36
  if params["is_save"]:
37
37
  params["result_map"][field.name] = None
38
38
  return
@@ -70,7 +70,7 @@ class DateGroupMixin:
70
70
  "The date %s must not be greater than max=%s !" % value_str,
71
71
  max_date_str,
72
72
  )
73
- accumulate_error(params["full_model_name"], err_msg, params)
73
+ accumulate_error(err_msg, params)
74
74
  # Validation the `min_date` field attribute.
75
75
  min_date = field.min_date
76
76
  if min_date is not None and value < min_date:
@@ -103,7 +103,7 @@ class DateGroupMixin:
103
103
  err_msg = translations._(
104
104
  "The date %s must not be less than min=%s !" % value_str, min_date_str
105
105
  )
106
- accumulate_error(params["full_model_name"], err_msg, params)
106
+ accumulate_error(err_msg, params)
107
107
  # Insert result.
108
108
  if params["is_save"]:
109
109
  params["result_map"][field.name] = value
@@ -22,7 +22,7 @@ class FileGroupMixin:
22
22
  value = field.value or None
23
23
 
24
24
  if not isinstance(value, (dict, type(None))):
25
- panic_type_error(params["full_model_name"], "dict | None", params)
25
+ panic_type_error("dict | None", params)
26
26
 
27
27
  if not params["is_update"]:
28
28
  if value is None:
@@ -36,7 +36,7 @@ class FileGroupMixin:
36
36
  if value is None:
37
37
  if field.required:
38
38
  err_msg = translations._("Required field !")
39
- accumulate_error(params["full_model_name"], err_msg, params)
39
+ accumulate_error(err_msg, params)
40
40
  if params["is_save"]:
41
41
  params["result_map"][field.name] = None
42
42
  return
@@ -57,14 +57,14 @@ class FileGroupMixin:
57
57
  params["result_map"][field.name] = None
58
58
  else:
59
59
  err_msg = translations._("Required field !")
60
- accumulate_error(params["full_model_name"], err_msg, params)
60
+ accumulate_error(err_msg, params)
61
61
  return
62
62
  # Accumulate an error if the file size exceeds the maximum value.
63
63
  if value["size"] > field.max_size:
64
64
  err_msg = translations._(
65
65
  "File size exceeds the maximum value %s !" % to_human_size(field.max_size)
66
66
  )
67
- accumulate_error(params["full_model_name"], err_msg, params)
67
+ accumulate_error(err_msg, params)
68
68
  return
69
69
  # Insert result.
70
70
  if params["is_save"] and (value["is_new_file"] or value["save_as_is"]):
@@ -23,22 +23,22 @@ class IDGroupMixin:
23
23
  """Checking id fields."""
24
24
  field = params["field_data"]
25
25
  # Get current value.
26
- value = field.value or None
26
+ value = field.value
27
27
 
28
28
  if not isinstance(value, (ObjectId, type(None))):
29
- panic_type_error(params["full_model_name"], "ObjectId | None", params)
29
+ panic_type_error("ObjectId | None", params)
30
30
 
31
31
  if value is None:
32
32
  if field.required:
33
33
  err_msg = translations._("Required field !")
34
- accumulate_error(params["full_model_name"], err_msg, params)
34
+ accumulate_error(err_msg, params)
35
35
  if params["is_save"]:
36
36
  params["result_map"][field.name] = None
37
37
  return
38
38
  # Validation of the MongoDB identifier in a string form.
39
39
  if not ObjectId.is_valid(value):
40
40
  err_msg = translations._("Invalid document ID !")
41
- accumulate_error(params["full_model_name"], err_msg, params)
41
+ accumulate_error(err_msg, params)
42
42
  # Insert result.
43
43
  if params["is_save"]:
44
44
  params["result_map"][field.name] = value
@@ -24,7 +24,7 @@ class ImgGroupMixin:
24
24
  value = field.value or None
25
25
 
26
26
  if not isinstance(value, (dict, type(None))):
27
- panic_type_error(params["full_model_name"], "dict | None", params)
27
+ panic_type_error("dict | None", params)
28
28
 
29
29
  if not params["is_update"]:
30
30
  if value is None:
@@ -38,7 +38,7 @@ class ImgGroupMixin:
38
38
  if value is None:
39
39
  if field.required:
40
40
  err_msg = translations._("Required field !")
41
- accumulate_error(params["full_model_name"], err_msg, params)
41
+ accumulate_error(err_msg, params)
42
42
  if params["is_save"]:
43
43
  params["result_map"][field.name] = None
44
44
  return
@@ -59,14 +59,14 @@ class ImgGroupMixin:
59
59
  params["result_map"][field.name] = None
60
60
  else:
61
61
  err_msg = translations._("Required field !")
62
- accumulate_error(params["full_model_name"], err_msg, params)
62
+ accumulate_error(err_msg, params)
63
63
  return
64
64
  # Accumulate an error if the file size exceeds the maximum value.
65
65
  if value["size"] > field.max_size:
66
66
  err_msg = translations._(
67
67
  "Image size exceeds the maximum value %s !" % to_human_size(field.max_size)
68
68
  )
69
- accumulate_error(params["full_model_name"], err_msg, params)
69
+ accumulate_error(err_msg, params)
70
70
  return
71
71
  # Create thumbnails.
72
72
  if value["is_new_img"]:
@@ -27,15 +27,15 @@ class NumGroupMixin:
27
27
 
28
28
  if "Float" in field.field_type:
29
29
  if not isinstance(value, (float, type(None))):
30
- panic_type_error(params["full_model_name"], "float | None", params)
30
+ panic_type_error("float | None", params)
31
31
  else:
32
32
  if not isinstance(value, (int, type(None))):
33
- panic_type_error(params["full_model_name"], "int | None", params)
33
+ panic_type_error("int | None", params)
34
34
 
35
35
  if value is None:
36
36
  if field.required:
37
37
  err_msg = translations._("Required field !")
38
- accumulate_error(params["full_model_name"], err_msg, params)
38
+ accumulate_error(err_msg, params)
39
39
  if params["is_save"]:
40
40
  params["result_map"][field.name] = None
41
41
  return
@@ -45,22 +45,18 @@ class NumGroupMixin:
45
45
  err_msg = translations._(
46
46
  "The value %d must not be greater than max=%d !" % value, max_number
47
47
  )
48
- accumulate_error(params["full_model_name"], err_msg, params)
48
+ accumulate_error(err_msg, params)
49
49
  # Validation the `min_number` field attribute.
50
50
  min_number = field.min_number
51
51
  if min_number is not None and value < min_number:
52
52
  err_msg = translations._(
53
53
  "The value %d must not be less than min=%d !" % value, min_number
54
54
  )
55
- accumulate_error(params["full_model_name"], err_msg, params)
55
+ accumulate_error(err_msg, params)
56
56
  # Validation the `unique` field attribute.
57
- if field.unique and not await check_uniqueness(
58
- self.__class__.META["is_migrate_model"], # type: ignore[attr-defined]
59
- value,
60
- params,
61
- ):
57
+ if field.unique and not await check_uniqueness(value, params):
62
58
  err_msg = translations._("Is not unique !")
63
- accumulate_error(params["full_model_name"], err_msg, params)
59
+ accumulate_error(err_msg, params)
64
60
  # Insert result.
65
61
  if params["is_save"]:
66
62
  params["result_map"][field.name] = value
@@ -28,24 +28,24 @@ class PassGroupMixin:
28
28
  value = field.value or None
29
29
 
30
30
  if not isinstance(value, (str, type(None))):
31
- panic_type_error(params["full_model_name"], "str | None", params)
31
+ panic_type_error("str | None", params)
32
32
 
33
33
  if value is None:
34
34
  if field.required:
35
35
  err_msg = translations._("Required field !")
36
- accumulate_error(params["full_model_name"], err_msg, params)
36
+ accumulate_error(err_msg, params)
37
37
  if params["is_save"]:
38
38
  params["result_map"][field.name] = None
39
39
  return
40
40
  # Validation Passwor.
41
41
  if not field.is_valid(value):
42
42
  err_msg = translations._("Invalid Password !")
43
- accumulate_error(params["full_model_name"], err_msg, params)
43
+ accumulate_error(err_msg, params)
44
44
  chars = "a-z A-Z 0-9 - . _ ! \" ` ' # % & , : ; < > = @ { } ~ $ ( ) * + / \\ ? [ ] ^ |"
45
45
  err_msg = translations._("Valid characters: %s" % chars)
46
- accumulate_error(params["full_model_name"], err_msg, params)
46
+ accumulate_error(err_msg, params)
47
47
  err_msg = translations._("Number of characters: from 8 to 256")
48
- accumulate_error(params["full_model_name"], err_msg, params)
48
+ accumulate_error(err_msg, params)
49
49
  # Insert result.
50
50
  if params["is_save"]:
51
51
  ph = PasswordHasher()
@@ -51,11 +51,7 @@ class SlugGroupMixin:
51
51
  # Convert to slug.
52
52
  value = slugify("-".join(raw_str_list))
53
53
  # Validation of uniqueness of the value.
54
- if not await check_uniqueness(
55
- self.__class__.META["is_migrate_model"], # type: ignore[attr-defined]
56
- value,
57
- params,
58
- ):
54
+ if not await check_uniqueness(value, params):
59
55
  err_msg = (
60
56
  f"Model: `{params['full_model_name']}` > "
61
57
  + f"Field: `{field.name}` > "
@@ -28,12 +28,12 @@ class TextGroupMixin:
28
28
  value = field.value or field.default or None
29
29
 
30
30
  if not isinstance(value, (str, type(None))):
31
- panic_type_error(params["full_model_name"], "str | None", params)
31
+ panic_type_error("str | None", params)
32
32
 
33
33
  if value is None:
34
34
  if field.required:
35
35
  err_msg = translations._("Required field !")
36
- accumulate_error(params["full_model_name"], err_msg, params)
36
+ accumulate_error(err_msg, params)
37
37
  if params["is_save"]:
38
38
  params["result_map"][field.name] = None
39
39
  return
@@ -41,15 +41,11 @@ class TextGroupMixin:
41
41
  maxlength: int | None = field.__dict__.get("maxlength")
42
42
  if maxlength is not None and len(value) > maxlength:
43
43
  err_msg = translations._("The length of the string exceeds maxlength=%d !" % maxlength)
44
- accumulate_error(params["full_model_name"], err_msg, params)
44
+ accumulate_error(err_msg, params)
45
45
  # Validation the `unique` field attribute.
46
- if field.unique and not await check_uniqueness(
47
- self.__class__.META["is_migrate_model"], # type: ignore[attr-defined]
48
- value,
49
- params,
50
- ):
46
+ if field.unique and not await check_uniqueness(value, params):
51
47
  err_msg = translations._("Is not unique !")
52
- accumulate_error(params["full_model_name"], err_msg, params)
48
+ accumulate_error(err_msg, params)
53
49
  # Validation Email, Url, IP, Color, Phone.
54
50
  field_type = field.field_type
55
51
  if "Email" in field_type:
@@ -62,19 +58,19 @@ class TextGroupMixin:
62
58
  params["field_data"].value = value
63
59
  except EmailNotValidError:
64
60
  err_msg = translations._("Invalid Email address !")
65
- accumulate_error(params["full_model_name"], err_msg, params)
61
+ accumulate_error(err_msg, params)
66
62
  elif "URL" in field_type and not field.is_valid(value):
67
63
  err_msg = translations._("Invalid URL address !")
68
- accumulate_error(params["full_model_name"], err_msg, params)
64
+ accumulate_error(err_msg, params)
69
65
  elif "IP" in field_type and not field.is_valid(value):
70
66
  err_msg = translations._("Invalid IP address !")
71
- accumulate_error(params["full_model_name"], err_msg, params)
67
+ accumulate_error(err_msg, params)
72
68
  elif "Color" in field_type and not field.is_valid(value):
73
69
  err_msg = translations._("Invalid Color code !")
74
- accumulate_error(params["full_model_name"], err_msg, params)
70
+ accumulate_error(err_msg, params)
75
71
  elif "Phone" in field_type and not field.is_valid(value):
76
72
  err_msg = translations._("Invalid Phone number !")
77
- accumulate_error(params["full_model_name"], err_msg, params)
73
+ accumulate_error(err_msg, params)
78
74
  # Insert result.
79
75
  if params["is_save"]:
80
76
  params["result_map"][field.name] = value
@@ -2,8 +2,6 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
- from termcolor import colored
6
-
7
5
  from ..errors import PanicError
8
6
 
9
7
 
@@ -21,17 +19,17 @@ def refresh_from_mongo_doc(inst_model: Any, mongo_doc: dict[str, Any]) -> None:
21
19
  field.value = data if field.group != "pass" else None
22
20
 
23
21
 
24
- def panic_type_error(model_name: str, value_type: str, params: dict[str, Any]) -> None:
22
+ def panic_type_error(value_type: str, params: dict[str, Any]) -> None:
25
23
  """Unacceptable type of value."""
26
24
  msg = (
27
- f"Model: `{model_name}` > "
25
+ f"Model: `{params['full_model_name']}` > "
28
26
  + f"Field: `{params['field_data'].name}` > "
29
27
  + f"Parameter: `value` => Must be `{value_type}` type!"
30
28
  )
31
29
  raise PanicError(msg)
32
30
 
33
31
 
34
- def accumulate_error(model_name: str, err_msg: str, params: dict[str, Any]) -> None:
32
+ def accumulate_error(err_msg: str, params: dict[str, Any]) -> None:
35
33
  """For accumulating errors to ModelName.field_name.errors ."""
36
34
  if not params["field_data"].hide:
37
35
  params["field_data"].errors.append(err_msg)
@@ -39,7 +37,7 @@ def accumulate_error(model_name: str, err_msg: str, params: dict[str, Any]) -> N
39
37
  params["is_error_symptom"] = True
40
38
  else:
41
39
  msg = (
42
- f">>hidden field<< -> Model: `{model_name}` > "
40
+ f">>hidden field<< -> Model: `{params['full_model_name']}` > "
43
41
  + f"Field: `{params['field_data'].name}`"
44
42
  + f" => {err_msg}"
45
43
  )
@@ -47,12 +45,11 @@ def accumulate_error(model_name: str, err_msg: str, params: dict[str, Any]) -> N
47
45
 
48
46
 
49
47
  async def check_uniqueness(
50
- is_migrate_model: bool,
51
48
  value: str | int | float,
52
49
  params: dict[str, Any],
53
50
  ) -> bool:
54
51
  """Check the uniqueness of the value in the collection."""
55
- if not is_migrate_model:
52
+ if not params["is_migrate_model"]:
56
53
  return True
57
54
  q_filter = {
58
55
  "$and": [
@@ -0,0 +1,189 @@
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 .errors import PanicError
15
+ from .fields import IDField # type: ignore[attr-defined]
16
+
17
+
18
+ class PseudoModel(metaclass=ABCMeta):
19
+ """For convert the Python class into a pseudo model Ramifice.
20
+
21
+ Used for a Model that do not migrate into the database.
22
+ """
23
+
24
+ META: dict[str, Any] = {}
25
+
26
+ def __init__(self) -> None: # noqa: D107
27
+ self._id = IDField(
28
+ label="Stub",
29
+ placeholder="Stub",
30
+ hint="Stub",
31
+ hide=True,
32
+ disabled=True,
33
+ )
34
+ self.fields()
35
+ self.inject()
36
+
37
+ for _, f_type in self.__dict__.items():
38
+ if not callable(f_type) and f_type.group == "img":
39
+ f_type.__dict__["add_width_height"] = True
40
+
41
+ def __del__(self) -> None: # noqa: D105
42
+ # If the model is not migrated,
43
+ # it must delete files and images in the destructor.
44
+ for _, f_type in self.__dict__.items():
45
+ if callable(f_type):
46
+ continue
47
+ value = f_type.value
48
+ if value is not None:
49
+ if f_type.group == "file":
50
+ value = value.get("path")
51
+ if value is not None:
52
+ os.remove(value)
53
+ elif f_type.group == "img":
54
+ value = value.get("imgs_dir_path")
55
+ if value is not None:
56
+ shutil.rmtree(value)
57
+
58
+ @abstractmethod
59
+ def fields(self) -> None:
60
+ """For adding fields."""
61
+ pass
62
+
63
+ def model_name(self) -> str:
64
+ """Get Model name - Class name."""
65
+ return self.__class__.__name__
66
+
67
+ def full_model_name(self) -> str:
68
+ """Get full Model name - module_name + . + ClassName."""
69
+ cls = self.__class__
70
+ return f"{cls.__module__}.{cls.__name__}"
71
+
72
+ def inject(self) -> None:
73
+ """Injecting metadata from Model.META in params of fields."""
74
+ metadata = self.__class__.META
75
+ if bool(metadata):
76
+ field_attrs = metadata["field_attrs"]
77
+ for f_name, f_type in self.__dict__.items():
78
+ if callable(f_type):
79
+ continue
80
+ f_type.id = field_attrs[f_name]["id"]
81
+ f_type.name = field_attrs[f_name]["name"]
82
+ if "Dyn" in f_type.field_type:
83
+ msg = (
84
+ f"Model: `{metadata['full_model_name']}` > "
85
+ + f"Field: `{f_name}` => "
86
+ + "Dynamic field only for a migrated Model."
87
+ )
88
+ raise PanicError(msg)
89
+
90
+ # Complect of methods for converting Model to JSON and back.
91
+ # --------------------------------------------------------------------------
92
+ def to_dict(self) -> dict[str, Any]:
93
+ """Convert object instance to a dictionary."""
94
+ json_dict: dict[str, Any] = {}
95
+ for name, data in self.__dict__.items():
96
+ if not callable(data):
97
+ json_dict[name] = data.to_dict()
98
+ return json_dict
99
+
100
+ def to_json(self) -> str:
101
+ """Convert object instance to a JSON string."""
102
+ return json.dumps(self.to_dict())
103
+
104
+ @classmethod
105
+ def from_dict(cls, json_dict: dict[str, Any]) -> Any:
106
+ """Convert JSON string to a object instance."""
107
+ obj = cls()
108
+ for name, data in json_dict.items():
109
+ obj.__dict__[name] = obj.__dict__[name].__class__.from_dict(data)
110
+ return obj
111
+
112
+ @classmethod
113
+ def from_json(cls, json_str: str) -> Any:
114
+ """Convert JSON string to a object instance."""
115
+ json_dict = json.loads(json_str)
116
+ return cls.from_dict(json_dict)
117
+
118
+ # --------------------------------------------------------------------------
119
+ def to_dict_only_value(self) -> dict[str, Any]:
120
+ """Convert model.field.value (only the `value` attribute) to a dictionary."""
121
+ json_dict: dict[str, Any] = {}
122
+ current_locale = translations.CURRENT_LOCALE
123
+ for name, data in self.__dict__.items():
124
+ if callable(data):
125
+ continue
126
+ value = data.value
127
+ if value is not None:
128
+ group = data.group
129
+ if group == "date":
130
+ value = (
131
+ format_date(
132
+ date=value,
133
+ format="short",
134
+ locale=current_locale,
135
+ )
136
+ if data.field_type == "DateField"
137
+ else format_datetime(
138
+ datetime=value,
139
+ format="short",
140
+ locale=current_locale,
141
+ )
142
+ )
143
+ elif group == "id":
144
+ value = str(value)
145
+ elif group == "pass":
146
+ value = None
147
+ json_dict[name] = value
148
+ return json_dict
149
+
150
+ def to_json_only_value(self) -> str:
151
+ """Convert model.field.value (only the `value` attribute) to a JSON string."""
152
+ return json.dumps(self.to_dict_only_value())
153
+
154
+ @classmethod
155
+ def from_dict_only_value(cls, json_dict: dict[str, Any]) -> Any:
156
+ """Convert JSON string to a object instance."""
157
+ obj = cls()
158
+ for name, data in obj.__dict__.items():
159
+ if callable(data):
160
+ continue
161
+ value = json_dict.get(name)
162
+ if value is not None:
163
+ group = data.group
164
+ if group == "date":
165
+ value = parse(value)
166
+ elif group == "id":
167
+ value = ObjectId(value)
168
+ obj.__dict__[name].value = value
169
+ return obj
170
+
171
+ @classmethod
172
+ def from_json_only_value(cls, json_str: str) -> Any:
173
+ """Convert JSON string to a object instance."""
174
+ json_dict = json.loads(json_str)
175
+ return cls.from_dict_only_value(json_dict)
176
+
177
+ def refrash_fields(self, only_value_dict: dict[str, Any]) -> None:
178
+ """Partial or complete update a `value` of fields."""
179
+ for name, data in self.__dict__.items():
180
+ if callable(data):
181
+ continue
182
+ value = only_value_dict.get(name)
183
+ if value is not None:
184
+ group = data.group
185
+ if group == "date":
186
+ value = parse(value)
187
+ elif group == "id":
188
+ value = ObjectId(value)
189
+ self.__dict__[name].value = value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramifice
3
- Version: 0.3.20
3
+ Version: 0.3.22
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
- ramifice/__init__.py,sha256=ISlaL2BprlJLE_N1fvtAqGrB3Dhniy9IZGoyWEYZhRU,678
1
+ ramifice/__init__.py,sha256=jOSfXKsCkSk7y_mkKMF32gKotkD8V4RmQYvPh4smVVE,679
2
2
  ramifice/add_valid.py,sha256=kvpMg7snL9tor0A23XRdgwiXazRwHfb8baoJUNjM_4Y,327
3
- ramifice/decorators.py,sha256=txbTYnKw5AtRZb7IshOzEHuoy8MAPB8wBcCTbk-6CcE,6181
3
+ ramifice/decorators.py,sha256=0_KKq2u7mX12iqWp4-vfGBR4cKqfkey7gL5P9C7bBNQ,6811
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=hKA_BrGBWcn-mYB9dHIJoSJYEteO6SdJdgwgjdaV7mc,7632
10
+ ramifice/model.py,sha256=xhLKosxnT3HkPr6j_BSkB7pvG2WNY_7uylcHo3Oq0vM,6521
11
+ ramifice/pseudo_model.py,sha256=D2eW6gYDKxSLOXyyeIOyP2k84vsaISGXytBHoiLwRdU,6981
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
@@ -57,25 +58,25 @@ ramifice/fields/general/file_group.py,sha256=n45KfPzFI_l5hXoKkPDG0Q-0mdC2obExV-3
57
58
  ramifice/fields/general/number_group.py,sha256=AqlCY-t6JHZ2QVBe7mk5nPt6z8M4VJ_RARRlSBoIxms,715
58
59
  ramifice/fields/general/text_group.py,sha256=gP6mUGXr-LTI1UegqgEMQ-5vtUJuJs0wDYKVkknW_5E,873
59
60
  ramifice/paladins/__init__.py,sha256=PIP3AXI2KBRXNcLJUF0d7ygJ7VLOAxlhb4HRKQ9MGYY,516
60
- ramifice/paladins/check.py,sha256=kseDMCMliRsYsldcRFo5itqroQQtHtcA_oNEd6wvfgU,6835
61
+ ramifice/paladins/check.py,sha256=W1eJRWxcaoKIFawJOF8rshy2-SKSPPuKXms9rxN7y64,6938
61
62
  ramifice/paladins/delete.py,sha256=RfCyew5tbBCT8u1c8nMgC2vIQlIWzu9Tvh6TuO7VBmM,3584
62
63
  ramifice/paladins/password.py,sha256=O1OvmeKuXbwtBX1sPTRVsoWYHPQn9isOe1rju6A3wbE,3264
63
64
  ramifice/paladins/refrash.py,sha256=DfBFGjPB6yYjga57GuCiWFBNHalmDO3EfMjkhgoWnVI,1207
64
65
  ramifice/paladins/save.py,sha256=7KxocEisEVYWkZtBpHZyP6drGMyOVvF2vaymm4HNHmg,4276
65
- ramifice/paladins/tools.py,sha256=LlrY3rtKmBBGXOd6Lf8w84jMULA5C6nwzpHIoN16ibc,2094
66
+ ramifice/paladins/tools.py,sha256=9RDjyRJ5j8E5jNxVYHx-L87hCxqpu9WadycGSz7LZY0,2038
66
67
  ramifice/paladins/validation.py,sha256=7lFDWjokUwMwVxWAVgd_GlWGpF4mukxIVpxvpdyA19A,1850
67
68
  ramifice/paladins/groups/__init__.py,sha256=hpqmWLsYAMvZHAbmMXluQSqLhkHOSTUAgLHyTM1LTYI,472
68
- ramifice/paladins/groups/bool_group.py,sha256=fpxCySJ5JL7w1dLFPX1025zEU8CRsWSyQyXb3Hr3Z2s,819
69
- ramifice/paladins/groups/choice_group.py,sha256=OJeZjpnqKnSF1_bechtNepCCPjmp54x1T0c28DAa11c,1777
70
- ramifice/paladins/groups/date_group.py,sha256=-IS9pAHXdWK7tO9KQ2UyG1uf7TA43DKYCOCtOM2_tZ0,3844
71
- ramifice/paladins/groups/file_group.py,sha256=9GzRY3sV8PYJh23ZPtMQYH5RDi4pibN-kIaSui4B5zY,3033
72
- ramifice/paladins/groups/id_group.py,sha256=tK3OL1pr-V8PHPyS7vmjmJi_sRU669EvkY_Fc4sRHzg,1352
73
- ramifice/paladins/groups/img_group.py,sha256=j184UyyYZ8gNuAivkP1A57o9JlrGS_0sht6QKiblbDs,5643
74
- ramifice/paladins/groups/num_group.py,sha256=Jvb-lwHxapQybbLerC4t-_yO8N7Coo1fIlZ9eAGHcGs,2508
75
- ramifice/paladins/groups/pass_group.py,sha256=SEKpR2voNQtmywugDXJKY4XqPTL91CrJ87h0QNMqQqs,1952
76
- ramifice/paladins/groups/slug_group.py,sha256=_IRil2PwpY7cH7WaExNksKz61kQjvc27blrEufgUB30,2323
77
- ramifice/paladins/groups/text_group.py,sha256=nYZGwAIsJD-tX8RBtFlWvngO9RU4V0CnREUhxvV2UDo,3493
78
- ramifice-0.3.20.dist-info/METADATA,sha256=ZpWGFOazkmaGbW9rMKja65dApz_DHCrA2_PBCynIqzw,18904
79
- ramifice-0.3.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
80
- ramifice-0.3.20.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
81
- ramifice-0.3.20.dist-info/RECORD,,
69
+ ramifice/paladins/groups/bool_group.py,sha256=oJc9mw9KGrnK_Pj7uXixYYQAJphcXLr_xSQv3PMUlcU,792
70
+ ramifice/paladins/groups/choice_group.py,sha256=ZV6t6qgh__BzWGRUSx2RdXH_K9BtBOo0eAZ3Yon0tfY,1723
71
+ ramifice/paladins/groups/date_group.py,sha256=bVhzmwsN3sKbmzvm4jI0i_Mzjwn8gdx4kxCUWq-0Yqs,3736
72
+ ramifice/paladins/groups/file_group.py,sha256=AtGu2sQ9A64dPo2Kpt4milp5HWPdULdPdQycwFW8ZgE,2925
73
+ ramifice/paladins/groups/id_group.py,sha256=zAlJrDTOdRY9e7eFYSXPjjsD05A4EsgSE6ibN3KKOMU,1263
74
+ ramifice/paladins/groups/img_group.py,sha256=o0bMI1WIA6t1QrhLaR2bBAAJUKCgHg7SiIFQsGTr2Cg,5535
75
+ ramifice/paladins/groups/num_group.py,sha256=lpyFG9a6WGOzu9f_9jwYVWZNFHxG0B0AVWvvbv_2mV0,2224
76
+ ramifice/paladins/groups/pass_group.py,sha256=7EXqssszWQaDldJgvyNFFPxj1KSdCN89xFXrfYTTZTQ,1817
77
+ ramifice/paladins/groups/slug_group.py,sha256=SVYxHcSSgCA51C18LVGxMQYuUw-6ryQlTi6B9T8Dzsw,2185
78
+ ramifice/paladins/groups/text_group.py,sha256=N_uHsL-JJnNp2JIzNxmNyK9hRupdZGTx9Cbe6muKSmI,3128
79
+ ramifice-0.3.22.dist-info/METADATA,sha256=YpuZPDu3wd9tubh_VDZdCSVQbl_aEaq1kZPjYqA_isU,18904
80
+ ramifice-0.3.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
81
+ ramifice-0.3.22.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
82
+ ramifice-0.3.22.dist-info/RECORD,,