Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
02458a1
ci: add Codecov token to workflow configuration
Dec 10, 2024
fe79bec
feat: add error handling for existing bank codes and names
Dec 11, 2024
7ae5528
ci: update GitHub Actions workflow to use new action versions
Dec 11, 2024
e2e4cb9
chore: bump version to 1.2.17.dev1
Dec 11, 2024
2a4566e
chore: update GitHub Actions workflows for release and testing
Dec 11, 2024
b356cd1
fix: update PyPI token reference in GitHub Actions workflow
Dec 11, 2024
982d131
fix: update PyPI token reference in release workflow to use PYPI_API_…
Dec 11, 2024
5a13284
Merge branch 'chore/update-github-actions' into feature/add-editable-…
Dec 13, 2024
cf757b3
refactor: rename and update error classes for bank code validation
Dec 13, 2024
04b96f1
feat: add configure_additional_bank function and corresponding tests
Dec 13, 2024
b3fc823
docs: update README to include instructions for add a new bank
Dec 16, 2024
17056cd
chore: bump version to 1.2.17.dev2
Dec 16, 2024
63aa59d
chore: bump version to 1.2.17
Dec 16, 2024
8c0a105
chore: update GitHub Actions workflows for release and testing
Dec 11, 2024
e7d7e47
fix: update PyPI token reference in GitHub Actions workflow
Dec 11, 2024
224adf2
fix: update PyPI token reference in release workflow to use PYPI_API_…
Dec 11, 2024
7c03593
refactor: rename and update error classes for bank code validation
Dec 13, 2024
eeeaa7d
feat: add configure_additional_bank function and corresponding tests
Dec 13, 2024
bab2612
docs: update README to include instructions for add a new bank
Dec 16, 2024
4bb61cf
chore: bump version to 1.2.17.dev2
Dec 16, 2024
247f3b4
chore: bump version to 1.2.17
Dec 16, 2024
4168501
Merge branch 'feature/add-editable-banks-config' of github.com:cuenca…
Dec 16, 2024
49d22b8
Merge branch 'main' into feature/add-editable-banks-config
gmorales96 Dec 16, 2024
51856ed
feat: enhance configure_additional_bank with input validation
Dec 17, 2024
aeace46
chore: update Python version to 3.8, bump pydantic requirement to >=2…
Dec 17, 2024
b6d5a98
chore: upgrade Pydantic to v2
Dec 17, 2024
007ee9f
docs: update Python version requirement in README
Dec 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agregar las versiones hasta la 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
9 changes: 8 additions & 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 Expand Up @@ -57,3 +57,10 @@ Para generar nuevo válido CLABES
import clabe
clabe.generate_new_clabes(10, '002123456')
```

Para generar un nuevo banco

```python
import clabe
clabe.configure_additional_bank('777', '713', 'New Bank')
```
2 changes: 2 additions & 0 deletions clabe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
'generate_new_clabes',
'get_bank_name',
'validate_clabe',
'configure_additional_bank',
]

from .banks import BANK_NAMES, BANKS
from .types import Clabe
from .validations import (
compute_control_digit,
configure_additional_bank,
generate_new_clabes,
get_bank_name,
validate_clabe,
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)
47 changes: 47 additions & 0 deletions clabe/validations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import random
from typing import List, Union

from pydantic_core import PydanticCustomError

from .banks import BANK_NAMES, BANKS

CLABE_LENGTH = 18
Expand Down Expand Up @@ -61,3 +63,48 @@ def generate_new_clabes(number_of_clabes: int, prefix: str) -> List[str]:
assert validate_clabe(clabe)
clabes.append(clabe)
return clabes


def configure_additional_bank(
bank_code_abm: str, bank_code_banxico: str, bank_name: str
) -> None:
"""
Configures an additional bank.

Args:
bank_code_abm (str): The ABM code for the bank.
bank_code_banxico (str): The Banxico code for the bank.
bank_name (str): The name of the bank.

Raises:
ValueError: If the bank_code_abm or bank_code_banxico
already exists in the provided dictionaries.
"""

if not all(
isinstance(x, str)
for x in [bank_code_abm, bank_code_banxico, bank_name]
):
raise TypeError('All parameters must be strings')

if not bank_code_abm.isdigit():
raise TypeError('debe ser numérico')

if not bank_code_banxico.isdigit():
raise TypeError('debe ser numérico')

if not bank_name.strip():
raise ValueError('bank_name cannot be empty')

if bank_code_abm in BANKS:
raise PydanticCustomError(
'clabe.bank_code_abm', 'código de banco ABM ya existe'
)

if bank_code_banxico in BANK_NAMES:
raise PydanticCustomError(
'clabe.bank_code_banxico', 'código de banco banxico ya existe'
)

BANKS[bank_code_abm] = bank_code_banxico
BANK_NAMES[bank_code_banxico] = bank_name.strip()
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
34 changes: 34 additions & 0 deletions tests/test_clabe.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import pytest
from pydantic_core import PydanticCustomError

from clabe import (
compute_control_digit,
configure_additional_bank,
generate_new_clabes,
get_bank_name,
validate_clabe,
Expand Down Expand Up @@ -36,3 +38,35 @@ def test_generate_new_clabes():
for clabe in clabes:
assert clabe.startswith(prefix)
assert validate_clabe(clabe)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

de preferencia no pongas los cambios del otro branch, para que no genere ruido
cuando se mezclen puedes hacer el rebase para que los tengas aplicados pero al ser pr distintos lo correcto es no mezclarlos


def test_configure_additional_bank_success():
configure_additional_bank('777', '713', 'New Bank')
assert get_bank_name('777') == 'New Bank'


def test_configure_additional_bank_existing_abm_code():
with pytest.raises(PydanticCustomError) as exc_info:
configure_additional_bank('002', '40002', 'Banamex')
assert exc_info.value.type == 'clabe.bank_code_abm'
assert 'código de banco ABM ya existe' in str(exc_info.value)


def test_configure_additional_bank_existing_banxico_code():
with pytest.raises(PydanticCustomError) as exc_info:
configure_additional_bank('666', '40137', 'New Bank')
assert exc_info.value.type == 'clabe.bank_code_banxico'
assert 'código de banco banxico ya existe' in str(exc_info.value)


def test_configure_additional_bank_invalid_inputs():
with pytest.raises(TypeError):
configure_additional_bank(3, 3, 3)
with pytest.raises(TypeError):
configure_additional_bank('A', 'B', 'C')
with pytest.raises(TypeError):
configure_additional_bank('666', 'B', 'C')
with pytest.raises(ValueError):
configure_additional_bank('777', '713', '')
with pytest.raises(TypeError):
configure_additional_bank('abc', 'def', 'Test Bank')
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',
}
Loading