Skip to content

Commit 39fb752

Browse files
feat: add alphanumeric cnpj
1 parent e1ce34a commit 39fb752

6 files changed

Lines changed: 158 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Utilitário `generate_alphanumeric_cnpj` [#685](https://github.com/brazilian-utils/python/issues/685)
13+
1014
## [2.4.0] - 2026-04-20
1115

1216
### Added

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ False
5252
- [format\_cnpj](#format_cnpj)
5353
- [remove\_symbols\_cnpj](#remove_symbols_cnpj)
5454
- [generate\_cnpj](#generate_cnpj)
55+
- [generate\_alphanumeric\_cnpj](#generate_alphanumeric_cnpj)
5556
- [CEP](#cep)
5657
- [is\_valid\_cep](#is_valid_cep)
5758
- [format\_cep](#format_cep)
@@ -213,7 +214,8 @@ Exemplo:
213214

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

219221
Argumentos:
@@ -306,6 +308,29 @@ Exemplo:
306308
"01745284123455"
307309
```
308310

311+
### generate_alphanumeric_cnpj
312+
313+
Gera uma string de CNPJ alfanumérico válida aleatória. Um número de filial
314+
opcional pode ser fornecido; o padrão é '1'.
315+
316+
Argumentos:
317+
318+
- branch (str): Um número de filial opcional a ser incluído no CNPJ.
319+
320+
Retorna:
321+
322+
- str: Um CNPJ alfanumérico válido gerado aleatoriamente.
323+
324+
Exemplo:
325+
326+
```python
327+
>>> from brutils import generate_alphanumeric_cnpj
328+
>>> generate_alphanumeric_cnpj()
329+
"9359QAG9000184"
330+
>>> generate_alphanumeric_cnpj('1234')
331+
"NX9K79E2123400"
332+
```
333+
309334
## CEP
310335

311336
### is_valid_cep

README_EN.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ False
5151
- [format\_cnpj](#format_cnpj)
5252
- [remove\_symbols\_cnpj](#remove_symbols_cnpj)
5353
- [generate\_cnpj](#generate_cnpj)
54+
- [generate\_alphanumeric\_cnpj](#generate_alphanumeric_cnpj)
5455
- [CEP](#cep)
5556
- [is\_valid\_cep](#is_valid_cep)
5657
- [format\_cep](#format_cep)
@@ -213,9 +214,10 @@ Example:
213214

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

220222
Args:
221223

@@ -308,6 +310,29 @@ Example:
308310
"01745284123455"
309311
```
310312

313+
### generate_alphanumeric_cnpj
314+
315+
Generates a random valid alphanumeric CNPJ string. An optional branch number
316+
parameter can be given; it defaults to '1'.
317+
318+
Args:
319+
320+
- branch (str): An optional branch number to be included in the CNPJ.
321+
322+
Returns:
323+
324+
- str: A randomly generated valid alphanumeric CNPJ string.
325+
326+
Example:
327+
328+
```python
329+
>>> from brutils import generate_alphanumeric_cnpj
330+
>>> generate_alphanumeric_cnpj()
331+
"9359QAG9000184"
332+
>>> generate_alphanumeric_cnpj('1234')
333+
"NX9K79E2123400"
334+
```
335+
311336
## CEP
312337

313338
### is_valid_cep

brutils/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# CNPJ Imports
1515
from brutils.cnpj import format_cnpj
1616
from brutils.cnpj import generate as generate_cnpj
17+
from brutils.cnpj import generate_alphanumeric as generate_alphanumeric_cnpj
1718
from brutils.cnpj import is_valid as is_valid_cnpj
1819
from brutils.cnpj import remove_symbols as remove_symbols_cnpj
1920

@@ -105,6 +106,7 @@
105106
# CNPJ
106107
"format_cnpj",
107108
"generate_cnpj",
109+
"generate_alphanumeric_cnpj",
108110
"is_valid_cnpj",
109111
"remove_symbols_cnpj",
110112
# CPF

brutils/cnpj.py

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from itertools import chain
2-
from random import randint
2+
from random import choices, randint
3+
from string import ascii_uppercase, digits
34

45
# FORMATTING
56
############
@@ -83,7 +84,12 @@ def display(cnpj: str) -> str | None:
8384
backward compatibility.
8485
"""
8586

86-
if not cnpj.isdigit() or len(cnpj) != 14 or len(set(cnpj)) == 1:
87+
if (
88+
len(cnpj) != 14
89+
or not _is_alphanumeric(cnpj[:12])
90+
or not cnpj[12:].isdigit()
91+
or len(set(cnpj)) == 1
92+
):
8793
return None
8894
return "{}.{}.{}/{}-{}".format(
8995
cnpj[:2], cnpj[2:5], cnpj[5:8], cnpj[8:12], cnpj[12:]
@@ -124,6 +130,27 @@ def format_cnpj(cnpj: str) -> str | None:
124130
############
125131

126132

133+
def _is_alphanumeric(cnpj: str) -> bool:
134+
"""
135+
Checks whether all characters are digits or uppercase letters.
136+
137+
Args:
138+
cnpj (str): The CNPJ string to be validated.
139+
140+
Returns:
141+
bool: True if all characters are either digits or uppercase letters,
142+
False otherwise.
143+
144+
Example:
145+
>>> _is_alphanumeric("035ABC1400Z142")
146+
True
147+
>>> _is_alphanumeric("0011-22200013!")
148+
False
149+
"""
150+
151+
return all(char in (digits + ascii_uppercase) for char in cnpj)
152+
153+
127154
def validate(cnpj: str) -> bool:
128155
"""
129156
Validates a CNPJ (Brazilian Company Registration Number) by comparing its
@@ -151,7 +178,12 @@ def validate(cnpj: str) -> bool:
151178
backward compatibility.
152179
"""
153180

154-
if not cnpj.isdigit() or len(cnpj) != 14 or len(set(cnpj)) == 1:
181+
if (
182+
len(cnpj) != 14
183+
or not _is_alphanumeric(cnpj[:12])
184+
or not cnpj[12:].isdigit()
185+
or len(set(cnpj)) == 1
186+
):
155187
return False
156188
return all(
157189
_hashdigit(cnpj, i + 13) == int(v) for i, v in enumerate(cnpj[12:])
@@ -209,6 +241,34 @@ def generate(branch: int = 1) -> str:
209241
return base + _checksum(base)
210242

211243

244+
def generate_alphanumeric(branch: str = "1") -> str:
245+
"""
246+
Generates a random valid alphanumeric CNPJ digit string. An optional branch
247+
number parameter can be given; it defaults to '1'.
248+
249+
Args:
250+
branch (str): An optional branch number to be included in the CNPJ.
251+
252+
Returns:
253+
str: A randomly generated valid alphanumeric CNPJ string.
254+
255+
Example:
256+
>>> generate_alphanumeric()
257+
"9359QAG9000184"
258+
>>> generate_alphanumeric('1234')
259+
"NX9K79E2123400"
260+
"""
261+
262+
branch = branch[:4] if len(branch) >= 4 else branch.zfill(4)
263+
branch = (
264+
"0001" if branch == "0000" or not _is_alphanumeric(branch) else branch
265+
)
266+
267+
base = "".join(choices(digits * 3 + ascii_uppercase, k=8)) + branch
268+
269+
return base + _checksum(base)
270+
271+
212272
def _hashdigit(cnpj: str, position: int) -> int:
213273
"""
214274
Calculates the checksum digit at the given `position` for the provided
@@ -230,7 +290,10 @@ def _hashdigit(cnpj: str, position: int) -> int:
230290

231291
weightgen = chain(range(position - 8, 1, -1), range(9, 1, -1))
232292
val = (
233-
sum(int(digit) * weight for digit, weight in zip(cnpj, weightgen)) % 11
293+
sum(
294+
(ord(digit) - 48) * weight for digit, weight in zip(cnpj, weightgen)
295+
)
296+
% 11
234297
)
235298
return 0 if val < 2 else 11 - val
236299

tests/test_cnpj.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
from brutils.cnpj import (
55
_checksum,
66
_hashdigit,
7+
_is_alphanumeric,
78
display,
89
format_cnpj,
910
generate,
11+
generate_alphanumeric,
1012
is_valid,
1113
remove_symbols,
1214
sieve,
@@ -27,12 +29,23 @@ def test_sieve(self):
2729

2830
def test_display(self):
2931
self.assertEqual(display("00000000000109"), "00.000.000/0001-09")
32+
self.assertEqual(display("12ABC34501DE35"), "12.ABC.345/01DE-35")
33+
self.assertIsNone(display("12ABC34501DEAA"))
3034
self.assertIsNone(display("00000000000000"))
3135
self.assertIsNone(display("0000000000000"))
3236
self.assertIsNone(display("0000000000000a"))
3337

38+
def test__is_alphanumeric(self):
39+
self.assertIs(_is_alphanumeric("12ABC34501DE35"), True)
40+
self.assertIs(_is_alphanumeric("12345678910111"), True)
41+
self.assertIs(_is_alphanumeric("123456a78b10C1"), False)
42+
self.assertIs(_is_alphanumeric("12.ABC.345/01DE-35"), False)
43+
3444
def test_validate(self):
3545
self.assertIs(validate("34665388000161"), True)
46+
self.assertIs(validate("12ABC34501DE35"), True)
47+
self.assertIs(validate("Z46ABC88000164"), True)
48+
self.assertIs(validate("12ABC34501DEAA"), False)
3649
self.assertIs(validate("52599927000100"), False)
3750
self.assertIs(validate("00000000000"), False)
3851

@@ -71,6 +84,14 @@ def test_generate(self):
7184
for _ in range(10_000):
7285
self.assertIs(validate(generate()), True)
7386
self.assertIsNotNone(display(generate()))
87+
self.assertIs(validate(generate(branch=1234)), True)
88+
89+
def test_generate_alphanumeric(self):
90+
for _ in range(10_000):
91+
generated = generate_alphanumeric()
92+
self.assertIs(validate(generated), True)
93+
self.assertIsNotNone(display(generated))
94+
self.assertIs(validate(generate_alphanumeric(branch="1234")), True)
7495

7596
def test__hashdigit(self):
7697
self.assertEqual(_hashdigit("00000000000000", 13), 0)
@@ -109,5 +130,15 @@ def test_when_cnpj_is_not_valid_returns_none(self, mock_is_valid):
109130
self.assertIsNone(format_cnpj("01838723000127"))
110131

111132

133+
class TestFormatCnpj(TestCase):
134+
def test_when_cnpj_is_alphanumeric_valid_returns_formatted_cnpj(self):
135+
self.assertEqual(
136+
format_cnpj("12ABC34501DE35"), "12.ABC.345/01DE-35"
137+
)
138+
139+
def test_when_cnpj_has_alphanumeric_check_digits_returns_none(self):
140+
self.assertIsNone(format_cnpj("12ABC34501DEAA"))
141+
142+
112143
if __name__ == "__main__":
113144
main()

0 commit comments

Comments
 (0)