Skip to content

Commit

Permalink
Upgrade Python version requirements and dependencies
Browse files Browse the repository at this point in the history
- Updated Python version from 3.7 to 3.8 in Makefile and README.md.
- Upgraded Pydantic dependency from 1.9.0 to 2.10.3 in requirements.txt and setup.py.
- Updated mypy version from 0.790 to 1.13.0 in requirements-test.txt.
- Refactored CLABE validation logic in types.py, removing custom error classes and integrating Pydantic's validation features.
- Removed unused error handling code and updated tests to reflect changes in validation logic.
- Updated GitHub Actions workflow to support Python versions 3.8 through 3.13.
- Bumped version to 2.0.0.dev0 in version.py.
  • Loading branch information
gabino committed Dec 18, 2024
1 parent b7fadcc commit 3f790a9
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8]
python-version: [3.8, 3.9, 3.10, 3.11, 3.12, 3.13]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SHELL := bash
PATH := ./venv/bin:${PATH}
PYTHON = python3.7
PYTHON = python3.8
PROJECT = clabe
isort = isort $(PROJECT) tests setup.py
black = black -S -l 79 --target-version py38 $(PROJECT) tests setup.py
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ https://es.wikipedia.org/wiki/CLABE

## Requerimientos

Python 3.6 o superior.
Python 3.8 o superior.

## Instalación

Expand Down
11 changes: 0 additions & 11 deletions clabe/errors.py

This file was deleted.

82 changes: 45 additions & 37 deletions clabe/types.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
from typing import TYPE_CHECKING, ClassVar
from typing import Any, ClassVar, Dict, Type

from pydantic.errors import NotDigitError
from pydantic.validators import (
constr_length_validator,
constr_strip_whitespace,
str_validator,
)
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import PydanticCustomError, core_schema

from .errors import BankCodeValidationError, ClabeControlDigitValidationError
from .validations import BANK_NAMES, BANKS, compute_control_digit

if TYPE_CHECKING:
from pydantic.typing import CallableGenerator


def validate_digits(v: str) -> str:
if not v.isdigit():
raise NotDigitError
return v


class Clabe(str):
"""
Expand All @@ -29,33 +15,55 @@ class Clabe(str):
min_length: ClassVar[int] = 18
max_length: ClassVar[int] = 18

def __init__(self, clabe: str):
def __init__(self, clabe: str) -> None:
self.bank_code_abm = clabe[:3]
self.bank_code_banxico = BANKS[clabe[:3]]
self.bank_name = BANK_NAMES[self.bank_code_banxico]

@property
def bank_code(self) -> str:
return self.bank_code_banxico

@classmethod
def __get_validators__(cls) -> 'CallableGenerator':
yield str_validator
yield constr_strip_whitespace
yield constr_length_validator
yield validate_digits
yield cls.validate_bank_code_abm
yield cls.validate_control_digit
yield cls
def __get_pydantic_json_schema__(
cls,
schema: core_schema.CoreSchema,
handler: GetJsonSchemaHandler,
) -> Dict[str, Any]:
json_schema = handler(schema)
json_schema.update(
type="string",
pattern="^[0-9]{18}$",
description="CLABE (Clave Bancaria Estandarizada)",
examples=["723010123456789019"],
)
return json_schema

@classmethod
def validate_bank_code_abm(cls, clabe: str) -> str:
if clabe[:3] not in BANKS.keys():
raise BankCodeValidationError
return clabe
def __get_pydantic_core_schema__(
cls,
_: Type[Any],
__: GetCoreSchemaHandler,
) -> core_schema.CoreSchema:
return core_schema.no_info_after_validator_function(
cls._validate,
core_schema.str_schema(
min_length=cls.min_length,
max_length=cls.max_length,
strip_whitespace=cls.strip_whitespace,
),
)

@classmethod
def validate_control_digit(cls, clabe: str) -> str:
def _validate(cls, clabe: str) -> 'Clabe':
if not clabe.isdigit():
raise PydanticCustomError('clabe', 'debe ser numérico')
if clabe[:3] not in BANKS:
raise PydanticCustomError(
'clabe.bank_code', 'código de banco no es válido'
)
if clabe[-1] != compute_control_digit(clabe):
raise ClabeControlDigitValidationError
return clabe

@property
def bank_code(self):
return self.bank_code_banxico
raise PydanticCustomError(
'clabe.control_digit', 'clabe dígito de control no es válido'
)
return cls(clabe)
2 changes: 1 addition & 1 deletion clabe/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.2.16'
__version__ = '2.0.0.dev0'
2 changes: 1 addition & 1 deletion requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ pytest-cov==2.11.*
black==22.3.0
isort==5.10.*
flake8==4.0.*
mypy==0.790
mypy==1.13.0
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pydantic==1.9.0
pydantic==2.10.3
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
packages=setuptools.find_packages(),
include_package_data=True,
package_data=dict(clabe=['py.typed']),
install_requires=['pydantic>=1.4,<2.0'],
install_requires=['pydantic>=2.10.3'],
classifiers=[
'Programming Language :: Python :: 3',
'License :: OSI Approved :: MIT License',
Expand Down
69 changes: 44 additions & 25 deletions tests/test_types.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import pytest
from pydantic import BaseModel
from pydantic.errors import NotDigitError
from pydantic import BaseModel, ValidationError

from clabe import BANK_NAMES, BANKS, compute_control_digit
from clabe.errors import (
BankCodeValidationError,
ClabeControlDigitValidationError,
)
from clabe.types import Clabe, validate_digits
from clabe import BANK_NAMES, BANKS
from clabe.types import Clabe

VALID_CLABE = '646180157042875763'

Expand All @@ -24,23 +19,47 @@ def test_valid_clabe():
assert cuenta.clabe.bank_code == cuenta.clabe.bank_code_banxico


def test_clabe_digits():
assert validate_digits(VALID_CLABE)


def test_clabe_not_digit():
with pytest.raises(NotDigitError):
validate_digits('h' * 18)

@pytest.mark.parametrize(
'clabe,expected_message',
[
pytest.param(
'h' * 18,
'debe ser numérico',
id='clabe_not_digit',
),
pytest.param(
'9' * 17,
'String should have at least 18 characters',
id='invalid_bank_code_abm',
),
pytest.param(
'111180157042875763',
'código de banco no es válido',
id='invalid_bank_code',
),
pytest.param(
'001' + '9' * 15,
'clabe dígito de control no es válido',
id='invalid_control_digit',
),
],
)
def test_invalid_clabe(clabe: Clabe, expected_message: str) -> None:
with pytest.raises(ValidationError) as exc:
Cuenta(clabe=clabe)
assert expected_message in str(exc.value)

def test_invalid_bank_code_abm():
clabe = '9' * 17
clabe += compute_control_digit(clabe)
with pytest.raises(BankCodeValidationError):
Clabe.validate_bank_code_abm(clabe)

def test_get_json_schema() -> None:
from pydantic import TypeAdapter

def test_invalid_control_digit():
clabe = '001' + '9' * 15
with pytest.raises(ClabeControlDigitValidationError):
Clabe.validate_control_digit(clabe)
adapter = TypeAdapter(Clabe)
schema = adapter.json_schema()
assert schema == {
'description': 'CLABE (Clave Bancaria Estandarizada)',
'examples': ['723010123456789019'],
'maxLength': 18,
'minLength': 18,
'pattern': '^[0-9]{18}$',
'type': 'string',
}

0 comments on commit 3f790a9

Please sign in to comment.