cuenca-validations 2.1.6.dev1__tar.gz → 2.1.7__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.
Files changed (34) hide show
  1. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/PKG-INFO +1 -1
  2. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/__init__.py +2 -0
  3. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/enums.py +16 -0
  4. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/identities.py +37 -21
  5. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/requests.py +23 -48
  6. cuenca_validations-2.1.7/cuenca_validations/version.py +1 -0
  7. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations.egg-info/PKG-INFO +1 -1
  8. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/tests/test_types.py +37 -66
  9. cuenca_validations-2.1.6.dev1/cuenca_validations/version.py +0 -1
  10. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/LICENSE +0 -0
  11. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/README.md +0 -0
  12. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/__init__.py +0 -0
  13. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/card_bins.py +0 -0
  14. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/errors.py +0 -0
  15. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/py.typed +0 -0
  16. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/card.py +0 -0
  17. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/files.py +0 -0
  18. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/general.py +0 -0
  19. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/helpers.py +0 -0
  20. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/morals.py +0 -0
  21. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/types/queries.py +0 -0
  22. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/typing.py +0 -0
  23. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations/validators.py +0 -0
  24. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations.egg-info/SOURCES.txt +0 -0
  25. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations.egg-info/dependency_links.txt +0 -0
  26. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations.egg-info/requires.txt +0 -0
  27. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/cuenca_validations.egg-info/top_level.txt +0 -0
  28. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/setup.cfg +0 -0
  29. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/setup.py +0 -0
  30. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/tests/__init__.py +0 -0
  31. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/tests/test_card.py +0 -0
  32. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/tests/test_errors.py +0 -0
  33. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/tests/test_helpers.py +0 -0
  34. {cuenca_validations-2.1.6.dev1 → cuenca_validations-2.1.7}/tests/test_statement.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuenca_validations
3
- Version: 2.1.6.dev1
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
@@ -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,
@@ -405,49 +404,30 @@ class TOSRequest(BaseModel):
405
404
 
406
405
 
407
406
  class UserTOSAgreementRequest(BaseModel):
408
- user_id: str
409
407
  tos_id: str
410
408
  location: Coordinate
411
409
 
412
410
 
413
411
  class UserRequest(BaseModel):
414
- id: Optional[str] = Field(
415
- None, description='if you want to create with specific `id`'
416
- )
417
412
  curp: Curp = Field(
418
- description='Previously validated in `curp_validations`'
419
- )
420
- phone_number: Optional[PhoneNumber] = Field(
421
- None, description='Only if you validated previously on your side'
422
- )
423
- email_address: Optional[EmailStr] = Field(
424
- None, description='Only if you validated previously on your side'
425
- )
426
- profession: Optional[str] = None
427
- address: Optional[Address] = None
428
- status: Optional[UserStatus] = Field(
429
- None,
430
- description='Status that the user will have when created. '
431
- 'Defined by platform',
413
+ description=(
414
+ 'Mexican government ID (18 characters). ' 'Must be pre-validated.'
415
+ )
432
416
  )
433
- required_level: Optional[int] = Field(
434
- None,
435
- ge=1,
436
- le=3,
437
- 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'
438
421
  )
