Skip to content
Closed
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
263 changes: 121 additions & 142 deletions brutils/cnpj.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import string
from itertools import chain
from random import randint
from random import choice, randint

# Mapeamento alfanumérico: A=10, B=11, ..., Z=35
_CHAR_VALUES = {c: i + 10 for i, c in enumerate(string.ascii_uppercase)}

# FORMATTING
############


def sieve(dirty: str) -> str:
"""
Removes specific symbols from a CNPJ (Brazilian Company Registration
Number) string.

This function takes a CNPJ string as input and removes all occurrences of
the '.', '/' and '-' characters from it.
Removes specific symbols from a CNPJ string.

Args:
cnpj (str): The CNPJ string containing symbols to be removed.
Expand All @@ -22,67 +22,23 @@ def sieve(dirty: str) -> str:
Example:
>>> sieve("12.345/6789-01")
"12345678901"
>>> sieve("98/76.543-2101")
"98765432101"

.. note::
This method should not be used in new code and is only provided for
backward compatibility.
"""

return "".join(filter(lambda char: char not in "./-", dirty))


def remove_symbols(dirty: str) -> str:
"""
This function is an alias for the `sieve` function, offering a more
descriptive name.

Args:
dirty (str): The dirty string containing symbols to be removed.

Returns:
str: A new string with the specified symbols removed.

Example:
>>> remove_symbols("12.345/6789-01")
"12345678901"
>>> remove_symbols("98/76.543-2101")
"98765432101"
"""

"""Alias for sieve()."""
return sieve(dirty)


def display(cnpj: str) -> str | None:
"""
Will format an adequately formatted numbers-only CNPJ string,
adding in standard formatting visual aid symbols for display.

Formats a CNPJ (Brazilian Company Registration Number) string for
visual display.

This function takes a CNPJ string as input, validates its format, and
formats it with standard visual aid symbols for display purposes.

Args:
cnpj (str): The CNPJ string to be formatted for display.

Returns:
str: The formatted CNPJ with visual aid symbols if it's valid,
None if it's not valid.

Example:
>>> display("12345678901234")
"12.345.678/9012-34"
>>> display("98765432100100")
"98.765.432/1001-00"
Formats a CNPJ string for visual display (legacy, numeric only).

.. note::
This method should not be used in new code and is only provided for
backward compatibility.
backward compatibility. Use format_cnpj() instead.
"""

