Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update/pydantic v2 #171

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- name: Set up Python
uses: actions/[email protected]
with:
python-version: 3.8
python-version: "3.8"
- name: Install dependencies
run: make install-test
- name: Lint
Expand All @@ -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',
}
Loading