brdocs-validation 0.4.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {brdocs_validation-0.4.0/brdocs_validation.egg-info → brdocs_validation-0.5.0}/PKG-INFO +5 -4
  2. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/__init__.py +3 -3
  3. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/__init__.py +1 -1
  4. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/cert.py +1 -1
  5. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/cnh.py +1 -1
  6. brdocs_validation-0.5.0/br_docs/validators/cnpj.py +77 -0
  7. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/cns.py +1 -1
  8. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/cpf.py +1 -1
  9. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/nis.py +1 -1
  10. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/renavam.py +1 -1
  11. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/te.py +1 -1
  12. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/types/format.py +1 -1
  13. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0/brdocs_validation.egg-info}/PKG-INFO +5 -4
  14. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/pyproject.toml +31 -2
  15. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/tests/test_brdocs.py +10 -1
  16. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/tests/test_format.py +2 -2
  17. brdocs_validation-0.4.0/br_docs/validators/cnpj.py +0 -25
  18. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/LICENSE.txt +0 -0
  19. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/README.md +0 -0
  20. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/sei.py +0 -0
  21. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/br_docs/validators/types/__init__.py +0 -0
  22. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/brdocs_validation.egg-info/SOURCES.txt +0 -0
  23. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/brdocs_validation.egg-info/dependency_links.txt +0 -0
  24. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/brdocs_validation.egg-info/requires.txt +0 -0
  25. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/brdocs_validation.egg-info/top_level.txt +0 -0
  26. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/setup.cfg +0 -0
  27. {brdocs_validation-0.4.0 → brdocs_validation-0.5.0}/tests/test_check_two_digits.py +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: brdocs-validation
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Validate brazilian documents using Type Hints in classes inheriting Pydantic's (V2) BaseModel
5
- Author-email: Vinícius Aguiar <vaguiararqdevsoftware@proton.me>
6
- Maintainer-email: Vinícius Aguiar <vaguiararqdevsoftware@proton.me>
5
+ Author-email: Vinícius Aguiar <vaguiararqdevsoftware@proton.me>, Rafael Kamimura <rafael.kamimura@proton.me>
6
+ Maintainer-email: Vinícius Aguiar <vaguiararqdevsoftware@proton.me>, Rafael Kamimura <rafael.kamimura@proton.me>
7
7
  Project-URL: Repository, https://github.com/vinicius-oa/BRdocs-validation
8
8
  Keywords: pydantic-v2,cpf-validador,cnpj-validador,validador-pispasep,validador-titulo-eleitor
9
9
  Classifier: Development Status :: 5 - Production/Stable
@@ -29,6 +29,7 @@ Requires-Dist: wheel; extra == "dev"
29
29
  Requires-Dist: twine; extra == "dev"
30
30
  Requires-Dist: tox; extra == "dev"
31
31
  Requires-Dist: ruff; extra == "dev"
32
+ Dynamic: license-file
32
33
 
