cuenca-validations 2.0.0.dev8__tar.gz → 2.0.0.dev10__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.0.0.dev8 → cuenca_validations-2.0.0.dev10}/PKG-INFO +11 -8
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/__init__.py +6 -3
- cuenca_validations-2.0.0.dev10/cuenca_validations/types/card.py +38 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/files.py +3 -2
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/general.py +21 -8
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/identities.py +19 -36
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/morals.py +3 -3
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/queries.py +4 -5
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/requests.py +44 -60
- cuenca_validations-2.0.0.dev10/cuenca_validations/typing.py +5 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/validators.py +2 -7
- cuenca_validations-2.0.0.dev10/cuenca_validations/version.py +1 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations.egg-info/PKG-INFO +11 -8
- cuenca_validations-2.0.0.dev10/cuenca_validations.egg-info/requires.txt +4 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/setup.py +10 -7
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/tests/test_card.py +12 -8
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/tests/test_errors.py +10 -7
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/tests/test_types.py +54 -63
- cuenca_validations-2.0.0.dev8/cuenca_validations/types/card.py +0 -42
- cuenca_validations-2.0.0.dev8/cuenca_validations/typing.py +0 -5
- cuenca_validations-2.0.0.dev8/cuenca_validations/version.py +0 -1
- cuenca_validations-2.0.0.dev8/cuenca_validations.egg-info/requires.txt +0 -7
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/LICENSE +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/README.md +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/__init__.py +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/card_bins.py +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/errors.py +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/py.typed +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/enums.py +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations.egg-info/SOURCES.txt +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations.egg-info/dependency_links.txt +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations.egg-info/top_level.txt +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/setup.cfg +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/tests/__init__.py +0 -0
- {cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/tests/test_statement.py +0 -0
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cuenca_validations
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev10
|
|
4
4
|
Summary: Cuenca common validations
|
|
5
5
|
Home-page: https://github.com/cuenca-mx/cuenca-validations
|
|
6
6
|
Author: Cuenca
|
|
7
7
|
Author-email: dev@cuenca.com
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
15
|
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.9
|
|
13
17
|
Description-Content-Type: text/markdown
|
|
14
18
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: clabe>=2.0.0
|
|
16
|
-
Requires-Dist: pydantic[email]>=2.0
|
|
17
|
-
Requires-Dist: pydantic-extra-types
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist: python-dateutil>=2.7.0
|
|
19
|
+
Requires-Dist: clabe>=2.0.0
|
|
20
|
+
Requires-Dist: pydantic[email]>=2.10.0
|
|
21
|
+
Requires-Dist: pydantic-extra-types>=2.10.0
|
|
22
|
+
Requires-Dist: python-dateutil>=2.9.0
|
|
20
23
|
|
|
21
24
|
# Cuenca - validations
|
|
22
25
|
|
|
@@ -49,7 +49,6 @@ __all__ = [
|
|
|
49
49
|
'KYCVerificationUpdateRequest',
|
|
50
50
|
'Language',
|
|
51
51
|
'LimitedWalletRequest',
|
|
52
|
-
'PageSize',
|
|
53
52
|
'PartnerRequest',
|
|
54
53
|
'PartnerUpdateRequest',
|
|
55
54
|
'PhoneNumber',
|
|
@@ -103,8 +102,10 @@ __all__ = [
|
|
|
103
102
|
'WalletQuery',
|
|
104
103
|
'WalletTransactionQuery',
|
|
105
104
|
'WebhookEvent',
|
|
106
|
-
'
|
|
105
|
+
'Digits',
|
|
107
106
|
'get_state_name',
|
|
107
|
+
'HttpUrlString',
|
|
108
|
+
'AnyUrlString',
|
|
108
109
|
]
|
|
109
110
|
|
|
110
111
|
from .card import StrictPaymentCardNumber
|
|
@@ -152,11 +153,13 @@ from .enums import (
|
|
|
152
153
|
)
|
|
153
154
|
from .files import BatchFileMetadata
|
|
154
155
|
from .general import (
|
|
156
|
+
AnyUrlString,
|
|
157
|
+
Digits,
|
|
158
|
+
HttpUrlString,
|
|
155
159
|
JSONEncoder,
|
|
156
160
|
SantizedDict,
|
|
157
161
|
StrictPositiveFloat,
|
|
158
162
|
StrictPositiveInt,
|
|
159
|
-
digits,
|
|
160
163
|
get_state_name,
|
|
161
164
|
)
|
|
162
165
|
from .identities import (
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pydantic_core import PydanticCustomError, core_schema
|
|
2
|
+
from pydantic_extra_types.payment import PaymentCardNumber
|
|
3
|
+
|
|
4
|
+
from ..card_bins import CARD_BINS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StrictPaymentCardNumber(PaymentCardNumber):
|
|
8
|
+
"""
|
|
9
|
+
Refactored `StrictPaymentCardNumber` to leverage Pydantic v2's
|
|
10
|
+
`PaymentCardNumber`, which now natively includes attributes such as
|
|
11
|
+
brand, bin, last4, and masked.
|
|
12
|
+
|
|
13
|
+
Previously, these attributes were manually computed in a custom
|
|
14
|
+
`PaymentCardNumber` class.
|
|
15
|
+
|
|
16
|
+
The `StrictPaymentCardNumber` class now wraps `PaymentCardNumber`
|
|
17
|
+
as a field, with additional validation to ensure the BIN is associated
|
|
18
|
+
with a known Mexican bank.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def validate(
|
|
23
|
+
cls, __input_value: str, _: core_schema.ValidationInfo
|
|
24
|
+
) -> 'StrictPaymentCardNumber':
|
|
25
|
+
card = super().validate(__input_value, _)
|
|
26
|
+
if card.bin not in CARD_BINS:
|
|
27
|
+
raise PydanticCustomError(
|
|
28
|
+
'payment_card_number.bin',
|
|
29
|
+
'The card number contains a BIN (first six digits) '
|
|
30
|
+
'that does not have a known association with a Mexican bank.'
|
|
31
|
+
'To add the association, please file an issue:'
|
|
32
|
+
'https://github.com/cuenca-mx/cuenca-validations/issues',
|
|
33
|
+
)
|
|
34
|
+
return cls(card)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def bank_code(self) -> str:
|
|
38
|
+
return CARD_BINS[self.bin]
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/files.py
RENAMED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel
|
|
3
|
+
from pydantic import BaseModel
|
|
4
4
|
|
|
5
5
|
from .enums import KYCFileType
|
|
6
|
+
from .general import HttpUrlString
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class BatchFileMetadata(BaseModel):
|
|
9
10
|
id: Optional[str] = None
|
|
10
11
|
is_back: bool
|
|
11
12
|
type: KYCFileType
|
|
12
|
-
url:
|
|
13
|
+
url: HttpUrlString
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/general.py
RENAMED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Any, Optional
|
|
2
|
+
from typing import Annotated, Any, Optional
|
|
3
3
|
|
|
4
|
-
from pydantic import BeforeValidator, Field
|
|
5
|
-
from typing_extensions import Annotated
|
|
4
|
+
from pydantic import AfterValidator, AnyUrl, BeforeValidator, Field, HttpUrl
|
|
6
5
|
|
|
7
6
|
from ..validators import sanitize_dict, sanitize_item
|
|
8
7
|
from .enums import State
|
|
9
8
|
|
|
9
|
+
# In Pydantic v2, URL fields like `HttpUrl` are stored as internal objects
|
|
10
|
+
# instead of `str`, which can break compatibility with code expecting str.
|
|
11
|
+
# Using `HttpUrlString` ensures the field is validated as a URL but stored as
|
|
12
|
+
# a `str` for compatibility.
|
|
13
|
+
# https://github.com/pydantic/pydantic/discussions/6395
|
|
14
|
+
|
|
15
|
+
HttpUrlString = Annotated[HttpUrl, AfterValidator(str)]
|
|
16
|
+
AnyUrlString = Annotated[AnyUrl, AfterValidator(str)]
|
|
17
|
+
|
|
10
18
|
|
|
11
19
|
class SantizedDict(dict):
|
|
12
20
|
def __init__(self, *args, **kwargs):
|
|
@@ -19,19 +27,24 @@ class JSONEncoder(json.JSONEncoder):
|
|
|
19
27
|
return sanitize_item(o, default=super().default)
|
|
20
28
|
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
MAX_VALUE_IN_DB = 21_474_836_47
|
|
31
|
+
|
|
32
|
+
StrictPositiveInt = Annotated[
|
|
33
|
+
int, Field(strict=True, gt=0, le=MAX_VALUE_IN_DB)
|
|
34
|
+
]
|
|
23
35
|
|
|
24
36
|
|
|
25
37
|
StrictPositiveFloat = Annotated[float, Field(strict=True, gt=0)]
|
|
26
38
|
|
|
27
39
|
|
|
28
|
-
def validate_only_digits(value:
|
|
29
|
-
|
|
40
|
+
def validate_only_digits(value: Any) -> str:
|
|
41
|
+
v_str = str(value)
|
|
42
|
+
if not v_str.isdigit():
|
|
30
43
|
raise ValueError("Value must contain only digits")
|
|
31
|
-
return
|
|
44
|
+
return v_str
|
|
32
45
|
|
|
33
46
|
|
|
34
|
-
def
|
|
47
|
+
def Digits(
|
|
35
48
|
min_length: Optional[int] = None, max_length: Optional[int] = None
|
|
36
49
|
) -> Any:
|
|
37
50
|
return Annotated[
|
|
@@ -1,51 +1,34 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
|
-
import
|
|
3
|
-
from typing import Any, Dict, List, Optional
|
|
2
|
+
from typing import Annotated, Any, Optional
|
|
4
3
|
|
|
5
4
|
from pydantic import (
|
|
6
5
|
BaseModel,
|
|
7
6
|
ConfigDict,
|
|
8
7
|
Field,
|
|
9
8
|
IPvAnyAddress,
|
|
9
|
+
StringConstraints,
|
|
10
10
|
model_validator,
|
|
11
11
|
)
|
|
12
12
|
from pydantic.types import StrictStr
|
|
13
13
|
|
|
14
14
|
from .enums import Country, KYCFileType, State, VerificationStatus
|
|
15
15
|
|
|
16
|
+
PhoneNumber = Annotated[
|
|
17
|
+
str,
|
|
18
|
+
StringConstraints(
|
|
19
|
+
min_length=10, max_length=15, pattern=r'^\+?[0-9]{10,14}$'
|
|
20
|
+
),
|
|
21
|
+
]
|
|
16
22
|
|
|
17
|
-
class PhoneNumber(StrictStr):
|
|
18
|
-
min_length = 10
|
|
19
|
-
max_length = 15
|
|
20
|
-
regex = re.compile(r'^\+?[0-9]{10,14}$')
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'max_length': cls.max_length,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class CurpField(StrictStr):
|
|
35
|
-
min_length = 18
|
|
36
|
-
max_length = 18
|
|
37
|
-
regex = re.compile(r'^[A-Z]{4}[0-9]{6}[A-Z]{6}[A-Z|0-9][0-9]$')
|
|
38
|
-
|
|
39
|
-
@classmethod
|
|
40
|
-
def __get_pydantic_core_schema__(
|
|
41
|
-
cls, source_type: Any, handler: Any
|
|
42
|
-
) -> Dict[str, Any]:
|
|
43
|
-
return {
|
|
44
|
-
'type': 'str',
|
|
45
|
-
'pattern': cls.regex.pattern,
|
|
46
|
-
'min_length': cls.min_length,
|
|
47
|
-
'max_length': cls.max_length,
|
|
48
|
-
}
|
|
24
|
+
CurpField = Annotated[
|
|
25
|
+
str,
|
|
26
|
+
StringConstraints(
|
|
27
|
+
min_length=18,
|
|
28
|
+
max_length=18,
|
|
29
|
+
pattern=r'^[A-Z]{4}[0-9]{6}[A-Z]{6}[A-Z|0-9][0-9]$',
|
|
30
|
+
),
|
|
31
|
+
]
|
|
49
32
|
|
|
50
33
|
|
|
51
34
|
class Rfc(StrictStr):
|
|
@@ -61,7 +44,7 @@ class Rfc(StrictStr):
|
|
|
61
44
|
@classmethod
|
|
62
45
|
def __get_pydantic_core_schema__(
|
|
63
46
|
cls, source_type: Any, handler: Any
|
|
64
|
-
) ->
|
|
47
|
+
) -> dict[str, Any]:
|
|
65
48
|
return {
|
|
66
49
|
'type': 'str',
|
|
67
50
|
'min_length': cls.min_length,
|
|
@@ -96,7 +79,7 @@ class Address(BaseModel):
|
|
|
96
79
|
|
|
97
80
|
@model_validator(mode='before')
|
|
98
81
|
@classmethod
|
|
99
|
-
def full_name_complete(cls, values:
|
|
82
|
+
def full_name_complete(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
100
83
|
if values.get('full_name'):
|
|
101
84
|
return values
|
|
102
85
|
if not values.get('street'):
|
|
@@ -162,7 +145,7 @@ class KYCFile(BaseModel):
|
|
|
162
145
|
status: Optional[VerificationStatus] = Field(
|
|
163
146
|
None, description='The status of the file depends on KYCValidation'
|
|
164
147
|
)
|
|
165
|
-
errors: Optional[
|
|
148
|
+
errors: Optional[list[VerificationErrors]] = Field(
|
|
166
149
|
None, description='List of document errors found during kyc validation'
|
|
167
150
|
)
|
|
168
151
|
verification_id: Optional[str] = Field(
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/morals.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, EmailStr
|
|
5
5
|
|
|
@@ -70,5 +70,5 @@ class ShareholderPhysical(PhysicalPerson):
|
|
|
70
70
|
class Shareholder(BaseModel):
|
|
71
71
|
name: str
|
|
72
72
|
percentage: int
|
|
73
|
-
shareholders:
|
|
74
|
-
legal_representatives:
|
|
73
|
+
shareholders: list[ShareholderPhysical]
|
|
74
|
+
legal_representatives: list[LegalRepresentative]
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/queries.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Annotated, Optional
|
|
3
3
|
|
|
4
4
|
from pydantic import (
|
|
5
5
|
BaseModel,
|
|
@@ -9,7 +9,6 @@ from pydantic import (
|
|
|
9
9
|
PositiveInt,
|
|
10
10
|
field_validator,
|
|
11
11
|
)
|
|
12
|
-
from typing_extensions import Annotated
|
|
13
12
|
|
|
14
13
|
from ..typing import DictStrAny
|
|
15
14
|
from ..validators import sanitize_dict
|
|
@@ -33,7 +32,7 @@ MAX_PAGE_SIZE = 100
|
|
|
33
32
|
class QueryParams(BaseModel):
|
|
34
33
|
count: bool = False
|
|
35
34
|
page_size: Annotated[
|
|
36
|
-
int, Field(
|
|
35
|
+
int, Field(gt=0, le=MAX_PAGE_SIZE, default=MAX_PAGE_SIZE)
|
|
37
36
|
]
|
|
38
37
|
limit: Optional[PositiveInt] = None
|
|
39
38
|
user_id: Optional[str] = None
|
|
@@ -61,10 +60,10 @@ class QueryParams(BaseModel):
|
|
|
61
60
|
},
|
|
62
61
|
)
|
|
63
62
|
|
|
64
|
-
def
|
|
63
|
+
def model_dump(self, *args, **kwargs) -> DictStrAny:
|
|
65
64
|
kwargs.setdefault('exclude_none', True)
|
|
66
65
|
kwargs.setdefault('exclude_unset', True)
|
|
67
|
-
d = super().
|
|
66
|
+
d = super().model_dump(*args, **kwargs)
|
|
68
67
|
if self.count:
|
|
69
68
|
d['count'] = 1
|
|
70
69
|
sanitize_dict(d)
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Annotated, Optional, Union
|
|
3
3
|
|
|
4
4
|
from clabe import Clabe
|
|
5
5
|
from pydantic import (
|
|
6
|
-
AnyUrl,
|
|
7
6
|
BaseModel,
|
|
8
7
|
ConfigDict,
|
|
9
8
|
EmailStr,
|
|
10
9
|
Field,
|
|
11
|
-
HttpUrl,
|
|
12
10
|
StrictStr,
|
|
13
11
|
StringConstraints,
|
|
14
12
|
field_validator,
|
|
15
13
|
model_validator,
|
|
16
14
|
)
|
|
17
15
|
from pydantic.networks import IPvAnyAddress
|
|
18
|
-
from typing_extensions import Annotated
|
|
19
16
|
|
|
20
17
|
from ..types.enums import (
|
|
21
18
|
AuthorizerTransaction,
|
|
@@ -51,7 +48,7 @@ from ..types.enums import (
|
|
|
51
48
|
from ..typing import DictStrAny
|
|
52
49
|
from ..validators import validate_age_requirement
|
|
53
50
|
from .card import PaymentCardNumber, StrictPaymentCardNumber
|
|
54
|
-
from .general import StrictPositiveInt
|
|
51
|
+
from .general import AnyUrlString, HttpUrlString, StrictPositiveInt
|
|
55
52
|
from .identities import (
|
|
56
53
|
Address,
|
|
57
54
|
Beneficiary,
|
|
@@ -75,17 +72,14 @@ from .morals import (
|
|
|
75
72
|
class BaseRequest(BaseModel):
|
|
76
73
|
model_config = ConfigDict(extra="forbid")
|
|
77
74
|
|
|
78
|
-
def
|
|
75
|
+
def model_dump(self, *args, **kwargs) -> DictStrAny:
|
|
79
76
|
kwargs.setdefault('exclude_none', True)
|
|
80
77
|
kwargs.setdefault('exclude_unset', True)
|
|
81
|
-
return super().
|
|
78
|
+
return super().model_dump(*args, **kwargs)
|
|
82
79
|
|
|
83
80
|
|
|
84
|
-
class
|
|
81
|
+
class BaseTransferRequest(BaseRequest):
|
|
85
82
|
recipient_name: StrictStr
|
|
86
|
-
account_number: Union[Clabe, PaymentCardNumber] = Field(
|
|
87
|
-
..., description='Destination Clabe or Card number'
|
|
88
|
-
)
|
|
89
83
|
amount: StrictPositiveInt = Field(
|
|
90
84
|
..., description='Always in cents, not in MXN pesos'
|
|
91
85
|
)
|
|
@@ -112,8 +106,16 @@ class TransferRequest(BaseRequest):
|
|
|
112
106
|
)
|
|
113
107
|
|
|
114
108
|
|
|
115
|
-
class
|
|
116
|
-
account_number: Union[Clabe,
|
|
109
|
+
class TransferRequest(BaseTransferRequest):
|
|
110
|
+
account_number: Union[Clabe, PaymentCardNumber] = Field(
|
|
111
|
+
..., description='Destination Clabe or Card number'
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class StrictTransferRequest(BaseTransferRequest):
|
|
116
|
+
account_number: Union[Clabe, StrictPaymentCardNumber] = Field(
|
|
117
|
+
..., description='Destination Clabe or Card number'
|
|
118
|
+
)
|
|
117
119
|
|
|
118
120
|
|
|
119
121
|
class CardUpdateRequest(BaseRequest):
|
|
@@ -131,15 +133,7 @@ class CardRequest(BaseRequest):
|
|
|
131
133
|
|
|
132
134
|
|
|
133
135
|
class CardActivationRequest(BaseModel):
|
|
134
|
-
number:
|
|
135
|
-
str,
|
|
136
|
-
StringConstraints(
|
|
137
|
-
strip_whitespace=True,
|
|
138
|
-
min_length=16,
|
|
139
|
-
max_length=16,
|
|
140
|
-
pattern=r'\d{16}',
|
|
141
|
-
),
|
|
142
|
-
]
|
|
136
|
+
number: PaymentCardNumber
|
|
143
137
|
exp_month: Annotated[int, Field(strict=True, ge=1, le=12)]
|
|
144
138
|
exp_year: Annotated[int, Field(strict=True, ge=18, le=99)]
|
|
145
139
|
cvv2: Annotated[
|
|
@@ -170,9 +164,9 @@ class UserCredentialUpdateRequest(BaseRequest):
|
|
|
170
164
|
),
|
|
171
165
|
)
|
|
172
166
|
|
|
173
|
-
def
|
|
167
|
+
def model_dump(self, *args, **kwargs) -> DictStrAny:
|
|
174
168
|
# Password can be None but BaseRequest excludes None
|
|
175
|
-
return BaseModel.
|
|
169
|
+
return BaseModel.model_dump(self, *args, **kwargs)
|
|
176
170
|
|
|
177
171
|
@model_validator(mode="before")
|
|
178
172
|
@classmethod
|
|
@@ -250,9 +244,7 @@ class ARPCRequest(BaseModel):
|
|
|
250
244
|
arqc: StrictStr
|
|
251
245
|
arpc_method: Annotated[
|
|
252
246
|
str,
|
|
253
|
-
StringConstraints(
|
|
254
|
-
strict=True, min_length=1, max_length=1
|
|
255
|
-
),
|
|
247
|
+
StringConstraints(strict=True, min_length=1, max_length=1),
|
|
256
248
|
]
|
|
257
249
|
transaction_data: StrictStr
|
|
258
250
|
response_code: StrictStr
|
|
@@ -285,8 +277,7 @@ class CardTransactionRequest(BaseModel):
|
|
|
285
277
|
authorizer_number: Optional[str] = None
|
|
286
278
|
|
|
287
279
|
|
|
288
|
-
class ReverseRequest(CardTransactionRequest):
|
|
289
|
-
...
|
|
280
|
+
class ReverseRequest(CardTransactionRequest): ... # noqa: E701
|
|
290
281
|
|
|
291
282
|
|
|
292
283
|
class CardNotificationRequest(CardTransactionRequest):
|
|
@@ -300,9 +291,9 @@ class ChargeRequest(CardNotificationRequest):
|
|
|
300
291
|
get_balance: Optional[bool] = False
|
|
301
292
|
atm_fee: Optional[StrictPositiveInt] = None
|
|
302
293
|
issuer: IssuerNetwork
|
|
303
|
-
cardholder_verification_method: Optional[
|
|
304
|
-
|
|
305
|
-
|
|
294
|
+
cardholder_verification_method: Optional[CardholderVerificationMethod] = (
|
|
295
|
+
None
|
|
296
|
+
)
|
|
306
297
|
ecommerce_indicator: Optional[EcommerceIndicator] = None
|
|
307
298
|
fraud_validation_id: Optional[str] = None
|
|
308
299
|
|
|
@@ -353,14 +344,14 @@ class FraudValidationRequest(BaseModel):
|
|
|
353
344
|
logical_network: Optional[str] = None
|
|
354
345
|
is_cvv: Optional[bool] = False
|
|
355
346
|
issuer: IssuerNetwork
|
|
356
|
-
cardholder_verification_method: Optional[
|
|
357
|
-
|
|
358
|
-
|
|
347
|
+
cardholder_verification_method: Optional[CardholderVerificationMethod] = (
|
|
348
|
+
None
|
|
349
|
+
)
|
|
359
350
|
ecommerce_indicator: Optional[EcommerceIndicator] = None
|
|
360
|
-
card_id: Optional[str] = None
|
|
361
|
-
user_id: Optional[str] = None
|
|
362
|
-
card_type: Optional[CardType] = None
|
|
363
|
-
card_status: Optional[CardStatus] = None
|
|
351
|
+
card_id: Optional[str] = None
|
|
352
|
+
user_id: Optional[str] = None
|
|
353
|
+
card_type: Optional[CardType] = None
|
|
354
|
+
card_status: Optional[CardStatus] = None
|
|
364
355
|
|
|
365
356
|
|
|
366
357
|
class TransactionTokenValidationUpdateRequest(BaseRequest):
|
|
@@ -533,19 +524,19 @@ class UserUpdateRequest(BaseModel):
|
|
|
533
524
|
email_verification_id: Optional[str] = None
|
|
534
525
|
phone_verification_id: Optional[str] = None
|
|
535
526
|
address: Optional[Address] = None
|
|
536
|
-
beneficiaries: Optional[
|
|
527
|
+
beneficiaries: Optional[list[Beneficiary]] = None
|
|
537
528
|
govt_id: Optional[KYCFile] = None
|
|
538
529
|
proof_of_address: Optional[KYCFile] = None
|
|
539
530
|
proof_of_life: Optional[KYCFile] = None
|
|
540
531
|
status: Optional[UserStatus] = None
|
|
541
532
|
terms_of_service: Optional[TOSRequest] = None
|
|
542
533
|
platform_terms_of_service: Optional[TOSAgreement] = None
|
|
543
|
-
curp_document_uri: Optional[
|
|
534
|
+
curp_document_uri: Optional[HttpUrlString] = None
|
|
544
535
|
|
|
545
536
|
@field_validator('beneficiaries')
|
|
546
537
|
@classmethod
|
|
547
538
|
def beneficiary_percentage(
|
|
548
|
-
cls, beneficiaries: Optional[
|
|
539
|
+
cls, beneficiaries: Optional[list[Beneficiary]] = None
|
|
549
540
|
):
|
|
550
541
|
if beneficiaries and sum(b.percentage for b in beneficiaries) > 100:
|
|
551
542
|
raise ValueError('The total percentage is more than 100.')
|
|
@@ -570,8 +561,8 @@ class UserLoginRequest(BaseRequest):
|
|
|
570
561
|
class SessionRequest(BaseRequest):
|
|
571
562
|
user_id: str
|
|
572
563
|
type: SessionType
|
|
573
|
-
success_url: Optional[
|
|
574
|
-
failure_url: Optional[
|
|
564
|
+
success_url: Optional[AnyUrlString] = None
|
|
565
|
+
failure_url: Optional[AnyUrlString] = None
|
|
575
566
|
model_config = ConfigDict(
|
|
576
567
|
json_schema_extra={
|
|
577
568
|
'example': {
|
|
@@ -585,15 +576,15 @@ class SessionRequest(BaseRequest):
|
|
|
585
576
|
|
|
586
577
|
|
|
587
578
|
class EndpointRequest(BaseRequest):
|
|
588
|
-
url:
|
|
589
|
-
events: Optional[
|
|
579
|
+
url: HttpUrlString
|
|
580
|
+
events: Optional[list[WebhookEvent]] = None
|
|
590
581
|
user_id: Optional[str] = None
|
|
591
582
|
|
|
592
583
|
|
|
593
584
|
class EndpointUpdateRequest(BaseRequest):
|
|
594
|
-
url: Optional[
|
|
585
|
+
url: Optional[HttpUrlString] = None
|
|
595
586
|
is_enable: Optional[bool] = None
|
|
596
|
-
events: Optional[
|
|
587
|
+
events: Optional[list[WebhookEvent]] = None
|
|
597
588
|
|
|
598
589
|
|
|
599
590
|
class FileUploadRequest(BaseRequest):
|
|
@@ -606,12 +597,12 @@ class FileUploadRequest(BaseRequest):
|
|
|
606
597
|
|
|
607
598
|
class FileRequest(BaseModel):
|
|
608
599
|
is_back: Optional[bool] = False
|
|
609
|
-
url:
|
|
600
|
+
url: HttpUrlString
|
|
610
601
|
type: KYCFileType
|
|
611
602
|
|
|
612
603
|
|
|
613
604
|
class FileBatchUploadRequest(BaseModel):
|
|
614
|
-
files:
|
|
605
|
+
files: list[FileRequest]
|
|
615
606
|
user_id: str
|
|
616
607
|
|
|
617
608
|
|
|
@@ -653,8 +644,6 @@ class VerificationAttemptRequest(BaseModel):
|
|
|
653
644
|
|
|
654
645
|
|
|
655
646
|
class LimitedWalletRequest(BaseRequest):
|
|
656
|
-
model_config = dict(arbitrary_types_allowed=True)
|
|
657
|
-
|
|
658
647
|
allowed_curp: CurpField
|
|
659
648
|
allowed_rfc: Optional[Rfc] = None
|
|
660
649
|
|
|
@@ -664,7 +653,6 @@ class KYCVerificationUpdateRequest(BaseRequest):
|
|
|
664
653
|
|
|
665
654
|
|
|
666
655
|
class PlatformRequest(BaseModel):
|
|
667
|
-
model_config = dict(arbitrary_types_allowed=True)
|
|
668
656
|
name: str
|
|
669
657
|
rfc: Optional[str] = None
|
|
670
658
|
establishment_date: Optional[dt.date] = None
|
|
@@ -686,7 +674,7 @@ class WebhookRequest(BaseModel):
|
|
|
686
674
|
class KYCValidationRequest(BaseRequest):
|
|
687
675
|
user_id: str
|
|
688
676
|
force: bool = False
|
|
689
|
-
documents:
|
|
677
|
+
documents: list[KYCFile] = []
|
|
690
678
|
|
|
691
679
|
|
|
692
680
|
class BankAccountValidationRequest(BaseModel):
|
|
@@ -740,8 +728,6 @@ class QuestionnairesRequest(BaseModel):
|
|
|
740
728
|
|
|
741
729
|
|
|
742
730
|
class PartnerRequest(BaseRequest):
|
|
743
|
-
model_config = dict(arbitrary_types_allowed=True)
|
|
744
|
-
|
|
745
731
|
legal_name: str
|
|
746
732
|
business_name: str
|
|
747
733
|
nationality: Country
|
|
@@ -756,8 +742,6 @@ class PartnerRequest(BaseRequest):
|
|
|
756
742
|
|
|
757
743
|
|
|
758
744
|
class PartnerUpdateRequest(BaseRequest):
|
|
759
|
-
model_config = dict(arbitrary_types_allowed=True)
|
|
760
|
-
|
|
761
745
|
legal_name: Optional[str] = None
|
|
762
746
|
business_name: Optional[str] = None
|
|
763
747
|
nationality: Optional[Country] = None
|
|
@@ -775,5 +759,5 @@ class PartnerUpdateRequest(BaseRequest):
|
|
|
775
759
|
license: Optional[LicenseDetails] = None
|
|
776
760
|
audit: Optional[AuditDetails] = None
|
|
777
761
|
vulnerable_activity: Optional[VulnerableActivityDetails] = None
|
|
778
|
-
legal_representatives: Optional[
|
|
779
|
-
shareholders: Optional[
|
|
762
|
+
legal_representatives: Optional[list[LegalRepresentative]] = None
|
|
763
|
+
shareholders: Optional[list[Shareholder]] = None
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/validators.py
RENAMED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import datetime as dt
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from typing import Any, Callable,
|
|
4
|
+
from typing import Any, Callable, Optional, Union
|
|
5
5
|
|
|
6
6
|
from dateutil.relativedelta import relativedelta
|
|
7
|
-
from pydantic import AnyUrl, HttpUrl
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
def sanitize_dict(d: dict) -> dict:
|
|
@@ -21,16 +20,12 @@ def sanitize_item(
|
|
|
21
20
|
:param default: Optional function to be used when there is no case
|
|
22
21
|
for this type of item, default `None` it returns the item as is.
|
|
23
22
|
"""
|
|
24
|
-
rv: Union[str,
|
|
23
|
+
rv: Union[str, list[Any]]
|
|
25
24
|
if isinstance(item, dt.date):
|
|
26
25
|
if isinstance(item, dt.datetime) and not item.tzinfo:
|
|
27
26
|
rv = item.astimezone(dt.timezone.utc).isoformat()
|
|
28
27
|
else:
|
|
29
28
|
rv = item.isoformat()
|
|
30
|
-
elif isinstance(item, HttpUrl):
|
|
31
|
-
rv = str(item)
|
|
32
|
-
elif isinstance(item, AnyUrl):
|
|
33
|
-
rv = str(item)
|
|
34
29
|
elif isinstance(item, list):
|
|
35
30
|
rv = [
|
|
36
31
|
sanitize_dict(e) if isinstance(e, dict) else sanitize_item(e)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.0.0.dev10'
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cuenca_validations
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev10
|
|
4
4
|
Summary: Cuenca common validations
|
|
5
5
|
Home-page: https://github.com/cuenca-mx/cuenca-validations
|
|
6
6
|
Author: Cuenca
|
|
7
7
|
Author-email: dev@cuenca.com
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
15
|
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.9
|
|
13
17
|
Description-Content-Type: text/markdown
|
|
14
18
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: clabe>=2.0.0
|
|
16
|
-
Requires-Dist: pydantic[email]>=2.0
|
|
17
|
-
Requires-Dist: pydantic-extra-types
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist: python-dateutil>=2.7.0
|
|
19
|
+
Requires-Dist: clabe>=2.0.0
|
|
20
|
+
Requires-Dist: pydantic[email]>=2.10.0
|
|
21
|
+
Requires-Dist: pydantic-extra-types>=2.10.0
|
|
22
|
+
Requires-Dist: python-dateutil>=2.9.0
|
|
20
23
|
|
|
21
24
|
# Cuenca - validations
|
|
22
25
|
|
|
@@ -23,17 +23,20 @@ setup(
|
|
|
23
23
|
packages=find_packages(),
|
|
24
24
|
include_package_data=True,
|
|
25
25
|
package_data=dict(cuenca_validations=['py.typed']),
|
|
26
|
-
python_requires='>=3.
|
|
26
|
+
python_requires='>=3.9',
|
|
27
27
|
install_requires=[
|
|
28
|
-
'clabe>=2.0.0
|
|
29
|
-
'pydantic[email]>=2.0',
|
|
30
|
-
'pydantic-extra-types
|
|
31
|
-
'
|
|
32
|
-
'python-dateutil>=2.7.0',
|
|
28
|
+
'clabe>=2.0.0',
|
|
29
|
+
'pydantic[email]>=2.10.0',
|
|
30
|
+
'pydantic-extra-types>=2.10.0',
|
|
31
|
+
'python-dateutil>=2.9.0',
|
|
33
32
|
],
|
|
34
33
|
classifiers=[
|
|
35
34
|
'Programming Language :: Python :: 3',
|
|
36
|
-
'Programming Language :: Python :: 3.
|
|
35
|
+
'Programming Language :: Python :: 3.9',
|
|
36
|
+
'Programming Language :: Python :: 3.10',
|
|
37
|
+
'Programming Language :: Python :: 3.11',
|
|
38
|
+
'Programming Language :: Python :: 3.12',
|
|
39
|
+
'Programming Language :: Python :: 3.13',
|
|
37
40
|
'License :: OSI Approved :: MIT License',
|
|
38
41
|
'Operating System :: OS Independent',
|
|
39
42
|
],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from pydantic import ValidationError
|
|
2
|
+
from pydantic import BaseModel, ValidationError
|
|
3
3
|
from pydantic_extra_types.payment import PaymentCardBrand
|
|
4
4
|
|
|
5
5
|
from cuenca_validations.types import StrictPaymentCardNumber
|
|
@@ -8,9 +8,13 @@ VALID_BBVA = '4772130000000003'
|
|
|
8
8
|
INVALID_BIN = '4050000000000001'
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
class CardModel(BaseModel):
|
|
12
|
+
card_number: StrictPaymentCardNumber
|
|
13
|
+
|
|
14
|
+
|
|
11
15
|
def test_invalid_bin_strict_payment():
|
|
12
16
|
with pytest.raises(ValidationError) as exc_info:
|
|
13
|
-
|
|
17
|
+
CardModel(card_number=INVALID_BIN)
|
|
14
18
|
print(exc_info.value)
|
|
15
19
|
assert 'payment_card_number.bin' in str(exc_info.value)
|
|
16
20
|
assert 'The card number contains a BIN (first six digits) ' in str(
|
|
@@ -19,9 +23,9 @@ def test_invalid_bin_strict_payment():
|
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
def test_valid_bin_strict_payment():
|
|
22
|
-
card =
|
|
23
|
-
assert card.brand == PaymentCardBrand.visa
|
|
24
|
-
assert card.bin == '477213'
|
|
25
|
-
assert card.last4 == '0003'
|
|
26
|
-
assert card.masked == '477213******0003'
|
|
27
|
-
assert card.bank_code == '40012'
|
|
26
|
+
card = CardModel(card_number=VALID_BBVA)
|
|
27
|
+
assert card.card_number.brand == PaymentCardBrand.visa
|
|
28
|
+
assert card.card_number.bin == '477213'
|
|
29
|
+
assert card.card_number.last4 == '0003'
|
|
30
|
+
assert card.card_number.masked == '477213******0003'
|
|
31
|
+
assert card.card_number.bank_code == '40012'
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
1
3
|
from cuenca_validations.errors import (
|
|
2
4
|
ApiError,
|
|
3
5
|
AuthMethodNotAllowedError,
|
|
@@ -16,8 +18,9 @@ def test_cuenca_error_base():
|
|
|
16
18
|
assert issubclass(CuencaError, Exception)
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
@pytest.mark.parametrize(
|
|
22
|
+
"error_class, expected_code, expected_status",
|
|
23
|
+
[
|
|
21
24
|
(WrongCredsError, 101, 401),
|
|
22
25
|
(MissingAuthorizationHeaderError, 102, 401),
|
|
23
26
|
(UserNotLoggedInError, 103, 401),
|
|
@@ -27,8 +30,8 @@ def test_error_codes_and_status():
|
|
|
27
30
|
(UserLocationError, 108, 401),
|
|
28
31
|
(InvalidOTPCodeError, 109, 401),
|
|
29
32
|
(ApiError, 500, 500),
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
],
|
|
34
|
+
)
|
|
35
|
+
def test_error_codes_and_status(error_class, expected_code, expected_status):
|
|
36
|
+
assert error_class.code == expected_code
|
|
37
|
+
assert error_class.status_code == expected_status
|
|
@@ -5,18 +5,18 @@ from enum import Enum
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
from freezegun import freeze_time
|
|
8
|
-
from pydantic import
|
|
8
|
+
from pydantic import BaseModel, ValidationError
|
|
9
9
|
|
|
10
10
|
from cuenca_validations.types import (
|
|
11
11
|
Address,
|
|
12
12
|
CardQuery,
|
|
13
|
+
Digits,
|
|
13
14
|
JSONEncoder,
|
|
14
15
|
QueryParams,
|
|
15
16
|
Rfc,
|
|
16
17
|
SantizedDict,
|
|
17
18
|
SessionRequest,
|
|
18
19
|
TransactionStatus,
|
|
19
|
-
digits,
|
|
20
20
|
get_state_name,
|
|
21
21
|
)
|
|
22
22
|
from cuenca_validations.types.enums import (
|
|
@@ -67,19 +67,21 @@ class TestClass:
|
|
|
67
67
|
|
|
68
68
|
def test_dict():
|
|
69
69
|
model = QueryParams(count=1, created_before=now)
|
|
70
|
-
assert model.
|
|
70
|
+
assert model.model_dump() == dict(
|
|
71
|
+
count=1, created_before=utcnow.isoformat()
|
|
72
|
+
)
|
|
71
73
|
|
|
72
74
|
|
|
73
75
|
def test_dict_with_exclude():
|
|
74
76
|
model = QueryParams(count=1, created_before=now, user_id='USXXXX')
|
|
75
|
-
assert model.
|
|
77
|
+
assert model.model_dump(exclude={'user_id'}) == dict(
|
|
76
78
|
count=1, created_before=utcnow.isoformat()
|
|
77
79
|
)
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
def test_dict_with_exclude_unset():
|
|
81
83
|
model = QueryParams(count=1, created_before=now)
|
|
82
|
-
assert model.
|
|
84
|
+
assert model.model_dump(exclude_unset=False) == dict(
|
|
83
85
|
count=1, created_before=utcnow.isoformat(), page_size=100
|
|
84
86
|
)
|
|
85
87
|
|
|
@@ -132,17 +134,6 @@ def test_json_encoder(value, result):
|
|
|
132
134
|
assert decoded['value'] == result
|
|
133
135
|
|
|
134
136
|
|
|
135
|
-
def test_sanitized_dict_with_urls():
|
|
136
|
-
data = SantizedDict(
|
|
137
|
-
api_url=HttpUrl('https://api.cuenca.com/v1'),
|
|
138
|
-
ftp_url=AnyUrl('ftp://files.example.com/'),
|
|
139
|
-
)
|
|
140
|
-
assert data == {
|
|
141
|
-
'api_url': 'https://api.cuenca.com/v1',
|
|
142
|
-
'ftp_url': 'ftp://files.example.com/',
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
137
|
def test_invalid_class():
|
|
147
138
|
"""
|
|
148
139
|
For a class that doesn't have a `to_dict` method and it is not a type of
|
|
@@ -150,8 +141,7 @@ def test_invalid_class():
|
|
|
150
141
|
raises a `TypeError`.
|
|
151
142
|
"""
|
|
152
143
|
|
|
153
|
-
class ClassWithoutToDict:
|
|
154
|
-
...
|
|
144
|
+
class ClassWithoutToDict: ... # noqa: E701
|
|
155
145
|
|
|
156
146
|
invalid_class = ClassWithoutToDict()
|
|
157
147
|
with pytest.raises(TypeError):
|
|
@@ -159,12 +149,20 @@ def test_invalid_class():
|
|
|
159
149
|
|
|
160
150
|
|
|
161
151
|
class Accounts(BaseModel):
|
|
162
|
-
number:
|
|
152
|
+
number: Digits(5, 8) # type: ignore
|
|
163
153
|
|
|
164
154
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
155
|
+
@pytest.mark.parametrize(
|
|
156
|
+
"input_number, expected",
|
|
157
|
+
[
|
|
158
|
+
('123456', '123456'),
|
|
159
|
+
(123456, '123456'),
|
|
160
|
+
('0012312', '0012312'),
|
|
161
|
+
],
|
|
162
|
+
)
|
|
163
|
+
def test_only_digits(input_number, expected):
|
|
164
|
+
acc = Accounts(number=input_number)
|
|
165
|
+
assert acc.number == expected
|
|
168
166
|
|
|
169
167
|
|
|
170
168
|
@pytest.mark.parametrize(
|
|
@@ -205,7 +203,7 @@ def test_card_query_exp_cvv_if_number_not_set(input_value):
|
|
|
205
203
|
|
|
206
204
|
def test_exclude_none_in_dict():
|
|
207
205
|
request = ApiKeyUpdateRequest(user_id='US123')
|
|
208
|
-
assert request.
|
|
206
|
+
assert request.model_dump() == dict(user_id='US123')
|
|
209
207
|
|
|
210
208
|
|
|
211
209
|
def test_update_one_property_at_a_time_request():
|
|
@@ -229,7 +227,7 @@ def test_update_one_property_at_a_time_request():
|
|
|
229
227
|
)
|
|
230
228
|
def test_update_credential_update_request_dict(data, expected_dict):
|
|
231
229
|
req = UserCredentialUpdateRequest(**data)
|
|
232
|
-
assert req.
|
|
230
|
+
assert req.model_dump() == expected_dict
|
|
233
231
|
|
|
234
232
|
|
|
235
233
|
def test_card_transaction_requests():
|
|
@@ -356,7 +354,7 @@ def test_user_request():
|
|
|
356
354
|
required_level=3,
|
|
357
355
|
terms_of_service=None,
|
|
358
356
|
)
|
|
359
|
-
assert UserRequest(**request).
|
|
357
|
+
assert UserRequest(**request).model_dump() == request
|
|
360
358
|
|
|
361
359
|
# changing curp so user is underage
|
|
362
360
|
request['curp'] = 'ABCD060604HDFSRN03'
|
|
@@ -393,7 +391,7 @@ def test_curp_validation_request():
|
|
|
393
391
|
assert all(field in error_msg for field in required_fields)
|
|
394
392
|
|
|
395
393
|
req_curp = CurpValidationRequest(**request)
|
|
396
|
-
assert req_curp.
|
|
394
|
+
assert req_curp.model_dump() == request
|
|
397
395
|
|
|
398
396
|
request['date_of_birth'] = dt.date(2006, 5, 17)
|
|
399
397
|
|
|
@@ -431,9 +429,9 @@ def test_user_update_request():
|
|
|
431
429
|
curp_document_uri='https://sandbox.cuenca.com/files/EF123',
|
|
432
430
|
)
|
|
433
431
|
update_req = UserUpdateRequest(**request)
|
|
434
|
-
beneficiaries = [b.
|
|
432
|
+
beneficiaries = [b.model_dump() for b in update_req.beneficiaries]
|
|
435
433
|
assert beneficiaries == request['beneficiaries']
|
|
436
|
-
assert
|
|
434
|
+
assert update_req.curp_document_uri == request['curp_document_uri']
|
|
437
435
|
|
|
438
436
|
request['beneficiaries'] = [
|
|
439
437
|
dict(
|
|
@@ -586,49 +584,42 @@ class TestFloatModel(BaseModel):
|
|
|
586
584
|
value: StrictPositiveFloat
|
|
587
585
|
|
|
588
586
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
model
|
|
593
|
-
assert model.value == 0.000001
|
|
587
|
+
@pytest.mark.parametrize("value", [10.5, 0.000001])
|
|
588
|
+
def test_strict_positive_float_valid(value):
|
|
589
|
+
model = TestFloatModel(value=value)
|
|
590
|
+
assert model.value == value
|
|
594
591
|
|
|
595
592
|
|
|
596
|
-
|
|
593
|
+
@pytest.mark.parametrize("value", [0.0, -1.5])
|
|
594
|
+
def test_strict_positive_float_invalid(value):
|
|
597
595
|
with pytest.raises(ValueError, match="Input should be greater than 0"):
|
|
598
|
-
TestFloatModel(value=
|
|
599
|
-
with pytest.raises(ValueError, match="Input should be greater than 0"):
|
|
600
|
-
TestFloatModel(value=-1.5)
|
|
596
|
+
TestFloatModel(value=value)
|
|
601
597
|
|
|
602
598
|
|
|
603
599
|
class TestIntModel(BaseModel):
|
|
604
600
|
value: StrictPositiveInt
|
|
605
601
|
|
|
606
602
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
model = TestIntModel(value=1)
|
|
612
|
-
assert model.value == 1
|
|
613
|
-
|
|
614
|
-
model = TestIntModel(value=21_474_836_47)
|
|
615
|
-
assert model.value == 21_474_836_47
|
|
603
|
+
@pytest.mark.parametrize("value", [100, 1, 21_474_836_47])
|
|
604
|
+
def test_strict_positive_int_valid(value):
|
|
605
|
+
model = TestIntModel(value=value)
|
|
606
|
+
assert model.value == value
|
|
616
607
|
|
|
617
608
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
with pytest.raises(
|
|
634
|
-
TestIntModel(value=
|
|
609
|
+
@pytest.mark.parametrize(
|
|
610
|
+
"value, expected_error, expected_message",
|
|
611
|
+
[
|
|
612
|
+
(0, ValueError, "Input should be greater than 0"),
|
|
613
|
+
(-5, ValueError, "Input should be greater than 0"),
|
|
614
|
+
(
|
|
615
|
+
21_474_836_48,
|
|
616
|
+
ValueError,
|
|
617
|
+
"Input should be less than or equal to 2147483647",
|
|
618
|
+
),
|
|
619
|
+
(5.5, ValueError, "Input should be a valid integer"),
|
|
620
|
+
("10", ValueError, "Input should be a valid integer"),
|
|
621
|
+
],
|
|
622
|
+
)
|
|
623
|
+
def test_strict_positive_int_invalid(value, expected_error, expected_message):
|
|
624
|
+
with pytest.raises(expected_error, match=expected_message):
|
|
625
|
+
TestIntModel(value=value)
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from pydantic import BaseModel, field_validator
|
|
2
|
-
from pydantic_core import PydanticCustomError
|
|
3
|
-
from pydantic_extra_types.payment import PaymentCardBrand, PaymentCardNumber
|
|
4
|
-
|
|
5
|
-
from ..card_bins import CARD_BINS
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class StrictPaymentCardNumber(BaseModel):
|
|
9
|
-
|
|
10
|
-
card_number: PaymentCardNumber
|
|
11
|
-
|
|
12
|
-
@field_validator('card_number')
|
|
13
|
-
def validate_bin(cls, card_number: PaymentCardNumber) -> PaymentCardNumber:
|
|
14
|
-
if card_number.bin not in CARD_BINS:
|
|
15
|
-
raise PydanticCustomError(
|
|
16
|
-
'payment_card_number.bin',
|
|
17
|
-
'The card number contains a BIN (first six digits) '
|
|
18
|
-
'that does not have a known association with a Mexican bank.'
|
|
19
|
-
'To add the association, please file an issue:'
|
|
20
|
-
'https://github.com/cuenca-mx/cuenca-validations/issues',
|
|
21
|
-
)
|
|
22
|
-
return card_number
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def brand(self) -> PaymentCardBrand:
|
|
26
|
-
return self.card_number.brand
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
def last4(self) -> str:
|
|
30
|
-
return self.card_number.last4
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def masked(self) -> str:
|
|
34
|
-
return self.card_number.masked
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def bin(self) -> str:
|
|
38
|
-
return self.card_number.bin
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def bank_code(self) -> str:
|
|
42
|
-
return CARD_BINS[self.card_number.bin]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.0.0.dev8'
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/__init__.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/card_bins.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/errors.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/py.typed
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev8 → cuenca_validations-2.0.0.dev10}/cuenca_validations/types/enums.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|