Skip to content

Commit

Permalink
Update/pydantic v2 (#411)
Browse files Browse the repository at this point in the history
* Update Python version and dependencies in Makefile and requirements files

* Update Python version requirement and dependencies in setup.py

* Update GitHub Actions workflows to use Python 3.13

* update syntax for Pydantic v2 compatibility

* re-record card activation with valid card number

* Remove unused test for valid card creation in test_cards.py

* Refactor type checks in tests to use isinstance

* Fix endpoint URL assertion and clean up session test assertions

* Update error message in test_invalid_params to reflect new validation response

* Update test_user_beneficiaries_update.yaml to correct phone number format in response body

* Resolve linting errors

* Replace built-in type hints (Dict, List) with dict and list

* Update pydantic-extra-types version to 2.10.2 in requirements.txt

* Update version to 2.0.0 in version.py

* Add mypy configuration for Pydantic plugin

* Refactor Card model to use string for card number instead of PaymentCardNumber type

* Update cuenca-validations version to 2.0.0 in requirements.txt

* Refactor CURP handling across multiple resources to use the updated Curp type instead of CurpField

* Refactor optional parameters in resource classes

* Update version to 2.0.0.dev7 in version.py for development release

---------

Co-authored-by: gabino <gabino@cuenca.com>
gmorales96 and gabino authored Jan 18, 2025
1 parent b308816 commit a42283c
Showing 53 changed files with 393 additions and 412 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -6,18 +6,18 @@ jobs:
publish-pypi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- name: Set up Python 3.8
uses: actions/setup-python@v2.3.1
- uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: pip install -qU setuptools wheel twine
- name: Generating distribution archives
run: python setup.py sdist bdist_wheel
- name: Publish distribution 📦 to PyPI
if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.pypi_password }}
18 changes: 9 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -7,10 +7,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5.1.0
- name: Set up Python 13
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: make install-test
- name: Lint
@@ -20,11 +20,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -36,16 +36,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5.1.0
- name: Setup Python 13
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: make install-test
- name: Generate coverage report
run: pytest --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.1.1
uses: codecov/codecov-action@v5
with:
file: ./coverage.xml
flags: unittests
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
SHELL := bash
PATH := ./venv/bin:${PATH}
PYTHON = python3.8
PYTHON = python3.13
PROJECT = cuenca
isort = isort $(PROJECT) tests setup.py examples
black = black -S -l 79 --target-version py38 $(PROJECT) tests setup.py examples
black = black -S -l 79 --target-version py313 $(PROJECT) tests setup.py examples


all: test
13 changes: 7 additions & 6 deletions cuenca/resources/api_keys.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
from typing import ClassVar, Optional

from cuenca_validations.types import ApiKeyQuery, ApiKeyUpdateRequest
from pydantic import ConfigDict

from ..http import Session, session as global_session
from .base import Creatable, Queryable, Retrievable, Updateable
@@ -12,11 +13,10 @@ class ApiKey(Creatable, Queryable, Retrievable, Updateable):
_query_params: ClassVar = ApiKeyQuery

secret: str
deactivated_at: Optional[dt.datetime]
user_id: Optional[str]

