Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Utilitário `generate_alphanumeric_cnpj` [#741](https://github.com/brazilian-utils/python/pull/741)

## [2.4.0] - 2026-04-20

### Added
Expand Down
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ False
- [format\_cnpj](#format_cnpj)
- [remove\_symbols\_cnpj](#remove_symbols_cnpj)
- [generate\_cnpj](#generate_cnpj)
- [generate\_alphanumeric\_cnpj](#generate_alphanumeric_cnpj)
- [CEP](#cep)
- [is\_valid\_cep](#is_valid_cep)
- [format\_cep](#format_cep)
Expand Down Expand Up @@ -213,7 +214,8 @@ Exemplo:

Verifica se os dígitos de verificação do CNPJ (Cadastro Nacional da Pessoa
Jurídica) fornecido correspondem ao seu número base. A entrada deve ser uma
string de dígitos com o comprimento apropriado. Esta função não verifica a
string de 14 caracteres, permitindo dígitos e letras maiúsculas nas 12
primeiras posições e dígitos nas 2 últimas. Esta função não verifica a
existência do CNPJ; ela só valida o formato da string.

Argumentos:
Expand Down Expand Up @@ -306,6 +308,29 @@ Exemplo:
"01745284123455"
```

### generate_alphanumeric_cnpj

Gera uma string de CNPJ alfanumérico válida aleatória. Um número de filial
opcional pode ser fornecido; o padrão é '1'.

Argumentos:

- branch (str): Um número de filial opcional a ser incluído no CNPJ.

Retorna:

- str: Um CNPJ alfanumérico válido gerado aleatoriamente.

Exemplo:

```python
>>> from brutils import generate_alphanumeric_cnpj
>>> generate_alphanumeric_cnpj()
"9359QAG9000184"
>>> generate_alphanumeric_cnpj('1234')
"NX9K79E2123400"
```

## CEP

### is_valid_cep
Expand Down
31 changes: 28 additions & 3 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ False
- [format\_cnpj](#format_cnpj)
- [remove\_symbols\_cnpj](#remove_symbols_cnpj)
- [generate\_cnpj](#generate_cnpj)
- [generate\_alphanumeric\_cnpj](#generate_alphanumeric_cnpj)
- [CEP](#cep)
- [is\_valid\_cep](#is_valid_cep)
- [format\_cep](#format_cep)
Expand Down Expand Up @@ -213,9 +214,10 @@ Example:

Returns whether or not the verifying checksum digits of the given CNPJ
(Brazilian Company Registration Number) match its base number.
Input should be a digit string of proper length.
This function does not verify the existence of the CNPJ; it only
validates the format of the string.
Input should be a 14-character string, allowing digits and uppercase letters
in the first 12 positions and digits in the last 2 positions. This function
does not verify the existence of the CNPJ; it only validates the format of the
string.

Args:

Expand Down Expand Up @@ -308,6 +310,29 @@ Example:
"01745284123455"
```

### generate_alphanumeric_cnpj

Generates a random valid alphanumeric CNPJ string. An optional branch number
parameter can be given; it defaults to '1'.

Args:

- branch (str): An optional branch number to be included in the CNPJ.

Returns:

- str: A randomly generated valid alphanumeric CNPJ string.

Example:

```python
>>> from brutils import generate_alphanumeric_cnpj
>>> generate_alphanumeric_cnpj()
"9359QAG9000184"
>>> generate_alphanumeric_cnpj('1234')
"NX9K79E2123400"
```

## CEP

### is_valid_cep
Expand Down
2 changes: 2 additions & 0 deletions brutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# CNPJ Imports
from brutils.cnpj import format_cnpj
from brutils.cnpj import generate as generate_cnpj
from brutils.cnpj import generate_alphanumeric as generate_alphanumeric_cnpj
from brutils.cnpj import is_valid as is_valid_cnpj
from brutils.cnpj import remove_symbols as remove_symbols_cnpj

Expand Down Expand Up @@ -105,6 +106,7 @@
# CNPJ
"format_cnpj",
"generate_cnpj",
"generate_alphanumeric_cnpj",
Comment thread
niltonpimentel02 marked this conversation as resolved.
Outdated
"is_valid_cnpj",
"remove_symbols_cnpj",
# CPF
Expand Down
71 changes: 67 additions & 4 deletions brutils/cnpj.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from itertools import chain
from random import randint
from random import choices, randint
from string import ascii_uppercase, digits

# FORMATTING
############
Expand Down Expand Up @@ -83,7 +84,12 @@ def display(cnpj: str) -> str | None:
backward compatibility.
"""

if not cnpj.isdigit() or len(cnpj) != 14 or len(set(cnpj)) == 1:
if (
len(cnpj) != 14
or not _is_alphanumeric(cnpj[:12])
or not cnpj[12:].isdigit()
or len(set(cnpj)) == 1
):
return None
return "{}.{}.{}/{}-{}".format(
cnpj[:2], cnpj[2:5], cnpj[5:8], cnpj[8:12], cnpj[12:]
Expand Down Expand Up @@ -124,6 +130,27 @@ def format_cnpj(cnpj: str) -> str | None:
############


def _is_alphanumeric(cnpj: str) -> bool:
"""
Checks whether all characters are digits or uppercase letters.

Args:
cnpj (str): The CNPJ string to be validated.

Returns:
bool: True if all characters are either digits or uppercase letters,
False otherwise.

Example:
>>> _is_alphanumeric("035ABC1400Z142")
True
>>> _is_alphanumeric("0011-22200013!")
False
"""

return all(char in (digits + ascii_uppercase) for char in cnpj)


def validate(cnpj: str) -> bool:
"""
Validates a CNPJ (Brazilian Company Registration Number) by comparing its
Expand Down Expand Up @@ -151,7 +178,12 @@ def validate(cnpj: str) -> bool:
backward compatibility.
"""

if not cnpj.isdigit() or len(cnpj) != 14 or len(set(cnpj)) == 1:
if (
len(cnpj) != 14
or not _is_alphanumeric(cnpj[:12])
or not cnpj[12:].isdigit()
or len(set(cnpj)) == 1
):
return False
return all(
_hashdigit(cnpj, i + 13) == int(v) for i, v in enumerate(cnpj[12:])
Expand Down Expand Up @@ -209,6 +241,34 @@ def generate(branch: int = 1) -> str:
return base + _checksum(base)


def generate_alphanumeric(branch: str = "1") -> str:
"""
Generates a random valid alphanumeric CNPJ digit string. An optional branch
number parameter can be given; it defaults to '1'.

Args:
branch (str): An optional branch number to be included in the CNPJ.

Returns:
str: A randomly generated valid alphanumeric CNPJ string.

Example:
>>> generate_alphanumeric()
"9359QAG9000184"
>>> generate_alphanumeric('1234')
"NX9K79E2123400"
"""

branch = branch[:4] if len(branch) >= 4 else branch.zfill(4)
branch = (
"0001" if branch == "0000" or not _is_alphanumeric(branch) else branch
)

base = "".join(choices(digits * 3 + ascii_uppercase, k=8)) + branch

return base + _checksum(base)


def _hashdigit(cnpj: str, position: int) -> int:
"""
Calculates the checksum digit at the given `position` for the provided
Expand All @@ -230,7 +290,10 @@ def _hashdigit(cnpj: str, position: int) -> int:

weightgen = chain(range(position - 8, 1, -1), range(9, 1, -1))
val = (
sum(int(digit) * weight for digit, weight in zip(cnpj, weightgen)) % 11
sum(
(ord(digit) - 48) * weight for digit, weight in zip(cnpj, weightgen)
)
% 11
)
return 0 if val < 2 else 11 - val

Expand Down
29 changes: 29 additions & 0 deletions tests/test_cnpj.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from brutils.cnpj import (
_checksum,
_hashdigit,
_is_alphanumeric,
display,
format_cnpj,
generate,
generate_alphanumeric,
is_valid,
remove_symbols,
sieve,
Expand All @@ -27,12 +29,23 @@ def test_sieve(self):

def test_display(self):
self.assertEqual(display("00000000000109"), "00.000.000/0001-09")
self.assertEqual(display("12ABC34501DE35"), "12.ABC.345/01DE-35")
self.assertIsNone(display("12ABC34501DEAA"))
self.assertIsNone(display("00000000000000"))
self.assertIsNone(display("0000000000000"))
self.assertIsNone(display("0000000000000a"))

def test__is_alphanumeric(self):
self.assertIs(_is_alphanumeric("12ABC34501DE35"), True)
self.assertIs(_is_alphanumeric("12345678910111"), True)
self.assertIs(_is_alphanumeric("123456a78b10C1"), False)
self.assertIs(_is_alphanumeric("12.ABC.345/01DE-35"), False)

def test_validate(self):
self.assertIs(validate("34665388000161"), True)
self.assertIs(validate("12ABC34501DE35"), True)
self.assertIs(validate("Z46ABC88000164"), True)
self.assertIs(validate("12ABC34501DEAA"), False)
self.assertIs(validate("52599927000100"), False)
self.assertIs(validate("00000000000"), False)

Expand Down Expand Up @@ -71,6 +84,14 @@ def test_generate(self):
for _ in range(10_000):
self.assertIs(validate(generate()), True)
self.assertIsNotNone(display(generate()))
self.assertIs(validate(generate(branch=1234)), True)

def test_generate_alphanumeric(self):
for _ in range(10_000):
generated = generate_alphanumeric()
self.assertIs(validate(generated), True)
self.assertIsNotNone(display(generated))
self.assertIs(validate(generate_alphanumeric(branch="1234")), True)

def test__hashdigit(self):
self.assertEqual(_hashdigit("00000000000000", 13), 0)
Expand Down Expand Up @@ -109,5 +130,13 @@ def test_when_cnpj_is_not_valid_returns_none(self, mock_is_valid):
self.assertIsNone(format_cnpj("01838723000127"))


class TestFormatCnpj(TestCase):
def test_when_cnpj_is_alphanumeric_valid_returns_formatted_cnpj(self):
self.assertEqual(format_cnpj("12ABC34501DE35"), "12.ABC.345/01DE-35")

def test_when_cnpj_has_alphanumeric_check_digits_returns_none(self):
self.assertIsNone(format_cnpj("12ABC34501DEAA"))


if __name__ == "__main__":
main()
Loading