439
- phone_verification_id: Optional[str] = Field(
440
- None,
441
- description='Only if you validated it previously with the '
442
- 'resource `verifications`',
422
+ phone_verification_id: str = Field(
423
+ ...,
424
+ description='ID of previously validated phone verification',
443
425
  )
444
- email_verification_id: Optional[str] = Field(
445
- None,
446
- description='Only if you validated it previously with the '
447
- 'resource `verifications`',
426
+ email_verification_id: str = Field(
427
+ ...,
428
+ description='ID of previously validated email verification',
448
429
  )
449
- terms_of_service: Optional[TOSRequest] = None
450
- signature: Optional[KYCFile] = None
430
+
451
431
  model_config = ConfigDict(
452
432
  json_schema_extra={
453
433
  'example': {
@@ -455,7 +435,7 @@ class UserRequest(BaseModel):
455
435
  'phone_number': '+525511223344',
456
436
  'email_address': 'user@example.com',
457
437
  'profession': 'engineer',
458
- 'address': Address.schema().get('example'),
438
+ 'address': AddressRequest.model_json_schema().get('example'),
459
439
  }
460
440
  },
461
441
  )
@@ -480,21 +460,16 @@ class UserRequest(BaseModel):
480
460
 
481
461
 
482
462
  class UserUpdateRequest(BaseModel):
483
- phone_number: Optional[PhoneNumber] = None
484
- email_address: Optional[EmailStr] = None
485
- profession: Optional[str] = None
463
+ profession: Optional[Profession] = None
486
464
  verification_id: Optional[str] = None
487
465
  email_verification_id: Optional[str] = None
488
466
  phone_verification_id: Optional[str] = None
489
- address: Optional[Address] = None
467
+ address: Optional[AddressRequest] = None
490
468
  beneficiaries: Optional[list[Beneficiary]] = None
491
469
  govt_id: Optional[KYCFile] = None
492
470
  proof_of_address: Optional[KYCFile] = None
493
471
  proof_of_life: Optional[KYCFile] = None
494
472
  signature: Optional[KYCFile] = None
495
- status: Optional[UserStatus] = None
496
- terms_of_service: Optional[TOSRequest] = None
497
- platform_terms_of_service: Optional[TOSAgreement] = None
498
473
  curp_document_uri: Optional[SerializableHttpUrl] = None
499
474
 
500
475
  @field_validator('beneficiaries')
@@ -502,8 +477,8 @@ class UserUpdateRequest(BaseModel):
502
477
  def beneficiary_percentage(
503
478
  cls, beneficiaries: Optional[list[Beneficiary]] = None
504
479
  ):
505
- if beneficiaries and sum(b.percentage for b in beneficiaries) > 100:
506
- 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%')
507
482
  return beneficiaries
508
483
 
509
484
 
@@ -710,7 +685,7 @@ class PartnerRequest(BaseRequest):
710
685
  web_site: str
711
686
  phone_number: PhoneNumber
712
687
  email_address: EmailStr
713
- address: Address
688
+ address: AddressRequest
714
689
 
715
690
 
716
691
  class PartnerUpdateRequest(BaseRequest):
@@ -724,7 +699,7 @@ class PartnerUpdateRequest(BaseRequest):
724
699
  web_site: Optional[str] = None
725
700
  phone_number: Optional[PhoneNumber] = None
726
701
  email_address: Optional[EmailStr] = None
727
- address: Optional[Address] = None
702
+ address: Optional[AddressRequest] = None
728
703
  business_details: Optional[BusinessDetails] = None
729
704
  transactional_profile: Optional[TransactionalProfile] = None
730
705
  external_account: Optional[Clabe] = None
@@ -0,0 +1 @@
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.dev1
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
@@ -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
  )
@@ -43,7 +43,6 @@ from cuenca_validations.types.requests import (
43
43
  UserCredentialUpdateRequest,
44
44
  UserListsRequest,
45
45
  UserRequest,
46
- UserTOSAgreementRequest,
47
46
  UserUpdateRequest,
48
47
  VerificationAttemptRequest,
49
48
  VerificationRequest,
@@ -304,60 +303,50 @@ def test_saving_update_request():
304
303
  SavingUpdateRequest(**data)
305
304
 
306
305
 
307
- def test_address_validation():
308
- data = dict(
309
- full_name='Varsovia 36, Col Cuahutemoc',
310
- )
311
- assert Address(**data)
312
- with pytest.raises(ValueError) as v:
313
- Address(**dict())
314
- assert 'required street' in str(v)
315
- data = dict(street='somestreet')
316
- with pytest.raises(ValueError) as v:
317
- Address(**data)
318
- assert 'required ext_number' in str(v)
319
- data = dict(
320
- street='varsovia',
321
- ext_number='36',
322
- state=State.DF,
323
- 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',
324
323
  )
325
- assert Address(**data)
324
+ assert UserRequest(**request).model_dump() == request
326
325
 
327
326
 
328
327
  @freeze_time('2022-01-01')
329
- def test_user_request():
328
+ def test_user_request_underage():
330
329
  request = dict(
331
- id=None,
332
- curp='ABCD920604HDFSRN03',
333
- phone_number='+525555555555',
334
- email_address='email@email.com',
330
+ curp='ABCD060604HDFSRN03', # underage CURP
335
331
  profession='worker',
336
- status='active',
337
332
  address=dict(
338
333
  street='calle 1',
339
334
  ext_number='2',
340
335
  int_number='3',
341
336
  colonia='Juarez',
342
337
  postal_code='09900',
343
- state=State.DF.value,
338
+ state=State.DF,
344
339
  country=Country.MX,
345
340
  city='Obrera',
346
- full_name=None,
347
341
  ),
348
342
  phone_verification_id='VE12345678',
349
343
  email_verification_id='VE0987654321',
350
- required_level=3,
351
- terms_of_service=None,
352
- signature=None,
353
344
  )
354
- assert UserRequest(**request).model_dump() == request
355
345
 
356
- # changing curp so user is underage
357
- request['curp'] = 'ABCD060604HDFSRN03'
358
346
  with pytest.raises(ValueError) as v:
359
347
  UserRequest(**request)
360
- assert 'User does not meet age requirement.' in str(v)
348
+
349
+ assert 'User does not meet age requirement.' in str(v)
361
350
 
362
351
 
363
352
  @freeze_time('2022-01-01')
@@ -439,7 +428,7 @@ def test_user_update_request():
439
428
  birth_date=dt.date(2020, 1, 1).isoformat(),
440
429
  phone_number='+525555555555',
441
430
  user_relationship='brother',
442
- percentage=50,
431
+ percentage=100,
443
432
  ),
444
433
  ]