class Config:
schema_extra = {
deactivated_at: Optional[dt.datetime] = None
user_id: Optional[str] = None
model_config = ConfigDict(
json_schema_extra={
'example': {
'id': 'AKNEUInh69SuKXXmK95sROwQ',
'updated_at': '2021-08-24T14:15:22Z',
@@ -26,6 +26,7 @@ class Config:
'user_id': 'USWqY5cvkISJOxHyEKjAKf8w',
}
}
)

@property
def active(self) -> bool:
@@ -74,4 +75,4 @@ def update(
req = ApiKeyUpdateRequest(
metadata=metadata, user_id=user_id, platform_id=platform_id
)
return cls._update(api_key_id, **req.dict(), session=session)
return cls._update(api_key_id, **req.model_dump(), session=session)
6 changes: 3 additions & 3 deletions cuenca/resources/arpc.py
Original file line number Diff line number Diff line change
@@ -23,8 +23,8 @@ class Arpc(Creatable):

created_at: dt.datetime
card_uri: str
is_valid_arqc: Optional[bool]
arpc: Optional[str]
is_valid_arqc: Optional[bool] = None
arpc: Optional[str] = None

@classmethod
def create(
@@ -52,4 +52,4 @@ def create(
unique_number=unique_number,
track_data_method=track_data_method,
)
return cls._create(session=session, **req.dict())
return cls._create(session=session, **req.model_dump())
6 changes: 2 additions & 4 deletions cuenca/resources/balance_entries.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import ClassVar, TypeVar, cast
from typing import ClassVar, Union, cast

from cuenca_validations.types import BalanceEntryQuery, EntryType

@@ -8,9 +8,7 @@
from .resources import retrieve_uri
from .service_providers import ServiceProvider

FundingInstrument = TypeVar(
'FundingInstrument', Account, ServiceProvider, Card
)
FundingInstrument = Union[Account, ServiceProvider, Card]


class BalanceEntry(Retrievable, Queryable):
19 changes: 10 additions & 9 deletions cuenca/resources/base.py
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
TransactionQuery,
TransactionStatus,
)
from pydantic import BaseModel, Extra
from pydantic import BaseModel, ConfigDict

from ..exc import MultipleResultsFound, NoResultFound
from ..http import Session, session as global_session
@@ -25,11 +25,12 @@ class Resource(BaseModel):

id: str

class Config:
extra = Extra.ignore
model_config = ConfigDict(
extra="ignore",
)

def to_dict(self):
return SantizedDict(self.dict())
return SantizedDict(self.model_dump())


class Retrievable(Resource):
@@ -78,7 +79,7 @@ def _update(


class Deactivable(Resource):
deactivated_at: Optional[dt.datetime]
deactivated_at: Optional[dt.datetime] = None

@classmethod
def deactivate(
@@ -157,7 +158,7 @@ def one(
**query_params: Any,
) -> R_co:
q = cast(Queryable, cls)._query_params(limit=2, **query_params)
resp = session.get(cls._resource, q.dict())
resp = session.get(cls._resource, q.model_dump())
items = resp['items']
len_items = len(items)
if not len_items:
@@ -174,7 +175,7 @@ def first(
**query_params: Any,
) -> Optional[R_co]:
q = cast(Queryable, cls)._query_params(limit=1, **query_params)
resp = session.get(cls._resource, q.dict())
resp = session.get(cls._resource, q.model_dump())
try:
item = resp['items'][0]
except IndexError:
@@ -191,7 +192,7 @@ def count(
**query_params: Any,
) -> int:
q = cast(Queryable, cls)._query_params(count=True, **query_params)
resp = session.get(cls._resource, q.dict())
resp = session.get(cls._resource, q.model_dump())
return resp['count']

@classmethod
@@ -203,7 +204,7 @@ def all(
) -> Generator[R_co, None, None]:
session = session or global_session
q = cast(Queryable, cls)._query_params(**query_params)
next_page_uri = f'{cls._resource}?{urlencode(q.dict())}'
next_page_uri = f'{cls._resource}?{urlencode(q.model_dump())}'
while next_page_uri:
page = session.get(next_page_uri)
yield from (cls(**item) for item in page['items'])
4 changes: 2 additions & 2 deletions cuenca/resources/card_activations.py
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ class CardActivation(Creatable):
created_at: dt.datetime
user_id: str
ip_address: str
card_uri: Optional[str]
card_uri: Optional[str] = None
success: bool

@classmethod
@@ -42,7 +42,7 @@ def create(
exp_year=exp_year,
cvv2=cvv2,
)
return cls._create(session=session, **req.dict())
return cls._create(session=session, **req.model_dump())

@property
def card(self) -> Optional[Card]:
10 changes: 5 additions & 5 deletions cuenca/resources/card_transactions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import ClassVar, List, Optional, cast
from typing import ClassVar, Optional, cast

from cuenca_validations.types import (
CardErrorType,
@@ -19,19 +19,19 @@ class CardTransaction(Transaction):

type: CardTransactionType
network: CardNetwork
related_card_transaction_uris: List[str]
related_card_transaction_uris: list[str]
card_uri: str
card_last4: str
card_type: CardType
metadata: dict
error_type: Optional[CardErrorType]
error_type: Optional[CardErrorType] = None

@property # type: ignore
def related_card_transactions(self) -> Optional[List['CardTransaction']]:
def related_card_transactions(self) -> Optional[list['CardTransaction']]:
if not self.related_card_transaction_uris:
return []
return cast(
List['CardTransaction'],
list['CardTransaction'],
retrieve_uris(self.related_card_transaction_uris),
)

12 changes: 6 additions & 6 deletions cuenca/resources/card_validations.py
Original file line number Diff line number Diff line change
@@ -18,11 +18,11 @@ class CardValidation(Creatable):
user_id: str
card_status: CardStatus
card_type: CardType
is_valid_cvv: Optional[bool]
is_valid_cvv2: Optional[bool]
is_valid_icvv: Optional[bool]
is_valid_pin_block: Optional[bool]
is_valid_exp_date: Optional[bool]
is_valid_cvv: Optional[bool] = None
is_valid_cvv2: Optional[bool] = None
is_valid_icvv: Optional[bool] = None
is_valid_pin_block: Optional[bool] = None
is_valid_exp_date: Optional[bool] = None
is_pin_attempts_exceeded: bool
is_expired: bool
platform_id: Optional[str] = None
@@ -51,7 +51,7 @@ def create(
pin_block=pin_block,
pin_attempts_exceeded=pin_attempts_exceeded,
)
return cls._create(session=session, **req.dict())
return cls._create(session=session, **req.model_dump())

@property
def card(self) -> Card:
8 changes: 4 additions & 4 deletions cuenca/resources/cards.py
Original file line number Diff line number Diff line change
@@ -21,12 +21,12 @@ class Card(Retrievable, Queryable, Creatable, Updateable):
_resource: ClassVar = 'cards'
_query_params: ClassVar = CardQuery

user_id: Optional[str]
user_id: Optional[str] = None
number: str
exp_month: int
exp_year: int
cvv2: str
pin: Optional[str]
pin: Optional[str] = None
type: CardType
status: CardStatus
issuer: CardIssuer
@@ -81,7 +81,7 @@ def create(
card_holder_user_id=card_holder_user_id,
is_dynamic_cvv=is_dynamic_cvv,
)
return cls._create(session=session, **req.dict())
return cls._create(session=session, **req.model_dump())

@classmethod
def update(
@@ -106,7 +106,7 @@ def update(
req = CardUpdateRequest(
status=status, pin_block=pin_block, is_dynamic_cvv=is_dynamic_cvv
)
return cls._update(card_id, session=session, **req.dict())
return cls._update(card_id, session=session, **req.model_dump())

@classmethod
def deactivate(
Loading

0 comments on commit a42283c

Please sign in to comment.