cuenca-validations 2.1.31.dev1__tar.gz → 2.1.32.dev0__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 (36) hide show
  1. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/PKG-INFO +1 -1
  2. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/__init__.py +6 -0
  3. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/requests.py +65 -2
  4. cuenca_validations-2.1.32.dev0/cuenca_validations/version.py +1 -0
  5. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/PKG-INFO +1 -1
  6. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/tests/test_requests.py +9 -40
  7. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/tests/test_types.py +93 -0
  8. cuenca_validations-2.1.31.dev1/cuenca_validations/version.py +0 -1
  9. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/LICENSE +0 -0
  10. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/README.md +0 -0
  11. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/__init__.py +0 -0
  12. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/card_bins.py +0 -0
  13. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/errors.py +0 -0
  14. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/py.typed +0 -0
  15. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/card.py +0 -0
  16. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/enums.py +0 -0
  17. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/files.py +0 -0
  18. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/general.py +0 -0
  19. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/helpers.py +0 -0
  20. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/identities.py +0 -0
  21. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/morals.py +0 -0
  22. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/queries.py +0 -0
  23. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/typing.py +0 -0
  24. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations/validators.py +0 -0
  25. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/SOURCES.txt +0 -0
  26. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/dependency_links.txt +0 -0
  27. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/requires.txt +0 -0
  28. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/top_level.txt +0 -0
  29. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/setup.cfg +0 -0
  30. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/setup.py +0 -0
  31. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/tests/__init__.py +0 -0
  32. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/tests/test_card.py +0 -0
  33. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/tests/test_errors.py +0 -0
  34. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/tests/test_helpers.py +0 -0
  35. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/tests/test_statement.py +0 -0
  36. {cuenca_validations-2.1.31.dev1 → cuenca_validations-2.1.32.dev0}/tests/test_validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuenca_validations
3
- Version: 2.1.31.dev1
3
+ Version: 2.1.32.dev0
4
4
  Summary: Cuenca common validations
5
5
  Home-page: https://github.com/cuenca-mx/cuenca-validations
6
6
  Author: Cuenca
