ramifice 0.3.14__py3-none-any.whl → 0.3.16__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/add_valid.py CHANGED
@@ -7,6 +7,6 @@ class AddValidMixin(metaclass=ABCMeta):
7
7
  """Additional validation of fields."""
8
8
 
9
9
  async def add_validation(self) -> dict[str, str]:
10
- """For additional validation of fields."""
10
+ """Additional validation of fields."""
11
11
  error_map: dict[str, str] = {}
12
12
  return error_map
ramifice/commons/one.py CHANGED
@@ -35,7 +35,7 @@ class OneMixin:
35
35
  *args: tuple,
36
36
  **kwargs: dict[str, Any],
37
37
  ) -> dict[str, Any] | None:
38
- """Find a single document."""
38
+ """Find a single document and converting to raw document."""
39
39
  # Get collection for current model.
40
40
  collection: AsyncCollection = store.MONGO_DATABASE[cls.META["collection_name"]] # type: ignore[index]
41
41
  # Get document.
ramifice/commons/tools.py CHANGED
@@ -1,4 +1,4 @@
1
- """Tools - A set of additional auxiliary methods for Commons."""
1
+ """Tool of Commons - A set of auxiliary methods."""
2
2
 
3
3
  from typing import Any
4
4
 
@@ -8,7 +8,7 @@ from .. import translations
8
8
 
9
9
 
10
10
  class ToolMixin:
11
- """A set of additional auxiliary methods for Commons."""
11
+ """Tool of Commons - A set of auxiliary methods."""
12
12
 
13
13
  @classmethod
14
14
  def password_to_none(cls: Any, mongo_doc: dict[str, Any]) -> dict[str, Any]:
ramifice/decorators.py CHANGED
@@ -11,7 +11,7 @@ from .fields import DateTimeField, IDField # type: ignore[attr-defined]
11
11
  from .hooks import HooksMixin
12
12
  from .indexing import IndexMixin
13
13
  from .model import Model
14
- from .paladins import CheckMixin, QPaladinsMixin, ToolMixin # type: ignore[attr-defined]
14
+ from .paladins import CheckMixin, QPaladinsMixin, ValidationMixin # type: ignore[attr-defined]
15
15
  from .store import REGEX
16
16
 
17
17
 
@@ -26,6 +26,20 @@ def model(
26
26
  is_delete_doc: bool = True,
27
27
  ) -> Any:
28
28
  """Decorator for converting into a Model."""
29
+ if not isinstance(service_name, str):
30
+ raise AssertionError("Parameter `service_name` - Must be `str` type!")
31
+ if not isinstance(fixture_name, (str, type(None))):
32
+ raise AssertionError("Parameter `fixture_name` - Must be `str | None` type!")
33
+ if not isinstance(db_query_docs_limit, int):
34
+ 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
+ if not isinstance(is_create_doc, bool):
38
+ raise AssertionError("Parameter `is_create_doc` - Must be `bool` type!")
39
+ if not isinstance(is_update_doc, bool):
40
+ raise AssertionError("Parameter `is_update_doc` - Must be `bool` type!")
41
+ if not isinstance(is_delete_doc, bool):
42
+ raise AssertionError("Parameter `is_delete_doc` - Must be `bool` type!")
29
43
 
30
44
  def decorator(cls: Any) -> Any:
31
45
  if REGEX["service_name"].match(service_name) is None:
@@ -67,7 +81,16 @@ def model(
67
81
  attrs,
68
82
  )
69
83
  else:
70
- return type(cls.__name__, (Model, ToolMixin, CheckMixin, AddValidMixin), attrs)
84
+ return type(
85
+ cls.__name__,
86
+ (
87
+ Model,
88
+ ValidationMixin,
89
+ CheckMixin,
90
+ AddValidMixin,
91
+ ),
92
+ attrs,
93
+ )
71
94
 
72
95
  return decorator
73
96
 
ramifice/model.py CHANGED
@@ -48,7 +48,7 @@ class Model(metaclass=ABCMeta):
48
48
 
49
49
  @abstractmethod
50
50
  def fields(self) -> None:
51
- """For add fields."""
51
+ """For adding fields."""
52
52
  pass
53
53
 
54
54
  def model_name(self) -> str:
@@ -5,16 +5,16 @@ from .delete import DeleteMixin
5
5
  from .password import PasswordMixin
6
6
  from .refrash import RefrashMixin
7
7
  from .save import SaveMixin
8
- from .tools import ToolMixin
8
+ from .validation import ValidationMixin
9
9
 
10
10
 
11
11
  class QPaladinsMixin(
12
- ToolMixin,
13
12
  CheckMixin,
14
13
  SaveMixin,
15
14
  PasswordMixin,
16
15
  DeleteMixin,
17
16
  RefrashMixin,
17
+ ValidationMixin,
18
18
  ):
19
19
  """Paladins - Model instance methods."""
20
20
 
