cuenca-validations 2.0.0.dev13__tar.gz → 2.0.1.dev1__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.dev13 → cuenca_validations-2.0.1.dev1}/PKG-INFO +2 -1
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/__init__.py +4 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/__init__.py +4 -8
- cuenca_validations-2.0.1.dev1/cuenca_validations/types/card.py +41 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/files.py +2 -3
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/general.py +9 -21
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/identities.py +21 -32
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/morals.py +2 -2
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/queries.py +2 -14
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/requests.py +43 -104
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/validators.py +0 -3
- cuenca_validations-2.0.1.dev1/cuenca_validations/version.py +1 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations.egg-info/PKG-INFO +2 -1
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations.egg-info/requires.txt +1 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/setup.py +1 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/tests/test_card.py +8 -4
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/tests/test_errors.py +0 -5
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/tests/test_types.py +21 -41
- cuenca_validations-2.0.0.dev13/cuenca_validations/types/card.py +0 -38
- cuenca_validations-2.0.0.dev13/cuenca_validations/version.py +0 -1
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/LICENSE +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/README.md +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/card_bins.py +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/errors.py +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/py.typed +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/enums.py +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/typing.py +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations.egg-info/SOURCES.txt +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations.egg-info/dependency_links.txt +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations.egg-info/top_level.txt +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/setup.cfg +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/tests/__init__.py +0 -0
- {cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/tests/test_statement.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: cuenca_validations
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1.dev1
|
|
4
4
|
Summary: Cuenca common validations
|
|
5
5
|
Home-page: https://github.com/cuenca-mx/cuenca-validations
|
|
6
6
|
Author: Cuenca
|
|
@@ -20,6 +20,7 @@ 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
22
|
Requires-Dist: python-dateutil>=2.9.0
|
|
23
|
+
Requires-Dist: phonenumbers>=8.13.0
|
|
23
24
|
Dynamic: author
|
|
24
25
|
Dynamic: author-email
|
|
25
26
|
Dynamic: classifier
|
|
@@ -22,7 +22,7 @@ __all__ = [
|
|
|
22
22
|
'CardTransactionType',
|
|
23
23
|
'CardType',
|
|
24
24
|
'Country',
|
|
25
|
-
'
|
|
25
|
+
'Curp',
|
|
26
26
|
'CurpValidationRequest',
|
|
27
27
|
'CommissionType',
|
|
28
28
|
'DepositNetwork',
|
|
@@ -101,10 +101,8 @@ __all__ = [
|
|
|
101
101
|
'WalletQuery',
|
|
102
102
|
'WalletTransactionQuery',
|
|
103
103
|
'WebhookEvent',
|
|
104
|
-
'
|
|
104
|
+
'digits',
|
|
105
105
|
'get_state_name',
|
|
106
|
-
'HttpUrlString',
|
|
107
|
-
'AnyUrlString',
|
|
108
106
|
]
|
|
109
107
|
|
|
110
108
|
from .card import StrictPaymentCardNumber
|
|
@@ -152,18 +150,16 @@ from .enums import (
|
|
|
152
150
|
)
|
|
153
151
|
from .files import BatchFileMetadata
|
|
154
152
|
from .general import (
|
|
155
|
-
AnyUrlString,
|
|
156
|
-
Digits,
|
|
157
|
-
HttpUrlString,
|
|
158
153
|
JSONEncoder,
|
|
159
154
|
SantizedDict,
|
|
160
155
|
StrictPositiveInt,
|
|
156
|
+
digits,
|
|
161
157
|
get_state_name,
|
|
162
158
|
)
|
|
163
159
|
from .identities import (
|
|
164
160
|
Address,
|
|
165
161
|
Beneficiary,
|
|
166
|
-
|
|
162
|
+
Curp,
|
|
167
163
|
KYCFile,
|
|
168
164
|
PhoneNumber,
|
|
169
165
|
Rfc,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, StringConstraints
|
|
4
|
+
from pydantic_core import PydanticCustomError, core_schema
|
|
5
|
+
from pydantic_extra_types.payment import PaymentCardNumber
|
|
6
|
+
|
|
7
|
+
from ..card_bins import CARD_BINS
|
|
8
|
+
|
|
9
|
+
ExpMonth = Annotated[int, Field(strict=True, ge=1, le=12)]
|
|
10
|
+
ExpYear = Annotated[int, Field(strict=True, ge=1, le=99)]
|
|
11
|
+
Cvv = Annotated[
|
|
12
|
+
str,
|
|
13
|
+
StringConstraints(
|
|
14
|
+
strip_whitespace=True,
|
|
15
|
+
min_length=3,
|
|
16
|
+
max_length=3,
|
|
17
|
+
pattern=r'\d{3}',
|
|
18
|
+
),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StrictPaymentCardNumber(PaymentCardNumber):
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def validate(
|
|
26
|
+
cls, card_number: str, validation_info: core_schema.ValidationInfo
|
|
27
|
+
) -> 'StrictPaymentCardNumber':
|
|
28
|
+
card = super().validate(card_number, validation_info)
|
|
29
|
+
if card.bin not in CARD_BINS:
|
|
30
|
+
raise PydanticCustomError(
|
|
31
|
+
'payment_card_number.bin',
|
|
32
|
+
'The card number contains a BIN (first six digits) that '
|
|
33
|
+
'does not have a known association with a Mexican bank. '
|
|
34
|
+
'To add the association, please file an issue: '
|
|
35
|
+
'https://github.com/cuenca-mx/cuenca-validations/issues',
|
|
36
|
+
)
|
|
37
|
+
return cls(card)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def bank_code(self) -> str:
|
|
41
|
+
return CARD_BINS[self.bin]
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/files.py
RENAMED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel
|
|
3
|
+
from pydantic import BaseModel, HttpUrl
|
|
4
4
|
|
|
5
5
|
from .enums import KYCFileType
|
|
6
|
-
from .general import HttpUrlString
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class BatchFileMetadata(BaseModel):
|
|
10
9
|
id: Optional[str] = None
|
|
11
10
|
is_back: bool
|
|
12
11
|
type: KYCFileType
|
|
13
|
-
url:
|
|
12
|
+
url: HttpUrl
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/general.py
RENAMED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Annotated, Any, Optional
|
|
3
3
|
|
|
4
|
-
from pydantic import
|
|
4
|
+
from pydantic import Field, StringConstraints
|
|
5
5
|
|
|
6
6
|
from ..validators import sanitize_dict, sanitize_item
|
|
7
7
|
from .enums import State
|
|
8
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
|
-
|
|
18
9
|
|
|
19
10
|
class SantizedDict(dict):
|
|
20
11
|
def __init__(self, *args, **kwargs):
|
|
@@ -34,20 +25,17 @@ StrictPositiveInt = Annotated[
|
|
|
34
25
|
]
|
|
35
26
|
|
|
36
27
|
|
|
37
|
-
def
|
|
38
|
-
v_str = str(value)
|
|
39
|
-
if not v_str.isdigit():
|
|
40
|
-
raise ValueError("Value must contain only digits")
|
|
41
|
-
return v_str
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def Digits(
|
|
28
|
+
def digits(
|
|
45
29
|
min_length: Optional[int] = None, max_length: Optional[int] = None
|
|
46
|
-
) -> Any:
|
|
30
|
+
) -> Annotated[Any, StringConstraints]:
|
|
47
31
|
return Annotated[
|
|
48
32
|
str,
|
|
49
|
-
|
|
50
|
-
|
|
33
|
+
StringConstraints(
|
|
34
|
+
strip_whitespace=True,
|
|
35
|
+
min_length=min_length,
|
|
36
|
+
max_length=max_length,
|
|
37
|
+
pattern=r'^\d+$',
|
|
38
|
+
),
|
|
51
39
|
]
|
|
52
40
|
|
|
53
41
|
|
|
@@ -6,22 +6,26 @@ from pydantic import (
|
|
|
6
6
|
ConfigDict,
|
|
7
7
|
Field,
|
|
8
8
|
IPvAnyAddress,
|
|
9
|
+
SecretStr,
|
|
9
10
|
StringConstraints,
|
|
10
11
|
model_validator,
|
|
11
12
|
)
|
|
12
|
-
from
|
|
13
|
+
from pydantic_extra_types.phone_numbers import PhoneNumber
|
|
13
14
|
|
|
14
15
|
from .enums import Country, KYCFileType, State, VerificationStatus
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
min_length=
|
|
17
|
+
Password = Annotated[
|
|
18
|
+
SecretStr,
|
|
19
|
+
Field(
|
|
20
|
+
min_length=6,
|
|
21
|
+
max_length=128,
|
|
22
|
+
description=(
|
|
23
|
+
'Any str with at least 6 characters, maximum 128 characters'
|
|
24
|
+
),
|
|
20
25
|
),
|
|
21
26
|
]
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
CurpField = Annotated[
|
|
28
|
+
Curp = Annotated[
|
|
25
29
|
str,
|
|
26
30
|
StringConstraints(
|
|
27
31
|
min_length=18,
|
|
@@ -31,25 +35,13 @@ CurpField = Annotated[
|
|
|
31
35
|
]
|
|
32
36
|
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
raise ValueError('Not a valid RFC.')
|
|
42
|
-
return cls(rfc)
|
|
43
|
-
|
|
44
|
-
@classmethod
|
|
45
|
-
def __get_pydantic_core_schema__(
|
|
46
|
-
cls, source_type: Any, handler: Any
|
|
47
|
-
) -> dict[str, Any]:
|
|
48
|
-
return {
|
|
49
|
-
'type': 'str',
|
|
50
|
-
'min_length': cls.min_length,
|
|
51
|
-
'max_length': cls.max_length,
|
|
52
|
-
}
|
|
38
|
+
Rfc = Annotated[
|
|
39
|
+
str,
|
|
40
|
+
StringConstraints(
|
|
41
|
+
min_length=12,
|
|
42
|
+
max_length=13,
|
|
43
|
+
),
|
|
44
|
+
]
|
|
53
45
|
|
|
54
46
|
|
|
55
47
|
class Address(BaseModel):
|
|
@@ -110,17 +102,14 @@ class Beneficiary(BaseModel):
|
|
|
110
102
|
|
|
111
103
|
class VerificationErrors(BaseModel):
|
|
112
104
|
identifier: str = Field(
|
|
113
|
-
|
|
105
|
+
description='Unique identifier for the step validation'
|
|
114
106
|
)
|
|
115
107
|
error: str = Field(
|
|
116
|
-
...,
|
|
117
108
|
description='Error throwed on validation,'
|
|
118
109
|
' can be StepError or SystemError in case of '
|
|
119
110
|
'KYCProvider intermittence',
|
|
120
111
|
)
|
|
121
|
-
code: str = Field(
|
|
122
|
-
..., description='Specific code of the failure in the step.'
|
|
123
|
-
)
|
|
112
|
+
code: str = Field(description='Specific code of the failure in the step.')
|
|
124
113
|
message: Optional[str] = Field(None, description='Error description')
|
|
125
114
|
model_config = ConfigDict(
|
|
126
115
|
json_schema_extra={
|
|
@@ -136,7 +125,7 @@ class VerificationErrors(BaseModel):
|
|
|
136
125
|
|
|
137
126
|
class KYCFile(BaseModel):
|
|
138
127
|
type: KYCFileType
|
|
139
|
-
uri_front: str = Field(
|
|
128
|
+
uri_front: str = Field(description='API uri to fetch the file')
|
|
140
129
|
uri_back: Optional[str] = Field(
|
|
141
130
|
None, description='API uri to fetch the file'
|
|
142
131
|
)
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/morals.py
RENAMED
|
@@ -3,7 +3,7 @@ from typing import Optional
|
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, EmailStr
|
|
5
5
|
|
|
6
|
-
from cuenca_validations.types import Address,
|
|
6
|
+
from cuenca_validations.types import Address, Curp, PhoneNumber, Rfc
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class BusinessDetails(BaseModel):
|
|
@@ -52,7 +52,7 @@ class PhysicalPerson(BaseModel):
|
|
|
52
52
|
names: str
|
|
53
53
|
first_surname: str
|
|
54
54
|
second_surname: Optional[str] = None
|
|
55
|
-
curp: Optional[
|
|
55
|
+
curp: Optional[Curp] = None
|
|
56
56
|
rfc: Optional[Rfc] = None
|
|
57
57
|
|
|
58
58
|
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/queries.py
RENAMED
|
@@ -24,7 +24,7 @@ from .enums import (
|
|
|
24
24
|
TransferNetwork,
|
|
25
25
|
UserStatus,
|
|
26
26
|
)
|
|
27
|
-
from .identities import
|
|
27
|
+
from .identities import Curp
|
|
28
28
|
|
|
29
29
|
MAX_PAGE_SIZE = 100
|
|
30
30
|
|
|
@@ -112,23 +112,11 @@ class ApiKeyQuery(QueryParams):
|
|
|
112
112
|
|
|
113
113
|
class CardQuery(QueryParams):
|
|
114
114
|
number: Optional[str] = None
|
|
115
|
-
exp_month: Optional[int] = None
|
|
116
|
-
exp_year: Optional[int] = None
|
|
117
|
-
cvv: Optional[str] = None
|
|
118
|
-
cvv2: Optional[str] = None
|
|
119
|
-
icvv: Optional[str] = None
|
|
120
|
-
pin_block: Optional[str] = None
|
|
121
115
|
issuer: Optional[CardIssuer] = None
|
|
122
116
|
funding_type: Optional[CardFundingType] = None
|
|
123
117
|
status: Optional[CardStatus] = None
|
|
124
118
|
type: Optional[CardType] = None
|
|
125
119
|
|
|
126
|
-
@field_validator('exp_month', 'exp_year', 'cvv2', 'cvv')
|
|
127
|
-
def query_by_exp_cvv_if_number_set(cls, v, values):
|
|
128
|
-
if not values.data.get('number'):
|
|
129
|
-
raise ValueError('Number must be set to query by exp or cvv')
|
|
130
|
-
return v
|
|
131
|
-
|
|
132
120
|
|
|
133
121
|
class StatementQuery(QueryParams):
|
|
134
122
|
year: int
|
|
@@ -170,7 +158,7 @@ class UserQuery(QueryParams):
|
|
|
170
158
|
|
|
171
159
|
|
|
172
160
|
class IdentityQuery(QueryParams):
|
|
173
|
-
curp: Optional[
|
|
161
|
+
curp: Optional[Curp] = None
|
|
174
162
|
rfc: Optional[str] = None
|
|
175
163
|
status: Optional[UserStatus] = None
|
|
176
164
|
|
|
@@ -3,10 +3,12 @@ from typing import Annotated, Optional, Union
|
|
|
3
3
|
|
|
4
4
|
from clabe import Clabe
|
|
5
5
|
from pydantic import (
|
|
6
|
+
AnyUrl,
|
|
6
7
|
BaseModel,
|
|
7
8
|
ConfigDict,
|
|
8
9
|
EmailStr,
|
|
9
10
|
Field,
|
|
11
|
+
HttpUrl,
|
|
10
12
|
StrictStr,
|
|
11
13
|
StringConstraints,
|
|
12
14
|
field_validator,
|
|
@@ -47,13 +49,20 @@ from ..types.enums import (
|
|
|
47
49
|
)
|
|
48
50
|
from ..typing import DictStrAny
|
|
49
51
|
from ..validators import validate_age_requirement
|
|
50
|
-
from .card import
|
|
51
|
-
|
|
52
|
+
from .card import (
|
|
53
|
+
Cvv,
|
|
54
|
+
ExpMonth,
|
|
55
|
+
ExpYear,
|
|
56
|
+
PaymentCardNumber,
|
|
57
|
+
StrictPaymentCardNumber,
|
|
58
|
+
)
|
|
59
|
+
from .general import StrictPositiveInt
|
|
52
60
|
from .identities import (
|
|
53
61
|
Address,
|
|
54
62
|
Beneficiary,
|
|
55
|
-
|
|
63
|
+
Curp,
|
|
56
64
|
KYCFile,
|
|
65
|
+
Password,
|
|
57
66
|
PhoneNumber,
|
|
58
67
|
Rfc,
|
|
59
68
|
TOSAgreement,
|
|
@@ -81,13 +90,13 @@ class BaseRequest(BaseModel):
|
|
|
81
90
|
class BaseTransferRequest(BaseRequest):
|
|
82
91
|
recipient_name: StrictStr
|
|
83
92
|
amount: StrictPositiveInt = Field(
|
|
84
|
-
|
|
93
|
+
description='Always in cents, not in MXN pesos'
|
|
85
94
|
)
|
|
86
95
|
descriptor: StrictStr = Field(
|
|
87
|
-
|
|
96
|
+
description='Short description for the recipient'
|
|
88
97
|
)
|
|
89
98
|
idempotency_key: str = Field(
|
|
90
|
-
|
|
99
|
+
description='Custom Id, must be unique for each transfer'
|
|
91
100
|
)
|
|
92
101
|
user_id: Optional[str] = Field(
|
|
93
102
|
None, description='Source user to take the funds'
|
|
@@ -108,13 +117,13 @@ class BaseTransferRequest(BaseRequest):
|
|
|
108
117
|
|
|
109
118
|
class TransferRequest(BaseTransferRequest):
|
|
110
119
|
account_number: Union[Clabe, PaymentCardNumber] = Field(
|
|
111
|
-
|
|
120
|
+
description='Destination Clabe or Card number'
|
|
112
121
|
)
|
|
113
122
|
|
|
114
123
|
|
|
115
124
|
class StrictTransferRequest(BaseTransferRequest):
|
|
116
125
|
account_number: Union[Clabe, StrictPaymentCardNumber] = Field(
|
|
117
|
-
|
|
126
|
+
description='Destination Clabe or Card number'
|
|
118
127
|
)
|
|
119
128
|
|
|
120
129
|
|
|
@@ -134,17 +143,9 @@ class CardRequest(BaseRequest):
|
|
|
134
143
|
|
|
135
144
|
class CardActivationRequest(BaseModel):
|
|
136
145
|
number: PaymentCardNumber
|
|
137
|
-
exp_month:
|
|
138
|
-
exp_year:
|
|
139
|
-
cvv2:
|
|
140
|
-
str,
|
|
141
|
-
StringConstraints(
|
|
142
|
-
strip_whitespace=True,
|
|
143
|
-
min_length=3,
|
|
144
|
-
max_length=3,
|
|
145
|
-
pattern=r'\d{3}',
|
|
146
|
-
),
|
|
147
|
-
]
|
|
146
|
+
exp_month: ExpMonth
|
|
147
|
+
exp_year: ExpYear
|
|
148
|
+
cvv2: Cvv
|
|
148
149
|
|
|
149
150
|
|
|
150
151
|
class ApiKeyUpdateRequest(BaseRequest):
|
|
@@ -155,14 +156,7 @@ class ApiKeyUpdateRequest(BaseRequest):
|
|
|
155
156
|
|
|
156
157
|
class UserCredentialUpdateRequest(BaseRequest):
|
|
157
158
|
is_active: Optional[bool] = None
|
|
158
|
-
password: Optional[
|
|
159
|
-
None,
|
|
160
|
-
min_length=6,
|
|
161
|
-
max_length=128,
|
|
162
|
-
description=(
|
|
163
|
-
'Any str with at least 6 characters, maximum 128 characters'
|
|
164
|
-
),
|
|
165
|
-
)
|
|
159
|
+
password: Optional[Password] = None
|
|
166
160
|
|
|
167
161
|
def model_dump(self, *args, **kwargs) -> DictStrAny:
|
|
168
162
|
# Password can be None but BaseRequest excludes None
|
|
@@ -178,53 +172,17 @@ class UserCredentialUpdateRequest(BaseRequest):
|
|
|
178
172
|
|
|
179
173
|
|
|
180
174
|
class UserCredentialRequest(BaseRequest):
|
|
181
|
-
password:
|
|
182
|
-
...,
|
|
183
|
-
min_length=6,
|
|
184
|
-
max_length=128,
|
|
185
|
-
description=(
|
|
186
|
-
'Any str with at least 6 characters, maximum 128 characters'
|
|
187
|
-
),
|
|
188
|
-
)
|
|
175
|
+
password: Password
|
|
189
176
|
user_id: Optional[str] = None
|
|
190
177
|
|
|
191
178
|
|
|
192
179
|
class CardValidationRequest(BaseModel):
|
|
193
|
-
number:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
strip_whitespace=True,
|
|
200
|
-
),
|
|
201
|
-
]
|
|
202
|
-
exp_month: Optional[Annotated[int, Field(strict=True, ge=1, le=12)]] = None
|
|
203
|
-
exp_year: Optional[Annotated[int, Field(strict=True, ge=18, le=99)]] = None
|
|
204
|
-
cvv: Optional[
|
|
205
|
-
Annotated[
|
|
206
|
-
str,
|
|
207
|
-
StringConstraints(
|
|
208
|
-
strip_whitespace=True, strict=True, min_length=3, max_length=3
|
|
209
|
-
),
|
|
210
|
-
]
|
|
211
|
-
] = None
|
|
212
|
-
cvv2: Optional[
|
|
213
|
-
Annotated[
|
|
214
|
-
str,
|
|
215
|
-
StringConstraints(
|
|
216
|
-
strip_whitespace=True, strict=True, min_length=3, max_length=3
|
|
217
|
-
),
|
|
218
|
-
]
|
|
219
|
-
] = None
|
|
220
|
-
icvv: Optional[
|
|
221
|
-
Annotated[
|
|
222
|
-
str,
|
|
223
|
-
StringConstraints(
|
|
224
|
-
strip_whitespace=True, strict=True, min_length=3, max_length=3
|
|
225
|
-
),
|
|
226
|
-
]
|
|
227
|
-
] = None
|
|
180
|
+
number: PaymentCardNumber
|
|
181
|
+
exp_month: Optional[ExpMonth] = None
|
|
182
|
+
exp_year: Optional[ExpYear] = None
|
|
183
|
+
cvv: Optional[Cvv] = None
|
|
184
|
+
cvv2: Optional[Cvv] = None
|
|
185
|
+
icvv: Optional[Cvv] = None
|
|
228
186
|
pin_block: Optional[
|
|
229
187
|
Annotated[str, StringConstraints(strip_whitespace=True)]
|
|
230
188
|
] = None
|
|
@@ -232,15 +190,7 @@ class CardValidationRequest(BaseModel):
|
|
|
232
190
|
|
|
233
191
|
|
|
234
192
|
class ARPCRequest(BaseModel):
|
|
235
|
-
number:
|
|
236
|
-
str,
|
|
237
|
-
StringConstraints(
|
|
238
|
-
min_length=16,
|
|
239
|
-
max_length=16,
|
|
240
|
-
pattern=r'\d{16}',
|
|
241
|
-
strip_whitespace=True,
|
|
242
|
-
),
|
|
243
|
-
]
|
|
193
|
+
number: PaymentCardNumber
|
|
244
194
|
arqc: StrictStr
|
|
245
195
|
arpc_method: Annotated[
|
|
246
196
|
str,
|
|
@@ -377,7 +327,7 @@ class CurpValidationRequest(BaseModel):
|
|
|
377
327
|
None, description='In format ISO 3166 Alpha-2'
|
|
378
328
|
)
|
|
379
329
|
gender: Optional[Gender] = None
|
|
380
|
-
manual_curp: Optional[
|
|
330
|
+
manual_curp: Optional[Curp] = Field(
|
|
381
331
|
None,
|
|
382
332
|
description='Force to validate this curp instead of use '
|
|
383
333
|
'the one we calculate',
|
|
@@ -452,8 +402,8 @@ class UserRequest(BaseModel):
|
|
|
452
402
|
id: Optional[str] = Field(
|
|
453
403
|
None, description='if you want to create with specific `id`'
|
|
454
404
|
)
|
|
455
|
-
curp:
|
|
456
|
-
|
|
405
|
+
curp: Curp = Field(
|
|
406
|
+
description='Previously validated in `curp_validations`'
|
|
457
407
|
)
|
|
458
408
|
phone_number: Optional[PhoneNumber] = Field(
|
|
459
409
|
None, description='Only if you validated previously on your side'
|
|
@@ -497,9 +447,7 @@ class UserRequest(BaseModel):
|
|
|
497
447
|
|
|
498
448
|
@field_validator('curp')
|
|
499
449
|
@classmethod
|
|
500
|
-
def validate_birth_date(
|
|
501
|
-
cls, curp: Optional[CurpField]
|
|
502
|
-
) -> Optional[CurpField]:
|
|
450
|
+
def validate_birth_date(cls, curp: Optional[Curp]) -> Optional[Curp]:
|
|
503
451
|
if curp:
|
|
504
452
|
current_date = dt.datetime.utcnow()
|
|
505
453
|
curp_date = curp[4:10]
|
|
@@ -531,7 +479,7 @@ class UserUpdateRequest(BaseModel):
|
|
|
531
479
|
status: Optional[UserStatus] = None
|
|
532
480
|
terms_of_service: Optional[TOSRequest] = None
|
|
533
481
|
platform_terms_of_service: Optional[TOSAgreement] = None
|
|
534
|
-
curp_document_uri: Optional[
|
|
482
|
+
curp_document_uri: Optional[HttpUrl] = None
|
|
535
483
|
|
|
536
484
|
@field_validator('beneficiaries')
|
|
537
485
|
@classmethod
|
|
@@ -544,14 +492,7 @@ class UserUpdateRequest(BaseModel):
|
|
|
544
492
|
|
|
545
493
|
|
|
546
494
|
class UserLoginRequest(BaseRequest):
|
|
547
|
-
password:
|
|
548
|
-
...,
|
|
549
|
-
min_length=6,
|
|
550
|
-
max_length=128,
|
|
551
|
-
description=(
|
|
552
|
-
'Any str with at least 6 characters, maximum 128 characters'
|
|
553
|
-
),
|
|
554
|
-
)
|
|
495
|
+
password: Password
|
|
555
496
|
user_id: Optional[str] = Field(None, description='Deprecated field')
|
|
556
497
|
model_config = ConfigDict(
|
|
557
498
|
json_schema_extra={'example': {'password': 'supersecret'}},
|
|
@@ -561,8 +502,8 @@ class UserLoginRequest(BaseRequest):
|
|
|
561
502
|
class SessionRequest(BaseRequest):
|
|
562
503
|
user_id: str
|
|
563
504
|
type: SessionType
|
|
564
|
-
success_url: Optional[
|
|
565
|
-
failure_url: Optional[
|
|
505
|
+
success_url: Optional[AnyUrl] = None
|
|
506
|
+
failure_url: Optional[AnyUrl] = None
|
|
566
507
|
model_config = ConfigDict(
|
|
567
508
|
json_schema_extra={
|
|
568
509
|
'example': {
|
|
@@ -576,13 +517,13 @@ class SessionRequest(BaseRequest):
|
|
|
576
517
|
|
|
577
518
|
|
|
578
519
|
class EndpointRequest(BaseRequest):
|
|
579
|
-
url:
|
|
520
|
+
url: HttpUrl
|
|
580
521
|
events: Optional[list[WebhookEvent]] = None
|
|
581
522
|
user_id: Optional[str] = None
|
|
582
523
|
|
|
583
524
|
|
|
584
525
|
class EndpointUpdateRequest(BaseRequest):
|
|
585
|
-
url: Optional[
|
|
526
|
+
url: Optional[HttpUrl] = None
|
|
586
527
|
is_enable: Optional[bool] = None
|
|
587
528
|
events: Optional[list[WebhookEvent]] = None
|
|
588
529
|
|
|
@@ -597,7 +538,7 @@ class FileUploadRequest(BaseRequest):
|
|
|
597
538
|
|
|
598
539
|
class FileRequest(BaseModel):
|
|
599
540
|
is_back: Optional[bool] = False
|
|
600
|
-
url:
|
|
541
|
+
url: HttpUrl
|
|
601
542
|
type: KYCFileType
|
|
602
543
|
|
|
603
544
|
|
|
@@ -644,12 +585,12 @@ class VerificationAttemptRequest(BaseModel):
|
|
|
644
585
|
|
|
645
586
|
|
|
646
587
|
class LimitedWalletRequest(BaseRequest):
|
|
647
|
-
allowed_curp:
|
|
588
|
+
allowed_curp: Curp
|
|
648
589
|
allowed_rfc: Optional[Rfc] = None
|
|
649
590
|
|
|
650
591
|
|
|
651
592
|
class KYCVerificationUpdateRequest(BaseRequest):
|
|
652
|
-
curp:
|
|
593
|
+
curp: Curp
|
|
653
594
|
|
|
654
595
|
|
|
655
596
|
class PlatformRequest(BaseModel):
|
|
@@ -682,9 +623,7 @@ class BankAccountValidationRequest(BaseModel):
|
|
|
682
623
|
|
|
683
624
|
|
|
684
625
|
class UserListsRequest(BaseModel):
|
|
685
|
-
curp: Optional[
|
|
686
|
-
None, description='Curp to review on lists'
|
|
687
|
-
)
|
|
626
|
+
curp: Optional[Curp] = Field(None, description='Curp to review on lists')
|
|
688
627
|
account_number: Optional[Union[Clabe, PaymentCardNumber]] = Field(
|
|
689
628
|
None, description='Account to review on lists'
|
|
690
629
|
)
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/validators.py
RENAMED
|
@@ -4,7 +4,6 @@ from enum import Enum
|
|
|
4
4
|
from typing import Any, Callable, Optional, Union
|
|
5
5
|
|
|
6
6
|
from dateutil.relativedelta import relativedelta
|
|
7
|
-
from pydantic import SecretStr
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
def sanitize_dict(d: dict) -> dict:
|
|
@@ -36,8 +35,6 @@ def sanitize_item(
|
|
|
36
35
|
rv = base64.b64encode(item).decode('utf-8')
|
|
37
36
|
elif isinstance(item, Enum):
|
|
38
37
|
rv = item.value
|
|
39
|
-
elif isinstance(item, SecretStr):
|
|
40
|
-
rv = item.get_secret_value()
|
|
41
38
|
elif hasattr(item, 'to_dict'):
|
|
42
39
|
rv = item.to_dict()
|
|
43
40
|
elif default:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.0.1.dev1'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: cuenca_validations
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1.dev1
|
|
4
4
|
Summary: Cuenca common validations
|
|
5
5
|
Home-page: https://github.com/cuenca-mx/cuenca-validations
|
|
6
6
|
Author: Cuenca
|
|
@@ -20,6 +20,7 @@ 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
22
|
Requires-Dist: python-dateutil>=2.9.0
|
|
23
|
+
Requires-Dist: phonenumbers>=8.13.0
|
|
23
24
|
Dynamic: author
|
|
24
25
|
Dynamic: author-email
|
|
25
26
|
Dynamic: classifier
|
|
@@ -15,10 +15,14 @@ class CardModel(BaseModel):
|
|
|
15
15
|
def test_invalid_bin_strict_payment():
|
|
16
16
|
with pytest.raises(ValidationError) as exc_info:
|
|
17
17
|
CardModel(card_number=INVALID_BIN)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
assert exc_info.value.errors()[0] == dict(
|
|
19
|
+
loc=('card_number',),
|
|
20
|
+
type='payment_card_number.bin',
|
|
21
|
+
msg='The card number contains a BIN (first six digits) that does '
|
|
22
|
+
'not have a known association with a Mexican bank. To add the '
|
|
23
|
+
'association, please file an issue: '
|
|
24
|
+
'https://github.com/cuenca-mx/cuenca-validations/issues',
|
|
25
|
+
input=INVALID_BIN,
|
|
22
26
|
)
|
|
23
27
|
|
|
24
28
|
|
|
@@ -3,7 +3,6 @@ import pytest
|
|
|
3
3
|
from cuenca_validations.errors import (
|
|
4
4
|
ApiError,
|
|
5
5
|
AuthMethodNotAllowedError,
|
|
6
|
-
CuencaError,
|
|
7
6
|
InvalidOTPCodeError,
|
|
8
7
|
MissingAuthorizationHeaderError,
|
|
9
8
|
NoPasswordFoundError,
|
|
@@ -14,10 +13,6 @@ from cuenca_validations.errors import (
|
|
|
14
13
|
)
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
def test_cuenca_error_base():
|
|
18
|
-
assert issubclass(CuencaError, Exception)
|
|
19
|
-
|
|
20
|
-
|
|
21
16
|
@pytest.mark.parametrize(
|
|
22
17
|
"error_class, expected_code, expected_status",
|
|
23
18
|
[
|
|
@@ -10,13 +10,12 @@ from pydantic import BaseModel, SecretStr, ValidationError
|
|
|
10
10
|
from cuenca_validations.types import (
|
|
11
11
|
Address,
|
|
12
12
|
CardQuery,
|
|
13
|
-
Digits,
|
|
14
13
|
JSONEncoder,
|
|
15
14
|
QueryParams,
|
|
16
|
-
Rfc,
|
|
17
15
|
SantizedDict,
|
|
18
16
|
SessionRequest,
|
|
19
17
|
TransactionStatus,
|
|
18
|
+
digits,
|
|
20
19
|
get_state_name,
|
|
21
20
|
)
|
|
22
21
|
from cuenca_validations.types.enums import (
|
|
@@ -50,12 +49,12 @@ now = dt.datetime.now()
|
|
|
50
49
|
utcnow = now.astimezone(dt.timezone.utc)
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
class
|
|
52
|
+
class EnumModel(Enum):
|
|
54
53
|
zero = 0
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
@dataclass
|
|
58
|
-
class
|
|
57
|
+
class DictModel:
|
|
59
58
|
uno: str
|
|
60
59
|
|
|
61
60
|
def to_dict(self):
|
|
@@ -89,13 +88,11 @@ def test_sanitized_dict():
|
|
|
89
88
|
time=now,
|
|
90
89
|
hello='there',
|
|
91
90
|
dates=[now],
|
|
92
|
-
secret=SecretStr('secret'),
|
|
93
91
|
) == dict(
|
|
94
92
|
status='succeeded',
|
|
95
93
|
time=utcnow.isoformat(),
|
|
96
94
|
hello='there',
|
|
97
95
|
dates=[utcnow.isoformat()],
|
|
98
|
-
secret='secret',
|
|
99
96
|
)
|
|
100
97
|
|
|
101
98
|
|
|
@@ -118,10 +115,10 @@ def test_count(count, truth):
|
|
|
118
115
|
@pytest.mark.parametrize(
|
|
119
116
|
'value, result',
|
|
120
117
|
[
|
|
121
|
-
(
|
|
118
|
+
(EnumModel.zero, 0),
|
|
122
119
|
(today, today.isoformat()),
|
|
123
120
|
(now, utcnow.isoformat()),
|
|
124
|
-
(
|
|
121
|
+
(DictModel(uno='uno'), dict(uno='uno', dos='dos')),
|
|
125
122
|
(b'test', 'dGVzdA=='), # b64 encode
|
|
126
123
|
],
|
|
127
124
|
)
|
|
@@ -148,14 +145,13 @@ def test_invalid_class():
|
|
|
148
145
|
|
|
149
146
|
|
|
150
147
|
class Accounts(BaseModel):
|
|
151
|
-
number:
|
|
148
|
+
number: digits(5, 8) # type: ignore
|
|
152
149
|
|
|
153
150
|
|
|
154
151
|
@pytest.mark.parametrize(
|
|
155
152
|
"input_number, expected",
|
|
156
153
|
[
|
|
157
154
|
('123456', '123456'),
|
|
158
|
-
(123456, '123456'),
|
|
159
155
|
('0012312', '0012312'),
|
|
160
156
|
],
|
|
161
157
|
)
|
|
@@ -167,9 +163,10 @@ def test_only_digits(input_number, expected):
|
|
|
167
163
|
@pytest.mark.parametrize(
|
|
168
164
|
'number, error',
|
|
169
165
|
[
|
|
170
|
-
(
|
|
171
|
-
('
|
|
172
|
-
('
|
|
166
|
+
(12345, 'Input should be a valid string'),
|
|
167
|
+
('123', 'String should have at least 5 characters'),
|
|
168
|
+
('1234567890', 'String should have at most 8 characters'),
|
|
169
|
+
('no_123', "String should match pattern '^\\d+$'"),
|
|
173
170
|
],
|
|
174
171
|
)
|
|
175
172
|
def test_invalid_digits(number, error):
|
|
@@ -178,14 +175,6 @@ def test_invalid_digits(number, error):
|
|
|
178
175
|
assert error in str(exception.value)
|
|
179
176
|
|
|
180
177
|
|
|
181
|
-
def test_card_query_exp_cvv_if_number_set():
|
|
182
|
-
values = dict(number='123456', exp_month=1, exp_year=2026)
|
|
183
|
-
card_query = CardQuery(**values)
|
|
184
|
-
assert all(
|
|
185
|
-
getattr(card_query, key) == value for key, value in values.items()
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
|
|
189
178
|
@pytest.mark.parametrize(
|
|
190
179
|
'input_value',
|
|
191
180
|
[
|
|
@@ -210,7 +199,7 @@ def test_update_one_property_at_a_time_request():
|
|
|
210
199
|
UserCredentialUpdateRequest(user_id='US123', password='123456')
|
|
211
200
|
|
|
212
201
|
req = UserCredentialUpdateRequest(password='123456')
|
|
213
|
-
assert not req.is_active and req.password == '123456'
|
|
202
|
+
assert not req.is_active and req.password.get_secret_value() == '123456'
|
|
214
203
|
|
|
215
204
|
req = UserCredentialUpdateRequest(is_active=True)
|
|
216
205
|
assert req.is_active and not req.password
|
|
@@ -219,7 +208,10 @@ def test_update_one_property_at_a_time_request():
|
|
|
219
208
|
@pytest.mark.parametrize(
|
|
220
209
|
'data,expected_dict',
|
|
221
210
|
[
|
|
222
|
-
(
|
|
211
|
+
(
|
|
212
|
+
dict(password='123456'),
|
|
213
|
+
dict(password=SecretStr('123456'), is_active=None),
|
|
214
|
+
),
|
|
223
215
|
(dict(is_active=True), dict(password=None, is_active=True)),
|
|
224
216
|
(dict(), dict(password=None, is_active=None)),
|
|
225
217
|
],
|
|
@@ -430,7 +422,10 @@ def test_user_update_request():
|
|
|
430
422
|
update_req = UserUpdateRequest(**request)
|
|
431
423
|
beneficiaries = [b.model_dump() for b in update_req.beneficiaries]
|
|
432
424
|
assert beneficiaries == request['beneficiaries']
|
|
433
|
-
assert
|
|
425
|
+
assert (
|
|
426
|
+
update_req.curp_document_uri.unicode_string()
|
|
427
|
+
== request['curp_document_uri']
|
|
428
|
+
)
|
|
434
429
|
|
|
435
430
|
request['beneficiaries'] = [
|
|
436
431
|
dict(
|
|
@@ -564,31 +559,16 @@ def test_bank_account_validation_clabe_request():
|
|
|
564
559
|
assert BankAccountValidationRequest(account_number='646180157098510917')
|
|
565
560
|
|
|
566
561
|
|
|
567
|
-
def test_rfc_field():
|
|
568
|
-
with pytest.raises(ValueError):
|
|
569
|
-
Rfc.validate('')
|
|
570
|
-
Rfc.validate('invalid')
|
|
571
|
-
Rfc.validate('ThisValueIsTooLongForRFC')
|
|
572
|
-
|
|
573
|
-
assert Rfc.validate('TAXM840916123')
|
|
574
|
-
|
|
575
|
-
|
|
576
562
|
def test_user_lists_request():
|
|
577
563
|
UserListsRequest(names='Pedro', first_surname='Paramo')
|
|
578
564
|
with pytest.raises(ValueError):
|
|
579
565
|
UserListsRequest()
|
|
580
566
|
|
|
581
567
|
|
|
582
|
-
class
|
|
568
|
+
class IntModel(BaseModel):
|
|
583
569
|
value: StrictPositiveInt
|
|
584
570
|
|
|
585
571
|
|
|
586
|
-
@pytest.mark.parametrize("value", [100, 1, 21_474_836_47])
|
|
587
|
-
def test_strict_positive_int_valid(value):
|
|
588
|
-
model = TestIntModel(value=value)
|
|
589
|
-
assert model.value == value
|
|
590
|
-
|
|
591
|
-
|
|
592
572
|
@pytest.mark.parametrize(
|
|
593
573
|
"value, expected_error, expected_message",
|
|
594
574
|
[
|
|
@@ -605,4 +585,4 @@ def test_strict_positive_int_valid(value):
|
|
|
605
585
|
)
|
|
606
586
|
def test_strict_positive_int_invalid(value, expected_error, expected_message):
|
|
607
587
|
with pytest.raises(expected_error, match=expected_message):
|
|
608
|
-
|
|
588
|
+
IntModel(value=value)
|
|
@@ -1,38 +0,0 @@
|
|
|
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]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.0.0.dev13'
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/card_bins.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/errors.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/py.typed
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/types/enums.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev13 → cuenca_validations-2.0.1.dev1}/cuenca_validations/typing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|