ramifice 0.3.23__py3-none-any.whl → 0.3.25__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/fields/color_field.py +0 -7
- ramifice/fields/email_field.py +0 -9
- ramifice/fields/ip_field.py +0 -9
- ramifice/fields/password_field.py +0 -7
- ramifice/fields/phone_field.py +0 -11
- ramifice/fields/url_field.py +0 -8
- ramifice/paladins/groups/pass_group.py +2 -1
- ramifice/paladins/groups/text_group.py +5 -4
- ramifice/utilities.py +17 -10
- {ramifice-0.3.23.dist-info → ramifice-0.3.25.dist-info}/METADATA +73 -5
- {ramifice-0.3.23.dist-info → ramifice-0.3.25.dist-info}/RECORD +13 -13
- {ramifice-0.3.23.dist-info → ramifice-0.3.25.dist-info}/WHEEL +0 -0
- {ramifice-0.3.23.dist-info → ramifice-0.3.25.dist-info}/licenses/LICENSE +0 -0
ramifice/fields/color_field.py
CHANGED
@@ -86,10 +86,3 @@ class ColorField(Field, TextGroup, JsonMixin):
|
|
86
86
|
JsonMixin.__init__(self)
|
87
87
|
|
88
88
|
self.default = default
|
89
|
-
|
90
|
-
def is_valid(self, value: str | None = None) -> bool:
|
91
|
-
"""Validate Color code."""
|
92
|
-
color_code = str(value or self.value or self.default)
|
93
|
-
if store.REGEX["color_code"].match(color_code) is None:
|
94
|
-
return False
|
95
|
-
return True
|
ramifice/fields/email_field.py
CHANGED
@@ -82,12 +82,3 @@ class EmailField(Field, TextGroup, JsonMixin):
|
|
82
82
|
JsonMixin.__init__(self)
|
83
83
|
|
84
84
|
self.default = default
|
85
|
-
|
86
|
-
def is_valid(self, value: str | None = None) -> bool:
|
87
|
-
"""Validate Email address."""
|
88
|
-
email = str(value or self.value or self.default)
|
89
|
-
try:
|
90
|
-
validate_email(email, check_deliverability=True)
|
91
|
-
except EmailNotValidError:
|
92
|
-
return False
|
93
|
-
return True
|
ramifice/fields/ip_field.py
CHANGED
@@ -82,12 +82,3 @@ class IPField(Field, TextGroup, JsonMixin):
|
|
82
82
|
JsonMixin.__init__(self)
|
83
83
|
|
84
84
|
self.default = default
|
85
|
-
|
86
|
-
def is_valid(self, value: str | None = None) -> bool:
|
87
|
-
"""Validate IP address."""
|
88
|
-
address = str(value or self.value or self.default)
|
89
|
-
try:
|
90
|
-
ipaddress.ip_address(address)
|
91
|
-
except ValueError:
|
92
|
-
return False
|
93
|
-
return True
|
@@ -61,13 +61,6 @@ class PasswordField(Field):
|
|
61
61
|
self.placeholder = placeholder
|
62
62
|
self.required = required
|
63
63
|
|
64
|
-
def is_valid(self, value: str | None = None) -> bool:
|
65
|
-
"""Validate Password."""
|
66
|
-
password = str(value or self.value)
|
67
|
-
if not store.REGEX["password"].match(password):
|
68
|
-
return False
|
69
|
-
return True
|
70
|
-
|
71
64
|
def to_dict(self) -> dict[str, Any]:
|
72
65
|
"""Convert object instance to a dictionary."""
|
73
66
|
json_dict: dict[str, Any] = {}
|
ramifice/fields/phone_field.py
CHANGED
@@ -87,14 +87,3 @@ class PhoneField(Field, TextGroup, JsonMixin):
|
|
87
87
|
JsonMixin.__init__(self)
|
88
88
|
|
89
89
|
self.default = default
|
90
|
-
|
91
|
-
def is_valid(self, value: str | None = None) -> bool:
|
92
|
-
"""Validate Phone number."""
|
93
|
-
number = str(value or self.value or self.default)
|
94
|
-
try:
|
95
|
-
phone = phonenumbers.parse(number)
|
96
|
-
if not phonenumbers.is_valid_number(phone):
|
97
|
-
return False
|
98
|
-
except phonenumbers.phonenumberutil.NumberParseException:
|
99
|
-
return False
|
100
|
-
return True
|
ramifice/fields/url_field.py
CHANGED
@@ -95,11 +95,3 @@ class URLField(Field, TextGroup, JsonMixin):
|
|
95
95
|
JsonMixin.__init__(self)
|
96
96
|
|
97
97
|
self.default = default
|
98
|
-
|
99
|
-
def is_valid(self, value: str | None = None) -> bool:
|
100
|
-
"""Validate URL address."""
|
101
|
-
url = str(value or self.value or self.default)
|
102
|
-
result = urlparse(url)
|
103
|
-
if not result.scheme or not result.netloc:
|
104
|
-
return False
|
105
|
-
return True
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
8
8
|
from argon2 import PasswordHasher
|
9
9
|
|
10
10
|
from ... import translations
|
11
|
+
from ...utilities import is_password
|
11
12
|
from ..tools import accumulate_error, panic_type_error
|
12
13
|
|
13
14
|
|
@@ -38,7 +39,7 @@ class PassGroupMixin:
|
|
38
39
|
params["result_map"][field.name] = None
|
39
40
|
return
|
40
41
|
# Validation Passwor.
|
41
|
-
if not
|
42
|
+
if not is_password(value):
|
42
43
|
err_msg = translations._("Invalid Password !")
|
43
44
|
accumulate_error(err_msg, params)
|
44
45
|
chars = "a-z A-Z 0-9 - . _ ! \" ` ' # % & , : ; < > = @ { } ~ $ ( ) * + / \\ ? [ ] ^ |"
|
@@ -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 ...utilities import is_color, is_ip, is_phone, is_url
|
13
14
|
from ..tools import accumulate_error, check_uniqueness, panic_type_error
|
14
15
|
|
15
16
|
|
@@ -59,16 +60,16 @@ class TextGroupMixin:
|
|
59
60
|
except EmailNotValidError:
|
60
61
|
err_msg = translations._("Invalid Email address !")
|
61
62
|
accumulate_error(err_msg, params)
|
62
|
-
elif "URL" in field_type and not
|
63
|
+
elif "URL" in field_type and not is_url(value):
|
63
64
|
err_msg = translations._("Invalid URL address !")
|
64
65
|
accumulate_error(err_msg, params)
|
65
|
-
elif "IP" in field_type and not
|
66
|
+
elif "IP" in field_type and not is_ip(value):
|
66
67
|
err_msg = translations._("Invalid IP address !")
|
67
68
|
accumulate_error(err_msg, params)
|
68
|
-
elif "Color" in field_type and not
|
69
|
+
elif "Color" in field_type and not is_color(value):
|
69
70
|
err_msg = translations._("Invalid Color code !")
|
70
71
|
accumulate_error(err_msg, params)
|
71
|
-
elif "Phone" in field_type and not
|
72
|
+
elif "Phone" in field_type and not is_phone(value):
|
72
73
|
err_msg = translations._("Invalid Phone number !")
|
73
74
|
accumulate_error(err_msg, params)
|
74
75
|
# Insert result.
|
ramifice/utilities.py
CHANGED
@@ -13,6 +13,13 @@ from email_validator import EmailNotValidError, validate_email
|
|
13
13
|
from .store import REGEX
|
14
14
|
|
15
15
|
|
16
|
+
def is_password(password: str | None) -> bool:
|
17
|
+
"""Validate Password."""
|
18
|
+
if not REGEX["password"].match(str(password)):
|
19
|
+
return False
|
20
|
+
return True
|
21
|
+
|
22
|
+
|
16
23
|
def to_human_size(size: int) -> str:
|
17
24
|
"""Convert number of bytes to readable format."""
|
18
25
|
idx = int(math.floor(math.log(size) / math.log(1024)))
|
@@ -21,13 +28,13 @@ def to_human_size(size: int) -> str:
|
|
21
28
|
return f"{size} {order}"
|
22
29
|
|
23
30
|
|
24
|
-
def get_file_size(path: str) ->
|
31
|
+
def get_file_size(path: str) -> int:
|
25
32
|
"""Get human readable version of file size."""
|
26
|
-
size = os.path.getsize(path)
|
27
|
-
return
|
33
|
+
size: int = os.path.getsize(path)
|
34
|
+
return size
|
28
35
|
|
29
36
|
|
30
|
-
def normal_email(email: str) -> str | None:
|
37
|
+
def normal_email(email: str | None) -> str | None:
|
31
38
|
"""Normalizing email address."""
|
32
39
|
normal: str | None = None
|
33
40
|
try:
|
@@ -38,7 +45,7 @@ def normal_email(email: str) -> str | None:
|
|
38
45
|
return normal
|
39
46
|
|
40
47
|
|
41
|
-
def is_email(email: str) -> bool:
|
48
|
+
def is_email(email: str | None) -> bool:
|
42
49
|
"""Validate Email address."""
|
43
50
|
try:
|
44
51
|
validate_email(str(email), check_deliverability=True)
|
@@ -47,7 +54,7 @@ def is_email(email: str) -> bool:
|
|
47
54
|
return True
|
48
55
|
|
49
56
|
|
50
|
-
def is_url(url: str) -> bool:
|
57
|
+
def is_url(url: str | None) -> bool:
|
51
58
|
"""Validate URL address."""
|
52
59
|
result = urlparse(str(url))
|
53
60
|
if not result.scheme or not result.netloc:
|
@@ -55,7 +62,7 @@ def is_url(url: str) -> bool:
|
|
55
62
|
return True
|
56
63
|
|
57
64
|
|
58
|
-
def is_ip(address: str | int) -> bool:
|
65
|
+
def is_ip(address: str | int | None) -> bool:
|
59
66
|
"""Validate IP address."""
|
60
67
|
try:
|
61
68
|
ipaddress.ip_address(str(address))
|
@@ -64,14 +71,14 @@ def is_ip(address: str | int) -> bool:
|
|
64
71
|
return True
|
65
72
|
|
66
73
|
|
67
|
-
def is_color(color_code: str) -> bool:
|
74
|
+
def is_color(color_code: str | None) -> bool:
|
68
75
|
"""Validate Color code."""
|
69
76
|
if REGEX["color_code"].match(str(color_code)) is None:
|
70
77
|
return False
|
71
78
|
return True
|
72
79
|
|
73
80
|
|
74
|
-
def is_phone(number: str) -> bool:
|
81
|
+
def is_phone(number: str | None) -> bool:
|
75
82
|
"""Validate Phone number."""
|
76
83
|
try:
|
77
84
|
phone = phonenumbers.parse(str(number))
|
@@ -87,6 +94,6 @@ def is_mongo_id(oid: Any) -> bool:
|
|
87
94
|
return ObjectId.is_valid(oid)
|
88
95
|
|
89
96
|
|
90
|
-
def hash_to_obj_id(hash: str) -> ObjectId | None:
|
97
|
+
def hash_to_obj_id(hash: str | None) -> ObjectId | None:
|
91
98
|
"""Get ObjectId from hash string."""
|
92
99
|
return ObjectId(hash) if bool(hash) else None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ramifice
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.25
|
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/
|
@@ -144,7 +144,7 @@ class User:
|
|
144
144
|
def fields(self):
|
145
145
|
# For custom translations.
|
146
146
|
gettext = translations.gettext
|
147
|
-
|
147
|
+
ngettext = translations.ngettext
|
148
148
|
self.avatar = ImageField(
|
149
149
|
label=gettext("Avatar"),
|
150
150
|
default="public/media/default/no-photo.png",
|
@@ -231,7 +231,7 @@ if __name__ == "__main__":
|
|
231
231
|
asyncio.run(main())
|
232
232
|
```
|
233
233
|
|
234
|
-
###
|
234
|
+
### How to create custom translations ?
|
235
235
|
|
236
236
|
```python
|
237
237
|
from ramifice import translations
|
@@ -247,21 +247,40 @@ uv run pybabel extract -o config/translations/custom.pot src
|
|
247
247
|
uv run pybabel init -i config/translations/custom.pot -d config/translations/custom -l en
|
248
248
|
uv run pybabel init -i config/translations/custom.pot -d config/translations/custom -l ru
|
249
249
|
...
|
250
|
+
|
251
|
+
# Hint: Do not forget to add translations for new languages.
|
250
252
|
uv run pybabel compile -d config/translations/custom
|
253
|
+
|
251
254
|
# Update your custom translations:
|
252
255
|
uv run pybabel extract -o config/translations/custom.pot src
|
253
256
|
uv run pybabel update -i config/translations/custom.pot -d config/translations/custom
|
257
|
+
# Hint: Do not forget to check the translations for existing languages.
|
254
258
|
uv run pybabel compile -d config/translations/custom
|
255
|
-
|
256
|
-
|
259
|
+
```
|
260
|
+
|
261
|
+
### How to add new languages to Ramifice ?
|
262
|
+
|
263
|
+
```python
|
264
|
+
from ramifice import translations
|
265
|
+
|
266
|
+
translations.DEFAULT_LOCALE = "en" # For Ramifice by default = "en"
|
267
|
+
translations.LANGUAGES = ["en", "ru", "de", "de_ch"] # For Ramifice by default = ["en", "ru"]
|
268
|
+
```
|
269
|
+
|
270
|
+
```shell
|
271
|
+
cd project_name
|
257
272
|
# Example:
|
258
273
|
uv run pybabel init -i config/translations/ramifice.pot -d config/translations/ramifice -l de
|
259
274
|
uv run pybabel init -i config/translations/ramifice.pot -d config/translations/ramifice -l de_ch
|
260
275
|
...
|
276
|
+
|
277
|
+
# Hint: Do not forget to add translations for new languages.
|
261
278
|
uv run pybabel compile -d config/translations/ramifice
|
279
|
+
|
262
280
|
# Update translations to Ramifice:
|
263
281
|
uv run pybabel extract -o config/translations/ramifice.pot ramifice
|
264
282
|
uv run pybabel update -i config/translations/ramifice.pot -d config/translations/ramifice
|
283
|
+
# Hint: Do not forget to check the translations for existing languages.
|
265
284
|
uv run pybabel compile -d config/translations/ramifice
|
266
285
|
```
|
267
286
|
|
@@ -500,6 +519,55 @@ await user.update_password( # uses verify_password
|
|
500
519
|
)
|
501
520
|
```
|
502
521
|
|
522
|
+
## General auxiliary methods
|
523
|
+
|
524
|
+
```python
|
525
|
+
from ramifice.utilities import (
|
526
|
+
get_file_size,
|
527
|
+
hash_to_obj_id,
|
528
|
+
is_color,
|
529
|
+
is_email,
|
530
|
+
is_ip,
|
531
|
+
is_mongo_id,
|
532
|
+
is_password,
|
533
|
+
is_phone,
|
534
|
+
is_url,
|
535
|
+
normal_email,
|
536
|
+
to_human_size,
|
537
|
+
)
|
538
|
+
|
539
|
+
if is_password("12345678"):
|
540
|
+
...
|
541
|
+
|
542
|
+
if is_email("kebasyaty@gmail.com"):
|
543
|
+
...
|
544
|
+
|
545
|
+
email = normal_email("kebasyaty@gmail.com") # + validate_email
|
546
|
+
|
547
|
+
if is_url("https://www.google.com"):
|
548
|
+
...
|
549
|
+
|
550
|
+
if is_ip("127.0.0.1"):
|
551
|
+
...
|
552
|
+
|
553
|
+
if is_color("#000"):
|
554
|
+
...
|
555
|
+
|
556
|
+
if is_phone("+447986123456"):
|
557
|
+
...
|
558
|
+
|
559
|
+
if is_mongo_id("666f6f2d6261722d71757578"):
|
560
|
+
...
|
561
|
+
|
562
|
+
from bson.objectid import ObjectId
|
563
|
+
_id: ObjectId | None = hash_to_obj_id("666f6f2d6261722d71757578")
|
564
|
+
|
565
|
+
size: str = to_human_size(2097152) # => 2.0 MB
|
566
|
+
|
567
|
+
path = "public/media/default/no_doc.odt"
|
568
|
+
size: int = get_file_size(path) # => 9843
|
569
|
+
```
|
570
|
+
|
503
571
|
## Contributing
|
504
572
|
|
505
573
|
1. Fork it (<https://github.com/kebasyaty/ramifice/fork>)
|
@@ -13,7 +13,7 @@ ramifice/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
ramifice/store.py,sha256=MpDEPvUvbs11FXjakNtHPm9MekIv5p1U3as2Y80lTyc,1860
|
14
14
|
ramifice/translations.py,sha256=GNGE0ULAA0aOY5pTxUd3MQW-nVaKvp6BeXWEcsR0s0o,4048
|
15
15
|
ramifice/types.py,sha256=ljo6VscUGWEJ4Km4JYo8GEfxd1YK1CUbS8CfHp_MlEA,2390
|
16
|
-
ramifice/utilities.py,sha256=
|
16
|
+
ramifice/utilities.py,sha256=2qfdSvqlL3rhT8-7MW83IoMjKQ0CZz0e24UTxMcOq90,2654
|
17
17
|
ramifice/commons/__init__.py,sha256=LHCsdpl4z8W-xUvAlOr1ad0ras9hspvCpuce4SAdfP0,472
|
18
18
|
ramifice/commons/general.py,sha256=fTnIRA3IGbi0lMKBCgeqkI7BGJWTbZVu0nOJYoNRSRU,4571
|
19
19
|
ramifice/commons/indexes.py,sha256=ABNRXeWZSreAE4_EDlsTN9aS8QiZbzhsatUJFn2EuOo,3849
|
@@ -35,21 +35,21 @@ ramifice/fields/choice_text_dyn_field.py,sha256=FyXeaTOfg3YfpgZ5TjXGGvFe2LasmbsM
|
|
35
35
|
ramifice/fields/choice_text_field.py,sha256=oy_yB_0cuWNM2CWZAC9ZMzkMtkyb7ViCvB3YcWJG41Y,3812
|
36
36
|
ramifice/fields/choice_text_mult_dyn_field.py,sha256=L-pmeIMa8Vh8O6G7f1TnmEI26dlyHbSOc2fPKN6tJoU,3043
|
37
37
|
ramifice/fields/choice_text_mult_field.py,sha256=1R1z7hzmdhhlEBoltxL67KwrId6WEapEuCxMI-O46_Q,4166
|
38
|
-
ramifice/fields/color_field.py,sha256=
|
38
|
+
ramifice/fields/color_field.py,sha256=zoqNw7o8VNE7qogd-7yV2Kh3SDb-u76FskpE2RTKHuQ,3494
|
39
39
|
ramifice/fields/date_field.py,sha256=Vlaxgp3WeDNMwbnQcTGu8kyQ7gc7M-7sFBPfzu-4MVw,5205
|
40
40
|
ramifice/fields/date_time_field.py,sha256=VpLeYJHs0df45fz4gnprtn7aipVFKGY4n02eRep7in0,5240
|
41
|
-
ramifice/fields/email_field.py,sha256=
|
41
|
+
ramifice/fields/email_field.py,sha256=NrP87ZpYsEFbI2jd4MiiLHi6MpE3uRr5XVm1GRwcWpM,3403
|
42
42
|
ramifice/fields/file_field.py,sha256=NMM9k-kVes7VarxvTQmsl2f2nvRKp-upjPHMDjNIrwY,7892
|
43
43
|
ramifice/fields/float_field.py,sha256=4l7gzg46qQ7ZbXS6Z8GUhiUBR26rXChvO_uFx-lbUds,4555
|
44
44
|
ramifice/fields/id_field.py,sha256=5wlY2j051TB2GPsw0iQkFiQBV_Y_o1W_QnCtBlaDuYQ,4126
|
45
45
|
ramifice/fields/image_field.py,sha256=u4YJ3vFtGj5uhVLjgrvV9gNLwLXT4jfVPUN2mlPKHfg,12269
|
46
46
|
ramifice/fields/integer_field.py,sha256=Gnm5IBSvWtCLGbg5pOSAdKa5xLzqXgiigVbzw8zcjas,4531
|
47
|
-
ramifice/fields/ip_field.py,sha256=
|
48
|
-
ramifice/fields/password_field.py,sha256=
|
49
|
-
ramifice/fields/phone_field.py,sha256=
|
47
|
+
ramifice/fields/ip_field.py,sha256=inImogNq0ldYZ8ceOG9vMj_cqbykmgnsTcsRzV_vWWg,3312
|
48
|
+
ramifice/fields/password_field.py,sha256=_O2VGcKAfrMkFSWFIE7foVB5r3K4EoO8TIQNNNooQxo,3207
|
49
|
+
ramifice/fields/phone_field.py,sha256=dPpEdiiaZtf1eYV7AgzrM_-ZfkGPQVaGZmMsnwXECto,3585
|
50
50
|
ramifice/fields/slug_field.py,sha256=t-9ckQ6ki7lNis7U05t4MDd0cE_cdm29es8zfRlHfSs,2655
|
51
51
|
ramifice/fields/text_field.py,sha256=AFkXCEI_4QjBdyYtp1DClwaCLkwjKurmLcrDtxhSGxc,3971
|
52
|
-
ramifice/fields/url_field.py,sha256=
|
52
|
+
ramifice/fields/url_field.py,sha256=FPfpJGeRSdiVVQ1Wf8USZ7BxOm25v3Kp2FLmd-mxvMw,3963
|
53
53
|
ramifice/fields/general/__init__.py,sha256=5OE0TwPQjvpB3lBPuEwKrhjR_1ehOWxB98OJP_n32MA,20
|
54
54
|
ramifice/fields/general/choice_group.py,sha256=TBJblwH8mB71wd1z2jcSs28H-zx3JZVBfkk4YCE1-pI,916
|
55
55
|
ramifice/fields/general/date_group.py,sha256=RKockc_X1bYAbpzxTzXdc0kxgx51MK0bjko_j34ixWU,1169
|
@@ -73,10 +73,10 @@ ramifice/paladins/groups/file_group.py,sha256=AtGu2sQ9A64dPo2Kpt4milp5HWPdULdPdQ
|
|
73
73
|
ramifice/paladins/groups/id_group.py,sha256=zAlJrDTOdRY9e7eFYSXPjjsD05A4EsgSE6ibN3KKOMU,1263
|
74
74
|
ramifice/paladins/groups/img_group.py,sha256=o0bMI1WIA6t1QrhLaR2bBAAJUKCgHg7SiIFQsGTr2Cg,5535
|
75
75
|
ramifice/paladins/groups/num_group.py,sha256=lpyFG9a6WGOzu9f_9jwYVWZNFHxG0B0AVWvvbv_2mV0,2224
|
76
|
-
ramifice/paladins/groups/pass_group.py,sha256=
|
76
|
+
ramifice/paladins/groups/pass_group.py,sha256=tjBSovPSCKATF46FazT0a1EQ2YiztRqOjBq0uu2nTU8,1852
|
77
77
|
ramifice/paladins/groups/slug_group.py,sha256=SVYxHcSSgCA51C18LVGxMQYuUw-6ryQlTi6B9T8Dzsw,2185
|
78
|
-
ramifice/paladins/groups/text_group.py,sha256=
|
79
|
-
ramifice-0.3.
|
80
|
-
ramifice-0.3.
|
81
|
-
ramifice-0.3.
|
82
|
-
ramifice-0.3.
|
78
|
+
ramifice/paladins/groups/text_group.py,sha256=o5DnlptN93vsHtb36qO-noEnPv7CSAiqVj1j603zKw0,3159
|
79
|
+
ramifice-0.3.25.dist-info/METADATA,sha256=RCix9UrN67JO5bCxjhQ4lgWYtiX-RlQJHzm7erzuuN8,20303
|
80
|
+
ramifice-0.3.25.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
81
|
+
ramifice-0.3.25.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
|
82
|
+
ramifice-0.3.25.dist-info/RECORD,,
|
File without changes
|
File without changes
|