From 8296377c72a3ca13eb5404f2382e63f11518821a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Cabrera?= Date: Wed, 9 Sep 2026 16:43:38 -0600 Subject: [PATCH 1/2] Add EmailOtp resource to cuenca module and update imports accordingly --- cuenca/__init__.py | 2 ++ cuenca/resources/__init__.py | 2 ++ cuenca/resources/email_otps.py | 27 +++++++++++++++++++++++++++ cuenca/version.py | 2 +- 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 cuenca/resources/email_otps.py diff --git a/cuenca/__init__.py b/cuenca/__init__.py index 568f4eb3..62ef7f34 100644 --- a/cuenca/__init__.py +++ b/cuenca/__init__.py @@ -25,6 +25,7 @@ 'LimitedWallet', 'LoginToken', 'Otp', + 'EmailOtp', 'OperatorLogin', 'PasswordReset', 'Platform', @@ -71,6 +72,7 @@ Commission, CurpValidation, Deposit, + EmailOtp, Endpoint, ExistPhones, File, diff --git a/cuenca/resources/__init__.py b/cuenca/resources/__init__.py index bbde7b5a..9b9011a2 100644 --- a/cuenca/resources/__init__.py +++ b/cuenca/resources/__init__.py @@ -25,6 +25,7 @@ 'LimitedWallet', 'LoginToken', 'Otp', + 'EmailOtp', 'OperatorLogin', 'PasswordReset', 'Platform', @@ -65,6 +66,7 @@ from .commissions import Commission from .curp_validations import CurpValidation from .deposits import Deposit +from .email_otps import EmailOtp from .endpoints import Endpoint from .exist_phones import ExistPhones from .file_batches import FileBatch diff --git a/cuenca/resources/email_otps.py b/cuenca/resources/email_otps.py new file mode 100644 index 00000000..ade5aa64 --- /dev/null +++ b/cuenca/resources/email_otps.py @@ -0,0 +1,27 @@ +from typing import ClassVar + +from pydantic import ConfigDict + +from ..http import Session, session as global_session +from .base import Creatable + + +class EmailOtp(Creatable): + _resource: ClassVar = 'email_otps' + owner_id: str | None = None + expires_at: str | None = None + + model_config = ConfigDict( + json_schema_extra={ + 'example': { + 'id': 'EOxxxxxxxxxxxxxxxxxxxxxx', + 'owner_id': 'SSxxxxxxxxxxxxxxxxxxxxxx', + 'expires_at': '2026-09-09T18:00:00', + } + } + ) + + @classmethod + def create(cls, session: Session = global_session) -> 'EmailOtp': + """Request a one-shot email OTP for the current Session.""" + return cls._create(session=session) diff --git a/cuenca/version.py b/cuenca/version.py index f189b057..ad5488e0 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.2.4' +__version__ = '2.2.5.dev0' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' From 0096d19d0aea3159dde7819a6e83ee46e81b1c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Cabrera?= Date: Wed, 9 Sep 2026 22:08:44 -0600 Subject: [PATCH 2/2] fix(email-otp): py3.9 types and add create test str|None breaks import on 3.9; Optional + datetime match repo. Co-authored-by: Cursor --- cuenca/resources/email_otps.py | 7 +++--- tests/resources/test_email_otps.py | 36 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 tests/resources/test_email_otps.py diff --git a/cuenca/resources/email_otps.py b/cuenca/resources/email_otps.py index ade5aa64..7f9c5f9d 100644 --- a/cuenca/resources/email_otps.py +++ b/cuenca/resources/email_otps.py @@ -1,4 +1,5 @@ -from typing import ClassVar +import datetime as dt +from typing import ClassVar, Optional from pydantic import ConfigDict @@ -8,8 +9,8 @@ class EmailOtp(Creatable): _resource: ClassVar = 'email_otps' - owner_id: str | None = None - expires_at: str | None = None + owner_id: Optional[str] = None + expires_at: Optional[dt.datetime] = None model_config = ConfigDict( json_schema_extra={ diff --git a/tests/resources/test_email_otps.py b/tests/resources/test_email_otps.py new file mode 100644 index 00000000..4834b0f4 --- /dev/null +++ b/tests/resources/test_email_otps.py @@ -0,0 +1,36 @@ +import datetime as dt +from unittest.mock import MagicMock, patch + +import pytest + +from cuenca import EmailOtp +from cuenca.http.client import Session + + +@pytest.fixture +def session() -> Session: + s = Session() + s.configure('api_key', 'api_secret', sandbox=True) + return s + + +@patch('cuenca.http.client.requests.Session.request') +def test_email_otp_create(mock_request: MagicMock, session: Session): + mock_request.return_value.ok = True + mock_request.return_value.content = ( + b'{"id":"EOxxxxxxxxxxxxxxxxxxxxxx",' + b'"owner_id":"SSxxxxxxxxxxxxxxxxxxxxxx",' + b'"expires_at":"2026-09-09T18:00:00"}' + ) + + email_otp = EmailOtp.create(session=session) + + assert email_otp.id == 'EOxxxxxxxxxxxxxxxxxxxxxx' + assert email_otp.owner_id == 'SSxxxxxxxxxxxxxxxxxxxxxx' + assert email_otp.expires_at == dt.datetime(2026, 9, 9, 18, 0, 0) + + mock_request.assert_called_once() + _, kwargs = mock_request.call_args + assert kwargs['method'] == 'post' + assert kwargs['url'] == 'https://sandbox.cuenca.com/email_otps' + assert kwargs['json'] == {}