@@ -44,6 +44,9 @@ __all__ = [
44
44
  'FileBatchUploadRequest',
45
45
  'FileRequest',
46
46
  'FileUploadRequest',
47
+ 'FraudFundsTransferAcceptedResponse',
48
+ 'FraudFundsTransferRequest',
49
+ 'FraudFundsTransferResultEvent',
47
50
  'Gender',
48
51
  'IncomeType',
49
52
  'IssuerNetwork',
@@ -230,6 +233,9 @@ from .requests import (
230
233
  FileBatchUploadRequest,
231
234
  FileRequest,
232
235
  FileUploadRequest,
236
+ FraudFundsTransferAcceptedResponse,
237
+ FraudFundsTransferRequest,
238
+ FraudFundsTransferResultEvent,
233
239
  KYCValidationRequest,
234
240
  LimitedWalletRequest,
235
241
  PartnerRequest,
@@ -1,7 +1,7 @@
1
1
  import datetime as dt
2
- from typing import Annotated, Any, Optional, Union
2
+ from typing import Annotated, Any, Literal, Optional, Union
3
3
 
4
- from clabe import Clabe
4
+ from clabe import Clabe, validate_clabe
5
5
  from pydantic import (
6
6
  BaseModel,
7
7
  ConfigDict,
@@ -309,6 +309,69 @@ class WalletTransactionRequest(BaseRequest):
309
309
  amount: StrictPositiveInt
310
310
 
311
311
 
312
+ class FraudFundsTransferRequest(BaseRequest):
313
+ user_id: NonEmptyStr
314
+ clabe: Clabe
315
+ concepto: NonEmptyStr
316
+ amount: Optional[StrictPositiveInt] = None
317
+ reason: Optional[NonEmptyStr] = None
318
+ request_id: Optional[NonEmptyStr] = None
319
+ requested_by: Optional[NonEmptyStr] = None
320
+
321
+ @field_validator('clabe', mode='before')
322
+ @classmethod
323
+ def validate_clabe_format(cls, clabe: str) -> str:
324
+ if not validate_clabe(clabe):
325
+ raise ValueError('La CLABE ingresada no es valida')
326
+ return clabe
327
+
328
+
329
+ class FraudFundsTransferAcceptedResponse(BaseRequest):
330
+ request_id: NonEmptyStr
331
+ status: Literal['queued']
332
+
333
+
334
+ class FraudFundsTransferResultEvent(BaseRequest):
335
+ schema_version: NonEmptyStr
336
+ event_type: Literal[
337
+ 'fraud_funds_transfer.succeeded',
338
+ 'fraud_funds_transfer.failed',
339
+ ]
340
+ request_id: NonEmptyStr
341
+ user_id: NonEmptyStr
342
+ transaction_id: Optional[NonEmptyStr] = None
343
+ amount: Optional[StrictPositiveInt] = None
344
+ clave_rastreo: Optional[NonEmptyStr] = None
345
+ reason_code: Optional[NonEmptyStr] = None
346
+ message: Optional[NonEmptyStr] = None
347
+ completed_at: dt.datetime
348
+
349
+ @model_validator(mode='after')
350
+ def validate_payload(self) -> 'FraudFundsTransferResultEvent':
351
+ if self.event_type == 'fraud_funds_transfer.succeeded':
352
+ required = {
353
+ 'transaction_id': self.transaction_id,
354
+ 'amount': self.amount,
355
+ }
356
+ missing = [
357
+ field for field, value in required.items() if value is None
358
+ ]
359
+ if missing:
360
+ raise ValueError(
361
+ f'{", ".join(missing)} required for succeeded event'
362
+ )
363
+ return self
364
+
365
+ required = {
366
+ 'reason_code': self.reason_code,
367
+ 'message': self.message,
368
+ }
369
+ missing = [field for field, value in required.items() if value is None]
370
+ if missing:
371
+ raise ValueError(f'{", ".join(missing)} required for failed event')
372
+ return self
373
+
374
+
312
375
  class FraudValidationRequest(BaseModel):
313
376
  amount: StrictPositiveInt
314
377
  merchant_name: str
@@ -0,0 +1 @@
1
+ __version__ = '2.1.32.dev0'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuenca_validations
3
- Version: 2.1.31.dev1
3
+ Version: 2.1.32.dev0
4
4
  Summary: Cuenca common validations
5
5
  Home-page: https://github.com/cuenca-mx/cuenca-validations
6
6
  Author: Cuenca
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  import pytest
4
2
  from pydantic import ValidationError
5
3
  from pydantic_extra_types.phone_numbers import PhoneNumber
@@ -40,44 +38,15 @@ def test_file_cuenca_url_invalid() -> None:
40
38
  UserTOSAgreementRequest(**request_data)
41
39
 
42
40
 
43
- _VALID_COORDINATE = (19.432607, -99.133209)
44
-
45
-
46
- @pytest.mark.parametrize(
47
- ('payload', 'expected_dump'),
48
- [
49
- pytest.param(
50
- {'location': _VALID_COORDINATE},
51
- {
52
- 'location': {
53
- 'latitude': 19.432607,
54
- 'longitude': -99.133209,
55
- },
56
- },
57
- id='serializes',
58
- ),
59
- pytest.param(
60
- {},
61
- None,
62
- id='missing_location',
63
- ),
64
- pytest.param(
65
- {'location': (91.0, 0.0)},
66
- None,
67
- id='bad_coordinate',
68
- ),
69
- ],
70
- )
71
- def test_password_reset_request(
72
- payload: DictStrAny,
73
- expected_dump: Optional[DictStrAny],
74
- ) -> None:
75
- if expected_dump is None:
76
- with pytest.raises(ValidationError):
77
- PasswordResetRequest.model_validate(payload)
78
- else:
79
- req = PasswordResetRequest.model_validate(payload)
80
- assert req.model_dump() == expected_dump
41
+ def test_password_reset_request_serializes() -> None:
42
+ payload: DictStrAny = {'location': (19.432607, -99.133209)}
43
+ req = PasswordResetRequest.model_validate(payload)
44
+ assert req.model_dump() == {
45
+ 'location': {
46
+ 'latitude': 19.432607,
47
+ 'longitude': -99.133209,
48
+ },
49
+ }
81
50
 
82
51
 
83
52
  def test_update_user_requires_at_least_one_param():
@@ -12,6 +12,9 @@ from pydantic.fields import FieldInfo
12
12
 
13
13
  from cuenca_validations.types import (
14
14
  CardQuery,
15
+ FraudFundsTransferAcceptedResponse,
16
+ FraudFundsTransferRequest,
17
+ FraudFundsTransferResultEvent,
15
18
  JSONEncoder,
16
19
  QueryParams,
17
20
  SantizedDict,
@@ -636,6 +639,96 @@ def test_bank_account_validation_clabe_request():
636
639
  assert BankAccountValidationRequest(account_number='646180157098510917')
637
640
 
638
641
 
642
+ def test_fraud_funds_transfer_models():
643
+ request = FraudFundsTransferRequest(
644
+ user_id='US123',
645
+ clabe='646180157098510917',
646
+ concepto=' fondos fraude ',
647
+ amount=100,
648
+ request_id='REQ123',
649
+ )
650
+
651
+ assert request.concepto == 'fondos fraude'
652
+ assert request.model_dump() == {
653
+ 'user_id': 'US123',
654
+ 'clabe': '646180157098510917',
655
+ 'concepto': 'fondos fraude',
656
+ 'amount': 100,
657
+ 'request_id': 'REQ123',
658
+ }
659
+
660
+ response = FraudFundsTransferAcceptedResponse(
661
+ request_id='REQ123',
662
+ status='queued',
663
+ )
664
+
665
+ assert response.status == 'queued'
666
+
667
+ succeeded_event = FraudFundsTransferResultEvent(
668
+ schema_version='1.0',
669
+ event_type='fraud_funds_transfer.succeeded',
670
+ request_id='REQ123',
671
+ user_id='US123',
672
+ transaction_id='TR123',
673
+ amount=100,
674
+ clave_rastreo='RASTREO123',
675
+ completed_at=now,
676
+ )
677
+
678
+ assert succeeded_event.transaction_id == 'TR123'
679
+
680
+ failed_event = FraudFundsTransferResultEvent(
681
+ schema_version='1.0',
682
+ event_type='fraud_funds_transfer.failed',
683
+ request_id='REQ123',
684
+ user_id='US123',
685
+ reason_code='insufficient_funds',
686
+ message='Insufficient funds',
687
+ completed_at=now,
688
+ )
689
+
690
+ assert failed_event.reason_code == 'insufficient_funds'
691
+
692
+
693
+ def test_fraud_funds_transfer_request_invalid_clabe():
694
+ with pytest.raises(ValidationError) as ex:
695
+ FraudFundsTransferRequest(
696
+ user_id='US123',
697
+ clabe='not-a-clabe',
698
+ concepto='fondos fraude',
699
+ )
700
+
701
+ assert 'La CLABE ingresada no es valida' in str(ex.value)
702
+
703
+
704
+ @pytest.mark.parametrize(
705
+ 'event_type,expected_error',
706
+ [
707
+ (
708
+ 'fraud_funds_transfer.succeeded',
709
+ 'transaction_id, amount required for succeeded event',
710
+ ),
711
+ (
712
+ 'fraud_funds_transfer.failed',
713
+ 'reason_code, message required for failed event',
714
+ ),
715
+ ],
716
+ )
717
+ def test_fraud_funds_transfer_result_event_requires_payload(
718
+ event_type, expected_error
719
+ ):
720
+ with pytest.raises(ValidationError) as ex:
721
+ FraudFundsTransferResultEvent(
722
+ schema_version='1.0',
723
+ event_type=event_type,
724
+ request_id='REQ123',
725
+ user_id='US123',
726
+ completed_at=now,
727
+ )
728
+
729
+ assert expected_error in str(ex.value)
730
+
731
+
639
732
  @pytest.mark.parametrize(
640
733
  'input_data',
641
734
  [
@@ -1 +0,0 @@
1
- __version__ = '2.1.31.dev1'