cuenca-validations 2.1.27__tar.gz → 2.1.28__tar.gz
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.
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/PKG-INFO +1 -1
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/requests.py +21 -7
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/validators.py +30 -0
- cuenca_validations-2.1.28/cuenca_validations/version.py +1 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations.egg-info/PKG-INFO +1 -1
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations.egg-info/SOURCES.txt +2 -1
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/tests/test_requests.py +29 -0
- cuenca_validations-2.1.28/tests/test_validators.py +35 -0
- cuenca_validations-2.1.27/cuenca_validations/version.py +0 -1
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/LICENSE +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/README.md +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/__init__.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/card_bins.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/errors.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/py.typed +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/__init__.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/card.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/enums.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/files.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/general.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/helpers.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/identities.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/morals.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/queries.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/typing.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations.egg-info/dependency_links.txt +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations.egg-info/requires.txt +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations.egg-info/top_level.txt +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/setup.cfg +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/setup.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/tests/__init__.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/tests/test_card.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/tests/test_errors.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/tests/test_helpers.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/tests/test_statement.py +0 -0
- {cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/tests/test_types.py +0 -0
{cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/requests.py
RENAMED
|
@@ -56,6 +56,7 @@ from ..types.enums import (
|
|
|
56
56
|
WebhookObject,
|
|
57
57
|
)
|
|
58
58
|
from ..typing import DictStrAny
|
|
59
|
+
from ..validators import normalize_email, normalize_phone_number
|
|
59
60
|
from .card import (
|
|
60
61
|
Cvv,
|
|
61
62
|
ExpMonth,
|
|
@@ -543,6 +544,16 @@ class UserUpdateRequest(BaseRequest):
|
|
|
543
544
|
raise ValueError('At least one parameter must be provided')
|
|
544
545
|
return values
|
|
545
546
|
|
|
547
|
+
@field_validator('email_address', mode='before')
|
|
548
|
+
@classmethod
|
|
549
|
+
def validate_email_address(cls, v: Optional[str]) -> Optional[str]:
|
|
550
|
+
return normalize_email(v) if v else v
|
|
551
|
+
|
|
552
|
+
@field_validator('phone_number', mode='before')
|
|
553
|
+
@classmethod
|
|
554
|
+
def validate_phone_number(cls, v: Optional[str]) -> Optional[str]:
|
|
555
|
+
return normalize_phone_number(v) if v else v
|
|
556
|
+
|
|
546
557
|
@field_validator('beneficiaries')
|
|
547
558
|
@classmethod
|
|
548
559
|
def beneficiary_percentage(
|
|
@@ -641,13 +652,16 @@ class VerificationRequest(BaseRequest):
|
|
|
641
652
|
},
|
|
642
653
|
)
|
|
643
654
|
|
|
644
|
-
@
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
655
|
+
@model_validator(mode='before')
|
|
656
|
+
@classmethod
|
|
657
|
+
def normalize_recipient(cls, data):
|
|
658
|
+
_type = data.get('type')
|
|
659
|
+
recipient = data.get('recipient')
|
|
660
|
+
if _type == VerificationType.email:
|
|
661
|
+
data['recipient'] = normalize_email(recipient)
|
|
662
|
+
else:
|
|
663
|
+
data['recipient'] = normalize_phone_number(recipient)
|
|
664
|
+
return data
|
|
651
665
|
|
|
652
666
|
|
|
653
667
|
class VerificationAttemptRequest(BaseRequest):
|
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import datetime as dt
|
|
3
|
+
import re
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Any, Callable, Optional, Union
|
|
5
6
|
|
|
7
|
+
SANITIZE_PHONE_NUMBER = re.compile(r'[+.()\-\s]')
|
|
8
|
+
STRIP_MX_MOBILE_PREFIX = re.compile(r'(52)(?:(?:044)|1)?(\d{10})$')
|
|
9
|
+
STRIP_US_DUPLICATE_PREFIX = re.compile(r'^11(\d{10})$')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def normalize_email(email: str) -> str:
|
|
13
|
+
"""Lowercase email and strip plus labels from the local part.
|
|
14
|
+
|
|
15
|
+
mateohhr@Yahoo.com -> mateohhr@yahoo.com
|
|
16
|
+
guerradzul+cuenca@gmail.com -> guerradzul@gmail.com
|
|
17
|
+
"""
|
|
18
|
+
local, _, domain = email.partition('@')
|
|
19
|
+
return f'{local.split("+")[0]}@{domain}'.lower()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def normalize_phone_number(phone_number: str) -> str:
|
|
23
|
+
"""Sanitize and normalize phone numbers to E.164 format.
|
|
24
|
+
|
|
25
|
+
Handles:
|
|
26
|
+
- Special characters: +52 (55) 1234-5678 -> +525512345678
|
|
27
|
+
- MX mobile prefix: +5215512345678 -> +525512345678
|
|
28
|
+
- MX 044 prefix: +520445512345678 -> +525512345678
|
|
29
|
+
- US duplicate prefix: +116504401222 -> +16504401222
|
|
30
|
+
"""
|
|
31
|
+
pn = SANITIZE_PHONE_NUMBER.sub('', phone_number)
|
|
32
|
+
pn = STRIP_MX_MOBILE_PREFIX.sub(r'\1\2', pn)
|
|
33
|
+
pn = STRIP_US_DUPLICATE_PREFIX.sub(r'1\1', pn)
|
|
34
|
+
return f'+{pn}'
|
|
35
|
+
|
|
6
36
|
|
|
7
37
|
def sanitize_dict(d: dict) -> dict:
|
|
8
38
|
for k, v in d.items():
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.1.28'
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from pydantic import ValidationError
|
|
3
|
+
from pydantic_extra_types.phone_numbers import PhoneNumber
|
|
3
4
|
|
|
5
|
+
from cuenca_validations.types.enums import VerificationType
|
|
4
6
|
from cuenca_validations.types.requests import (
|
|
5
7
|
UserTOSAgreementRequest,
|
|
6
8
|
UserUpdateRequest,
|
|
9
|
+
VerificationRequest,
|
|
7
10
|
)
|
|
8
11
|
from cuenca_validations.typing import DictStrAny
|
|
9
12
|
|
|
@@ -53,3 +56,29 @@ def test_update_user_update_govt() -> None:
|
|
|
53
56
|
with pytest.raises(ValueError) as ex:
|
|
54
57
|
UserUpdateRequest(**govt_id)
|
|
55
58
|
assert 'uri_back must be provided for type ine' in str(ex.value)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_verification_request_normalizes_email() -> None:
|
|
62
|
+
req = VerificationRequest(
|
|
63
|
+
recipient='user+cuenca@Gmail.com',
|
|
64
|
+
type=VerificationType.email,
|
|
65
|
+
)
|
|
66
|
+
assert req.recipient == 'user@gmail.com'
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_verification_request_normalizes_phone() -> None:
|
|
70
|
+
req = VerificationRequest(
|
|
71
|
+
recipient='+116504401222',
|
|
72
|
+
type=VerificationType.phone,
|
|
73
|
+
)
|
|
74
|
+
assert req.recipient == '+16504401222'
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_user_update_request_normalizes_email() -> None:
|
|
78
|
+
req = UserUpdateRequest(email_address='user+tag@Gmail.com')
|
|
79
|
+
assert req.email_address == 'user@gmail.com'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_user_update_request_normalizes_phone() -> None:
|
|
83
|
+
req = UserUpdateRequest(phone_number=PhoneNumber('+116504401222'))
|
|
84
|
+
assert req.phone_number == '+16504401222'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from cuenca_validations.validators import (
|
|
4
|
+
normalize_email,
|
|
5
|
+
normalize_phone_number,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.parametrize(
|
|
10
|
+
'raw, normalized',
|
|
11
|
+
[
|
|
12
|
+
('user@Yahoo.com', 'user@yahoo.com'), # uppercase domain
|
|
13
|
+
('user@iCloud.com', 'user@icloud.com'), # uppercase domain
|
|
14
|
+
('user@Gmail.com', 'user@gmail.com'), # uppercase domain
|
|
15
|
+
('user+cuenca@gmail.com', 'user@gmail.com'), # plus label
|
|
16
|
+
('user@gmail.com', 'user@gmail.com'), # already normalized
|
|
17
|
+
],
|
|
18
|
+
)
|
|
19
|
+
def test_normalize_email(raw: str, normalized: str) -> None:
|
|
20
|
+
assert normalize_email(raw) == normalized
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.mark.parametrize(
|
|
24
|
+
'raw, normalized',
|
|
25
|
+
[
|
|
26
|
+
('+116503456789', '+16503456789'), # US duplicate country code
|
|
27
|
+
('+5215512345678', '+525512345678'), # MX mobile prefix
|
|
28
|
+
('+520445512345678', '+525512345678'), # MX 044 prefix
|
|
29
|
+
('+52 (55) 1234-5678', '+525512345678'), # special characters
|
|
30
|
+
('+525512345678', '+525512345678'), # already correct MX
|
|
31
|
+
('+16503456789', '+16503456789'), # already correct US
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
def test_normalize_phone_number(raw: str, normalized: str) -> None:
|
|
35
|
+
assert normalize_phone_number(raw) == normalized
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.1.27'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations/types/identities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations.egg-info/requires.txt
RENAMED
|
File without changes
|
{cuenca_validations-2.1.27 → cuenca_validations-2.1.28}/cuenca_validations.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|