cuenca-validations 2.1.6.dev2__py3-none-any.whl → 2.1.7__py3-none-any.whl

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.
@@ -55,6 +55,7 @@ __all__ = [
55
55
  'PlatformRequest',
56
56
  'PlatformType',
57
57
  'PosCapability',
58
+ 'Profession',
58
59
  'QueryParams',
59
60
  'Rfc',
60
61
  'QuestionnairesRequest',
@@ -134,6 +135,7 @@ from .enums import (
134
135
  Language,
135
136
  PlatformType,
136
137
  PosCapability,
138
+ Profession,
137
139
  SATRegimeCode,
138
140
  SavingCategory,
139
141
  ServiceProviderCategory,
@@ -688,3 +688,19 @@ class SATRegimeCode(str, Enum):
688
688
  ING_PREM = "615" # Régimen de los ingresos por obtención de premios
689
689
  AE_PLAT_TEC = "625" # Régimen de las Actividades Empresariales con ingresos a través de Plataformas Tecnológicas # noqa: E501
690
690
  RS_CONF = "626" # Régimen Simplificado de Confianza
691
+
692
+
693
+ class Profession(str, Enum):
694
+ artisticas = 'Actividades Artísticas'
695
+ agropecuario = 'Agricultura, Ganadería o Pesca'
696
+ comercio = 'Comercio'
697
+ estudiante = 'Estudiante'
698
+ empleado = 'Empleado(a/e)'
699
+ emprendimiento = 'Emprendimiento'
700
+ hogar = 'Hogar'
701
+ profesor = 'Profesor(a/e)'
702
+ profesionista = 'Profesionista'
703
+ servidor_publico = 'Servidor(a/e) Público'
704
+ sistemas = 'Sistemas y Comunicaciones'
705
+ independiente = 'Trabajador(a/e) Independiente'
706
+ oficios = 'Oficios Varios'
@@ -1,18 +1,11 @@
1
1
  import datetime as dt
2
- from typing import Annotated, Any, Optional
2
+ from typing import Annotated, Optional
3
3
 
4
- from pydantic import (
5
- BaseModel,
6
- ConfigDict,
7
- Field,
8
- SecretStr,
9
- StringConstraints,
10
- model_validator,
11
- )
4
+ from pydantic import BaseModel, ConfigDict, Field, SecretStr, StringConstraints
12
5
  from pydantic_extra_types.phone_numbers import PhoneNumber
13
6
 
14
7
  from .enums import Country, KYCFileType, State, VerificationStatus
15
- from .general import SerializableIPvAnyAddress
8
+ from .general import NonEmptyStr, SerializableIPvAnyAddress
16
9
 
17
10
  Password = Annotated[
18
11
  SecretStr,
@@ -43,6 +36,13 @@ Rfc = Annotated[
43
36
  ),
44
37
  ]
45
38
 
39
+ # NOTE:
40
+ # The Address model is kept for compatibility with legacy models and data
41
+ # that expect all address fields to be optional. This allows older systems
42
+ # or stored data using Address to continue working without breaking changes.
43
+ # For new request validation, use AddressRequest, which enforces required
44
+ # fields and is intended for validating incoming data.
45
+
46
46
 
47
47
  class Address(BaseModel):
48
48
  street: Optional[str] = None
@@ -69,16 +69,32 @@ class Address(BaseModel):
69
69
  }
70
70
  )
71
71
 
72
- @model_validator(mode='before')
73
- @classmethod
74
- def full_name_complete(cls, values: dict[str, Any]) -> dict[str, Any]:
75
- if values.get('full_name'):
76
- return values
77
- if not values.get('street'):
78
- raise ValueError('required street')
79
- if not values.get('ext_number'):
80
- raise ValueError('required ext_number')
81
- return values
72
+
73
+ class AddressRequest(BaseModel):
74
+ # This model is mainly for request validation, enforcing required fields.
75
+ street: NonEmptyStr
76
+ ext_number: NonEmptyStr
77
+ int_number: Optional[NonEmptyStr] = None
78
+ colonia: NonEmptyStr
79
+ postal_code: NonEmptyStr
80
+ state: Optional[State] = None
81
+ country: Country
82
+ city: NonEmptyStr
83
+
84
+ model_config = ConfigDict(
85
+ json_schema_extra={
86
+ "example": {
87
+ "street": "Reforma",
88
+ "ext_number": "265",
89
+ "int_number": "5",
90
+ "colonia": "Cuauhtémoc",
91
+ "postal_code": "06500",
92
+ "state": "DF",
93
+ "country": "MX",
94
+ "city": "Cuauhtémoc",
95
+ }
96
+ }
97
+ )
82
98
 
