From c561e14e67d899035c75ef1ef695fd85c828a05e Mon Sep 17 00:00:00 2001 From: Hudson Brendon Date: Wed, 27 Mar 2024 19:04:09 -0300 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20adiciona=20valida=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20cep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydantic_br_validator/__init__.py | 4 + pydantic_br_validator/fields/cep_field.py | 45 ++++++ .../validators/cep_validator.py | 19 +++ tests/test_cep.py | 130 ++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 pydantic_br_validator/fields/cep_field.py create mode 100644 pydantic_br_validator/validators/cep_validator.py create mode 100644 tests/test_cep.py diff --git a/pydantic_br_validator/__init__.py b/pydantic_br_validator/__init__.py index 60a1bc8..b5c9c6b 100644 --- a/pydantic_br_validator/__init__.py +++ b/pydantic_br_validator/__init__.py @@ -20,6 +20,10 @@ Certidao = str CertidaoMask = str CertidaoDigits = str + CEP = str + CEPMask = str + CEPDigits = str else: from .fields.cnpj_field import * # noqa from .fields.cpf_field import * # noqa + from .fields.cep_field import * # noqa diff --git a/pydantic_br_validator/fields/cep_field.py b/pydantic_br_validator/fields/cep_field.py new file mode 100644 index 0000000..e5e0083 --- /dev/null +++ b/pydantic_br_validator/fields/cep_field.py @@ -0,0 +1,45 @@ +from ..validators.cep_validator import CEPValidator +from .base_field import Base, BaseDigits, BaseMask + +__all__ = [ + "CEP", + "CEPMask", + "CEPDigits", +] + + +class CEP(Base): + """ + Only Accepts string of CEP. + + Attributes: + number (str): CEP number. + + """ + + format = "cep" + Validator = CEPValidator + + +class CEPMask(BaseMask): + """ + Only Accepts string of CEP with mask. + + Attributes: + number (str): CEP number. + """ + + format = "cep" + Validator = CEPValidator + + +class CEPDigits(BaseDigits): + """ + Only Accepts string of CEP without mask. + + Attributes: + number (str): CEP number. + """ + + format = "cep" + Validator = CEPValidator diff --git a/pydantic_br_validator/validators/cep_validator.py b/pydantic_br_validator/validators/cep_validator.py new file mode 100644 index 0000000..bae6da9 --- /dev/null +++ b/pydantic_br_validator/validators/cep_validator.py @@ -0,0 +1,19 @@ +from .base_validator import FieldMaskValidator + +__all__ = ["CEPValidator"] + + +class CEPValidator(FieldMaskValidator): + def __init__(self, cep) -> None: + self.cep = cep + + def validate_mask(self) -> bool: + if len(self.cep) == 9: + return self.cep[5] == "-" + + def validate(self) -> bool: + cep = self.cep.replace("-", "") + + if len(cep) != 8: + return False + return True diff --git a/tests/test_cep.py b/tests/test_cep.py new file mode 100644 index 0000000..14bd95e --- /dev/null +++ b/tests/test_cep.py @@ -0,0 +1,130 @@ +import re + +import pytest +from faker import Faker +from pydantic import BaseModel, ValidationError + +from pydantic_br_validator import ( + CNPJ, + CNPJDigits, + CNPJMask, + FieldDigitError, + FieldInvalidError, + FieldMaskError, + FieldTypeError, +) + +TOTAL_CEP = 10 +fake = Faker("pt-BR") + + +def cep_digits(): + ceps = [re.sub("[^0-9]", "", fake.cep()) for _ in range(TOTAL_CEP)] + return ceps + + +def cep_mask(): + ceps = [fake.cep() for _ in range(TOTAL_CEP)] + return ceps + + +def cep_mixed(): + ceps = [fake.cep() for _ in range(int(TOTAL_CEP / 2))] + ceps += [re.sub("[^0-9]", "", fake.cep()) for _ in range(int(TOTAL_CEP / 2))] + return ceps + + +@pytest.fixture +def company(): + class Company(BaseModel): + cnpj: CNPJ + + yield Company + + +@pytest.fixture +def company_masks(): + class Company(BaseModel): + cnpj: CNPJMask + + yield Company + + +@pytest.fixture +def company_digits(): + class Company(BaseModel): + cnpj: CNPJDigits + + yield Company + + +@pytest.mark.parametrize("cnpj", cep_mixed()) +def test_must_be_string(company, cnpj): + c1 = company(cnpj=cnpj) + assert isinstance(c1.cnpj, str) + + +@pytest.mark.parametrize("cnpj", cep_mask()) +def test_mascara_must_be_string(company_masks, cnpj): + c1 = company_masks(cnpj=cnpj) + assert isinstance(c1.cnpj, str) + + +@pytest.mark.parametrize("cnpj", cnpj_digits()) +def test_digits_must_be_string(company_digits, cnpj): + c1 = company_digits(cnpj=cnpj) + assert isinstance(c1.cnpj, str) + + +@pytest.mark.parametrize("cnpj", cnpj_mask()) +def test_must_accept_with_mask(company_masks, cnpj): + c1 = company_masks(cnpj=cnpj) + assert c1.cnpj == cnpj + + +@pytest.mark.parametrize("cnpj", cnpj_digits()) +def test_must_accept_only_numbers(company_digits, cnpj): + c1 = company_digits(cnpj=cnpj) + assert c1.cnpj == cnpj + + +@pytest.mark.parametrize("cnpj", cnpj_digits()) +def test_must_fail_when_use_digits_in_mask_class(company_masks, cnpj): + with pytest.raises(ValidationError) as e: + company_masks(cnpj=cnpj) + assert FieldMaskError.msg_template in str(e.value) + + +@pytest.mark.parametrize("cnpj", cnpj_mask()) +def test_must_fail_when_use_mask_in_digits_class(company_digits, cnpj): + with pytest.raises(ValidationError) as e: + company_digits(cnpj=cnpj) + assert FieldDigitError.msg_template in str(e.value) + + +@pytest.mark.parametrize("cnpj", cnpj_digits()) +def test_must_fail_when_use_another_type(company_digits, cnpj): + with pytest.raises(ValidationError) as e: + company_digits(cnpj=int(cnpj)) + assert FieldTypeError.msg_template in str(e.value) + + +@pytest.mark.parametrize("cnpj", cnpj_digits()) +def test_must_fail_when_use_invalid_cnpjs(company, cnpj): + with pytest.raises(ValidationError) as e: + company(cnpj=cnpj[-1]) + assert FieldInvalidError.msg_template in str(e.value) + + +@pytest.mark.parametrize("cnpj", cnpj_mixed()) +def test_must_fail_when_use_digits_cont_above_cnpjs(company, cnpj): + with pytest.raises(ValidationError) as e: + company(cnpj=cnpj * 2) + assert FieldInvalidError.msg_template in str(e.value) + + +@pytest.mark.parametrize("cnpj", cnpj_mixed()) +def test_must_fail_when_use_digits_cont_below_cnpjs(company, cnpj): + with pytest.raises(ValidationError) as e: + company(cnpj=cnpj[:5]) + assert FieldInvalidError.msg_template in str(e.value) From b44924c9cfee01b77ad213371113dfab0ccfcc35 Mon Sep 17 00:00:00 2001 From: Hudson Brendon Date: Fri, 29 Mar 2024 17:21:07 -0300 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20corrige=20testes=20da=20valida?= =?UTF-8?q?=C3=A7=C3=A3o=20do=20cep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_cep.py | 180 +++++++++++++++++++++------------------------- 1 file changed, 81 insertions(+), 99 deletions(-) diff --git a/tests/test_cep.py b/tests/test_cep.py index 14bd95e..21aaf11 100644 --- a/tests/test_cep.py +++ b/tests/test_cep.py @@ -1,130 +1,112 @@ -import re - import pytest -from faker import Faker from pydantic import BaseModel, ValidationError from pydantic_br_validator import ( - CNPJ, - CNPJDigits, - CNPJMask, - FieldDigitError, - FieldInvalidError, - FieldMaskError, - FieldTypeError, + CEP, + CEPDigits, + CEPMask, ) - -TOTAL_CEP = 10 -fake = Faker("pt-BR") - - -def cep_digits(): - ceps = [re.sub("[^0-9]", "", fake.cep()) for _ in range(TOTAL_CEP)] - return ceps - - -def cep_mask(): - ceps = [fake.cep() for _ in range(TOTAL_CEP)] - return ceps - - -def cep_mixed(): - ceps = [fake.cep() for _ in range(int(TOTAL_CEP / 2))] - ceps += [re.sub("[^0-9]", "", fake.cep()) for _ in range(int(TOTAL_CEP / 2))] - return ceps +from pydantic_br_validator.field_erros import FieldInvalidError, FieldTypeError + +cep_mock = [ + ("59151650", "59151-650", "59151650"), + ("57602660", "57602-660", "57602660"), + ("78735819", "78735-819", "78735819"), + ("69315235", "69315-235", "69315235"), + ("15056133", "15056-133", "15056133"), + ("60761971", "60761-971", "60761971"), + ("72760022", "72760-022", "72760022"), + ("79020160", "79020-160", "79020160"), + ("64040670", "64040-670", "64040670"), + ("76965520", "76965-520", "76965520"), +] @pytest.fixture -def company(): - class Company(BaseModel): - cnpj: CNPJ +def address(): + class Address(BaseModel): + cep: CEP + cep_mask: CEPMask + cep_digits: CEPDigits - yield Company - - -@pytest.fixture -def company_masks(): - class Company(BaseModel): - cnpj: CNPJMask - - yield Company - - -@pytest.fixture -def company_digits(): - class Company(BaseModel): - cnpj: CNPJDigits + yield Address - yield Company +@pytest.mark.parametrize("cep, cep_mask, cep_digits", cep_mock) +def test_must_cep_string(address, cep, cep_mask, cep_digits): + address_1 = address(cep=cep, cep_mask=cep_mask, cep_digits=cep_digits) + assert isinstance(address_1.cep, str) + assert isinstance(address_1.cep_mask, str) + assert isinstance(address_1.cep_digits, str) -@pytest.mark.parametrize("cnpj", cep_mixed()) -def test_must_be_string(company, cnpj): - c1 = company(cnpj=cnpj) - assert isinstance(c1.cnpj, str) +@pytest.mark.parametrize("cep, cep_mask, cep_digits", cep_mock) +def test_must_accept_only_numbers(address, cep, cep_mask, cep_digits): + address_1 = address(cep=cep, cep_mask=cep_mask, cep_digits=cep_digits) + assert address_1.cep == cep + assert address_1.cep_mask == cep_mask + assert address_1.cep_digits == cep_digits -@pytest.mark.parametrize("cnpj", cep_mask()) -def test_mascara_must_be_string(company_masks, cnpj): - c1 = company_masks(cnpj=cnpj) - assert isinstance(c1.cnpj, str) - -@pytest.mark.parametrize("cnpj", cnpj_digits()) -def test_digits_must_be_string(company_digits, cnpj): - c1 = company_digits(cnpj=cnpj) - assert isinstance(c1.cnpj, str) - - -@pytest.mark.parametrize("cnpj", cnpj_mask()) -def test_must_accept_with_mask(company_masks, cnpj): - c1 = company_masks(cnpj=cnpj) - assert c1.cnpj == cnpj - - -@pytest.mark.parametrize("cnpj", cnpj_digits()) -def test_must_accept_only_numbers(company_digits, cnpj): - c1 = company_digits(cnpj=cnpj) - assert c1.cnpj == cnpj - - -@pytest.mark.parametrize("cnpj", cnpj_digits()) -def test_must_fail_when_use_digits_in_mask_class(company_masks, cnpj): +@pytest.mark.parametrize("cep, cep_mask, cep_digits", cep_mock) +def test_must_fail_when_use_another_type(address, cep, cep_mask, cep_digits): with pytest.raises(ValidationError) as e: - company_masks(cnpj=cnpj) - assert FieldMaskError.msg_template in str(e.value) - - -@pytest.mark.parametrize("cnpj", cnpj_mask()) -def test_must_fail_when_use_mask_in_digits_class(company_digits, cnpj): - with pytest.raises(ValidationError) as e: - company_digits(cnpj=cnpj) - assert FieldDigitError.msg_template in str(e.value) + address( + cep=int(cep), + cep_mask=str(cep_mask), + cep_digits=int(cep_digits), + ) + assert FieldTypeError.msg_template in str(e.value) -@pytest.mark.parametrize("cnpj", cnpj_digits()) -def test_must_fail_when_use_another_type(company_digits, cnpj): +@pytest.mark.parametrize("cep, cep_mask, cep_digits", cep_mock) +def test_must_fail_when_use_invalid_cep(address, cep, cep_mask, cep_digits): with pytest.raises(ValidationError) as e: - company_digits(cnpj=int(cnpj)) - assert FieldTypeError.msg_template in str(e.value) + invalid_cep = cep[:5] + cep[6:] + invalid_cep_mask = cep_mask[:5] + cep_mask[6:] + invalid_cep_digits = cep_digits[:5] + cep_digits[6:] + address( + cep=invalid_cep, + cep_mask=invalid_cep_mask, + cep_digits=invalid_cep_digits, + ) + assert FieldInvalidError.msg_template in str(e.value) -@pytest.mark.parametrize("cnpj", cnpj_digits()) -def test_must_fail_when_use_invalid_cnpjs(company, cnpj): +@pytest.mark.parametrize("cep, cep_mask, cep_digits", cep_mock) +def test_must_fail_when_use_digits_count_above_cep(address, cep, cep_mask, cep_digits): with pytest.raises(ValidationError) as e: - company(cnpj=cnpj[-1]) + address(cep=cep * 2, cep_mask=cep_mask * 2, cep_digits=cep_digits * 2) assert FieldInvalidError.msg_template in str(e.value) -@pytest.mark.parametrize("cnpj", cnpj_mixed()) -def test_must_fail_when_use_digits_cont_above_cnpjs(company, cnpj): +@pytest.mark.parametrize("cep, cep_mask, cep_digits", cep_mock) +def test_must_fail_when_use_digits_count_below_cep(address, cep, cep_mask, cep_digits): with pytest.raises(ValidationError) as e: - company(cnpj=cnpj * 2) + address(cep=cep[:5], cep_mask=cep_mask[:5], cep_digits=cep_digits[:5]) assert FieldInvalidError.msg_template in str(e.value) -@pytest.mark.parametrize("cnpj", cnpj_mixed()) -def test_must_fail_when_use_digits_cont_below_cnpjs(company, cnpj): +@pytest.mark.parametrize( + "cep, cep_mask, cep_digits", + [ + ("000000000000", "00000-000", "00000000"), + ("111111111111", "11111-111", "11111111"), + ("222222222222", "22222-222", "22222222"), + ("333333333333", "33333-333", "33333333"), + ("444444444444", "44444-444", "44444444"), + ("555555555555", "55555-555", "55555555"), + ("666666666666", "66666-666", "66666666"), + ("777777777777", "77777-777", "77777777"), + ("888888888888", "88888-888", "88888888"), + ("999999999999", "99999-999", "99999999"), + ], +) +def test_must_fail_when_use_sequecial_digits(address, cep, cep_mask, cep_digits): with pytest.raises(ValidationError) as e: - company(cnpj=cnpj[:5]) + address( + cep=cep, + cep_mask=cep_mask, + cep_digits=cep_digits, + ) assert FieldInvalidError.msg_template in str(e.value) From 2aa42d0b5b636982dff2680cfb1948bfe88ca41c Mon Sep 17 00:00:00 2001 From: Hudson Brendon Date: Fri, 29 Mar 2024 17:26:15 -0300 Subject: [PATCH 3/3] feat: atualiza readme --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 858628b..6065357 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,40 @@ cliente = Cliente( pprint(cliente.dict()) ``` +# CEP + +```python +from pprint import pprint + +from pydantic import BaseModel + +from pydantic_br_validator import CEP, CEPDigits, CEPMask + + +class Endereco(BaseModel): + rua: str + numero: str + bairro: str + cidade: str + cep: CEP # aceita CEP válidos com ou sem máscara + cep_mask: CEPMask # aceita CEP válido apenas com máscara + cep_digits: CEPDigits # aceita CEP válido apnas com dígitos + + +endereco = Endereco( + rua="Avenida Paulista", + numero="100", + bairro="Aclimação", + cidade="São Paulo", + cep="01310100", + cep_mask="01310-100", + cep_digits="01310100", +) + + +pprint(endereco.dict()) +``` + # Licença Este projeto está licenciado sob os termos da licença do [MIT licença](https://en.wikipedia.org/wiki/MIT_License)