ramifice 0.4.12__py3-none-any.whl → 0.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,7 +26,7 @@ class GeneralMixin:
26
26
  if field is None:
27
27
  continue
28
28
  if field.field_type == "TextField":
29
- field.value = data.get(lang, "") if data is not None else None
29
+ field.value = data.get(lang, "- -") if data is not None else None
30
30
  elif field.group == "pass":
31
31
  field.value = None
32
32
  else:
ramifice/commons/tools.py CHANGED
@@ -54,7 +54,7 @@ def mongo_doc_to_raw_doc(
54
54
  value = mongo_doc[f_name]
55
55
  if value is not None:
56
56
  if field_type == "TextField" and f_data.multi_language:
57
- value = value.get(lang, "") if value is not None else None
57
+ value = value.get(lang, "- -") if value is not None else None
58
58
  elif "Date" in field_type:
59
59
  if "Time" in field_type:
60
60
  value = format_datetime(
@@ -7,7 +7,7 @@ from typing import Any
7
7
 
8
8
  from pymongo.asynchronous.collection import AsyncCollection
9
9
 
10
- from ..utils import globals
10
+ from ..utils import globals, translations
11
11
  from ..utils.errors import PanicError
12
12
  from ..utils.unit import Unit
13
13
 
@@ -34,59 +34,52 @@ class UnitMixin:
34
34
  # Check the presence of a Model state.
35
35
  if model_state is None:
36
36
  raise PanicError("Error: Model State - Not found!")
37
- # Get the dynamic field type.
38
- field_type = model_state["field_name_and_type"][unit.field]
39
37
  # Get dynamic field data.
40
- choices: dict[str, float | int | str] | None = model_state["data_dynamic_fields"][
41
- unit.field
42
- ]
43
- # Check whether the type of value is valid for the type of field.
44
- if not (
45
- ("ChoiceFloat" in field_type and isinstance(unit.value, float))
46
- or ("ChoiceInt" in field_type and isinstance(unit.value, int))
47
- or ("ChoiceText" in field_type and isinstance(unit.value, str))
48
- ):
49
- msg = (
50
- "Error: Method: `unit_manager(unit: Unit)` => unit.value - "
51
- + f"The type of value `{type(unit.value)}` "
52
- + f"does not correspond to the type of field `{field_type}`!"
53
- )
54
- raise PanicError(msg)
38
+ choices: list | None = model_state["data_dynamic_fields"][unit.field]
39
+ # Get Title.
40
+ title = unit.title
41
+ title = {lang: title.get(lang, "- -") for lang in translations.LANGUAGES}
42
+ main_lang = translations.DEFAULT_LOCALE
43
+ main_title = title[main_lang]
55
44
  # Add Unit to Model State.
56
45
  if not unit.is_delete:
57
46
  if choices is not None:
58
- choices = {**choices, **{unit.title: unit.value}}
47
+ choices.append({"title": title, "value": unit.value})
59
48
  else:
60
- choices = {unit.title: unit.value}
49
+ choices = [{"title": title, "value": unit.value}]
61
50
  model_state["data_dynamic_fields"][unit.field] = choices
62
- # Delete Unit from Model State.
63
51
  else:
52
+ # Delete Unit from Model State.
64
53
  if choices is None:
65
54
  msg = (
66
55
  "Error: It is not possible to delete Unit."
67
- + f"Unit `{unit.title}: {unit.value}` not exists!"
56
+ + f"Title `{main_title}` not exists!"
68
57
  )
69
58
  raise PanicError(msg)
70
- is_key_exists: bool = unit.title in choices.keys()
59
+ is_key_exists: bool = False
60
+ for item in choices:
61
+ if main_title == item["title"][main_lang]:
62
+ is_key_exists = True
63
+ break
71
64
  if not is_key_exists:
72
65
  msg = (
73
66
  "Error: It is not possible to delete Unit."
74
- + f"Unit `{unit.title}: {unit.value}` not exists!"
67
+ + f"Title `{main_title}` not exists!"
75
68
  )
76
69
  raise PanicError(msg)
77
- del choices[unit.title]
70
+ choices = [item for item in choices if item["title"][main_lang] != main_title]
78
71
  model_state["data_dynamic_fields"][unit.field] = choices or None
79
- # Update the state of the Model in the super collection.
72
+ # Update state of current Model in super collection.
80
73
  await super_collection.replace_one(
81
74
  filter={"collection_name": model_state["collection_name"]},
82
75
  replacement=model_state,
83
76
  )
84
- # Update metadata of the current Model.
77
+ # Update metadata of current Model.
85
78
  cls.META["data_dynamic_fields"][unit.field] = choices or None
86
79
  # Update documents in the collection of the current Model.
87
80
  if unit.is_delete:
88
81
  unit_field: str = unit.field
89
- unit_value: float | int | str = unit.value
82
+ unit_value: float | int | str | None = unit.value
90
83
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
91
84
  async for mongo_doc in collection.find():
92
85
  field_value = mongo_doc[unit_field]
ramifice/models/model.py CHANGED
@@ -68,6 +68,7 @@ class Model(metaclass=ABCMeta):
68
68
  """Injecting metadata from Model.META in params of fields."""
69
69
  metadata = self.__class__.META
70
70
  if bool(metadata):
71
+ lang = translations.CURRENT_LOCALE
71
72
  field_attrs = metadata["field_attrs"]
72
73
  data_dynamic_fields = metadata["data_dynamic_fields"]
73
74
  for f_name, f_type in self.__dict__.items():
@@ -75,7 +76,15 @@ class Model(metaclass=ABCMeta):
75
76
  f_type.id = field_attrs[f_name]["id"]
76
77
  f_type.name = field_attrs[f_name]["name"]
77
78
  if "Dyn" in f_type.field_type:
78
- f_type.choices = data_dynamic_fields[f_name]
79
+ dyn_data = data_dynamic_fields[f_name]
80
+ if dyn_data is not None:
81
+ f_type.choices = {
82
+ item["title"].get(lang, "- -"): item["value"] for item in dyn_data
83
+ }
84
+ else:
85
+ # This is necessary for
86
+ # `paladins > refrash > RefrashMixin > refrash_from_db`.
87
+ f_type.choices = None
79
88
 
80
89
  # Complect of methods for converting Model to JSON and back.
81
90
  # --------------------------------------------------------------------------
@@ -164,7 +173,7 @@ class Model(metaclass=ABCMeta):
164
173
  json_dict = json.loads(json_str)
165
174
  return cls.from_dict_only_value(json_dict)
166
175
 
167
- def refrash_fields(self, only_value_dict: dict[str, Any]) -> None:
176
+ def refrash_fields_only_value(self, only_value_dict: dict[str, Any]) -> None:
168
177
  """Partial or complete update a `value` of fields."""
169
178
  for name, data in self.__dict__.items():
170
179
  if callable(data):
ramifice/models/pseudo.py CHANGED
@@ -166,7 +166,7 @@ class PseudoModel(metaclass=ABCMeta):
166
166
  json_dict = json.loads(json_str)
167
167
  return cls.from_dict_only_value(json_dict)
168
168
 
169
- def refrash_fields(self, only_value_dict: dict[str, Any]) -> None:
169
+ def refrash_fields_only_value(self, only_value_dict: dict[str, Any]) -> None:
170
170
  """Partial or complete update a `value` of fields."""
171
171
  for name, data in self.__dict__.items():
172
172
  if callable(data):
@@ -92,12 +92,12 @@ class TextGroupMixin:
92
92
  else (
93
93
  {lang: value for lang in translations.LANGUAGES}
94
94
  if isinstance(value, str)
95
- else {lang: value.get(lang, "") for lang in translations.LANGUAGES}
95
+ else {lang: value.get(lang, "- -") for lang in translations.LANGUAGES}
96
96
  )
97
97
  )
98
98
  if isinstance(value, dict):
99
99
  for lang in translations.LANGUAGES:
100
- mult_lang_text[lang] = value.get(lang, "")
100
+ mult_lang_text[lang] = value.get(lang, "- -")
101
101
  else:
102
102
  mult_lang_text[translations.CURRENT_LOCALE] = value
103
103
  value = mult_lang_text
@@ -19,7 +19,7 @@ def refresh_from_mongo_doc(inst_model: Any, mongo_doc: dict[str, Any]) -> None:
19
19
  for name, data in mongo_doc.items():
20
20
  field = model_dict[name]
21
21
  if field.field_type == "TextField" and field.multi_language:
22
- field.value = data.get(lang, "") if data is not None else None
22
+ field.value = data.get(lang, "- -") if data is not None else None
23
23
  elif field.group == "pass":
24
24
  field.value = None
25
25
  else:
ramifice/utils/unit.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """Unit - Data management in dynamic fields."""
2
2
 
3
+ from typing import Any
4
+
3
5
  from .errors import PanicError
4
6
  from .mixins.json_converter import JsonMixin
5
7
 
@@ -17,19 +19,22 @@ class Unit(JsonMixin):
17
19
  def __init__( # noqa: D107
18
20
  self,
19
21
  field: str,
20
- title: str,
21
- value: float | int | str,
22
+ title: dict[str, str], # Example: {"en": "Title", "ru": "Заголовок"}
23
+ value: float | int | str | None = None, # None for is_delete=True
22
24
  is_delete: bool = False,
23
25
  ):
24
26
  # Check the match of types.
25
27
  if not isinstance(field, str):
26
28
  msg = "Class: `Unit` > Field: `field` => Not а `str` type!"
27
29
  raise PanicError(msg)
28
- if not isinstance(title, str):
29
- msg = "Class: `Unit` > Field: `title` => Not а `str` type!"
30
+ if not isinstance(title, dict):
31
+ msg = (
32
+ "Class: `Unit` > Field: `title` => Not а `str` type! "
33
+ + 'Example: {"en": "Title", "ru": "Заголовок"}'
34
+ )
30
35
  raise PanicError(msg)
31
- if not isinstance(value, (float, int, str)):
32
- msg = "Class: `Unit` > Field: `value` => Not а `float | int | str` type!"
36
+ if not isinstance(value, (float, int, str, type(None))):
37
+ msg = "Class: `Unit` > Field: `value` => Not а `float | int | str | None` type!"
33
38
  raise PanicError(msg)
34
39
  if not isinstance(is_delete, bool):
35
40
  msg = "Class: `Unit` > Field: `is_delete` => Not а `bool` type!"
@@ -42,10 +47,10 @@ class Unit(JsonMixin):
42
47
  self.value = value
43
48
  self.is_delete = is_delete
44
49
 
45
- self.check_empty_arguments()
50
+ self.check_value_arguments()
46
51
 
47
- def check_empty_arguments(self) -> None:
48
- """Error: If any of the arguments in the Unit is empty.
52
+ def check_value_arguments(self) -> None:
53
+ """Check if the values correspond to the arguments.
49
54
 
50
55
  Returns:
51
56
  `None` or raised exception `PanicError`.
@@ -56,6 +61,14 @@ class Unit(JsonMixin):
56
61
  field_name = "field"
57
62
  elif len(self.title) == 0:
58
63
  field_name = "title"
64
+ elif self.value is None and self.is_delete == False:
65
+ msg = (
66
+ "Method: `unit_manager` > "
67
+ + "Argument: `unit` > "
68
+ + f"Field: `{field_name}` => "
69
+ + "For `value` = None, `is_delete` should be True!"
70
+ )
71
+ raise PanicError(msg)
59
72
  elif isinstance(self.value, str) and len(self.value) == 0:
60
73
  field_name = "value"
61
74
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramifice
3
- Version: 0.4.12
3
+ Version: 0.5.1
4
4
  Summary: ORM-like API MongoDB for Python language.
5
5
  Project-URL: Homepage, https://github.com/kebasyaty/ramifice
6
6
  Project-URL: Documentation, https://kebasyaty.github.io/ramifice/
@@ -129,35 +129,42 @@ uv add ramifice
129
129
  It is recommended to look at examples [here](https://github.com/kebasyaty/ramifice/tree/v0/examples "here").
130
130
 
131
131
  ```python
132
+ import re
132
133
  import asyncio
133
- import pprint
134
134
  from datetime import datetime
135
+ import pprint
135
136
 
136
137
  from pymongo import AsyncMongoClient
137
138
  from ramifice import model, translations, migration
138
139
  from ramifice.fields import (
139
140
  BooleanField,
140
141
  DateField,
142
+ DateTimeField,
141
143
  EmailField,
142
- FileField,
143
144
  ImageField,
144
145
  PasswordField,
146
+ PhoneField,
147
+ SlugField,
145
148
  TextField,
146
149
  )
150
+ from ramifice.utils.tools import to_human_size
147
151
 
148
152
 
149
153
  @model(service_name="Accounts")
150
154
  class User:
151
155
  """Model of User."""
152
156
 
153
- def fields(self):
157
+ def fields(self) -> None:
154
158
  """For adding fields."""
155
159
  # For custom translations.
156
160
  gettext = translations.gettext
157
- ngettext = translations.ngettext
161
+ # ngettext = translations.ngettext
158
162
  self.avatar = ImageField(
159
163
  label=gettext("Avatar"),
164
+ placeholder=gettext("Upload your photo"),
160
165
  default="public/media/default/no-photo.png",
166
+ # Directory for images inside media directory.
167
+ target_dir="users/avatars",
161
168
  # Available 4 sizes from lg to xs or None.
162
169
  # Hint: By default = None
163
170
  thumbnails={"lg": 512, "md": 256, "sm": 128, "xs": 64},
@@ -166,48 +173,90 @@ class User:
166
173
  high_quality=True,
167
174
  # The maximum size of the original image in bytes.
168
175
  # Hint: By default = 2 MB
169
- max_size=524288 # 0.5 MB = 524288 Bytes (in binary)
170
- )
171
- self.resume = FileField(
172
- label=gettext("Resume"),
173
- default="public/media/default/no_doc.odt",
176
+ max_size=524288, # 512 KB = 0.5 MB = 524288 Bytes (in binary)
177
+ warning=[
178
+ gettext("Maximum size: %s" % to_human_size(524288)),
179
+ ],
174
180
  )
175
181
  self.username = TextField(
176
182
  label=gettext("Username"),
183
+ placeholder=gettext("Enter your username"),
184
+ maxlength=150,
177
185
  required=True,
178
186
  unique=True,
187
+ warning=[
188
+ gettext("Allowed chars: %s" % "a-z A-Z 0-9 _"),
189
+ ],
179
190
  )
180
191
  self.first_name = TextField(
181
- label=gettext("First name"),
182
- required=True,
192
+ label=gettext("First name"),
193
+ placeholder=gettext("Enter your First name"),
194
+ multi_language=True, # Support for several language.
195
+ maxlength=150,
196
+ required=True,
183
197
  )
184
198
  self.last_name = TextField(
185
199
  label=gettext("Last name"),
200
+ placeholder=gettext("Enter your Last name"),
201
+ multi_language=True, # Support for several language.
202
+ maxlength=150,
186
203
  required=True,
187
204
  )
188
205
  self.email = EmailField(
189
206
  label=gettext("Email"),
207
+ placeholder=gettext("Enter your email"),
190
208
  required=True,
191
209
  unique=True,
192
210
  )
211
+ self.phone = PhoneField(
212
+ label=gettext("Phone number"),
213
+ placeholder=gettext("Enter your phone number"),
214
+ unique=True,
215
+ )
193
216
  self.birthday = DateField(
194
- label=gettext("Birthday"),
217
+ label=gettext("Birthday"),
218
+ placeholder=gettext("Enter your date of birth"),
195
219
  )
196
220
  self.description = TextField(
197
- label=gettext("About yourself"),
198
- # Support for several language.
199
- multi_language=True,
221
+ label=gettext("About yourself"),
222
+ placeholder=gettext("Tell us a little about yourself ..."),
223
+ multi_language=True, # Support for several language.
200
224
  )
201
225
  self.password = PasswordField(
202
- label=gettext("Password"),
226
+ label=gettext("Password"),
227
+ placeholder=gettext("Enter your password"),
203
228
  )
204
229
  self.сonfirm_password = PasswordField(
205
230
  label=gettext("Confirm password"),
231
+ placeholder=gettext("Repeat your password"),
206
232
  # If true, the value of this field is not saved in the database.
207
233
  ignored=True,
208
234
  )
209
- self.is_admin = BooleanField(
235
+ self.is_admin = BooleanField(
210
236
  label=gettext("Is Administrator?"),
237
+ warning=[
238
+ gettext("Can this user access the admin panel?"),
239
+ ],
240
+ )
241
+ self.is_active = BooleanField(
242
+ label=gettext("Is active?"),
243
+ warning=[
244
+ gettext("Is this an active account?"),
245
+ ],
246
+ )
247
+ self.slug = SlugField(
248
+ label=gettext("Slug"),
249
+ slug_sources=["username"],
250
+ disabled=True,
251
+ hide=True,
252
+ )
253
+ self.last_login = DateTimeField(
254
+ label=gettext("Last login"),
255
+ disabled=True,
256
+ hide=True,
257
+ warning=[
258
+ gettext("Date and time of user last login."),
259
+ ],
211
260
  )
212
261
 
213
262
  # Optional method.
@@ -218,9 +267,13 @@ class User:
218
267
 
219
268
  # Get clean data.
220
269
  id = self.id.value
270
+ username = self.username.value
221
271
  password = self.password.value
222
272
  сonfirm_password = self.сonfirm_password.value
223
273
 
274
+ if re.match(r"^[a-zA-Z0-9_]+$", username) is None: # type: ignore[arg-type]
275
+ error_map["username"] = gettext("Allowed chars: %s" % "a-z A-Z 0-9 _")
276
+
224
277
  if id is None and (password != сonfirm_password):
225
278
  error_map["password"] = gettext("Passwords do not match!")
226
279
  return error_map
@@ -239,16 +292,21 @@ async def main():
239
292
  translations.change_locale("en")
240
293
 
241
294
  user = User()
295
+ # user.avatar.from_path("public/media/default/no-photo.png")
242
296
  user.username.value = "pythondev"
243
- user.avatar.from_path("public/media/default/no-photo.png")
244
- user.resume.from_path("public/media/default/no_doc.odt")
245
- user.first_name.value = "John"
246
- user.last_name.value = "Smith"
297
+ user.first_name.value = {"en": "John", "ru": "Джон"}
298
+ # user.first_name.value = "John"
299
+ user.last_name.value = {"en": "Smith", "ru": "Смит"}
300
+ # user.last_name.value = "Smith"
247
301
  user.email.value = "John_Smith@gmail.com"
302
+ user.phone.value = "+447986123456"
248
303
  user.birthday.value = datetime(2000, 1, 25)
304
+ user.description.value = {"en": "I program on Python!", "ru": "Я программирую на Python!"}
305
+ # user.description.value = "I program on Python!"
249
306
  user.password.value = "12345678"
250
307
  user.сonfirm_password.value = "12345678"
251
308
  user.is_admin.value = True
309
+ user.is_active.value = True
252
310
 
253
311
  # Create User.
254
312
  if not await user.save():
@@ -256,7 +314,7 @@ async def main():
256
314
  user.print_err()
257
315
 
258
316
  # Update User.
259
- user.username.value = "pythondev-123"
317
+ user.username.value = "pythondev_123"
260
318
  if not await user.save():
261
319
  user.print_err()
262
320
 
@@ -273,11 +331,13 @@ async def main():
273
331
  # Remove User.
274
332
  await user.delete(remove_files=False)
275
333
 
276
- # Remove collection.
334
+ # Remove User.
277
335
  # (if necessary)
278
- await User.collection().drop()
336
+ # await user.delete()
279
337
 
280
- await client.close()
338
+ # Remove collection.
339
+ # (if necessary)
340
+ # await User.collection().drop()
281
341
 
282
342
 
283
343
  if __name__ == "__main__":
@@ -1,12 +1,12 @@
1
1
  ramifice/__init__.py,sha256=IuI20h84QWyFftykvte_HLft2iVxwpu0K592NypMwfo,759
2
2
  ramifice/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  ramifice/commons/__init__.py,sha256=FTG78sTU0ay0rvmrq5fUmVjL-I-S7wgQq3BLcyqnjrQ,433
4
- ramifice/commons/general.py,sha256=Vq0IMuaZj-BqwENTDMg_DouMjV8EjzDy181Ujtw3JQs,5358
4
+ ramifice/commons/general.py,sha256=SmtJge18Yjz7cpc-KXo238rQl465WtTqzNnuRjHl8qs,5361
5
5
  ramifice/commons/indexes.py,sha256=hAcWKZ9MMgLbRtoQ6Af0b8r-PY0dbEYmMBl8z_d1DRo,3646
6
6
  ramifice/commons/many.py,sha256=jkah1kO1CXWPB0xW0dbMelCFBSirRYkX9a333EcPE_M,9202
7
7
  ramifice/commons/one.py,sha256=uZsgwg_GgA1VMZDPqhRFMmCdtvcv6xsL4DWbCOwGV3w,6730
8
- ramifice/commons/tools.py,sha256=hCnB95nwSCUufig9cSA5Kj9jvqRIJJ2GUSzfUK4FNXM,2363
9
- ramifice/commons/unit_manager.py,sha256=IkWqXu1PHHal0aGfx6zme81iXPeygxPqEWO-u_8RXoI,4356
8
+ ramifice/commons/tools.py,sha256=ectaILVYCSwNbB0VAys6nIhdsJbwuxAupNvcQfweC88,2366
9
+ ramifice/commons/unit_manager.py,sha256=o8Ux2y95nddo762hf1YDyp07QwvQyotR5iVoG5FZaZI,4001
10
10
  ramifice/fields/__init__.py,sha256=yRfX7Tvpuh27Ggcx5u9e1RRYK7Wu59EVJYxxetmuP1w,1290
11
11
  ramifice/fields/bool_field.py,sha256=WWubSwsFJZu8b3MviDd2zXnNYOQaYw8toklFNSTVFLA,2072
12
12
  ramifice/fields/choice_float_dyn_field.py,sha256=Yrd9cNKLm0mhU-P3IojwFRLSbuxdp7f2NMGJhrRWsf8,3093
@@ -45,15 +45,15 @@ ramifice/fields/general/number_group.py,sha256=AqlCY-t6JHZ2QVBe7mk5nPt6z8M4VJ_RA
45
45
  ramifice/fields/general/text_group.py,sha256=6GD2Fe6Toz6zZjAAlcy5rPVCAGh6Yn1ltdIrFg9RF18,1057
46
46
  ramifice/models/__init__.py,sha256=h_QQ5rSJNZ-7kmx7wIFd8E8RmUS94b_x4jdwMbq8zm4,15
47
47
  ramifice/models/decorator.py,sha256=U1ukWWq2voEwvKonQAzihqGYVsoyPrOQ2jDXJZ-UcMc,7251
48
- ramifice/models/model.py,sha256=ZvgIbcuSXuAkBl_FofOZXGwWMwlqKB-PwTMyXiqK-Fo,6610
49
- ramifice/models/pseudo.py,sha256=PhLQM4zXdaOtTSmNFzwN4twlUmdvA1D_-YOMJtaOIwM,6754
48
+ ramifice/models/model.py,sha256=MmHsb73G-h1io3EP1qy6cSOxqJ0CeaB1bzxgTT4XP_c,7113
49
+ ramifice/models/pseudo.py,sha256=0PBLHUmoT5eXzIaZtTQ20IaNUPRAuRGGfRWbUypPtfc,6765
50
50
  ramifice/paladins/__init__.py,sha256=PIP3AXI2KBRXNcLJUF0d7ygJ7VLOAxlhb4HRKQ9MGYY,516
51
51
  ramifice/paladins/check.py,sha256=ennDiVAoCZIS3aKBK0eA-Vt8VJQnbIv90be5IFYraFg,7026
52
52
  ramifice/paladins/delete.py,sha256=tw50E98D5eFZ7gHGnh_8ztUB1LeTeWWKZvIcQqlgbF8,3352
53
53
  ramifice/paladins/password.py,sha256=w1XWh3bsncH1VTVjCLxyKI2waxMvltwcsPWW3V9Ib84,3027
54
54
  ramifice/paladins/refrash.py,sha256=fw-9x_NKGzreipBt_F9KF6FTsYm9hSzfq4ubi1FHYrQ,1065
55
55
  ramifice/paladins/save.py,sha256=EG0_v-imCQPax2pJIAXPoQcSL99g8abY2EeGJMvXBW4,3864
56
- ramifice/paladins/tools.py,sha256=fn1gve72oFRWDmFw9ESe5wd6bY5av5qyrrvWf0tw8Ns,2703
56
+ ramifice/paladins/tools.py,sha256=Xm1RhkV1GX5B6GbyGmJMBzFyDQcenBxcRjlUamIz5pA,2706
57
57
  ramifice/paladins/validation.py,sha256=gcEJXIEPu1g7Z54vl14YTs5rCmxOEYsgQH1usFfyy7k,1730
58
58
  ramifice/paladins/groups/__init__.py,sha256=hpqmWLsYAMvZHAbmMXluQSqLhkHOSTUAgLHyTM1LTYI,472
59
59
  ramifice/paladins/groups/bool_group.py,sha256=oJc9mw9KGrnK_Pj7uXixYYQAJphcXLr_xSQv3PMUlcU,792
@@ -65,7 +65,7 @@ ramifice/paladins/groups/img_group.py,sha256=RoD_QnW0F0DAzrGQmRi8jMZnWy2IomlFn6A
65
65
  ramifice/paladins/groups/num_group.py,sha256=6jT7nfIiVoQTlI2UdkcQOlHIDYjllFqSH7Nb87uJgzg,2274
66
66
  ramifice/paladins/groups/pass_group.py,sha256=YU5a-NwohEutoEx2N5JmGfg8uPiYiW0XJ8XYsOih6eA,1859
67
67
  ramifice/paladins/groups/slug_group.py,sha256=joBB5litljbv2h5JKEMzF71s_DKMWH6nzgThLiLZx2E,2307
68
- ramifice/paladins/groups/text_group.py,sha256=iftXyyqloQr9yJki__d0LwCEeerkcFquDHyI5rB8fio,4348
68
+ ramifice/paladins/groups/text_group.py,sha256=EiHGuiJKm-A4UrsV4DHoI7whL-LdQCIEvW6jX4YjfCo,4354
69
69
  ramifice/utils/__init__.py,sha256=xixHoOX4ja5jIUZemem1qn4k4aonv3G3Q76azQK_pkU,43
70
70
  ramifice/utils/errors.py,sha256=iuhq7fzpUmsOyeXeg2fJjta8yAuqlXLKsZVMpfUhtHE,1901
71
71
  ramifice/utils/fixtures.py,sha256=qsv9cg06lc82XaRlhI1j5vftmOQTTwOcDXCg_lnpK04,3159
@@ -73,13 +73,13 @@ ramifice/utils/globals.py,sha256=uR20um3Qg_1SG1t7WyWbpq8kQD-9Mslyr_c1yh5Hw9w,175
73
73
  ramifice/utils/migration.py,sha256=ol8ysNGBr0wuOwo00ZbbN0U8YKiiYVgyRYja0axdiHM,11059
74
74
  ramifice/utils/tools.py,sha256=sOKzwnvf6vdTNf9r6PKAdw6aB4undat2Z8tzS3M1GnQ,2733
75
75
  ramifice/utils/translations.py,sha256=GNGE0ULAA0aOY5pTxUd3MQW-nVaKvp6BeXWEcsR0s0o,4048
76
- ramifice/utils/unit.py,sha256=qJ2SpClsFcMRcwB_ZA-QlrB5T9OinCBiWx5KqQ9vH_A,2266
76
+ ramifice/utils/unit.py,sha256=cFnhXt8sO0X-wiuiSU7_YzmRkpdNFcf2qxfR8YPsuiE,2861
77
77
  ramifice/utils/mixins/__init__.py,sha256=-UxYokZSlEaqoIs0aPVGtzK2CgN88mTzyhryHEtt0Yo,181
78
78
  ramifice/utils/mixins/add_valid.py,sha256=TLOObedzXNA9eCylfAVbVCqIKE5sV-P5AdIN7avj-GA,407
79
79
  ramifice/utils/mixins/hooks.py,sha256=33jvJRhfnJeL2Hd_YFXk3M_7wjqHaByU2wRjKyboL6s,914
80
80
  ramifice/utils/mixins/indexing.py,sha256=Z0427HoaVRyNmSNN8Fx0mSICgAKV-gDdu3iR5qYUEbs,329
81
81
  ramifice/utils/mixins/json_converter.py,sha256=WhigXyDAV-FfILaZuwvRFRIk0D90Rv3dG5t-mv5fVyc,1107
82
- ramifice-0.4.12.dist-info/METADATA,sha256=u46bs5blVVIv_vh4jrW5ERLTFe7z8O_11-U-VK7UvLg,21965
83
- ramifice-0.4.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
84
- ramifice-0.4.12.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
85
- ramifice-0.4.12.dist-info/RECORD,,
82
+ ramifice-0.5.1.dist-info/METADATA,sha256=TdHC07yAg5Z-s1O2V8fKVkJ_BCncdHf9ig1f9AarYt4,24406
83
+ ramifice-0.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
84
+ ramifice-0.5.1.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
85
+ ramifice-0.5.1.dist-info/RECORD,,