Skip to content
Open
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: 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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Acho que não faz muito sentido ter outra função, CNPJ vai ser alfanumérico de qualquer forma daqui a pra frente, então generate_cnpj deveria suportar isso. Que acha?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

faz sentido isso que tu falou.. talvez seria interessante deixar a função original do jeito que está e passar um param se quer usar essa função do alphanumeric e chamar essa generate_alphanumeric_cnpj.. não podemos perder de vista que a lib também é usada de forma educacional então pra deixar mais simples e entendível antes de colocar tudo na mesma função.. o que tu acha? se for uma m.. essa minha ideia pode falar aí.. quero fazer o melhor mas de forma mais simplista possível.. inevitavelmente vai gerar um breaking changes na próxima release então acho que isso ficaria ok.. pode jogar umas ideias de código aí se achar necessário pra essa função ou nomes de params e afins.

"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