445
434
  assert UserUpdateRequest(**request)
@@ -450,12 +439,21 @@ def test_user_update_request():
450
439
  birth_date=dt.date(2020, 1, 1).isoformat(),
451
440
  phone_number='+525555555555',
452
441
  user_relationship='brother',
453
- 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,
454
450
  ),
455
451
  ]
456
- with pytest.raises(ValueError) as v:
452
+ with pytest.raises(ValidationError) as v:
457
453
  UserUpdateRequest(**request)
458
- 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')
459
457
 
460
458
  tos_request = dict(
461
459
  terms_of_service=dict(
@@ -486,12 +484,6 @@ def test_user_update_request():
486
484
  )
487
485
  UserUpdateRequest(**kyc_request)
488
486
 
489
- # chagning to invalid request
490
- tos_request['terms_of_service']['ip'] = 'not valid ip'
491
- with pytest.raises(ValueError) as v:
492
- UserUpdateRequest(**tos_request)
493
- assert 'not valid ip' in str(v.value)
494
-
495
487
 
496
488
  def test_session_request():
497
489
  data = dict(
@@ -708,24 +700,3 @@ def test_log_config(
708
700
  def test_get_log_config_no_log_config():
709
701
  field = FieldInfo(default=None)
710
702
  assert get_log_config(field) is None
711
-
712
-
713
- @pytest.mark.parametrize(
714
- 'location',
715
- [
716
- (1500, -99.139584),
717
- (-91, 45.1),
718
- (45, 181),
719
- (45, -181),
720
- ('abc', 45),
721
- (45, 'abc'),
722
- (45, 45, 45),
723
- ],
724
- )
725
- def test_location_validation_invalid_format(location):
726
- with pytest.raises(ValueError):
727
- UserTOSAgreementRequest(
728
- user_id='US123',
729
- tos_id='TS123',
730
- location=location,
731
- )
@@ -1 +0,0 @@
1
- __version__ = '2.1.6.dev1'