33
34
  ![Test](https://github.com/vinicius-oa/BRdocs-validation/actions/workflows/test.yml/badge.svg)
34
35
  ![codecov](https://codecov.io/gh/vinicius-oa/BRdocs-validation/graph/badge.svg?token=Z211YIKO8L)
@@ -1,5 +1,6 @@
1
+ from typing import Annotated
2
+
1
3
  from pydantic.functional_validators import AfterValidator
2
- from typing_extensions import Annotated
3
4
 
4
5
  from br_docs.validators.cert import CERTv
5
6
  from br_docs.validators.cnh import CNHv
@@ -8,9 +9,8 @@ from br_docs.validators.cns import CNSv
8
9
  from br_docs.validators.cpf import CPFv
9
10
  from br_docs.validators.nis import NISv
10
11
  from br_docs.validators.renavam import RENAVAMv
11
- from br_docs.validators.te import TEv
12
12
  from br_docs.validators.sei import SEIv
13
-
13
+ from br_docs.validators.te import TEv
14
14
 
15
15
  CPF = Annotated[str, AfterValidator(CPFv())]
16
16
  CNPJ = Annotated[str, AfterValidator(CNPJv())]
@@ -16,7 +16,7 @@ class CheckDigits(ABC, ValuesRegex):
16
16
  return value
17
17
 
18
18
  def validate(self, value: str):
19
- numbers = list(map(int, re.findall(r"\d", value)))
19
+ numbers = list(map(int, re.findall(r'\d', value)))
20
20
  check_digits = [numbers.pop(x) for x in range(-self.CHECK_DIGITS, 0)]
21
21
  digits_calculated = self.calculate_digits(numbers)
22
22
  for check_digit, digit_calculated in zip(check_digits, digits_calculated):
@@ -5,7 +5,7 @@ from br_docs.validators import CheckDigits
5
5
 
6
6
  class CERTv(CheckDigits):
7
7
  """ Certidões de casamento, nascimento e óbito """
8
- Patterns = re.compile(r"^\d{32}$"),
8
+ Patterns = re.compile(r'^\d{32}$'),
9
9
  CHECK_DIGITS = 2
10
10
  CertAlgarismsMultipliers = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
11
11
 
@@ -4,7 +4,7 @@ from br_docs.validators import CheckDigits
4
4
 
5
5
 
6
6
  class CNHv(CheckDigits):
7
- Patterns = re.compile(r"^\d{11}$"),
7
+ Patterns = re.compile(r'^\d{11}$'),
8
8
  CHECK_DIGITS = 2
9
9
 
10
10
  @classmethod
@@ -0,0 +1,77 @@
1
+ import re
2
+ from math import ceil
3
+
4
+ from pydantic_core import PydanticCustomError
5
+
6
+ from br_docs.validators import CheckDigits
7
+
8
+
9
+ class CNPJv(CheckDigits):
10
+ """
11
+ CNPJ validator supporting both numeric (legacy) and alphanumeric (2026+) formats.
12
+ Algorithm: https://www.serpro.gov.br/ (calculodvcnpjalfanaumerico.pdf)
13
+ """
14
+
15
+ Patterns = (
16
+ re.compile(r'^\d{14}$'), # Numeric unformatted
17
+ re.compile(
18
+ r'^[0-9]{2}\.[0-9]{3}\.[0-9]{3}/[0-9]{4}-[0-9]{2}$'
19
+ ), # Numeric formatted
20
+ re.compile(r'^[0-9A-Z]{12}\d{2}$', re.IGNORECASE), # Alphanumeric unformatted
21
+ re.compile(
22
+ r'^[0-9A-Z]{2}\.[0-9A-Z]{3}\.[0-9A-Z]{3}/[0-9A-Z]{4}-\d{2}$', re.IGNORECASE
23
+ ), # Alphanumeric formatted
24
+ )
25
+ CHECK_DIGITS = 2
26
+ CnpjAlgarismsMultipliers = 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2
27
+
28
+ def validate(self, value: str):
29
+ """Validate CNPJ with alphanumeric support."""
30
+ clean = value.upper().replace('.', '').replace('/', '').replace('-', '')
31
+ base_chars = clean[:-2]
32
+ check_str = clean[-2:]
33
+ try:
34
+ check_1, check_2 = int(check_str[0]), int(check_str[1])
35
+ except ValueError:
36
+ raise PydanticCustomError('invalid', 'Invalid value')
37
+ calc_1, calc_2 = self.calculate_digits_alphanumeric(base_chars)
38
+ if check_1 != calc_1 or check_2 != calc_2:
39
+ raise PydanticCustomError('invalid', 'Invalid value')
40
+
41
+ @staticmethod
42
+ def char_to_value(char: str) -> int:
43
+ return ord(char) - 48
44
+
45
+ @classmethod
46
+ def calculate_digits_alphanumeric(cls, base_chars: str) -> tuple[int, int]:
47
+ values = [cls.char_to_value(c) for c in base_chars]
48
+ d1 = cls._calc_digit(values)
49
+ d2 = cls._calc_digit(values + [d1])
50
+ return d1, d2
51
+
52
+ @staticmethod
53
+ def _calc_digit(values: list[int]) -> int:
54
+ n = len(values)
55
+ weights = []
56
+ for _ in range(ceil(n / 8)):
57
+ weights.extend(range(2, 10))
58
+ weights = weights[:n]
59
+ weights.reverse()
60
+ total = sum(v * w for v, w in zip(values, weights))
61
+ rem = total % 11
62
+ return 0 if rem < 2 else 11 - rem
63
+
64
+ @classmethod
65
+ def calculate_digits(cls, non_digits: list[int]) -> tuple[int, int]:
66
+ """Legacy method for backward compatibility."""
67
+ calc = (
68
+ sum(x * y for x, y in zip(non_digits, cls.CnpjAlgarismsMultipliers[1:]))
69
+ % 11
70
+ )
71
+ d1 = 0 if calc < 2 else 11 - calc
72
+ non_digits.append(d1)
73
+ calc2 = (
74
+ sum(x * y for x, y in zip(non_digits, cls.CnpjAlgarismsMultipliers)) % 11
75
+ )
76
+ d2 = 0 if calc2 < 2 else 11 - calc2
77
+ return d1, d2
@@ -6,7 +6,7 @@ from br_docs.validators import CheckDigits
6
6
 
7
7
 
8
8
  class CNSv(CheckDigits):
9
- Patterns = re.compile(r"^\d{15}$"),
9
+ Patterns = re.compile(r'^\d{15}$'),
10
10
 
11
11
  def validate(self, value: str):
12
12
  valid_value = self.calculate_digits(list(map(int, value)))[0]
@@ -4,7 +4,7 @@ from br_docs.validators import CheckDigits
4
4
 
5
5
 
6
6
  class CPFv(CheckDigits):
7
- Patterns = re.compile(r"^(?!(\d)\1{10}$)\d{11}$"), re.compile(r"^[0-9]{3}\.[0-9]{3}\.[0-9]{3}-[0-9]{2}$")
7
+ Patterns = re.compile(r'^(?!(\d)\1{10}$)\d{11}$'), re.compile(r'^[0-9]{3}\.[0-9]{3}\.[0-9]{3}-[0-9]{2}$')
8
8
  CHECK_DIGITS = 2
9
9
 
10
10
  @staticmethod
@@ -6,7 +6,7 @@ from br_docs.validators import CheckDigits
6
6
  class NISv(CheckDigits):
7
7
  """ NIS is the number of NIT, PIS, PASEP and NIS itself. """
8
8
 
9
- Patterns = re.compile(r"^\d{11}$"), re.compile(r"^[0-9]{3}\.[0-9]{5}\.[0-9]{2}-[0-9]$")
9
+ Patterns = re.compile(r'^\d{11}$'), re.compile(r'^[0-9]{3}\.[0-9]{5}\.[0-9]{2}-[0-9]$')
10
10
  NisAlgarismsMultipliers = 3, 2, 9, 8, 7, 6, 5, 4, 3, 2
11
11
  CHECK_DIGITS = 1
12
12
 
@@ -4,7 +4,7 @@ from br_docs.validators import CheckDigits
4
4
 
5
5
 
6
6
  class RENAVAMv(CheckDigits):
7
- Patterns = re.compile(r"^\d{9,11}$"),
7
+ Patterns = re.compile(r'^\d{9,11}$'),
8
8
  CHECK_DIGITS = 1
9
9
 
10
10
  @staticmethod
@@ -7,7 +7,7 @@ from br_docs.validators import CheckDigits
7
7
 
8
8
  class TEv(CheckDigits):
9
9
  """ Titulo de eleitor """
10
- Patterns = re.compile(r"^\d{12}$"),
10
+ Patterns = re.compile(r'^\d{12}$'),
11
11
 
12
12
  def validate(self, value: str):
13
13
  valid_value = self.calculate_digits(list(map(int, value)))[0]
@@ -13,6 +13,6 @@ class ValuesRegex:
13
13
  next(filter(lambda x: x.match(value), self.Patterns))
14
14
  except StopIteration:
15
15
  raise PydanticCustomError(
16
- "format",
16
+ 'format',
17
17
  "Invalid value's format"
18
18
  )
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: brdocs-validation
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Validate brazilian documents using Type Hints in classes inheriting Pydantic's (V2) BaseModel
5
- Author-email: Vinícius Aguiar <vaguiararqdevsoftware@proton.me>
6
- Maintainer-email: Vinícius Aguiar <vaguiararqdevsoftware@proton.me>
5
+ Author-email: Vinícius Aguiar <vaguiararqdevsoftware@proton.me>, Rafael Kamimura <rafael.kamimura@proton.me>
6
+ Maintainer-email: Vinícius Aguiar <vaguiararqdevsoftware@proton.me>, Rafael Kamimura <rafael.kamimura@proton.me>
7
7
  Project-URL: Repository, https://github.com/vinicius-oa/BRdocs-validation
8
8
  Keywords: pydantic-v2,cpf-validador,cnpj-validador,validador-pispasep,validador-titulo-eleitor
9
9
  Classifier: Development Status :: 5 - Production/Stable
@@ -29,6 +29,7 @@ Requires-Dist: wheel; extra == "dev"
29
29
  Requires-Dist: twine; extra == "dev"
30
30
  Requires-Dist: tox; extra == "dev"
31
31
  Requires-Dist: ruff; extra == "dev"
32
+ Dynamic: license-file
32
33
 
33
34
  ![Test](https://github.com/vinicius-oa/BRdocs-validation/actions/workflows/test.yml/badge.svg)
34
35
  ![codecov](https://codecov.io/gh/vinicius-oa/BRdocs-validation/graph/badge.svg?token=Z211YIKO8L)
@@ -4,16 +4,18 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "brdocs-validation"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  dependencies = [
9
9
  "pydantic>=2.0"
10
10
  ]
11
11
  requires-python = ">= 3.10"
12
12
  authors = [
13
13
  {name = "Vinícius Aguiar", email = "vaguiararqdevsoftware@proton.me"},
14
+ {name = "Rafael Kamimura", email = "rafael.kamimura@proton.me"},
14
15
  ]
15
16
  maintainers = [
16
- {name = "Vinícius Aguiar", email = "vaguiararqdevsoftware@proton.me"}
17
+ {name = "Vinícius Aguiar", email = "vaguiararqdevsoftware@proton.me"},
18
+ {name = "Rafael Kamimura", email = "rafael.kamimura@proton.me"},
17
19
  ]
18
20
  description = "Validate brazilian documents using Type Hints in classes inheriting Pydantic's (V2) BaseModel"
19
21
  readme = "README.md"
@@ -46,6 +48,33 @@ dev = [
46
48
  "ruff"
47
49
  ]
48
50
 
51
+ [tool.ruff]
52
+ target-version = "py310"
53
+ line-length = 88
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "I", "W", "UP", "B", "C4", "Q"]
57
+ ignore = ["E501", "B905", "B904"]
58
+
59
+ [tool.ruff.lint.flake8-quotes]
60
+ inline-quotes = "single"
61
+ docstring-quotes = "double"
62
+
63
+ [tool.ruff.format]
64
+ quote-style = "single"
65
+
66
+ [tool.uv]
67
+ dev-dependencies = [
68
+ "build",
69
+ "mypy",
70
+ "pytest>=8",
71
+ "pytest-sugar",
72
+ "wheel",
73
+ "twine",
74
+ "tox>=4",
75
+ "ruff",
76
+ ]
77
+
49
78
  [tool.tox]
50
79
  legacy_tox_ini = """
51
80
  [tox]
@@ -4,7 +4,7 @@ import pytest
4
4
  from pydantic import create_model
5
5
  from pydantic_core import ValidationError
6
6
 
7
- from br_docs import CNH, CPF, CNPJ, NIS, CNS, RENAVAM, TE, CERT, SEI
7
+ from br_docs import CERT, CNH, CNPJ, CNS, CPF, NIS, RENAVAM, SEI, TE
8
8
 
9
9
 
10
10
  @contextmanager
@@ -74,3 +74,12 @@ def test_invalid_sei(invalid_sei_list):
74
74
  value_type=SEI,
75
75
  ):
76
76
  pass
77
+
78
+
79
+ def test_alphanumeric_cnpj(alphanumeric_cnpj_list):
80
+ with validate(
81
+ model_name='TestAlphanumericCNPJ',
82
+ values=alphanumeric_cnpj_list,
83
+ value_type=CNPJ,
84
+ ):
85
+ pass
@@ -11,8 +11,8 @@ from tests import DUMMY_VALUES
11
11
  def test_check_format(value):
12
12
  # Arrange
13
13
  class Dummy(ValuesRegex):
14
- Patterns = re.compile(r"^\d{4}$"),
14
+ Patterns = re.compile(r'^\d{4}$'),
15
15
 
16
16
  # Assert
17
- with pytest.raises(PydanticCustomError, match='Invalid value\'s format'):
17
+ with pytest.raises(PydanticCustomError, match="Invalid value's format"):
18
18
  Dummy().check_format(value)
@@ -1,25 +0,0 @@
1
- import re
2
-
3
- from br_docs.validators import CheckDigits
4
-
5
-
6
- class CNPJv(CheckDigits):
7
- Patterns = re.compile(r"^\d{14}$"), re.compile(r"^[0-9]{2}\.[0-9]{3}\.[0-9]{3}/[0-9]{4}-[0-9]{2}$"),
8
- CnpjAlgarismsMultipliers = 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2,
9
- CHECK_DIGITS = 2
10
-
11
- @classmethod
12
- def calculate_digits(cls, non_digits: list[int]) -> tuple[int, int]:
13
- """ https://web.archive.org/web/20240222180515/https://www.macoratti.net/alg_cnpj.htm """
14
- calc = sum(x*y for x, y in zip(non_digits, cls.CnpjAlgarismsMultipliers[1:])) % 11
15
- if calc < 2:
16
- digit_one = 0
17
- else:
18
- digit_one = 11 - calc
19
- non_digits.append(digit_one)
20
- calc_2 = sum(x*y for x, y in zip(non_digits, cls.CnpjAlgarismsMultipliers)) % 11
21
- if calc_2 < 2:
22
- digit_two = 0
23
- else:
24
- digit_two = 11 - calc_2
25
- return digit_one, digit_two