83
99
 
84
100
  class Beneficiary(BaseModel):
@@ -86,7 +102,7 @@ class Beneficiary(BaseModel):
86
102
  birth_date: dt.date
87
103
  phone_number: PhoneNumber
88
104
  user_relationship: str
89
- percentage: int
105
+ percentage: Annotated[int, Field(ge=1, le=100)]
90
106
  model_config = ConfigDict(
91
107
  json_schema_extra={
92
108
  "example": {
@@ -32,6 +32,7 @@ from ..types.enums import (
32
32
  KYCValidationSource,
33
33
  PlatformType,
34
34
  PosCapability,
35
+ Profession,
35
36
  SavingCategory,
36
37
  SessionType,
37
38
  State,
@@ -39,7 +40,6 @@ from ..types.enums import (
39
40
  TrackDataMethod,
40
41
  TransactionTokenValidationStatus,
41
42
  UserCardNotification,
42
- UserStatus,
43
43
  VerificationType,
44
44
  WalletTransactionType,
45
45
  WebhookEvent,
@@ -64,14 +64,13 @@ from .general import (
64
64
  StrictPositiveInt,
65
65
  )
66
66
  from .identities import (
67
- Address,
67
+ AddressRequest,
68
68
  Beneficiary,
69
69
  Curp,
70
70
  KYCFile,
71
71
  Password,
72
72
  PhoneNumber,
73
73
  Rfc,
74
- TOSAgreement,
75
74
  )
76
75
  from .morals import (
77
76
  AuditDetails,
@@ -410,43 +409,25 @@ class UserTOSAgreementRequest(BaseModel):
410
409
 
411
410
 
412
411
  class UserRequest(BaseModel):
413
- id: Optional[str] = Field(
414
- None, description='if you want to create with specific `id`'
415
- )
416
412
  curp: Curp = Field(
417
- description='Previously validated in `curp_validations`'
418
- )
419
- phone_number: Optional[PhoneNumber] = Field(
420
- None, description='Only if you validated previously on your side'
421
- )
422
- email_address: Optional[EmailStr] = Field(
423
- None, description='Only if you validated previously on your side'
424
- )
425
- profession: Optional[str] = None
426
- address: Optional[Address] = None
427
- status: Optional[UserStatus] = Field(
428
- None,
429
- description='Status that the user will have when created. '
430
- 'Defined by platform',
413
+ description=(
414
+ 'Mexican government ID (18 characters). ' 'Must be pre-validated.'
415
+ )
431
416
  )
432
- required_level: Optional[int] = Field(
433
- None,
434
- ge=1,
435
- le=3,
436
- description='Maximum level a User can reach. Defined by platform',
417
+
418
+ profession: Profession = Field(description='User profession or occupation')
419
+ address: AddressRequest = Field(
420
+ description='User residential address information'
437
421
  )
438
- phone_verification_id: Optional[str] = Field(
439
- None,
440
- description='Only if you validated it previously with the '
441
- 'resource `verifications`',
422
+ phone_verification_id: str = Field(
423
+ ...,
424
+ description='ID of previously validated phone verification',
442
425
  )
443
- email_verification_id: Optional[str] = Field(
444
- None,
445
- description='Only if you validated it previously with the '
446
- 'resource `verifications`',
426
+ email_verification_id: str = Field(
427
+ ...,
428
+ description='ID of previously validated email verification',
447
429
  )
448
- terms_of_service: Optional[TOSRequest] = None
449
- signature: Optional[KYCFile] = None
430
+
450
431
  model_config = ConfigDict(
451
432
  json_schema_extra={
452
433
  'example': {
@@ -454,7 +435,7 @@ class UserRequest(BaseModel):
454
435
  'phone_number': '+525511223344',
455
436
  'email_address': 'user@example.com',
456
437
  'profession': 'engineer',
457
- 'address': Address.schema().get('example'),
438
+ 'address': AddressRequest.model_json_schema().get('example'),
458
439
  }
459
440
  },
460
441
  )
@@ -479,21 +460,16 @@ class UserRequest(BaseModel):
479
460
 
480
461
 
481
462
  class UserUpdateRequest(BaseModel):
482
- phone_number: Optional[PhoneNumber] = None
483
- email_address: Optional[EmailStr] = None
484
- profession: Optional[str] = None
463
+ profession: Optional[Profession] = None
485
464
  verification_id: Optional[str] = None
486
465
  email_verification_id: Optional[str] = None
487
466
  phone_verification_id: Optional[str] = None
488
- address: Optional[Address] = None
467
+ address: Optional[AddressRequest] = None
489
468
  beneficiaries: Optional[list[Beneficiary]] = None
490
469
  govt_id: Optional[KYCFile] = None
491
470
  proof_of_address: Optional[KYCFile] = None
492
471
  proof_of_life: Optional[KYCFile] = None
493
472
  signature: Optional[KYCFile] = None
494
- status: Optional[UserStatus] = None
495
- terms_of_service: Optional[TOSRequest] = None
496
- platform_terms_of_service: Optional[TOSAgreement] = None
497
473
  curp_document_uri: Optional[SerializableHttpUrl] = None
498
474
 
499
475
  @field_validator('beneficiaries')
@@ -501,8 +477,8 @@ class UserUpdateRequest(BaseModel):
501
477
  def beneficiary_percentage(
502
478
  cls, beneficiaries: Optional[list[Beneficiary]] = None
503
479
  ):
504
- if beneficiaries and sum(b.percentage for b in beneficiaries) > 100:
505
- raise ValueError('The total percentage is more than 100.')
480
+ if beneficiaries and sum(b.percentage for b in beneficiaries) != 100:
481
+ raise ValueError('The total percentage should be 100%')
506
482
  return beneficiaries
507
483
 
508
484
 
@@ -709,7 +685,7 @@ class PartnerRequest(BaseRequest):
709
685
  web_site: str
710
686
  phone_number: PhoneNumber
711
687
  email_address: EmailStr
712
- address: Address
688
+ address: AddressRequest
713
689
 
714
690
 
715
691
  class PartnerUpdateRequest(BaseRequest):
@@ -723,7 +699,7 @@ class PartnerUpdateRequest(BaseRequest):
723
699
  web_site: Optional[str] = None
724
700
  phone_number: Optional[PhoneNumber] = None
725
701
  email_address: Optional[EmailStr] = None
726
- address: Optional[Address] = None
702
+ address: Optional[AddressRequest] = None
727
703
  business_details: Optional[BusinessDetails] = None
728
704
  transactional_profile: Optional[TransactionalProfile] = None
729
705
  external_account: Optional[Clabe] = None
@@ -1 +1 @@
1
- __version__ = '2.1.6.dev2'
1
+ __version__ = '2.1.7'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuenca_validations
3
- Version: 2.1.6.dev2
3
+ Version: 2.1.7
4
4
  Summary: Cuenca common validations
5
5
  Home-page: https://github.com/cuenca-mx/cuenca-validations
6
6
  Author: Cuenca
@@ -4,25 +4,25 @@ cuenca_validations/errors.py,sha256=OtM8EgiKqYdz9Hn66AbBO96orL1or7efkyt0vh0Zxbs,
4
4
  cuenca_validations/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  cuenca_validations/typing.py,sha256=1QCu81IbVZZpyInjyeAuO-nF36gpT5Gi4o6V9PozuOU,204
6
6
  cuenca_validations/validators.py,sha256=wzwLnJ4wHggZvqp3mearbFkzvDERGeTNvJkuofQnuMc,1484
7
- cuenca_validations/version.py,sha256=L9jkQV4Xs3OYEoL7u0FxZDhvifZlPogwPYrv8N16pyY,27
8
- cuenca_validations/types/__init__.py,sha256=UNb7auBcD0qp-x9TX7TNNj8IKo7uaI7xHaf9PYmaziY,4765
7
+ cuenca_validations/version.py,sha256=gn24eE-H9PhcKxsZYGGjMzlHZJ0OWYZfm8p9x7Jvpso,22
8
+ cuenca_validations/types/__init__.py,sha256=1xGfD2tlFsPlhIy-T7BZPs-vvEgDoc6ZW2iXFLbyQpM,4799
9
9
  cuenca_validations/types/card.py,sha256=UGzz8NTFAverUmdUKAK1oGHnOnjSNTpIRUm93vKSSGY,1295
10
- cuenca_validations/types/enums.py,sha256=Vm9HGf83jPkEp8iZ-KGtKGWQGx5q-cIUuQj32y_QcvU,18997
10
+ cuenca_validations/types/enums.py,sha256=533fUCip5CXMD7m4Ww8VAcB5Yr4A0EmT5GBRt2mF4b0,19510
11
11
  cuenca_validations/types/files.py,sha256=2CszbwF9ytXV9suFFwyDjYG4XxY8UhCjRw3HttVXXNw,269
12
12
  cuenca_validations/types/general.py,sha256=vJmQBD_Iv_hsxD8x3_Bip-NlYAiE2rmXSPQKj4kTtto,2621
13
13
  cuenca_validations/types/helpers.py,sha256=6rHUhwoQ7jJZtGcW3LX-W5ZDl42PWE1RoBpGme7KCkk,610
14
- cuenca_validations/types/identities.py,sha256=-TWRDRpmIaCI4nE4hVy1FQtQtlxuErtmEtzJW1FBzec,4956
14
+ cuenca_validations/types/identities.py,sha256=F6XFeQY4N53eWSZb-mg81s3B0yuhEQ6OkEykX7wTFmI,5673
15
15
  cuenca_validations/types/morals.py,sha256=m8kAedevmwfSPTA9GYe03l7pkgipynwYgKfejyVtnuI,1813
16
16
  cuenca_validations/types/queries.py,sha256=KCRx0sPzWDtDDbZysmFGVgANgfqil17EITWaG7tGQ-A,4700
17
- cuenca_validations/types/requests.py,sha256=6aEoMe7e-FPJs3_ouECV_Y6ZtyyJVyXfTed8wHtx_xw,21338
18
- cuenca_validations-2.1.6.dev2.dist-info/licenses/LICENSE,sha256=wR76FmxBbfnQpwELkkE5iMF8sFIafEMgXLTE4N4WPTc,1063
17
+ cuenca_validations/types/requests.py,sha256=u2lUkeIQu2KqSo1PU0XNb1mFczBXZZoLy1p7vivQwiU,20412
18
+ cuenca_validations-2.1.7.dist-info/licenses/LICENSE,sha256=wR76FmxBbfnQpwELkkE5iMF8sFIafEMgXLTE4N4WPTc,1063
19
19
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  tests/test_card.py,sha256=QAfRz7e11gWICPnFJZ2tiYgUsFV3C9TwzJXrDnDNXFw,1202
21
21
  tests/test_errors.py,sha256=ixiIgEuBuzfsL5p4uCFdF32XqFRtTPF6EVhGJ0keOrI,930
22
22
  tests/test_helpers.py,sha256=ubzpi1UXCryLQdgsT_Zm2IX-XE_4L0dnHnhLwH06xK8,748
23
23
  tests/test_statement.py,sha256=IOE0rRRBgBZSJv_FLaETEyn5NzzXKMNTqgjv99GX-68,1436
24
- tests/test_types.py,sha256=K_YNFz0Kac3k8fJNeXuYGkL1hjXDQu084jCX6xEMEE4,19348
25
- cuenca_validations-2.1.6.dev2.dist-info/METADATA,sha256=EJWfvWsPiRGTgR-Ln3gZss_fi2mlbiN19JhwUaP3n-I,1599
26
- cuenca_validations-2.1.6.dev2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
27
- cuenca_validations-2.1.6.dev2.dist-info/top_level.txt,sha256=4233xdOs2HtuT-GFRjcDcwK0IwdwvWdczOtk0fPB6Gw,25
28
- cuenca_validations-2.1.6.dev2.dist-info/RECORD,,
24
+ tests/test_types.py,sha256=svH813Q47p9Ziq0jcuPIvrIuelPoVKfuFyxdMAgPsdw,19070
25
+ cuenca_validations-2.1.7.dist-info/METADATA,sha256=tZkzjpFGrT_vb0_8KZAF8dd6s9fUIwIMtf3Leoxru6c,1594
26
+ cuenca_validations-2.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
+ cuenca_validations-2.1.7.dist-info/top_level.txt,sha256=4233xdOs2HtuT-GFRjcDcwK0IwdwvWdczOtk0fPB6Gw,25
28
+ cuenca_validations-2.1.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
tests/test_types.py CHANGED
@@ -10,7 +10,6 @@ from pydantic import AfterValidator, BaseModel, SecretStr, ValidationError
10
10
  from pydantic.fields import FieldInfo
11
11
 
12
12
  from cuenca_validations.types import (
13
- Address,
14
13
  CardQuery,
15
14
  JSONEncoder,
16
15
  QueryParams,
@@ -23,6 +22,7 @@ from cuenca_validations.types import (
23
22
  from cuenca_validations.types.enums import (
24
23
  Country,
25
24
  EcommerceIndicator,
25
+ Profession,
26
26
  SessionType,
27
27
  State,
28
28
  )
@@ -303,60 +303,50 @@ def test_saving_update_request():
303
303
  SavingUpdateRequest(**data)
304
304
 
305
305
 
306
- def test_address_validation():
307
- data = dict(
308
- full_name='Varsovia 36, Col Cuahutemoc',
309
- )
310
- assert Address(**data)
311
- with pytest.raises(ValueError) as v:
312
- Address(**dict())
313
- assert 'required street' in str(v)
314
- data = dict(street='somestreet')
315
- with pytest.raises(ValueError) as v:
316
- Address(**data)
317
- assert 'required ext_number' in str(v)
318
- data = dict(
319
- street='varsovia',
320
- ext_number='36',
321
- state=State.DF,
322
- country=Country.MX,
306
+ @freeze_time('2022-01-01')
307
+ def test_user_request():
308
+ request = dict(
309
+ curp='ABCD920604HDFSRN03',
310
+ profession=Profession.empleado,
311
+ address=dict(
312
+ street='calle 1',
313
+ ext_number='2',
314
+ int_number='3',
315
+ colonia='Juarez',
316
+ postal_code='09900',
317
+ state=State.DF,
318
+ country=Country.MX,
319
+ city='Obrera',
320
+ ),
321
+ phone_verification_id='VE12345678',
322
+ email_verification_id='VE0987654321',
323
323
  )
324
- assert Address(**data)
324
+ assert UserRequest(**request).model_dump() == request
325
325
 
326
326
 
327
327
  @freeze_time('2022-01-01')
328
- def test_user_request():
328
+ def test_user_request_underage():
329
329
  request = dict(
330
- id=None,
331
- curp='ABCD920604HDFSRN03',
332
- phone_number='+525555555555',
333
- email_address='email@email.com',
330
+ curp='ABCD060604HDFSRN03', # underage CURP
334
331
  profession='worker',
335
- status='active',
336
332
  address=dict(
337
333
  street='calle 1',
338
334
  ext_number='2',
339
335
  int_number='3',
340
336
  colonia='Juarez',
341
337
  postal_code='09900',
342
- state=State.DF.value,
338
+ state=State.DF,
343
339
  country=Country.MX,
344
340
  city='Obrera',
345
- full_name=None,
346
341
  ),
347
342
  phone_verification_id='VE12345678',
348
343
  email_verification_id='VE0987654321',
349
- required_level=3,
350
- terms_of_service=None,
351
- signature=None,
352
344
  )
353
- assert UserRequest(**request).model_dump() == request
354
345
 
355
- # changing curp so user is underage
356
- request['curp'] = 'ABCD060604HDFSRN03'
357
346
  with pytest.raises(ValueError) as v:
358
347
  UserRequest(**request)
359
- assert 'User does not meet age requirement.' in str(v)
348
+
349
+ assert 'User does not meet age requirement.' in str(v)
360
350
 
361
351
 
362
352
  @freeze_time('2022-01-01')
@@ -438,7 +428,7 @@ def test_user_update_request():
438
428
  birth_date=dt.date(2020, 1, 1).isoformat(),
439
429
  phone_number='+525555555555',
440
430
  user_relationship='brother',
441
- percentage=50,
431
+ percentage=100,
442
432
  ),
443
433
  ]
444
434
  assert UserUpdateRequest(**request)
@@ -449,12 +439,21 @@ def test_user_update_request():
449
439
  birth_date=dt.date(2020, 1, 1).isoformat(),
450
440
  phone_number='+525555555555',
451
441
  user_relationship='brother',
452
- percentage=101,
442
+ percentage=50,
443
+ ),
444
+ dict(
445
+ name='José Pérez',
446
+ birth_date=dt.date(2020, 1, 2).isoformat(),
447
+ phone_number='+525544444444',
448
+ user_relationship='brother',
449
+ percentage=51,
453
450
  ),
454
451
  ]
455
- with pytest.raises(ValueError) as v:
452
+ with pytest.raises(ValidationError) as v:
456
453
  UserUpdateRequest(**request)
457
- assert 'The total percentage is more than 100.' in str(v)
454
+
455
+ assert 'The total percentage should be 100%' in str(v)
456
+ request.pop('beneficiaries')
458
457
 
459
458
  tos_request = dict(
460
459
  terms_of_service=dict(
@@ -485,12 +484,6 @@ def test_user_update_request():
485
484
  )
486
485
  UserUpdateRequest(**kyc_request)
487
486
 
488
- # chagning to invalid request
489
- tos_request['terms_of_service']['ip'] = 'not valid ip'
490
- with pytest.raises(ValueError) as v:
491
- UserUpdateRequest(**tos_request)
492
- assert 'not valid ip' in str(v.value)
493
-
494
487
 
495
488
  def test_session_request():
496
489
  data = dict(