if not cnpj.isdigit() or len(cnpj) != 14 or len(set(cnpj)) == 1:
return None
return "{}.{}.{}/{}-{}".format(
Expand All @@ -92,29 +48,25 @@ def display(cnpj: str) -> str | None:

def format_cnpj(cnpj: str) -> str | None:
"""
Formats a CNPJ (Brazilian Company Registration Number) string for visual
display.
Formats a CNPJ string for visual display.

This function takes a CNPJ string as input, validates its format, and
formats it with standard visual aid symbols for display purposes.
Supports both the classic numeric format and the new alphanumeric format
introduced by RFB Nota Técnica 49/2024 (effective July 2026).

Args:
cnpj (str): The CNPJ string to be formatted for display.
cnpj (str): The CNPJ string to be formatted (14 characters, no symbols).

Returns:
str: The formatted CNPJ with visual aid symbols if it's valid,
None if it's not valid.
str: The formatted CNPJ if valid, None otherwise.

Example:
>>> format_cnpj("03560714000142")
'03.560.714/0001-42'
>>> format_cnpj("98765432100100")
None
>>> format_cnpj("B3S30714000142")
'B3.S30.714/0001-42'
"""

if not is_valid(cnpj):
return None

return "{}.{}.{}/{}-{}".format(
cnpj[:2], cnpj[2:5], cnpj[5:8], cnpj[8:12], cnpj[12:14]
)
Expand All @@ -124,50 +76,120 @@ def format_cnpj(cnpj: str) -> str | None:
############


def _char_to_val(c: str) -> int:
"""
Converts a CNPJ character to its numeric value for checksum calculation.

Digits map to their integer value; uppercase letters map to 10-35
(A=10, B=11, ..., Z=35), as defined by RFB Nota Técnica 49/2024.

Args:
c (str): A single character (digit or uppercase letter).

Returns:
int: The numeric value of the character.
"""
if c.isdigit():
return int(c)
return _CHAR_VALUES[c.upper()]


def _is_valid_chars(cnpj: str) -> bool:
"""
Checks if all characters in a CNPJ are valid (digits or uppercase letters).

Args:
cnpj (str): The CNPJ string to check.

Returns:
bool: True if all characters are valid, False otherwise.
"""
return all(c.isdigit() or c in _CHAR_VALUES for c in cnpj)


def _hashdigit(cnpj: str, position: int) -> int:
"""
Calculates the checksum digit at the given position for the provided CNPJ.

Supports both numeric and alphanumeric CNPJs per RFB Nota Técnica 49/2024.

Args:
cnpj (str): The CNPJ string.
position (int): The position of the checksum digit (13 or 14).

Returns:
int: The calculated checksum digit.
"""
weightgen = chain(range(position - 8, 1, -1), range(9, 1, -1))
val = sum(_char_to_val(c) * w for c, w in zip(cnpj, weightgen)) % 11
return 0 if val < 2 else 11 - val


def _checksum(basenum: str) -> str:
"""
Calculates the verifying checksum digits for a given CNPJ base number.

Supports both numeric and alphanumeric base numbers.

Args:
basenum (str): The 12-character CNPJ base number.

Returns:
str: The two verifying checksum digits.
"""
d1 = str(_hashdigit(basenum, 13))
d2 = str(_hashdigit(basenum + d1, 14))
return d1 + d2


def validate(cnpj: str) -> bool:
"""
Validates a CNPJ (Brazilian Company Registration Number) by comparing its
verifying checksum digits to its base number.
Validates a CNPJ by comparing its verifying checksum digits to its base.

This function checks the validity of a CNPJ by comparing its verifying
checksum digits to its base number. The input should be a string of digits
with the appropriate length.
Supports both the classic numeric format and the new alphanumeric format
introduced by RFB Nota Técnica 49/2024 (effective July 2026).

Args:
cnpj (str): The CNPJ to be validated.
cnpj (str): The CNPJ to be validated (14 characters, no symbols).

Returns:
bool: True if the checksum digits match the base number,
False otherwise.
bool: True if valid, False otherwise.

Example:
>>> validate("03560714000142")
True
>>> validate("00111222000133")
False
>>> validate("B3S30714000142")
True

.. note::
This method should not be used in new code and is only provided for
backward compatibility.
"""

if not cnpj.isdigit() or len(cnpj) != 14 or len(set(cnpj)) == 1:
if (
not isinstance(cnpj, str)
or len(cnpj) != 14
or not _is_valid_chars(cnpj)
or len(set(cnpj.upper())) == 1
):
return False
return all(
_hashdigit(cnpj, i + 13) == int(v) for i, v in enumerate(cnpj[12:])
)
return all(_hashdigit(cnpj, i + 13) == int(cnpj[12 + i]) for i in range(2))


def is_valid(cnpj: str) -> bool:
"""
Returns whether or not the verifying checksum digits of the given `cnpj`
match its base number.
Returns whether the verifying checksum digits of the given CNPJ match
its base number.

Supports both the classic numeric format and the new alphanumeric format
introduced by RFB Nota Técnica 49/2024 (effective July 2026).

This function does not verify the existence of the CNPJ; it only
validates the format of the string.

Args:
cnpj (str): The CNPJ to be validated, a 14-digit string
cnpj (str): The CNPJ to be validated (14 characters, no symbols).

Returns:
bool: True if the checksum digits match the base number,
Expand All @@ -178,85 +200,42 @@ def is_valid(cnpj: str) -> bool:
True
>>> is_valid("00111222000133")
False
>>> is_valid("B3S30714000142")
True
"""

return isinstance(cnpj, str) and validate(cnpj)


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

Args:
branch (int): An optional branch number to be included in the CNPJ.
branch (int): An optional branch number (default: 1).
alphanumeric (bool): If True, generates a new alphanumeric CNPJ
per RFB Nota Técnica 49/2024. Default: False.

Returns:
str: A randomly generated valid CNPJ string.

Example:
>>> generate()
"30180536000105"
>>> generate(1234)
"01745284123455"
>>> generate(alphanumeric=True)
"B3S30714000142"
"""

branch %= 10000
branch += int(branch == 0)
branch = str(branch).zfill(4)
base = str(randint(0, 99999999)).zfill(8) + branch

return base + _checksum(base)


def _hashdigit(cnpj: str, position: int) -> int:
"""
Calculates the checksum digit at the given `position` for the provided
`cnpj`. The input must contain all elements before `position`.

Args:
cnpj (str): The CNPJ for which the checksum digit is calculated.
position (int): The position of the checksum digit to be calculated.

Returns:
int: The calculated checksum digit.

Example:
>>> _hashdigit("12345678901234", 13)
3
>>> _hashdigit("98765432100100", 14)
9
"""

weightgen = chain(range(position - 8, 1, -1), range(9, 1, -1))
val = (
sum(int(digit) * weight for digit, weight in zip(cnpj, weightgen)) % 11
)
return 0 if val < 2 else 11 - val
if alphanumeric:
charset = string.digits + string.ascii_uppercase
base = "".join(choice(charset) for _ in range(8)) + branch
# Ensure at least one letter in the base (distinguishes from numeric)
if base[:8].isdigit():
pos = randint(0, 7)
base = base[:pos] + choice(string.ascii_uppercase) + base[pos + 1 :]
else:
base = str(randint(0, 99999999)).zfill(8) + branch


def _checksum(basenum: str) -> str:
"""
Calculates the verifying checksum digits for a given CNPJ base number.

This function computes the verifying checksum digits for a provided CNPJ
base number. The `basenum` should be a digit-string of the appropriate
length.

Args:
basenum (str): The base number of the CNPJ for which verifying checksum
digits are calculated.

Returns:
str: The verifying checksum digits.

Example:
>>> _checksum("123456789012")
"30"
>>> _checksum("987654321001")
"41"
"""

verifying_digits = str(_hashdigit(basenum, 13))
verifying_digits += str(_hashdigit(basenum + verifying_digits, 14))
return verifying_digits
return base + _checksum(base)
Loading
Loading