@@ -40,7 +40,10 @@ class CheckMixin(
40
40
  async def check(
41
41
  self, is_save: bool = False, collection: AsyncCollection | None = None
42
42
  ) -> dict[str, Any]:
43
- """Validation of Model data before saving to the database."""
43
+ """Validation of Model data before saving to the database.
44
+
45
+ It is also used to verify Models that do not migrate to the database.
46
+ """
44
47
  cls_model = self.__class__
45
48
  if not cls_model.META["is_migrate_model"] and is_save: # type: ignore[attr-defined]
46
49
  msg = (
@@ -74,6 +77,7 @@ class CheckMixin(
74
77
  "result_map": result_map, # Data to save or update to the database.
75
78
  "collection": collection,
76
79
  "field_data": None,
80
+ "full_model_name": cls_model.META["full_model_name"], # type: ignore[attr-defined]
77
81
  }
78
82
  #
79
83
  # Run checking fields.
@@ -107,7 +111,7 @@ class CheckMixin(
107
111
  elif group == "id":
108
112
  self.id_group(params)
109
113
  elif group == "slug":
110
- self.slug_group(params)
114
+ await self.slug_group(params)
111
115
  elif group == "pass":
112
116
  self.pass_group(params)
113
117
 
@@ -15,7 +15,7 @@ class DeleteMixin:
15
15
 
16
16
  async def delete(
17
17
  self,
18
- delete_files: bool = True,
18
+ remove_files: bool = True,
19
19
  projection: Any | None = None,
20
20
  sort: Any | None = None,
21
21
  hint: Any | None = None,
@@ -67,12 +67,12 @@ class DeleteMixin:
67
67
  + "The document was not deleted, the document is absent in the database."
68
68
  )
69
69
  raise PanicError(msg)
70
- # Delete orphaned files.
70
+ # Delete orphaned files and add None to field.value.
71
71
  file_data: dict[str, Any] | None = None
72
72
  for field_name, field_data in self.__dict__.items():
73
73
  if callable(field_data):
74
74
  continue
75
- if delete_files and not field_data.ignored:
75
+ if remove_files and not field_data.ignored:
76
76
  group = field_data.group
77
77
  if group == "file":
78
78
  file_data = mongo_doc[field_name]
@@ -6,6 +6,8 @@ Supported fields:
6
6
 
7
7
  from typing import Any
8
8
 
9
+ from ..tools import panic_type_error
10
+
9
11
 
10
12
  class BoolGroupMixin:
11
13
  """Group for checking boolean fields.
@@ -21,7 +23,7 @@ class BoolGroupMixin:
21
23
  value = field.value
22
24
 
23
25
  if not isinstance(value, (bool, type(None))):
24
- self.panic_type_error("bool", params) # type: ignore[attr-defined]
26
+ panic_type_error(params["full_model_name"], "bool | None", params)
25
27
 
26
28
  if not params["is_update"] and value is None:
27
29
  value = field.default
@@ -10,6 +10,7 @@ Supported fields:
10
10
  from typing import Any
11
11
 
12
12
  from ... import translations
13
+ from ..tools import accumulate_error
13
14
 
14
15
 
15
16
  class ChoiceGroupMixin:
@@ -31,14 +32,14 @@ class ChoiceGroupMixin:
31
32
  if value is None:
32
33
  if field.required:
33
34
  err_msg = translations._("Required field !")
34
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
35
+ accumulate_error(params["full_model_name"], err_msg, params)
35
36
  if params["is_save"]:
36
37
  params["result_map"][field.name] = None
37
38
  return
38
39
  # Does the field value match the possible options in choices.
39
40
  if not field.has_value():
40
41
  err_msg = translations._("Your choice does not match the options offered !")
41
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
42
+ accumulate_error(params["full_model_name"], err_msg, params)
42
43
  # Insert result.
43
44
  if params["is_save"]:
44
45
  params["result_map"][field.name] = value
@@ -10,6 +10,7 @@ from typing import Any
10
10
  from babel.dates import format_date, format_datetime
11
11
 
12
12
  from ... import translations
13
+ from ..tools import accumulate_error, panic_type_error
13
14
 
14
15
 
15
16
  class DateGroupMixin:
@@ -26,12 +27,12 @@ class DateGroupMixin:
26
27
  value = field.value or field.default or None
27
28
 
28
29
  if not isinstance(value, (datetime, type(None))):
29
- self.panic_type_error("datetime", params) # type: ignore[attr-defined]
30
+ panic_type_error(params["full_model_name"], "datetime | None", params)
30
31
 
31
32
  if value is None:
32
33
  if field.required:
33
34
  err_msg = translations._("Required field !")
34
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
35
+ accumulate_error(params["full_model_name"], err_msg, params)
35
36
  if params["is_save"]:
36
37
  params["result_map"][field.name] = None
37
38
  return
@@ -69,7 +70,7 @@ class DateGroupMixin:
69
70
  "The date %s must not be greater than max=%s !" % value_str,
70
71
  max_date_str,
71
72
  )
72
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
73
+ accumulate_error(params["full_model_name"], err_msg, params)
73
74
  # Validation the `min_date` field attribute.
74
75
  min_date = field.min_date
75
76
  if min_date is not None and value < min_date:
@@ -102,7 +103,7 @@ class DateGroupMixin:
102
103
  err_msg = translations._(
103
104
  "The date %s must not be less than min=%s !" % value_str, min_date_str
104
105
  )
105
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
106
+ accumulate_error(params["full_model_name"], err_msg, params)
106
107
  # Insert result.
107
108
  if params["is_save"]:
108
109
  params["result_map"][field.name] = value
@@ -7,7 +7,8 @@ import os
7
7
  from typing import Any
8
8
 
9
9
  from ... import translations
10
- from ...tools import to_human_size
10
+ from ...utilities import to_human_size
11
+ from ..tools import accumulate_error, panic_type_error
11
12
 
12
13
 
13
14
  class FileGroupMixin:
@@ -22,7 +23,7 @@ class FileGroupMixin:
22
23
  value = field.value or None
23
24
 
24
25
  if not isinstance(value, (dict, type(None))):
25
- self.panic_type_error("dict", params) # type: ignore[attr-defined]
26
+ panic_type_error(params["full_model_name"], "dict | None", params)
26
27
 
27
28
  if not params["is_update"]:
28
29
  if value is None:
@@ -36,7 +37,7 @@ class FileGroupMixin:
36
37
  if value is None:
37
38
  if field.required:
38
39
  err_msg = translations._("Required field !")
39
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
40
+ accumulate_error(params["full_model_name"], err_msg, params)
40
41
  if params["is_save"]:
41
42
  params["result_map"][field.name] = None
42
43
  return
@@ -57,14 +58,14 @@ class FileGroupMixin:
57
58
  params["result_map"][field.name] = None
58
59
  else:
59
60
  err_msg = translations._("Required field !")
60
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
61
+ accumulate_error(params["full_model_name"], err_msg, params)
61
62
  return
62
63
  # Accumulate an error if the file size exceeds the maximum value.
63
64
  if value["size"] > field.max_size:
64
65
  err_msg = translations._(
65
66
  "File size exceeds the maximum value %s !" % to_human_size(field.max_size)
66
67
  )
67
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
68
+ accumulate_error(params["full_model_name"], err_msg, params)
68
69
  return
69
70
  # Return if there is no need to save.
70
71
  if not params["is_save"]:
@@ -9,6 +9,7 @@ from typing import Any
9
9
  from bson.objectid import ObjectId
10
10
 
11
11
  from ... import translations
12
+ from ..tools import accumulate_error, panic_type_error
12
13
 
13
14
 
14
15
  class IDGroupMixin:
@@ -25,19 +26,19 @@ class IDGroupMixin:
25
26
  value = field.value or None
26
27
 
27
28
  if not isinstance(value, (ObjectId, type(None))):
28
- self.panic_type_error("ObjectId", params) # type: ignore[attr-defined]
29
+ panic_type_error(params["full_model_name"], "ObjectId | None", params)
29
30
 
30
31
  if value is None:
31
32
  if field.required:
32
33
  err_msg = translations._("Required field !")
33
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
34
+ accumulate_error(params["full_model_name"], err_msg, params)
34
35
  if params["is_save"]:
35
36
  params["result_map"][field.name] = None
36
37
  return
37
38
  # Validation of the MongoDB identifier in a string form.
38
39
  if not ObjectId.is_valid(value):
39
40
  err_msg = translations._("Invalid document ID !")
40
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
41
+ accumulate_error(params["full_model_name"], err_msg, params)
41
42
  # Insert result.
42
43
  if params["is_save"]:
43
44
  params["result_map"][field.name] = value
@@ -9,7 +9,8 @@ from typing import Any
9
9
  from PIL import Image
10
10
 
11
11
  from ... import translations
12
- from ...tools import to_human_size
12
+ from ...utilities import to_human_size
13
+ from ..tools import accumulate_error, panic_type_error
13
14
 
14
15
 
15
16
  class ImgGroupMixin:
@@ -24,7 +25,7 @@ class ImgGroupMixin:
24
25
  value = field.value or None
25
26
 
26
27
  if not isinstance(value, (dict, type(None))):
27
- self.panic_type_error("dict", params) # type: ignore[attr-defined]
28
+ panic_type_error(params["full_model_name"], "dict | None", params)
28
29
 
29
30
  if not params["is_update"]:
30
31
  if value is None:
@@ -38,7 +39,7 @@ class ImgGroupMixin:
38
39
  if value is None:
39
40
  if field.required:
40
41
  err_msg = translations._("Required field !")
41
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
42
+ accumulate_error(params["full_model_name"], err_msg, params)
42
43
  if params["is_save"]:
43
44
  params["result_map"][field.name] = None
44
45
  return
@@ -59,14 +60,14 @@ class ImgGroupMixin:
59
60
  params["result_map"][field.name] = None
60
61
  else:
61
62
  err_msg = translations._("Required field !")
62
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
63
+ accumulate_error(params["full_model_name"], err_msg, params)
63
64
  return
64
65
  # Accumulate an error if the file size exceeds the maximum value.
65
66
  if value["size"] > field.max_size:
66
67
  err_msg = translations._(
67
68
  "Image size exceeds the maximum value %s !" % to_human_size(field.max_size)
68
69
  )
69
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
70
+ accumulate_error(params["full_model_name"], err_msg, params)
70
71
  return
71
72
  # Return if there is no need to save.
72
73
  if not params["is_save"]:
@@ -7,6 +7,7 @@ Supported fields:
7
7
  from typing import Any
8
8
 
9
9
  from ... import translations
10
+ from ..tools import accumulate_error, check_uniqueness, panic_type_error
10
11
 
11
12
 
12
13
  class NumGroupMixin:
@@ -26,15 +27,15 @@ class NumGroupMixin:
26
27
 
27
28
  if "Float" in field.field_type:
28
29
  if not isinstance(value, (float, type(None))):
29
- self.panic_type_error("float", params) # type: ignore[attr-defined]
30
+ panic_type_error(params["full_model_name"], "float | None", params)
30
31
  else:
31
32
  if not isinstance(value, (int, type(None))):
32
- self.panic_type_error("int", params) # type: ignore[attr-defined]
33
+ panic_type_error(params["full_model_name"], "int | None", params)
33
34
 
34
35
  if value is None:
35
36
  if field.required:
36
37
  err_msg = translations._("Required field !")
37
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
38
+ accumulate_error(params["full_model_name"], err_msg, params)
38
39
  if params["is_save"]:
39
40
  params["result_map"][field.name] = None
40
41
  return
@@ -44,18 +45,22 @@ class NumGroupMixin:
44
45
  err_msg = translations._(
45
46
  "The value %d must not be greater than max=%d !" % value, max_number
46
47
  )
47
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
48
+ accumulate_error(params["full_model_name"], err_msg, params)
48
49
  # Validation the `min_number` field attribute.
49
50
  min_number = field.min_number
50
51
  if min_number is not None and value < min_number:
51
52
  err_msg = translations._(
52
53
  "The value %d must not be less than min=%d !" % value, min_number
53
54
  )
54
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
55
+ accumulate_error(params["full_model_name"], err_msg, params)
55
56
  # Validation the `unique` field attribute.
56
- if field.unique and not await self.check_uniqueness(value, params): # type: ignore[attr-defined]
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
62
  err_msg = translations._("Is not unique !")
58
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
63
+ accumulate_error(params["full_model_name"], err_msg, params)
59
64
  # Insert result.
60
65
  if params["is_save"]:
61
66
  params["result_map"][field.name] = value
@@ -8,6 +8,7 @@ from typing import Any
8
8
  from argon2 import PasswordHasher
9
9
 
10
10
  from ... import translations
11
+ from ..tools import accumulate_error, panic_type_error
11
12
 
12
13
 
13
14
  class PassGroupMixin:
@@ -27,24 +28,24 @@ class PassGroupMixin:
27
28
  value = field.value or None
28
29
 
29
30
  if not isinstance(value, (str, type(None))):
30
- self.panic_type_error("str", params) # type: ignore[attr-defined]
31
+ panic_type_error(params["full_model_name"], "str | None", params)
31
32
 
32
33
  if value is None:
33
34
  if field.required:
34
35
  err_msg = translations._("Required field !")
35
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
36
+ accumulate_error(params["full_model_name"], err_msg, params)
36
37
  if params["is_save"]:
37
38
  params["result_map"][field.name] = None
38
39
  return
39
40
  # Validation Passwor.
40
41
  if not field.is_valid(value):
41
42
  err_msg = translations._("Invalid Password !")
42
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
43
+ accumulate_error(params["full_model_name"], err_msg, params)
43
44
  chars = "a-z A-Z 0-9 - . _ ! \" ` ' # % & , : ; < > = @ { } ~ $ ( ) * + / \\ ? [ ] ^ |"
44
45
  err_msg = translations._("Valid characters: %s" % chars)
45
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
46
+ accumulate_error(params["full_model_name"], err_msg, params)
46
47
  err_msg = translations._("Number of characters: from 8 to 256")
47
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
48
+ accumulate_error(params["full_model_name"], err_msg, params)
48
49
  # Insert result.
49
50
  if params["is_save"]:
50
51
  ph = PasswordHasher()
@@ -1,6 +1,7 @@
1
1
  """Group for checking slug fields.
2
2
 
3
- Supported fields: SlugField
3
+ Supported fields:
4
+ SlugField
4
5
  """
5
6
 
6
7
  from typing import Any
@@ -8,15 +9,17 @@ from typing import Any
8
9
  from slugify import slugify
9
10
 
10
11
  from ...errors import PanicError
12
+ from ..tools import check_uniqueness
11
13
 
12
14
 
13
15
  class SlugGroupMixin:
14
16
  """Group for checking slug fields.
15
17
 
16
- Supported fields: SlugField
18
+ Supported fields:
19
+ SlugField
17
20
  """
18
21
 
19
- def slug_group(self, params: dict[str, Any]) -> None:
22
+ async def slug_group(self, params: dict[str, Any]) -> None:
20
23
  """Checking slug fields."""
21
24
  if not params["is_save"]:
22
25
  return
@@ -36,7 +39,7 @@ class SlugGroupMixin:
36
39
  raw_str_list.append(value if field_name != "_id" else str(value))
37
40
  else:
38
41
  err_msg = (
39
- f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
42
+ f"Model: `{params['full_model_name']}` > "
40
43
  + f"Field: `{field.name}` => "
41
44
  + f"{field_name} - "
42
45
  + "This field is specified in slug_sources. "
@@ -45,5 +48,20 @@ class SlugGroupMixin:
45
48
  raise PanicError(err_msg)
46
49
  # Insert result.
47
50
  if params["is_save"]:
48
- value = "-".join(raw_str_list)
49
- params["result_map"][field.name] = slugify(value)
51
+ # Convert to slug.
52
+ value = slugify("-".join(raw_str_list))
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
+ ):
59
+ err_msg = (
60
+ f"Model: `{params['full_model_name']}` > "
61
+ + f"Field: `{field.name}` > "
62
+ + f"Parameter: `slug_sources` => "
63
+ + "At least one field should be unique!"
64
+ )
65
+ raise PanicError(err_msg)
66
+ # Add value to map.
67
+ params["result_map"][field.name] = value
@@ -10,6 +10,7 @@ from typing import Any
10
10
  from email_validator import EmailNotValidError, validate_email
11
11
 
12
12
  from ... import translations
13
+ from ..tools import accumulate_error, check_uniqueness, panic_type_error
13
14
 
14
15
 
15
16
  class TextGroupMixin:
@@ -27,12 +28,12 @@ class TextGroupMixin:
27
28
  value = field.value or field.default or None
28
29
 
29
30
  if not isinstance(value, (str, type(None))):
30
- self.panic_type_error("str", params) # type: ignore[attr-defined]
31
+ panic_type_error(params["full_model_name"], "str | None", params)
31
32
 
32
33
  if value is None:
33
34
  if field.required:
34
35
  err_msg = translations._("Required field !")
35
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
36
+ accumulate_error(params["full_model_name"], err_msg, params)
36
37
  if params["is_save"]:
37
38
  params["result_map"][field.name] = None
38
39
  return
@@ -40,11 +41,15 @@ class TextGroupMixin:
40
41
  maxlength: int | None = field.__dict__.get("maxlength")
41
42
  if maxlength is not None and len(value) > maxlength:
42
43
  err_msg = translations._("The length of the string exceeds maxlength=%d !" % maxlength)
43
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
44
+ accumulate_error(params["full_model_name"], err_msg, params)
44
45
  # Validation the `unique` field attribute.
45
- if field.unique and not await self.check_uniqueness(value, params): # type: ignore[attr-defined]
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
51
  err_msg = translations._("Is not unique !")
47
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
52
+ accumulate_error(params["full_model_name"], err_msg, params)
48
53
  # Validation Email, Url, IP, Color, Phone.
49
54
  field_type = field.field_type
50
55
  if "Email" in field_type:
@@ -57,19 +62,19 @@ class TextGroupMixin:
57
62
  params["field_data"].value = value
58
63
  except EmailNotValidError:
59
64
  err_msg = translations._("Invalid Email address !")
60
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
65
+ accumulate_error(params["full_model_name"], err_msg, params)
61
66
  elif "URL" in field_type and not field.is_valid(value):
62
67
  err_msg = translations._("Invalid URL address !")
63
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
68
+ accumulate_error(params["full_model_name"], err_msg, params)
64
69
  elif "IP" in field_type and not field.is_valid(value):
65
70
  err_msg = translations._("Invalid IP address !")
66
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
71
+ accumulate_error(params["full_model_name"], err_msg, params)
67
72
  elif "Color" in field_type and not field.is_valid(value):
68
73
  err_msg = translations._("Invalid Color code !")
69
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
74
+ accumulate_error(params["full_model_name"], err_msg, params)
70
75
  elif "Phone" in field_type and not field.is_valid(value):
71
76
  err_msg = translations._("Invalid Phone number !")
72
- self.accumulate_error(err_msg, params) # type: ignore[attr-defined]
77
+ accumulate_error(params["full_model_name"], err_msg, params)
73
78
  # Insert result.
74
79
  if params["is_save"]:
75
80
  params["result_map"][field.name] = value
@@ -6,6 +6,7 @@ from pymongo.asynchronous.collection import AsyncCollection
6
6
 
7
7
  from .. import store
8
8
  from ..errors import PanicError
9
+ from .tools import refresh_from_mongo_doc
9
10
 
10
11
 
11
12
  class RefrashMixin:
@@ -25,4 +26,4 @@ class RefrashMixin:
25
26
  )
26
27
  raise PanicError(msg)
27
28
  self.inject() # type: ignore[attr-defined]
28
- self.refrash_from_mongo_doc(mongo_doc) # type: ignore[attr-defined]
29
+ refresh_from_mongo_doc(self, mongo_doc)
ramifice/paladins/save.py CHANGED
@@ -7,6 +7,7 @@ from pymongo.asynchronous.collection import AsyncCollection
7
7
 
8
8
  from .. import store
9
9
  from ..errors import PanicError
10
+ from .tools import ignored_fields_to_none, refresh_from_mongo_doc
10
11
 
11
12
 
12
13
  class SaveMixin:
@@ -33,7 +34,7 @@ class SaveMixin:
33
34
  result_check["is_valid"] = False
34
35
  # Leave the method if the check fails.
35
36
  if not result_check["is_valid"]:
36
- self.ignored_fields_to_none() # type: ignore[attr-defined]
37
+ ignored_fields_to_none(self)
37
38
  return False
38
39
  # Get data for document.
39
40
  checked_data: dict[str, Any] = result_check["data"]
@@ -48,8 +49,17 @@ class SaveMixin:
48
49
  # Run hook.
49
50
  await self.post_update() # type: ignore[attr-defined]
50
51
  # Refresh Model.
51
- mongo_doc = await collection.find_one({"_id": checked_data["_id"]})
52
- self.refrash_from_mongo_doc(mongo_doc) # type: ignore[attr-defined]
52
+ mongo_doc: dict[str, Any] | None = await collection.find_one(
53
+ {"_id": checked_data["_id"]}
54
+ )
55
+ if mongo_doc is None:
56
+ msg = (
57
+ f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
58
+ + "Method: `save` => "
59
+ + "Geted value is None - it is impossible to refresh the current Model."
60
+ )
61
+ raise PanicError(msg)
62
+ refresh_from_mongo_doc(self, mongo_doc)
53
63
  else:
54
64
  # Add date and time.
55
65
  today = datetime.now()
@@ -63,8 +73,15 @@ class SaveMixin:
63
73
  await self.post_create() # type: ignore[attr-defined]
64
74
  # Refresh Model.
65
75
  mongo_doc = await collection.find_one({"_id": checked_data["_id"]})
76
+ if mongo_doc is None:
77
+ msg = (
78
+ f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
79
+ + "Method: `save` => "
80
+ + "Geted value is None - it is impossible to refresh the current Model."
81
+ )
82
+ raise PanicError(msg)
66
83
  if mongo_doc is not None:
67
- self.refrash_from_mongo_doc(mongo_doc) # type: ignore[attr-defined]
84
+ refresh_from_mongo_doc(self, mongo_doc)
68
85
  else:
69
86
  msg = (
70
87
  f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
@@ -1,6 +1,5 @@
1
- """Tools - A set of additional auxiliary methods for Paladins."""
1
+ """Tool of Paladins - A set of auxiliary methods."""
2
2
 
3
- from datetime import datetime
4
3
  from typing import Any
5
4
 
6
5
  from termcolor import colored
@@ -8,93 +7,57 @@ from termcolor import colored
8
7
  from ..errors import PanicError
9
8
 
10
9
 
11
- class ToolMixin:
12
- """A set of additional auxiliary methods for Paladins."""
10
+ def ignored_fields_to_none(inst_model: Any) -> None:
11
+ """Reset the values of ignored fields to None."""
12
+ for _, field_data in inst_model.__dict__.items():
13
+ if not callable(field_data) and field_data.ignored and field_data.name != "_id":
14
+ field_data.value = None
13
15
 
14
- async def is_valid(self) -> bool:
15
- """Check data validity.
16
16
 
17
- The main use is to check data from web forms.
18
- """
19
- result_check: dict[str, Any] = await self.check() # type: ignore[attr-defined]
20
- return result_check["is_valid"]
17
+ def refresh_from_mongo_doc(inst_model: Any, mongo_doc: dict[str, Any]) -> None:
18
+ """Update object instance from Mongo document."""
19
+ for name, data in mongo_doc.items():
20
+ field = inst_model.__dict__[name]
21
+ field.value = data if field.group != "pass" else None
21
22
 
22
- def print_err(self) -> None:
23
- """Printing errors to the console.
24
23
 
25
- Convenient to use during development.
26
- """
27
- is_err: bool = False
28
- for field_name, field_data in self.__dict__.items():
29
- if callable(field_data):
30
- continue
31
- if len(field_data.errors) > 0:
32
- # title
33
- if not is_err:
34
- print(colored("\nERRORS:", "red", attrs=["bold"]))
35
- print(colored("Model: ", "blue", attrs=["bold"]), end="")
36
- print(colored(f"`{self.full_model_name()}`", "blue")) # type: ignore[attr-defined]
37
- is_err = True
38
- # field name
39
- print(colored("Field: ", "green", attrs=["bold"]), end="")
40
- print(colored(f"`{field_name}`:", "green"))
41
- # error messages
42
- print(colored("\n".join(field_data.errors), "red"))
43
- if len(self._id.alerts) > 0: # type: ignore[attr-defined]
44
- # title
45
- print(colored("AlERTS:", "yellow", attrs=["bold"]))
46
- # messages
47
- print(colored("\n".join(self._id.alerts), "yellow"), end="\n\n") # type: ignore[attr-defined]
48
- else:
49
- print(end="\n\n")
24
+ def panic_type_error(model_name: str, value_type: str, params: dict[str, Any]) -> None:
25
+ """Unacceptable type of value."""
26
+ msg = (
27
+ f"Model: `{model_name}` > "
28
+ + f"Field: `{params['field_data'].name}` > "
29
+ + f"Parameter: `value` => Must be `{value_type}` type!"
30
+ )
31
+ raise PanicError(msg)
50
32
 
51
- def accumulate_error(self, err_msg: str, params: dict[str, Any]) -> None:
52
- """For accumulating errors to ModelName.field_name.errors ."""
53
- if not params["field_data"].hide:
54
- params["field_data"].errors.append(err_msg)
55
- if not params["is_error_symptom"]:
56
- params["is_error_symptom"] = True
57
- else:
58
- msg = (
59
- f">>hidden field<< - Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
60
- + f"Field: `{params['field_data'].name}`"
61
- + f" => {err_msg}"
62
- )
63
- raise PanicError(msg)
64
33
 
65
- def panic_type_error(self, value_type: str, params: dict[str, Any]) -> None:
66
- """Unacceptable type of value."""
34
+ def accumulate_error(model_name: str, err_msg: str, params: dict[str, Any]) -> None:
35
+ """For accumulating errors to ModelName.field_name.errors ."""
36
+ if not params["field_data"].hide:
37
+ params["field_data"].errors.append(err_msg)
38
+ if not params["is_error_symptom"]:
39
+ params["is_error_symptom"] = True
40
+ else:
67
41
  msg = (
68
- f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
69
- + f"Field: `{params['field_data'].name}` > "
70
- + f"Parameter: `value` => Must be `{value_type}` type!"
42
+ f">>hidden field<< -> Model: `{model_name}` > "
43
+ + f"Field: `{params['field_data'].name}`"
44
+ + f" => {err_msg}"
71
45
  )
72
46
  raise PanicError(msg)
73
47
 
74
- async def check_uniqueness(
75
- self,
76
- value: str | int | float | datetime,
77
- params: dict[str, Any],
78
- ) -> bool:
79
- """Check the uniqueness of the value in the collection."""
80
- if not self.__class__.META["is_migrate_model"]: # type: ignore[attr-defined]
81
- return True
82
- q_filter = {
83
- "$and": [
84
- {"_id": {"$ne": params["doc_id"]}},
85
- {params["field_data"].name: value},
86
- ],
87
- }
88
- return await params["collection"].find_one(q_filter) is None
89
48
 
90
- def ignored_fields_to_none(self) -> None:
91
- """Reset the values of ignored fields to None."""
92
- for _, field_data in self.__dict__.items():
93
- if not callable(field_data) and field_data.ignored and field_data.name != "_id":
94
- field_data.value = None
95
-
96
- def refrash_from_mongo_doc(self, mongo_doc: dict[str, Any]) -> None:
97
- """Update object instance from Mongo document."""
98
- for name, data in mongo_doc.items():
99
- field = self.__dict__[name]
100
- field.value = data if field.group != "pass" else None
49
+ async def check_uniqueness(
50
+ is_migrate_model: bool,
51
+ value: str | int | float,
52
+ params: dict[str, Any],
53
+ ) -> bool:
54
+ """Check the uniqueness of the value in the collection."""
55
+ if not is_migrate_model:
56
+ return True
57
+ q_filter = {
58
+ "$and": [
59
+ {"_id": {"$ne": params["doc_id"]}},
60
+ {params["field_data"].name: value},
61
+ ],
62
+ }
63
+ return await params["collection"].find_one(q_filter) is None
@@ -0,0 +1,47 @@
1
+ """Validation of Model and printing errors to console."""
2
+
3
+ from typing import Any
4
+
5
+ from termcolor import colored
6
+
7
+
8
+ class ValidationMixin:
9
+ """Validation of Model and printing errors to console."""
10
+
11
+ async def is_valid(self) -> bool:
12
+ """Check data validity.
13
+
14
+ The main use is to check data from web forms.
15
+ It is also used to verify Models that do not migrate to the database.
16
+ """
17
+ result_check: dict[str, Any] = await self.check() # type: ignore[attr-defined]
18
+ return result_check["is_valid"]
19
+
20
+ def print_err(self) -> None:
21
+ """Printing errors to console.
22
+
23
+ Convenient to use during development.
24
+ """
25
+ is_err: bool = False
26
+ for field_name, field_data in self.__dict__.items():
27
+ if callable(field_data):
28
+ continue
29
+ if len(field_data.errors) > 0:
30
+ # title
31
+ if not is_err:
32
+ print(colored("\nERRORS:", "red", attrs=["bold"]))
33
+ print(colored("Model: ", "blue", attrs=["bold"]), end="")
34
+ print(colored(f"`{self.full_model_name()}`", "blue")) # type: ignore[attr-defined]
35
+ is_err = True
36
+ # field name
37
+ print(colored("Field: ", "green", attrs=["bold"]), end="")
38
+ print(colored(f"`{field_name}`:", "green"))
39
+ # error messages
40
+ print(colored("\n".join(field_data.errors), "red"))
41
+ if len(self._id.alerts) > 0: # type: ignore[attr-defined]
42
+ # title
43
+ print(colored("AlERTS:", "yellow", attrs=["bold"]))
44
+ # messages
45
+ print(colored("\n".join(self._id.alerts), "yellow"), end="\n\n") # type: ignore[attr-defined]
46
+ else:
47
+ print(end="\n\n")
ramifice/translations.py CHANGED
@@ -25,6 +25,7 @@ zh | zh_cn
25
25
 
26
26
  import copy
27
27
  import gettext as _gettext
28
+ from gettext import NullTranslations
28
29
  from typing import Any
29
30
 
30
31
  # Language code by default.
@@ -35,7 +36,7 @@ CURRENT_LOCALE: str = copy.deepcopy(DEFAULT_LOCALE)
35
36
  LANGUAGES: list[str] = ["en", "ru"]
36
37
 
37
38
  # Add translations for Ramifice.
38
- ramifice_translations = {
39
+ ramifice_translations: dict[str, NullTranslations] = {
39
40
  lang: _gettext.translation(
40
41
  domain="messages",
41
42
  localedir="config/translations/ramifice",
@@ -47,7 +48,7 @@ ramifice_translations = {
47
48
  }
48
49
 
49
50
  # Add translations for custom project.
50
- custom_translations = {
51
+ custom_translations: dict[str, NullTranslations] = {
51
52
  lang: _gettext.translation(
52
53
  domain="messages",
53
54
  localedir="config/translations/custom",
@@ -104,11 +105,11 @@ def get_custom_translator(lang_code: str) -> Any:
104
105
 
105
106
 
106
107
  # The object of the current translation, for Ramifice.
107
- _ = get_ramifice_translator(DEFAULT_LOCALE).gettext
108
+ _: Any = get_ramifice_translator(DEFAULT_LOCALE).gettext
108
109
 
109
110
  # The object of the current translation, for custom project.
110
- gettext = get_custom_translator(DEFAULT_LOCALE).gettext
111
- ngettext = get_custom_translator(DEFAULT_LOCALE).ngettext
111
+ gettext: Any = get_custom_translator(DEFAULT_LOCALE).gettext
112
+ ngettext: Any = get_custom_translator(DEFAULT_LOCALE).ngettext
112
113
 
113
114
 
114
115
  def change_locale(lang_code: str) -> None:
@@ -128,5 +129,6 @@ def change_locale(lang_code: str) -> None:
128
129
  if lang_code != CURRENT_LOCALE:
129
130
  CURRENT_LOCALE = lang_code if lang_code in LANGUAGES else DEFAULT_LOCALE
130
131
  _ = get_ramifice_translator(CURRENT_LOCALE).gettext
131
- gettext = get_custom_translator(CURRENT_LOCALE).gettext
132
- ngettext = get_custom_translator(CURRENT_LOCALE).ngettext
132
+ translator: NullTranslations = get_custom_translator(CURRENT_LOCALE)
133
+ gettext = translator.gettext
134
+ ngettext = translator.ngettext
ramifice/types.py CHANGED
@@ -15,7 +15,7 @@ class Unit(JsonMixin):
15
15
  """Unit of information for `choices` parameter in dynamic field types.
16
16
 
17
17
  Attributes:
18
- field -- The name of the field.
18
+ field -- The name of the dynamic field.
19
19
  title -- The name of the choice item.
20
20
  value -- The value of the choice item.
21
21
  is_delete -- True - if you need to remove the item of choice.
@@ -1,4 +1,4 @@
1
- """Global collection of auxiliary tools."""
1
+ """Global collection of auxiliary methods."""
2
2
 
3
3
  import ipaddress
4
4
  import math
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramifice
3
- Version: 0.3.14
3
+ Version: 0.3.16
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/
@@ -181,7 +181,7 @@ class User:
181
181
  )
182
182
 
183
183
  async def add_validation(self) -> dict[str, str]:
184
- """For additional validation of fields."""
184
+ """Additional validation of fields."""
185
185
  error_map: dict[str, str] = {}
186
186
  if self.password != self.сonfirm_password:
187
187
  error_map["password"] = "Passwords do not match!"
@@ -210,8 +210,7 @@ async def main():
210
210
  user.сonfirm_password.value = "12345678"
211
211
 
212
212
  if not await user.save():
213
- # Convenient to use during development.
214
- user.print_err()
213
+ user.print_err() # Convenient to use during development.
215
214
 
216
215
  doc_count = await User.estimated_document_count()
217
216
  print(f"Document count: {doc_count}") # => 1
@@ -220,9 +219,9 @@ async def main():
220
219
  user_details = await User.find_one_to_raw_doc({"_id": user._id.value})
221
220
  pprint.pprint(user_details)
222
221
 
223
- # await user.delete()
224
- # doc_count = await User.estimated_document_count()
225
- # print(f"Document count: {doc_count}") # => 0
222
+ await user.delete(remove_files=False)
223
+ doc_count = await User.estimated_document_count()
224
+ print(f"Document count: {doc_count}") # => 0
226
225
 
227
226
  await client.close()
228
227
 
@@ -351,6 +350,155 @@ class User:
351
350
  )
352
351
  ```
353
352
 
353
+ ## Class methods
354
+
355
+ _List of frequently used methods:_
356
+
357
+ ```python
358
+ # Gets an estimate of the count of documents in a collection using collection metadata.
359
+ count: int = await User.estimated_document_count()
360
+
361
+ # Gets an estimate of the count of documents in a collection using collection metadata.
362
+ q_filter = {"first_name": "John"}
363
+ count: int = await User.count_documents(q_filter)
364
+
365
+ # Runs an aggregation framework pipeline.
366
+ from bson.bson import BSON
367
+ pipeline = [
368
+ {"$unwind": "$tags"},
369
+ {"$group": {"_id": "$tags", "count": {"$sum": 1}}},
370
+ {"$sort": BSON([("count", -1), ("_id", -1)])},
371
+ ]
372
+ docs = await User.aggregate(pipeline)
373
+
374
+ # Finds the distinct values for a specified field across a single collection.
375
+ q_filter = "key_name"
376
+ values = await User.distinct(q_filter)
377
+
378
+ # Get collection name.
379
+ name = await User.collection_name()
380
+
381
+ # The full name is of the form database_name.collection_name.
382
+ name = await User.collection_full_name()
383
+
384
+ # Get AsyncBatabase for the current Model.
385
+ database = await User.database()
386
+
387
+ # Get AsyncCollection for the current Model.
388
+ collection = await User.collection()
389
+
390
+ # Find a single document.
391
+ q_filter = {"email": "John_Smith@gmail.com"}
392
+ mongo_doc = await User.find_one(q_filter)
393
+
394
+ # Find a single document and converting to raw document.
395
+ q_filter = {"email": "John_Smith@gmail.com"}
396
+ raw_doc = await User.find_one_to_raw_doc(q_filter)
397
+
398
+ # Find a single document and convert it to a Model instance.
399
+ q_filter = {"email": "John_Smith@gmail.com"}
400
+ user = await User.find_one_to_instance(q_filter)
401
+
402
+ # Find a single document and convert it to a JSON string.
403
+ q_filter = {"email": "John_Smith@gmail.com"}
404
+ json = await User.find_one_to_json(q_filter)
405
+
406
+ # Find a single document and delete it.
407
+ q_filter = {"email": "John_Smith@gmail.com"}
408
+ delete_result = await User.delete_one(q_filter)
409
+
410
+ # Find a single document and delete it, return original.
411
+ q_filter = {"email": "John_Smith@gmail.com"}
412
+ mongo_doc = await User.find_one_and_delete(q_filter)
413
+
414
+ # Find documents.
415
+ q_filter = {"first_name": "John"}
416
+ mongo_docs = await User.find_many(q_filter)
417
+
418
+ # Find documents and convert to a raw documents.
419
+ q_filter = {"first_name": "John"}
420
+ raw_docs = await User.find_many_to_raw_docs(q_filter)
421
+
422
+ # Find documents and convert to a json string.
423
+ q_filter = {"email": "John_Smith@gmail.com"}
424
+ json = await User.find_many_to_json(q_filter)
425
+
426
+ # Find documents matching with Model.
427
+ q_filter = {"email": "John_Smith@gmail.com"}
428
+ delete_result = await User.delete_many(q_filter)
429
+
430
+ # Creates an index on this collection.
431
+ from pymongo import ASCENDING
432
+ keys = [("email", ASCENDING)]
433
+ result: str = await User.create_index(keys, name="idx_email")
434
+
435
+ # Drops the specified index on this collection.
436
+ User.drop_index("idx_email")
437
+
438
+ # Create one or more indexes on this collection.
439
+ from pymongo import ASCENDING, DESCENDING
440
+ index_1 = IndexModel([("username", DESCENDING), ("email", ASCENDING)], name="idx_username_email")
441
+ index_2 = IndexModel([("first_name", DESCENDING)], name="idx_first_name")
442
+ result: list[str] = await User.create_indexes([index_1, index_2])
443
+
444
+ # Drops all indexes on this collection.
445
+ User.drop_index()
446
+
447
+ # Get information on this collection’s indexes.
448
+ result = await User.index_information()
449
+
450
+ # Get a cursor over the index documents for this collection.
451
+ async for index in await User.list_indexes():
452
+ print(index)
453
+
454
+ # Units Management.
455
+ # Management for `choices` parameter in dynamic field types.
456
+ # Units are stored in a separate collection.
457
+ from ramifice.types import Unit
458
+ unit = Unit(
459
+ field="field_name", # The name of the dynamic field.
460
+ title="Title", # The name of the choice item.
461
+ value="Some text ...", # The value of the choice item.
462
+ # Hint: float | int | str
463
+ is_delete=False, # True - if you need to remove the item of choice.
464
+ # by default = False (add item to choice)
465
+ )
466
+ User.unit_manager(unit)
467
+ ```
468
+
469
+ ## Instance methods
470
+
471
+ _List of frequently used methods:_
472
+
473
+ ```python
474
+ # Check data validity.
475
+ # The main use is to check data from web forms.
476
+ # It is also used to verify Models that do not migrate to the database.
477
+ user = User()
478
+ if not await user.is_valid():
479
+ user.print_err() # Convenient to use during development.
480
+
481
+ # Create or update document in database.
482
+ # This method pre-uses the `check` method.
483
+ user = User()
484
+ if not await user.save():
485
+ user.print_err() # Convenient to use during development.
486
+
487
+ # Delete document from database.
488
+ user = User()
489
+ await user.delete()
490
+ # or
491
+ await user.delete(remove_files=False)
492
+
493
+ # Verification, replacement and recoverang of password.
494
+ user = User()
495
+ await user.verify_password(password="12345678")
496
+ await user.update_password( # uses verify_password
497
+ old_password="12345678",
498
+ new_password="O2eA4GIr38KGGlS",
499
+ )
500
+ ```
501
+
354
502
  ## Contributing
355
503
 
356
504
  1. Fork it (<https://github.com/kebasyaty/ramifice/fork>)
@@ -1,24 +1,24 @@
1
1
  ramifice/__init__.py,sha256=ISlaL2BprlJLE_N1fvtAqGrB3Dhniy9IZGoyWEYZhRU,678
2
- ramifice/add_valid.py,sha256=nXIB9SSNtc3a6amCRM4k7P03zrcTKRz9kf-3MrdjZ-A,331
3
- ramifice/decorators.py,sha256=Jy5KKSRBcnxNsRFmIRTbk9uKhPHhdfdFF5wkg8naGhA,5043
2
+ ramifice/add_valid.py,sha256=kvpMg7snL9tor0A23XRdgwiXazRwHfb8baoJUNjM_4Y,327
3
+ ramifice/decorators.py,sha256=hTivOrCbTqnnbbbicgoET3bLSICSjMzqctLKzf2YseM,6144
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=lksHC2f_19pDHp6Sg9pdwqA14jKxMsS8T7gfBdzIsDw,10932
9
9
  ramifice/mixins.py,sha256=gKLmWQ-QrGO3K5_k-h1tDa08lkCql_dte2Jy05q1wsM,1125
10
- ramifice/model.py,sha256=7AWVvXdP2wBY2ffx1Kx_8Ao-8VD5WGo1eOEKkFrAP68,6533
10
+ ramifice/model.py,sha256=eJCrwURQuZApY84IpxvecS6dHTvT4MK3BpGFTEWz-H4,6536
11
11
  ramifice/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  ramifice/store.py,sha256=Kqd2Zw7Wj2vnn_0eE6KmIOkWJRhDc6AZ0ZyqxENBetQ,2730
13
- ramifice/tools.py,sha256=7TZqc9ds1M668M_PcYVjnO062CLJ_mkprMmVKzQ7W2k,2525
14
- ramifice/translations.py,sha256=zz-F0sdAw5itmih74Un5SUfef9BE3d8R32JhGNaKmcU,3913
15
- ramifice/types.py,sha256=qH6d3W3e2k40cFaCPTAvQlLI1kp0Kg6DuiykP6nB-0E,2382
13
+ ramifice/translations.py,sha256=GNGE0ULAA0aOY5pTxUd3MQW-nVaKvp6BeXWEcsR0s0o,4048
14
+ ramifice/types.py,sha256=ljo6VscUGWEJ4Km4JYo8GEfxd1YK1CUbS8CfHp_MlEA,2390
15
+ ramifice/utilities.py,sha256=cX-qnNsGhP3ltA7wnhm5V8K5_FUKq2LpD-c5YdOHLJM,2527
16
16
  ramifice/commons/__init__.py,sha256=LHCsdpl4z8W-xUvAlOr1ad0ras9hspvCpuce4SAdfP0,472
17
17
  ramifice/commons/general.py,sha256=fTnIRA3IGbi0lMKBCgeqkI7BGJWTbZVu0nOJYoNRSRU,4571
18
18
  ramifice/commons/indexes.py,sha256=ABNRXeWZSreAE4_EDlsTN9aS8QiZbzhsatUJFn2EuOo,3849
19
19
  ramifice/commons/many.py,sha256=uxWgtXE40s0s6siLeqi55oAM3H9Z6S-1wKgNoJlVytg,8312
20
- ramifice/commons/one.py,sha256=v5hK_Ia-wPKGwk_3ydg1G8gy_LAuXIyw0y8N4dDCzYk,5600
21
- ramifice/commons/tools.py,sha256=x5Nv80guKyjp8YMmjvysdsrWPs88HalNdZOhfwNBYjE,2238
20
+ ramifice/commons/one.py,sha256=XKWYYZn7SZ4At_yhjPSM1zXMoQzvt8wSM0F8kgWxhNU,5631
21
+ ramifice/commons/tools.py,sha256=1oJHgb_WpWwoGtMcGQ-ta1oUMJLyN8QxuMbY83Sk36o,2220
22
22
  ramifice/commons/units.py,sha256=sYlRThKQPdBDJB3togJz8qyASpQ7gNZndbudUGDQEwE,4402
23
23
  ramifice/fields/__init__.py,sha256=yRfX7Tvpuh27Ggcx5u9e1RRYK7Wu59EVJYxxetmuP1w,1290
24
24
  ramifice/fields/bool_field.py,sha256=RUoHeU6L2wX5YfGyzsZBxg3OVlu3tP6eNhzWnHsU_G0,2041
@@ -56,25 +56,26 @@ ramifice/fields/general/field.py,sha256=Iwjx0idIUIFwcQw4TQEqejKct1EGaaJv1zZ1poSW
56
56
  ramifice/fields/general/file_group.py,sha256=n45KfPzFI_l5hXoKkPDG0Q-0mdC2obExV-3PJ-MN5tA,1071
57
57
  ramifice/fields/general/number_group.py,sha256=AqlCY-t6JHZ2QVBe7mk5nPt6z8M4VJ_RARRlSBoIxms,715
58
58
  ramifice/fields/general/text_group.py,sha256=gP6mUGXr-LTI1UegqgEMQ-5vtUJuJs0wDYKVkknW_5E,873
59
- ramifice/paladins/__init__.py,sha256=taMBhJw3UwM_Qqvzm-8iPWdUEDWu-uKtJV1LmWeeKvM,499
60
- ramifice/paladins/check.py,sha256=K2aAwVXxKORmLtNLA9bf5QAWCF5eSCfIur3Oli-LVag,6641
61
- ramifice/paladins/delete.py,sha256=EFhr5yJDrvv1Sy3Z_x5gYjuMCDLuSYlRFdPDkvOFhgg,3556
59
+ ramifice/paladins/__init__.py,sha256=PIP3AXI2KBRXNcLJUF0d7ygJ7VLOAxlhb4HRKQ9MGYY,516
60
+ ramifice/paladins/check.py,sha256=kseDMCMliRsYsldcRFo5itqroQQtHtcA_oNEd6wvfgU,6835
61
+ ramifice/paladins/delete.py,sha256=RfCyew5tbBCT8u1c8nMgC2vIQlIWzu9Tvh6TuO7VBmM,3584
62
62
  ramifice/paladins/password.py,sha256=O1OvmeKuXbwtBX1sPTRVsoWYHPQn9isOe1rju6A3wbE,3264
63
- ramifice/paladins/refrash.py,sha256=bfVmpyxlDIY2ASOd-BnCWTnlMO7tbYvN-A83CaR_73s,1193
64
- ramifice/paladins/save.py,sha256=IeVBc3aLpJsdxNOS_O5GrnACZftWdbVP8qVpsd7pD1Y,3549
65
- ramifice/paladins/tools.py,sha256=Q1D678j-1jr5qsM75gz2pGJBtH-wIW12LmNsvz2B-Zw,4036
63
+ ramifice/paladins/refrash.py,sha256=DfBFGjPB6yYjga57GuCiWFBNHalmDO3EfMjkhgoWnVI,1207
64
+ ramifice/paladins/save.py,sha256=7KxocEisEVYWkZtBpHZyP6drGMyOVvF2vaymm4HNHmg,4276
65
+ ramifice/paladins/tools.py,sha256=LlrY3rtKmBBGXOd6Lf8w84jMULA5C6nwzpHIoN16ibc,2094
66
+ ramifice/paladins/validation.py,sha256=7lFDWjokUwMwVxWAVgd_GlWGpF4mukxIVpxvpdyA19A,1850
66
67
  ramifice/paladins/groups/__init__.py,sha256=hpqmWLsYAMvZHAbmMXluQSqLhkHOSTUAgLHyTM1LTYI,472
67
- ramifice/paladins/groups/bool_group.py,sha256=bTdubd6tmdNsxPsh92lgI9fLmg5ppyGAbWyOZkJiPmE,780
68
- ramifice/paladins/groups/choice_group.py,sha256=Wa60SLiF8AdFAKC1eYdqaAb-ZuO1ICAia8If0oXTx-U,1755
69
- ramifice/paladins/groups/date_group.py,sha256=KTOP_022SgVqQdiYoGR2T5RkI-Wdu3U3rjicn6BXU00,3813
70
- ramifice/paladins/groups/file_group.py,sha256=cnwbFaN3DueERBgw9HmRvDs2THLXAYJCUdCPNcxJFpY,3263
71
- ramifice/paladins/groups/id_group.py,sha256=H0YUJ5_7VO51Bs2JFcCVr9B5QjAXxzVqh6DKLlp9BbE,1313
72
- ramifice/paladins/groups/img_group.py,sha256=WZsihfivrConmlJ9B9hefpJyFAa-EUH3mvmsI4Ns5dI,5744
73
- ramifice/paladins/groups/num_group.py,sha256=b60uSdDRCNzdv10tXYazfeWdY9JfBdU_8-lngkASXqg,2381
74
- ramifice/paladins/groups/pass_group.py,sha256=89atp3RxvE45LIm4g_uGTFwfDRdSfOXY5s2vY0cqjgI,1929
75
- ramifice/paladins/groups/slug_group.py,sha256=AhTODxabYTWC41Xssnp9gqklUz9-GBS0lWyeHLXOYmw,1652
76
- ramifice/paladins/groups/text_group.py,sha256=ThZOcx0XCCZ_UZRS9FIix3T8zte2YlpHUZ61EwVMVGc,3397
77
- ramifice-0.3.14.dist-info/METADATA,sha256=9sk21Xb5YYxVcgzKWpaVF9fAJMCocvzouhkY7hdHFQk,14182
78
- ramifice-0.3.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
79
- ramifice-0.3.14.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
80
- ramifice-0.3.14.dist-info/RECORD,,
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=oQ61TCGoRF8kArOY0F2IXXUTOiRyyu0oh3F1reV8rmU,3298
72
+ ramifice/paladins/groups/id_group.py,sha256=tK3OL1pr-V8PHPyS7vmjmJi_sRU669EvkY_Fc4sRHzg,1352
73
+ ramifice/paladins/groups/img_group.py,sha256=Yi2LG-RK_0NV5at8L8i_OYp--XDzqh66bw3xA9v2HeE,5779
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.16.dist-info/METADATA,sha256=e2_MkRAQOyshI5iKiXLLcjPQA2UfLmZ5LCJ4jJHEC7I,18896
79
+ ramifice-0.3.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
80
+ ramifice-0.3.16.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
81
+ ramifice-0.3.16.dist-info/RECORD,,