cuenca-validations 2.0.0.dev12__tar.gz → 2.0.0.dev14__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.dev12 → cuenca_validations-2.0.0.dev14}/PKG-INFO +2 -1
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/__init__.py +4 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/errors.py +20 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/__init__.py +2 -6
- cuenca_validations-2.0.0.dev14/cuenca_validations/types/card.py +36 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/files.py +2 -3
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/general.py +9 -21
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/identities.py +18 -30
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/queries.py +0 -12
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/requests.py +36 -93
- cuenca_validations-2.0.0.dev14/cuenca_validations/version.py +1 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations.egg-info/PKG-INFO +2 -1
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations.egg-info/requires.txt +1 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/setup.py +1 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/tests/test_card.py +6 -4
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/tests/test_types.py +19 -19
- cuenca_validations-2.0.0.dev12/cuenca_validations/types/card.py +0 -38
- cuenca_validations-2.0.0.dev12/cuenca_validations/version.py +0 -1
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/LICENSE +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/README.md +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/card_bins.py +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/py.typed +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/enums.py +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/morals.py +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/typing.py +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/validators.py +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations.egg-info/SOURCES.txt +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations.egg-info/dependency_links.txt +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations.egg-info/top_level.txt +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/setup.cfg +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/tests/__init__.py +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/tests/test_errors.py +0 -0
- {cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/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.0.
|
|
3
|
+
Version: 2.0.0.dev14
|
|
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
|
{cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/errors.py
RENAMED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
from pydantic_core import PydanticCustomError
|
|
2
|
+
|
|
1
3
|
__all__ = [
|
|
2
4
|
'ApiError',
|
|
3
5
|
'AuthMethodNotAllowedError',
|
|
6
|
+
'CardBinValidationError',
|
|
4
7
|
'CuencaError',
|
|
5
8
|
'ERROR_CODES',
|
|
6
9
|
'InvalidOTPCodeError',
|
|
@@ -13,6 +16,23 @@ __all__ = [
|
|
|
13
16
|
]
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
class CardBinValidationError(PydanticCustomError):
|
|
20
|
+
code = 'payment_card_number.bin'
|
|
21
|
+
msg_template = (
|
|
22
|
+
'The card number contains a BIN (first six digits) that does not have'
|
|
23
|
+
'a known association with a Mexican bank. To add the association,'
|
|
24
|
+
'please file an issue:'
|
|
25
|
+
'https://github.com/cuenca-mx/cuenca-validations/issues'
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def __new__(cls):
|
|
29
|
+
return super().__new__(
|
|
30
|
+
cls,
|
|
31
|
+
error_type=cls.code,
|
|
32
|
+
message_template=cls.msg_template,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
16
36
|
class CuencaError(Exception):
|
|
17
37
|
"""Exceptions related to ApiKeys, Login, Password, etc"""
|
|
18
38
|
|
|
@@ -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,12 +150,10 @@ 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 (
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, StringConstraints
|
|
4
|
+
from pydantic_core import core_schema
|
|
5
|
+
from pydantic_extra_types.payment import PaymentCardNumber
|
|
6
|
+
|
|
7
|
+
from ..card_bins import CARD_BINS
|
|
8
|
+
from ..errors import CardBinValidationError
|
|
9
|
+
|
|
10
|
+
ExpMonth = Annotated[int, Field(strict=True, ge=1, le=12)]
|
|
11
|
+
ExpYear = Annotated[int, Field(strict=True, ge=18, le=99)]
|
|
12
|
+
Cvv2 = Annotated[
|
|
13
|
+
str,
|
|
14
|
+
StringConstraints(
|
|
15
|
+
strip_whitespace=True,
|
|
16
|
+
min_length=3,
|
|
17
|
+
max_length=3,
|
|
18
|
+
pattern=r'\d{3}',
|
|
19
|
+
),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StrictPaymentCardNumber(PaymentCardNumber):
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def validate(
|
|
27
|
+
cls, card_number: str, validation_info: core_schema.ValidationInfo
|
|
28
|
+
) -> 'StrictPaymentCardNumber':
|
|
29
|
+
card = super().validate(card_number, validation_info)
|
|
30
|
+
if card.bin not in CARD_BINS:
|
|
31
|
+
raise CardBinValidationError
|
|
32
|
+
return cls(card)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def bank_code(self) -> str:
|
|
36
|
+
return CARD_BINS[self.bin]
|
{cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/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
|
|
@@ -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
|
|
|
@@ -9,18 +9,21 @@ from pydantic import (
|
|
|
9
9
|
StringConstraints,
|
|
10
10
|
model_validator,
|
|
11
11
|
)
|
|
12
|
-
from
|
|
12
|
+
from pydantic_extra_types.phone_numbers import PhoneNumber
|
|
13
13
|
|
|
14
14
|
from .enums import Country, KYCFileType, State, VerificationStatus
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Password = Annotated[
|
|
17
17
|
str,
|
|
18
|
-
|
|
19
|
-
min_length=
|
|
18
|
+
Field(
|
|
19
|
+
min_length=6,
|
|
20
|
+
max_length=128,
|
|
21
|
+
description=(
|
|
22
|
+
'Any str with at least 6 characters, maximum 128 characters'
|
|
23
|
+
),
|
|
20
24
|
),
|
|
21
25
|
]
|
|
22
26
|
|
|
23
|
-
|
|
24
27
|
CurpField = Annotated[
|
|
25
28
|
str,
|
|
26
29
|
StringConstraints(
|
|
@@ -31,25 +34,13 @@ CurpField = Annotated[
|
|
|
31
34
|
]
|
|
32
35
|
|
|
33
36
|
|
|
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
|
-
}
|
|
37
|
+
Rfc = Annotated[
|
|
38
|
+
str,
|
|
39
|
+
StringConstraints(
|
|
40
|
+
min_length=12,
|
|
41
|
+
max_length=13,
|
|
42
|
+
),
|
|
43
|
+
]
|
|
53
44
|
|
|
54
45
|
|
|
55
46
|
class Address(BaseModel):
|
|
@@ -110,17 +101,14 @@ class Beneficiary(BaseModel):
|
|
|
110
101
|
|
|
111
102
|
class VerificationErrors(BaseModel):
|
|
112
103
|
identifier: str = Field(
|
|
113
|
-
|
|
104
|
+
description='Unique identifier for the step validation'
|
|
114
105
|
)
|
|
115
106
|
error: str = Field(
|
|
116
|
-
...,
|
|
117
107
|
description='Error throwed on validation,'
|
|
118
108
|
' can be StepError or SystemError in case of '
|
|
119
109
|
'KYCProvider intermittence',
|
|
120
110
|
)
|
|
121
|
-
code: str = Field(
|
|
122
|
-
..., description='Specific code of the failure in the step.'
|
|
123
|
-
)
|
|
111
|
+
code: str = Field(description='Specific code of the failure in the step.')
|
|
124
112
|
message: Optional[str] = Field(None, description='Error description')
|
|
125
113
|
model_config = ConfigDict(
|
|
126
114
|
json_schema_extra={
|
|
@@ -136,7 +124,7 @@ class VerificationErrors(BaseModel):
|
|
|
136
124
|
|
|
137
125
|
class KYCFile(BaseModel):
|
|
138
126
|
type: KYCFileType
|
|
139
|
-
uri_front: str = Field(
|
|
127
|
+
uri_front: str = Field(description='API uri to fetch the file')
|
|
140
128
|
uri_back: Optional[str] = Field(
|
|
141
129
|
None, description='API uri to fetch the file'
|
|
142
130
|
)
|
|
@@ -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
|
|
@@ -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
|
+
Cvv2,
|
|
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
|
CurpField,
|
|
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: Cvv2
|
|
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[Cvv2] = None
|
|
184
|
+
cvv2: Optional[Cvv2] = None
|
|
185
|
+
icvv: Optional[Cvv2] = 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,
|
|
@@ -453,7 +403,7 @@ class UserRequest(BaseModel):
|
|
|
453
403
|
None, description='if you want to create with specific `id`'
|
|
454
404
|
)
|
|
455
405
|
curp: CurpField = Field(
|
|
456
|
-
|
|
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'
|
|
@@ -531,7 +481,7 @@ class UserUpdateRequest(BaseModel):
|
|
|
531
481
|
status: Optional[UserStatus] = None
|
|
532
482
|
terms_of_service: Optional[TOSRequest] = None
|
|
533
483
|
platform_terms_of_service: Optional[TOSAgreement] = None
|
|
534
|
-
curp_document_uri: Optional[
|
|
484
|
+
curp_document_uri: Optional[HttpUrl] = None
|
|
535
485
|
|
|
536
486
|
@field_validator('beneficiaries')
|
|
537
487
|
@classmethod
|
|
@@ -544,14 +494,7 @@ class UserUpdateRequest(BaseModel):
|
|
|
544
494
|
|
|
545
495
|
|
|
546
496
|
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
|
-
)
|
|
497
|
+
password: Password
|
|
555
498
|
user_id: Optional[str] = Field(None, description='Deprecated field')
|
|
556
499
|
model_config = ConfigDict(
|
|
557
500
|
json_schema_extra={'example': {'password': 'supersecret'}},
|
|
@@ -561,8 +504,8 @@ class UserLoginRequest(BaseRequest):
|
|
|
561
504
|
class SessionRequest(BaseRequest):
|
|
562
505
|
user_id: str
|
|
563
506
|
type: SessionType
|
|
564
|
-
success_url: Optional[
|
|
565
|
-
failure_url: Optional[
|
|
507
|
+
success_url: Optional[AnyUrl] = None
|
|
508
|
+
failure_url: Optional[AnyUrl] = None
|
|
566
509
|
model_config = ConfigDict(
|
|
567
510
|
json_schema_extra={
|
|
568
511
|
'example': {
|
|
@@ -576,13 +519,13 @@ class SessionRequest(BaseRequest):
|
|
|
576
519
|
|
|
577
520
|
|
|
578
521
|
class EndpointRequest(BaseRequest):
|
|
579
|
-
url:
|
|
522
|
+
url: HttpUrl
|
|
580
523
|
events: Optional[list[WebhookEvent]] = None
|
|
581
524
|
user_id: Optional[str] = None
|
|
582
525
|
|
|
583
526
|
|
|
584
527
|
class EndpointUpdateRequest(BaseRequest):
|
|
585
|
-
url: Optional[
|
|
528
|
+
url: Optional[HttpUrl] = None
|
|
586
529
|
is_enable: Optional[bool] = None
|
|
587
530
|
events: Optional[list[WebhookEvent]] = None
|
|
588
531
|
|
|
@@ -597,7 +540,7 @@ class FileUploadRequest(BaseRequest):
|
|
|
597
540
|
|
|
598
541
|
class FileRequest(BaseModel):
|
|
599
542
|
is_back: Optional[bool] = False
|
|
600
|
-
url:
|
|
543
|
+
url: HttpUrl
|
|
601
544
|
type: KYCFileType
|
|
602
545
|
|
|
603
546
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.0.0.dev14'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: cuenca_validations
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev14
|
|
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
|
|
@@ -2,6 +2,7 @@ import pytest
|
|
|
2
2
|
from pydantic import BaseModel, ValidationError
|
|
3
3
|
from pydantic_extra_types.payment import PaymentCardBrand
|
|
4
4
|
|
|
5
|
+
from cuenca_validations.errors import CardBinValidationError
|
|
5
6
|
from cuenca_validations.types import StrictPaymentCardNumber
|
|
6
7
|
|
|
7
8
|
VALID_BBVA = '4772130000000003'
|
|
@@ -15,10 +16,11 @@ class CardModel(BaseModel):
|
|
|
15
16
|
def test_invalid_bin_strict_payment():
|
|
16
17
|
with pytest.raises(ValidationError) as exc_info:
|
|
17
18
|
CardModel(card_number=INVALID_BIN)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
assert exc_info.value.errors()[0] == dict(
|
|
20
|
+
loc=('card_number',),
|
|
21
|
+
type=CardBinValidationError.code,
|
|
22
|
+
msg=CardBinValidationError.msg_template,
|
|
23
|
+
input=INVALID_BIN,
|
|
22
24
|
)
|
|
23
25
|
|
|
24
26
|
|
|
@@ -10,13 +10,13 @@ from pydantic import BaseModel, ValidationError
|
|
|
10
10
|
from cuenca_validations.types import (
|
|
11
11
|
Address,
|
|
12
12
|
CardQuery,
|
|
13
|
-
Digits,
|
|
14
13
|
JSONEncoder,
|
|
15
14
|
QueryParams,
|
|
16
15
|
Rfc,
|
|
17
16
|
SantizedDict,
|
|
18
17
|
SessionRequest,
|
|
19
18
|
TransactionStatus,
|
|
19
|
+
digits,
|
|
20
20
|
get_state_name,
|
|
21
21
|
)
|
|
22
22
|
from cuenca_validations.types.enums import (
|
|
@@ -146,28 +146,29 @@ def test_invalid_class():
|
|
|
146
146
|
|
|
147
147
|
|
|
148
148
|
class Accounts(BaseModel):
|
|
149
|
-
number:
|
|
149
|
+
number: digits(5, 8) # type: ignore
|
|
150
150
|
|
|
151
151
|
|
|
152
152
|
@pytest.mark.parametrize(
|
|
153
153
|
"input_number, expected",
|
|
154
154
|
[
|
|
155
155
|
('123456', '123456'),
|
|
156
|
-
(123456, '123456'),
|
|
157
156
|
('0012312', '0012312'),
|
|
158
157
|
],
|
|
159
158
|
)
|
|
160
159
|
def test_only_digits(input_number, expected):
|
|
161
160
|
acc = Accounts(number=input_number)
|
|
161
|
+
print(acc.model_dump())
|
|
162
162
|
assert acc.number == expected
|
|
163
163
|
|
|
164
164
|
|
|
165
165
|
@pytest.mark.parametrize(
|
|
166
166
|
'number, error',
|
|
167
167
|
[
|
|
168
|
-
(
|
|
169
|
-
('
|
|
170
|
-
('
|
|
168
|
+
(12345, 'Input should be a valid string'),
|
|
169
|
+
('123', 'String should have at least 5 characters'),
|
|
170
|
+
('1234567890', 'String should have at most 8 characters'),
|
|
171
|
+
('no_123', "String should match pattern '^\\d+$'"),
|
|
171
172
|
],
|
|
172
173
|
)
|
|
173
174
|
def test_invalid_digits(number, error):
|
|
@@ -176,14 +177,6 @@ def test_invalid_digits(number, error):
|
|
|
176
177
|
assert error in str(exception.value)
|
|
177
178
|
|
|
178
179
|
|
|
179
|
-
def test_card_query_exp_cvv_if_number_set():
|
|
180
|
-
values = dict(number='123456', exp_month=1, exp_year=2026)
|
|
181
|
-
card_query = CardQuery(**values)
|
|
182
|
-
assert all(
|
|
183
|
-
getattr(card_query, key) == value for key, value in values.items()
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
180
|
@pytest.mark.parametrize(
|
|
188
181
|
'input_value',
|
|
189
182
|
[
|
|
@@ -428,7 +421,10 @@ def test_user_update_request():
|
|
|
428
421
|
update_req = UserUpdateRequest(**request)
|
|
429
422
|
beneficiaries = [b.model_dump() for b in update_req.beneficiaries]
|
|
430
423
|
assert beneficiaries == request['beneficiaries']
|
|
431
|
-
assert
|
|
424
|
+
assert (
|
|
425
|
+
update_req.curp_document_uri.unicode_string()
|
|
426
|
+
== request['curp_document_uri']
|
|
427
|
+
)
|
|
432
428
|
|
|
433
429
|
request['beneficiaries'] = [
|
|
434
430
|
dict(
|
|
@@ -562,13 +558,17 @@ def test_bank_account_validation_clabe_request():
|
|
|
562
558
|
assert BankAccountValidationRequest(account_number='646180157098510917')
|
|
563
559
|
|
|
564
560
|
|
|
561
|
+
class TestRfc(BaseModel):
|
|
562
|
+
rfc: Rfc
|
|
563
|
+
|
|
564
|
+
|
|
565
565
|
def test_rfc_field():
|
|
566
566
|
with pytest.raises(ValueError):
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
567
|
+
TestRfc(rfc='')
|
|
568
|
+
TestRfc(rfc='invalid')
|
|
569
|
+
TestRfc(rfc='ThisValueIsTooLongForRFC')
|
|
570
570
|
|
|
571
|
-
assert
|
|
571
|
+
assert TestRfc(rfc='TAXM840916123')
|
|
572
572
|
|
|
573
573
|
|
|
574
574
|
def test_user_lists_request():
|
|
@@ -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.dev12'
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/card_bins.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/py.typed
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/enums.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/types/morals.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/typing.py
RENAMED
|
File without changes
|
{cuenca_validations-2.0.0.dev12 → cuenca_validations-2.0.0.dev14}/cuenca_validations/validators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|