cuenca-validations 2.0.0.dev9__tar.gz → 2.0.0.dev11__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.dev9 → cuenca_validations-2.0.0.dev11}/PKG-INFO +2 -2
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/types/__init__.py +6 -3
- cuenca_validations-2.0.0.dev11/cuenca_validations/types/card.py +38 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/types/files.py +3 -2
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/types/general.py +21 -8
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/types/identities.py +19 -36
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/types/morals.py +3 -3
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/types/queries.py +4 -7
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/types/requests.py +37 -52
- cuenca_validations-2.0.0.dev11/cuenca_validations/typing.py +5 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/validators.py +2 -7
- cuenca_validations-2.0.0.dev11/cuenca_validations/version.py +1 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations.egg-info/PKG-INFO +2 -2
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations.egg-info/requires.txt +1 -1
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/setup.py +1 -1
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/tests/test_card.py +12 -8
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/tests/test_errors.py +10 -7
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/tests/test_types.py +54 -62
- cuenca_validations-2.0.0.dev9/cuenca_validations/types/card.py +0 -42
- cuenca_validations-2.0.0.dev9/cuenca_validations/typing.py +0 -5
- cuenca_validations-2.0.0.dev9/cuenca_validations/version.py +0 -1
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/LICENSE +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/README.md +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/__init__.py +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/card_bins.py +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/errors.py +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/py.typed +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/types/enums.py +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations.egg-info/SOURCES.txt +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations.egg-info/dependency_links.txt +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations.egg-info/top_level.txt +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/setup.cfg +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/tests/__init__.py +0 -0
- {cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/tests/test_statement.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cuenca_validations
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev11
|
|
4
4
|
Summary: Cuenca common validations
|
|
5
5
|
Home-page: https://github.com/cuenca-mx/cuenca-validations
|
|
6
6
|
Author: Cuenca
|
|
@@ -19,7 +19,7 @@ License-File: LICENSE
|
|
|
19
19
|
Requires-Dist: clabe>=2.0.0
|
|
20
20
|
Requires-Dist: pydantic[email]>=2.10.0
|
|
21
21
|
Requires-Dist: pydantic-extra-types>=2.10.0
|
|
22
|
-
Requires-Dist: python-dateutil>=2.
|
|
22
|
+
Requires-Dist: python-dateutil>=2.9.0
|
|
23
23
|
|
|
24
24
|
# Cuenca - validations
|
|
25
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.dev9 → cuenca_validations-2.0.0.dev11}/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.dev9 → cuenca_validations-2.0.0.dev11}/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.dev9 → cuenca_validations-2.0.0.dev11}/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.dev9 → cuenca_validations-2.0.0.dev11}/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
|
|
@@ -32,9 +31,7 @@ MAX_PAGE_SIZE = 100
|
|
|
32
31
|
|
|
33
32
|
class QueryParams(BaseModel):
|
|
34
33
|
count: bool = False
|
|
35
|
-
page_size: Annotated[
|
|
36
|
-
int, Field(gt=0, le=MAX_PAGE_SIZE, default=MAX_PAGE_SIZE)
|
|
37
|
-
]
|
|
34
|
+
page_size: Optional[Annotated[int, Field(gt=0, le=MAX_PAGE_SIZE)]] = None
|
|
38
35
|
limit: Optional[PositiveInt] = None
|
|
39
36
|
user_id: Optional[str] = None
|
|
40
37
|
created_before: Optional[dt.datetime] = None
|
|
@@ -61,10 +58,10 @@ class QueryParams(BaseModel):
|
|
|
61
58
|
},
|
|
62
59
|
)
|
|
63
60
|
|
|
64
|
-
def
|
|
61
|
+
def model_dump(self, *args, **kwargs) -> DictStrAny:
|
|
65
62
|
kwargs.setdefault('exclude_none', True)
|
|
66
63
|
kwargs.setdefault('exclude_unset', True)
|
|
67
|
-
d = super().
|
|
64
|
+
d = super().model_dump(*args, **kwargs)
|
|
68
65
|
if self.count:
|
|
69
66
|
d['count'] = 1
|
|
70
67
|
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
|
|
@@ -356,10 +348,10 @@ class FraudValidationRequest(BaseModel):
|
|
|
356
348
|
None
|
|
357
349
|
)
|
|
358
350
|
ecommerce_indicator: Optional[EcommerceIndicator] = None
|
|
359
|
-
card_id: Optional[str] = None
|
|
360
|
-
user_id: Optional[str] = None
|
|
361
|
-
card_type: Optional[CardType] = None
|
|
362
|
-
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
|
|
363
355
|
|
|
364
356
|
|
|
365
357
|
class TransactionTokenValidationUpdateRequest(BaseRequest):
|
|
@@ -532,19 +524,19 @@ class UserUpdateRequest(BaseModel):
|
|
|
532
524
|
email_verification_id: Optional[str] = None
|
|
533
525
|
phone_verification_id: Optional[str] = None
|
|
534
526
|
address: Optional[Address] = None
|
|
535
|
-
beneficiaries: Optional[
|
|
527
|
+
beneficiaries: Optional[list[Beneficiary]] = None
|
|
536
528
|
govt_id: Optional[KYCFile] = None
|
|
537
529
|
proof_of_address: Optional[KYCFile] = None
|
|
538
530
|
proof_of_life: Optional[KYCFile] = None
|
|
539
531
|
status: Optional[UserStatus] = None
|
|
540
532
|
terms_of_service: Optional[TOSRequest] = None
|
|
541
533
|
platform_terms_of_service: Optional[TOSAgreement] = None
|
|
542
|
-
curp_document_uri: Optional[
|
|
534
|
+
curp_document_uri: Optional[HttpUrlString] = None
|
|
543
535
|
|
|
544
536
|
@field_validator('beneficiaries')
|
|
545
537
|
@classmethod
|
|
546
538
|
def beneficiary_percentage(
|
|
547
|
-
cls, beneficiaries: Optional[
|
|
539
|
+
cls, beneficiaries: Optional[list[Beneficiary]] = None
|
|
548
540
|
):
|
|
549
541
|
if beneficiaries and sum(b.percentage for b in beneficiaries) > 100:
|
|
550
542
|
raise ValueError('The total percentage is more than 100.')
|
|
@@ -569,8 +561,8 @@ class UserLoginRequest(BaseRequest):
|
|
|
569
561
|
class SessionRequest(BaseRequest):
|
|
570
562
|
user_id: str
|
|
571
563
|
type: SessionType
|
|
572
|
-
success_url: Optional[
|
|
573
|
-
failure_url: Optional[
|
|
564
|
+
success_url: Optional[AnyUrlString] = None
|
|
565
|
+
failure_url: Optional[AnyUrlString] = None
|
|
574
566
|
model_config = ConfigDict(
|
|
575
567
|
json_schema_extra={
|
|
576
568
|
'example': {
|
|
@@ -584,15 +576,15 @@ class SessionRequest(BaseRequest):
|
|
|
584
576
|
|
|
585
577
|
|
|
586
578
|
class EndpointRequest(BaseRequest):
|
|
587
|
-
url:
|
|
588
|
-
events: Optional[
|
|
579
|
+
url: HttpUrlString
|
|
580
|
+
events: Optional[list[WebhookEvent]] = None
|
|
589
581
|
user_id: Optional[str] = None
|
|
590
582
|
|
|
591
583
|
|
|
592
584
|
class EndpointUpdateRequest(BaseRequest):
|
|
593
|
-
url: Optional[
|
|
585
|
+
url: Optional[HttpUrlString] = None
|
|
594
586
|
is_enable: Optional[bool] = None
|
|
595
|
-
events: Optional[
|
|
587
|
+
events: Optional[list[WebhookEvent]] = None
|
|
596
588
|
|
|
597
589
|
|
|
598
590
|
class FileUploadRequest(BaseRequest):
|
|
@@ -605,12 +597,12 @@ class FileUploadRequest(BaseRequest):
|
|
|
605
597
|
|
|
606
598
|
class FileRequest(BaseModel):
|
|
607
599
|
is_back: Optional[bool] = False
|
|
608
|
-
url:
|
|
600
|
+
url: HttpUrlString
|
|
609
601
|
type: KYCFileType
|
|
610
602
|
|
|
611
603
|
|
|
612
604
|
class FileBatchUploadRequest(BaseModel):
|
|
613
|
-
files:
|
|
605
|
+
files: list[FileRequest]
|
|
614
606
|
user_id: str
|
|
615
607
|
|
|
616
608
|
|
|
@@ -652,8 +644,6 @@ class VerificationAttemptRequest(BaseModel):
|
|
|
652
644
|
|
|
653
645
|
|
|
654
646
|
class LimitedWalletRequest(BaseRequest):
|
|
655
|
-
model_config = dict(arbitrary_types_allowed=True)
|
|
656
|
-
|
|
657
647
|
allowed_curp: CurpField
|
|
658
648
|
allowed_rfc: Optional[Rfc] = None
|
|
659
649
|
|
|
@@ -663,7 +653,6 @@ class KYCVerificationUpdateRequest(BaseRequest):
|
|
|
663
653
|
|
|
664
654
|
|
|
665
655
|
class PlatformRequest(BaseModel):
|
|
666
|
-
model_config = dict(arbitrary_types_allowed=True)
|
|
667
656
|
name: str
|
|
668
657
|
rfc: Optional[str] = None
|
|
669
658
|
establishment_date: Optional[dt.date] = None
|
|
@@ -685,7 +674,7 @@ class WebhookRequest(BaseModel):
|
|
|
685
674
|
class KYCValidationRequest(BaseRequest):
|
|
686
675
|
user_id: str
|
|
687
676
|
force: bool = False
|
|
688
|
-
documents:
|
|
677
|
+
documents: list[KYCFile] = []
|
|
689
678
|
|
|
690
679
|
|
|
691
680
|
class BankAccountValidationRequest(BaseModel):
|
|
@@ -739,8 +728,6 @@ class QuestionnairesRequest(BaseModel):
|
|
|
739
728
|
|
|
740
729
|
|
|
741
730
|
class PartnerRequest(BaseRequest):
|
|
742
|
-
model_config = dict(arbitrary_types_allowed=True)
|
|
743
|
-
|
|
744
731
|
legal_name: str
|
|
745
732
|
business_name: str
|
|
746
733
|
nationality: Country
|
|
@@ -755,8 +742,6 @@ class PartnerRequest(BaseRequest):
|
|
|
755
742
|
|
|
756
743
|
|
|
757
744
|
class PartnerUpdateRequest(BaseRequest):
|
|
758
|
-
model_config = dict(arbitrary_types_allowed=True)
|
|
759
|
-
|
|
760
745
|
legal_name: Optional[str] = None
|
|
761
746
|
business_name: Optional[str] = None
|
|
762
747
|
nationality: Optional[Country] = None
|
|
@@ -774,5 +759,5 @@ class PartnerUpdateRequest(BaseRequest):
|
|
|
774
759
|
license: Optional[LicenseDetails] = None
|
|
775
760
|
audit: Optional[AuditDetails] = None
|
|
776
761
|
vulnerable_activity: Optional[VulnerableActivityDetails] = None
|
|
777
|
-
legal_representatives: Optional[
|
|
778
|
-
shareholders: Optional[
|
|
762
|
+
legal_representatives: Optional[list[LegalRepresentative]] = None
|
|
763
|
+
shareholders: Optional[list[Shareholder]] = None
|
{cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/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.dev11'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cuenca_validations
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev11
|
|
4
4
|
Summary: Cuenca common validations
|
|
5
5
|
Home-page: https://github.com/cuenca-mx/cuenca-validations
|
|
6
6
|
Author: Cuenca
|
|
@@ -19,7 +19,7 @@ License-File: LICENSE
|
|
|
19
19
|
Requires-Dist: clabe>=2.0.0
|
|
20
20
|
Requires-Dist: pydantic[email]>=2.10.0
|
|
21
21
|
Requires-Dist: pydantic-extra-types>=2.10.0
|
|
22
|
-
Requires-Dist: python-dateutil>=2.
|
|
22
|
+
Requires-Dist: python-dateutil>=2.9.0
|
|
23
23
|
|
|
24
24
|
# Cuenca - validations
|
|
25
25
|
|
|
@@ -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
|
-
model = QueryParams(count=1, created_before=now)
|
|
82
|
-
assert model.
|
|
83
|
+
model = QueryParams(count=1, created_before=now, page_size=100)
|
|
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
|
|
@@ -158,12 +149,20 @@ def test_invalid_class():
|
|
|
158
149
|
|
|
159
150
|
|
|
160
151
|
class Accounts(BaseModel):
|
|
161
|
-
number:
|
|
152
|
+
number: Digits(5, 8) # type: ignore
|
|
162
153
|
|
|
163
154
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
167
166
|
|
|
168
167
|
|
|
169
168
|
@pytest.mark.parametrize(
|
|
@@ -204,7 +203,7 @@ def test_card_query_exp_cvv_if_number_not_set(input_value):
|
|
|
204
203
|
|
|
205
204
|
def test_exclude_none_in_dict():
|
|
206
205
|
request = ApiKeyUpdateRequest(user_id='US123')
|
|
207
|
-
assert request.
|
|
206
|
+
assert request.model_dump() == dict(user_id='US123')
|
|
208
207
|
|
|
209
208
|
|
|
210
209
|
def test_update_one_property_at_a_time_request():
|
|
@@ -228,7 +227,7 @@ def test_update_one_property_at_a_time_request():
|
|
|
228
227
|
)
|
|
229
228
|
def test_update_credential_update_request_dict(data, expected_dict):
|
|
230
229
|
req = UserCredentialUpdateRequest(**data)
|
|
231
|
-
assert req.
|
|
230
|
+
assert req.model_dump() == expected_dict
|
|
232
231
|
|
|
233
232
|
|
|
234
233
|
def test_card_transaction_requests():
|
|
@@ -355,7 +354,7 @@ def test_user_request():
|
|
|
355
354
|
required_level=3,
|
|
356
355
|
terms_of_service=None,
|
|
357
356
|
)
|
|
358
|
-
assert UserRequest(**request).
|
|
357
|
+
assert UserRequest(**request).model_dump() == request
|
|
359
358
|
|
|
360
359
|
# changing curp so user is underage
|
|
361
360
|
request['curp'] = 'ABCD060604HDFSRN03'
|
|
@@ -392,7 +391,7 @@ def test_curp_validation_request():
|
|
|
392
391
|
assert all(field in error_msg for field in required_fields)
|
|
393
392
|
|
|
394
393
|
req_curp = CurpValidationRequest(**request)
|
|
395
|
-
assert req_curp.
|
|
394
|
+
assert req_curp.model_dump() == request
|
|
396
395
|
|
|
397
396
|
request['date_of_birth'] = dt.date(2006, 5, 17)
|
|
398
397
|
|
|
@@ -430,9 +429,9 @@ def test_user_update_request():
|
|
|
430
429
|
curp_document_uri='https://sandbox.cuenca.com/files/EF123',
|
|
431
430
|
)
|
|
432
431
|
update_req = UserUpdateRequest(**request)
|
|
433
|
-
beneficiaries = [b.
|
|
432
|
+
beneficiaries = [b.model_dump() for b in update_req.beneficiaries]
|
|
434
433
|
assert beneficiaries == request['beneficiaries']
|
|
435
|
-
assert
|
|
434
|
+
assert update_req.curp_document_uri == request['curp_document_uri']
|
|
436
435
|
|
|
437
436
|
request['beneficiaries'] = [
|
|
438
437
|
dict(
|
|
@@ -585,49 +584,42 @@ class TestFloatModel(BaseModel):
|
|
|
585
584
|
value: StrictPositiveFloat
|
|
586
585
|
|
|
587
586
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
model
|
|
592
|
-
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
|
|
593
591
|
|
|
594
592
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
TestFloatModel(value=0.0)
|
|
593
|
+
@pytest.mark.parametrize("value", [0.0, -1.5])
|
|
594
|
+
def test_strict_positive_float_invalid(value):
|
|
598
595
|
with pytest.raises(ValueError, match="Input should be greater than 0"):
|
|
599
|
-
TestFloatModel(value
|
|
596
|
+
TestFloatModel(value=value)
|
|
600
597
|
|
|
601
598
|
|
|
602
599
|
class TestIntModel(BaseModel):
|
|
603
600
|
value: StrictPositiveInt
|
|
604
601
|
|
|
605
602
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
model = TestIntModel(value=1)
|
|
611
|
-
assert model.value == 1
|
|
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
|
|
612
607
|
|
|
613
|
-
model = TestIntModel(value=21_474_836_47)
|
|
614
|
-
assert model.value == 21_474_836_47
|
|
615
608
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
TestIntModel(value="10")
|
|
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.dev9'
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/__init__.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/card_bins.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/errors.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/cuenca_validations/py.typed
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev9 → cuenca_validations-2.0.0.dev11}/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
|